From 690b1d572b0ae91b5943b8781418900338b2a2a7 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 29 Jul 2015 19:06:10 +0300 Subject: [PATCH 001/245] Initial Commit --- Display.xcodeproj/project.pbxproj | 390 ++++++++++++++++++ .../contents.xcworkspacedata | 7 + .../xcschemes/xcschememanagement.plist | 27 ++ Display/Display.h | 19 + Display/Info.plist | 26 ++ DisplayTests/DisplayTests.swift | 36 ++ DisplayTests/Info.plist | 24 ++ 7 files changed, 529 insertions(+) create mode 100644 Display.xcodeproj/project.pbxproj create mode 100644 Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata create mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist create mode 100644 Display/Display.h create mode 100644 Display/Info.plist create mode 100644 DisplayTests/DisplayTests.swift create mode 100644 DisplayTests/Info.plist diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj new file mode 100644 index 0000000000..d6efabdd51 --- /dev/null +++ b/Display.xcodeproj/project.pbxproj @@ -0,0 +1,390 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; + D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + D05CC26F1B69316F00E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC25A1B69316F00E235A3 /* Project object */; + proxyType = 1; + remoteGlobalIDString = D05CC2621B69316F00E235A3; + remoteInfo = Display; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; + D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DisplayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = ""; }; + D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + D05CC25F1B69316F00E235A3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D05CC26A1B69316F00E235A3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + D05CC2591B69316F00E235A3 = { + isa = PBXGroup; + children = ( + D05CC2651B69316F00E235A3 /* Display */, + D05CC2711B69316F00E235A3 /* DisplayTests */, + D05CC2641B69316F00E235A3 /* Products */, + ); + sourceTree = ""; + }; + D05CC2641B69316F00E235A3 /* Products */ = { + isa = PBXGroup; + children = ( + D05CC2631B69316F00E235A3 /* Display.framework */, + D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */, + ); + name = Products; + sourceTree = ""; + }; + D05CC2651B69316F00E235A3 /* Display */ = { + isa = PBXGroup; + children = ( + D05CC2661B69316F00E235A3 /* Display.h */, + D05CC2681B69316F00E235A3 /* Info.plist */, + ); + path = Display; + sourceTree = ""; + }; + D05CC2711B69316F00E235A3 /* DisplayTests */ = { + isa = PBXGroup; + children = ( + D05CC2721B69316F00E235A3 /* DisplayTests.swift */, + D05CC2741B69316F00E235A3 /* Info.plist */, + ); + path = DisplayTests; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + D05CC2601B69316F00E235A3 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D05CC2671B69316F00E235A3 /* Display.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + D05CC2621B69316F00E235A3 /* Display */ = { + isa = PBXNativeTarget; + buildConfigurationList = D05CC2771B69316F00E235A3 /* Build configuration list for PBXNativeTarget "Display" */; + buildPhases = ( + D05CC25E1B69316F00E235A3 /* Sources */, + D05CC25F1B69316F00E235A3 /* Frameworks */, + D05CC2601B69316F00E235A3 /* Headers */, + D05CC2611B69316F00E235A3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = Display; + productName = Display; + productReference = D05CC2631B69316F00E235A3 /* Display.framework */; + productType = "com.apple.product-type.framework"; + }; + D05CC26C1B69316F00E235A3 /* DisplayTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = D05CC27A1B69316F00E235A3 /* Build configuration list for PBXNativeTarget "DisplayTests" */; + buildPhases = ( + D05CC2691B69316F00E235A3 /* Sources */, + D05CC26A1B69316F00E235A3 /* Frameworks */, + D05CC26B1B69316F00E235A3 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + D05CC2701B69316F00E235A3 /* PBXTargetDependency */, + ); + name = DisplayTests; + productName = DisplayTests; + productReference = D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + D05CC25A1B69316F00E235A3 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0700; + ORGANIZATIONNAME = Telegram; + TargetAttributes = { + D05CC2621B69316F00E235A3 = { + CreatedOnToolsVersion = 7.0; + }; + D05CC26C1B69316F00E235A3 = { + CreatedOnToolsVersion = 7.0; + }; + }; + }; + buildConfigurationList = D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = D05CC2591B69316F00E235A3; + productRefGroup = D05CC2641B69316F00E235A3 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + D05CC2621B69316F00E235A3 /* Display */, + D05CC26C1B69316F00E235A3 /* DisplayTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + D05CC2611B69316F00E235A3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D05CC26B1B69316F00E235A3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + D05CC25E1B69316F00E235A3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + D05CC2691B69316F00E235A3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + D05CC2701B69316F00E235A3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = D05CC2621B69316F00E235A3 /* Display */; + targetProxy = D05CC26F1B69316F00E235A3 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + D05CC2751B69316F00E235A3 /* 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + D05CC2761B69316F00E235A3 /* 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + D05CC2781B69316F00E235A3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + D05CC2791B69316F00E235A3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + D05CC27B1B69316F00E235A3 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + D05CC27C1B69316F00E235A3 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D05CC2751B69316F00E235A3 /* Debug */, + D05CC2761B69316F00E235A3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + D05CC2771B69316F00E235A3 /* Build configuration list for PBXNativeTarget "Display" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D05CC2781B69316F00E235A3 /* Debug */, + D05CC2791B69316F00E235A3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; + D05CC27A1B69316F00E235A3 /* Build configuration list for PBXNativeTarget "DisplayTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D05CC27B1B69316F00E235A3 /* Debug */, + D05CC27C1B69316F00E235A3 /* Release */, + ); + defaultConfigurationIsVisible = 0; + }; +/* End XCConfigurationList section */ + }; + rootObject = D05CC25A1B69316F00E235A3 /* Project object */; +} diff --git a/Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 0000000000..1752e17513 --- /dev/null +++ b/Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist new file mode 100644 index 0000000000..29bf3de275 --- /dev/null +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -0,0 +1,27 @@ + + + + + SchemeUserState + + Display.xcscheme + + orderHint + 0 + + + SuppressBuildableAutocreation + + D05CC2621B69316F00E235A3 + + primary + + + D05CC26C1B69316F00E235A3 + + primary + + + + + diff --git a/Display/Display.h b/Display/Display.h new file mode 100644 index 0000000000..94f35b8246 --- /dev/null +++ b/Display/Display.h @@ -0,0 +1,19 @@ +// +// Display.h +// Display +// +// Created by Peter on 29/07/15. +// Copyright © 2015 Telegram. All rights reserved. +// + +#import + +//! Project version number for Display. +FOUNDATION_EXPORT double DisplayVersionNumber; + +//! Project version string for Display. +FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/Display/Info.plist b/Display/Info.plist new file mode 100644 index 0000000000..d3de8eefb6 --- /dev/null +++ b/Display/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSPrincipalClass + + + diff --git a/DisplayTests/DisplayTests.swift b/DisplayTests/DisplayTests.swift new file mode 100644 index 0000000000..c2d1a8257a --- /dev/null +++ b/DisplayTests/DisplayTests.swift @@ -0,0 +1,36 @@ +// +// DisplayTests.swift +// DisplayTests +// +// Created by Peter on 29/07/15. +// Copyright © 2015 Telegram. All rights reserved. +// + +import XCTest +@testable import Display + +class DisplayTests: XCTestCase { + + override func setUp() { + super.setUp() + // Put setup code here. This method is called before the invocation of each test method in the class. + } + + override func tearDown() { + // Put teardown code here. This method is called after the invocation of each test method in the class. + super.tearDown() + } + + func testExample() { + // This is an example of a functional test case. + // Use XCTAssert and related functions to verify your tests produce the correct results. + } + + func testPerformanceExample() { + // This is an example of a performance test case. + self.measureBlock { + // Put the code you want to measure the time of here. + } + } + +} diff --git a/DisplayTests/Info.plist b/DisplayTests/Info.plist new file mode 100644 index 0000000000..ba72822e87 --- /dev/null +++ b/DisplayTests/Info.plist @@ -0,0 +1,24 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + BNDL + CFBundleShortVersionString + 1.0 + CFBundleSignature + ???? + CFBundleVersion + 1 + + From 0cf72b8a57f4a4df93e8dfdf916c75d75922fa2e Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 30 Jul 2015 01:09:30 +0300 Subject: [PATCH 002/245] no message --- .gitmodules | 3 + Display.xcodeproj/project.pbxproj | 291 ++++++++++++++- Display/BarButtonItemWrapper.swift | 49 +++ Display/CAAnimationUtils.swift | 32 ++ Display/CALayer+ImplicitAnimations.h | 20 ++ Display/CALayer+ImplicitAnimations.m | 142 ++++++++ Display/Display.h | 13 +- ...teractiveTransitionGestureRecognizer.swift | 44 +++ Display/NSBag.h | 9 + Display/NSBag.m | 64 ++++ Display/NavigationBackArrowLight@2x.png | Bin 0 -> 3096 bytes Display/NavigationBackButtonNode.swift | 143 ++++++++ Display/NavigationBar.swift | 120 +++++++ Display/NavigationBarProxy.h | 7 + Display/NavigationBarProxy.m | 67 ++++ Display/NavigationButtonNode.swift | 126 +++++++ Display/NavigationController.swift | 232 ++++++++++++ Display/NavigationControllerProxy.h | 7 + Display/NavigationControllerProxy.m | 16 + Display/NavigationItemTransitionState.swift | 4 + Display/NavigationItemWrapper.swift | 332 ++++++++++++++++++ Display/NavigationTitleNode.swift | 50 +++ Display/NavigationTransitionView.swift | 82 +++++ Display/NotificationCenterUtils.h | 9 + Display/NotificationCenterUtils.m | 51 +++ Display/RuntimeUtils.h | 20 ++ Display/RuntimeUtils.m | 56 +++ Display/UIBarButtonItem+Proxy.h | 15 + Display/UIBarButtonItem+Proxy.m | 83 +++++ Display/UIKitUtils.h | 7 + Display/UIKitUtils.m | 18 + Display/UIKitUtils.swift | 31 ++ Display/UINavigationItem+Proxy.h | 15 + Display/UINavigationItem+Proxy.m | 101 ++++++ Display/UIViewController+Navigation.h | 7 + Display/UIViewController+Navigation.m | 55 +++ Display/UIWindow+OrientationChange.h | 13 + Display/UIWindow+OrientationChange.m | 85 +++++ Display/ViewController.swift | 51 +++ Display/Window.swift | 134 +++++++ submodules/AsyncDisplayKit | 1 + 41 files changed, 2602 insertions(+), 3 deletions(-) create mode 100644 .gitmodules create mode 100644 Display/BarButtonItemWrapper.swift create mode 100644 Display/CAAnimationUtils.swift create mode 100644 Display/CALayer+ImplicitAnimations.h create mode 100644 Display/CALayer+ImplicitAnimations.m create mode 100644 Display/InteractiveTransitionGestureRecognizer.swift create mode 100644 Display/NSBag.h create mode 100644 Display/NSBag.m create mode 100644 Display/NavigationBackArrowLight@2x.png create mode 100644 Display/NavigationBackButtonNode.swift create mode 100644 Display/NavigationBar.swift create mode 100644 Display/NavigationBarProxy.h create mode 100644 Display/NavigationBarProxy.m create mode 100644 Display/NavigationButtonNode.swift create mode 100644 Display/NavigationController.swift create mode 100644 Display/NavigationControllerProxy.h create mode 100644 Display/NavigationControllerProxy.m create mode 100644 Display/NavigationItemTransitionState.swift create mode 100644 Display/NavigationItemWrapper.swift create mode 100644 Display/NavigationTitleNode.swift create mode 100644 Display/NavigationTransitionView.swift create mode 100644 Display/NotificationCenterUtils.h create mode 100644 Display/NotificationCenterUtils.m create mode 100644 Display/RuntimeUtils.h create mode 100644 Display/RuntimeUtils.m create mode 100644 Display/UIBarButtonItem+Proxy.h create mode 100644 Display/UIBarButtonItem+Proxy.m create mode 100644 Display/UIKitUtils.h create mode 100644 Display/UIKitUtils.m create mode 100644 Display/UIKitUtils.swift create mode 100644 Display/UINavigationItem+Proxy.h create mode 100644 Display/UINavigationItem+Proxy.m create mode 100644 Display/UIViewController+Navigation.h create mode 100644 Display/UIViewController+Navigation.m create mode 100644 Display/UIWindow+OrientationChange.h create mode 100644 Display/UIWindow+OrientationChange.m create mode 100644 Display/ViewController.swift create mode 100644 Display/Window.swift create mode 160000 submodules/AsyncDisplayKit diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000000..661f0a52ac --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/AsyncDisplayKit"] + path = submodules/AsyncDisplayKit + url = https://github.com/facebook/AsyncDisplayKit.git diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index d6efabdd51..b338b70377 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -10,6 +10,45 @@ D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; + D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */; }; + D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; + D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; + D05CC2B61B69339A00E235A3 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */; }; + D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; + D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; + D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */; }; + D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; }; + D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */; }; + D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EF1B6955D000E235A3 /* UIViewController+Navigation.m */; }; + D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F01B6955D000E235A3 /* UIViewController+Navigation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2F11B6955D000E235A3 /* UINavigationItem+Proxy.m */; }; + D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F21B6955D000E235A3 /* UINavigationItem+Proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2F31B6955D000E235A3 /* UIKitUtils.m */; }; + D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F41B6955D000E235A3 /* UIKitUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */; }; + D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2F61B6955D000E235A3 /* UIWindow+OrientationChange.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */; }; + D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3071B69575900E235A3 /* NSBag.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3051B69575900E235A3 /* NSBag.m */; }; + D05CC3081B69575900E235A3 /* NSBag.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3061B69575900E235A3 /* NSBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */; }; + D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30A1B695A9500E235A3 /* NavigationBar.swift */; }; + D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */; }; + D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */; }; + D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */; }; + D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */; }; + D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */; }; + D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */; }; + D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */; }; + D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */; }; + D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3141B695A9600E235A3 /* NavigationControllerProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */; }; + D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; + D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -20,6 +59,41 @@ remoteGlobalIDString = D05CC2621B69316F00E235A3; remoteInfo = Display; }; + D05CC2AB1B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09AC195D04C000B7D73C; + remoteInfo = AsyncDisplayKit; + }; + D05CC2AD1B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 058D09BC195D04C000B7D73C; + remoteInfo = AsyncDisplayKitTests; + }; + D05CC2AF1B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; + remoteInfo = AsyncDisplayKitTestHost; + }; + D05CC2B11B6932E900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 2; + remoteGlobalIDString = B35061DA1B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; + D05CC32A1B697C0900E235A3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + proxyType = 1; + remoteGlobalIDString = B35061D91B010EDF0018CF92; + remoteInfo = "AsyncDisplayKit-iOS"; + }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -29,6 +103,45 @@ D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DisplayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = ""; }; D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = ""; }; + D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; + D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; + D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = submodules/AsyncDisplayKit/AsyncDisplayKit.xcodeproj; sourceTree = ""; }; + D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; + D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CALayer+ImplicitAnimations.m"; sourceTree = ""; }; + D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CALayer+ImplicitAnimations.h"; sourceTree = ""; }; + D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; + D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuntimeUtils.h; sourceTree = ""; }; + D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitUtils.swift; sourceTree = ""; }; + D05CC2EF1B6955D000E235A3 /* UIViewController+Navigation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIViewController+Navigation.m"; sourceTree = ""; }; + D05CC2F01B6955D000E235A3 /* UIViewController+Navigation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIViewController+Navigation.h"; sourceTree = ""; }; + D05CC2F11B6955D000E235A3 /* UINavigationItem+Proxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UINavigationItem+Proxy.m"; sourceTree = ""; }; + D05CC2F21B6955D000E235A3 /* UINavigationItem+Proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UINavigationItem+Proxy.h"; sourceTree = ""; }; + D05CC2F31B6955D000E235A3 /* UIKitUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIKitUtils.m; sourceTree = ""; }; + D05CC2F41B6955D000E235A3 /* UIKitUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = UIKitUtils.h; sourceTree = ""; }; + D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIWindow+OrientationChange.m"; sourceTree = ""; }; + D05CC2F61B6955D000E235A3 /* UIWindow+OrientationChange.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIWindow+OrientationChange.h"; sourceTree = ""; }; + D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NotificationCenterUtils.m; sourceTree = ""; }; + D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationCenterUtils.h; sourceTree = ""; }; + D05CC3051B69575900E235A3 /* NSBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBag.m; sourceTree = ""; }; + D05CC3061B69575900E235A3 /* NSBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBag.h; sourceTree = ""; }; + D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionView.swift; sourceTree = ""; }; + D05CC30A1B695A9500E235A3 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = ""; }; + D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemWrapper.swift; sourceTree = ""; }; + D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemTransitionState.swift; sourceTree = ""; }; + D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBackButtonNode.swift; sourceTree = ""; }; + D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationButtonNode.swift; sourceTree = ""; }; + D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTitleNode.swift; sourceTree = ""; }; + D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButtonItemWrapper.swift; sourceTree = ""; }; + D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+Proxy.m"; sourceTree = ""; }; + D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+Proxy.h"; sourceTree = ""; }; + D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationControllerProxy.m; sourceTree = ""; }; + D05CC3141B695A9600E235A3 /* NavigationControllerProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavigationControllerProxy.h; sourceTree = ""; }; + D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NavigationBarProxy.h; sourceTree = ""; }; + D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationBarProxy.m; sourceTree = ""; }; + D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; + D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -36,6 +149,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D05CC2B61B69339A00E235A3 /* AsyncDisplayKit.framework in Frameworks */, + D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -53,6 +168,7 @@ D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( + D05CC2A31B6932D500E235A3 /* Frameworks */, D05CC2651B69316F00E235A3 /* Display */, D05CC2711B69316F00E235A3 /* DisplayTests */, D05CC2641B69316F00E235A3 /* Products */, @@ -71,8 +187,11 @@ D05CC2651B69316F00E235A3 /* Display */ = { isa = PBXGroup; children = ( - D05CC2661B69316F00E235A3 /* Display.h */, - D05CC2681B69316F00E235A3 /* Info.plist */, + D05CC3001B6955D500E235A3 /* Utils */, + D05CC3211B695AA600E235A3 /* Navigation */, + D05CC2A11B69326C00E235A3 /* Window.swift */, + D05CC2E21B69552C00E235A3 /* ViewController.swift */, + D05CC2E11B69534100E235A3 /* Supporting Files */, ); path = Display; sourceTree = ""; @@ -86,6 +205,84 @@ path = DisplayTests; sourceTree = ""; }; + D05CC2A31B6932D500E235A3 /* Frameworks */ = { + isa = PBXGroup; + children = ( + D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */, + D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; + D05CC2A51B6932E800E235A3 /* Products */ = { + isa = PBXGroup; + children = ( + D05CC2AC1B6932E900E235A3 /* libAsyncDisplayKit.a */, + D05CC2AE1B6932E900E235A3 /* AsyncDisplayKitTests.xctest */, + D05CC2B01B6932E900E235A3 /* AsyncDisplayKitTestHost.app */, + D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */, + ); + name = Products; + sourceTree = ""; + }; + D05CC2E11B69534100E235A3 /* Supporting Files */ = { + isa = PBXGroup; + children = ( + D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */, + D05CC2661B69316F00E235A3 /* Display.h */, + D05CC2681B69316F00E235A3 /* Info.plist */, + ); + name = "Supporting Files"; + sourceTree = ""; + }; + D05CC3001B6955D500E235A3 /* Utils */ = { + isa = PBXGroup; + children = ( + D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */, + D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */, + D05CC2F01B6955D000E235A3 /* UIViewController+Navigation.h */, + D05CC2EF1B6955D000E235A3 /* UIViewController+Navigation.m */, + D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */, + D05CC2F41B6955D000E235A3 /* UIKitUtils.h */, + D05CC2F31B6955D000E235A3 /* UIKitUtils.m */, + D05CC2F21B6955D000E235A3 /* UINavigationItem+Proxy.h */, + D05CC2F11B6955D000E235A3 /* UINavigationItem+Proxy.m */, + D05CC2F61B6955D000E235A3 /* UIWindow+OrientationChange.h */, + D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */, + D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */, + D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */, + D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */, + D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */, + D05CC3061B69575900E235A3 /* NSBag.h */, + D05CC3051B69575900E235A3 /* NSBag.m */, + D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */, + D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */, + D05CC3141B695A9600E235A3 /* NavigationControllerProxy.h */, + D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */, + D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */, + D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */, + D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */, + ); + name = Utils; + sourceTree = ""; + }; + D05CC3211B695AA600E235A3 /* Navigation */ = { + isa = PBXGroup; + children = ( + D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */, + D05CC30A1B695A9500E235A3 /* NavigationBar.swift */, + D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */, + D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, + D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */, + D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */, + D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */, + D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */, + D05CC29F1B69326400E235A3 /* NavigationController.swift */, + D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, + ); + name = Navigation; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -93,7 +290,18 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */, + D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */, + D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */, + D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */, + D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */, + D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */, + D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */, + D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */, + D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, + D05CC3081B69575900E235A3 /* NSBag.h in Headers */, D05CC2671B69316F00E235A3 /* Display.h in Headers */, + D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -112,6 +320,7 @@ buildRules = ( ); dependencies = ( + D05CC32B1B697C0900E235A3 /* PBXTargetDependency */, ); name = Display; productName = Display; @@ -142,6 +351,7 @@ D05CC25A1B69316F00E235A3 /* Project object */ = { isa = PBXProject; attributes = { + LastSwiftUpdateCheck = 0700; LastUpgradeCheck = 0700; ORGANIZATIONNAME = Telegram; TargetAttributes = { @@ -163,6 +373,12 @@ mainGroup = D05CC2591B69316F00E235A3; productRefGroup = D05CC2641B69316F00E235A3 /* Products */; projectDirPath = ""; + projectReferences = ( + { + ProductGroup = D05CC2A51B6932E800E235A3 /* Products */; + ProjectRef = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; + }, + ); projectRoot = ""; targets = ( D05CC2621B69316F00E235A3 /* Display */, @@ -171,11 +387,43 @@ }; /* End PBXProject section */ +/* Begin PBXReferenceProxy section */ + D05CC2AC1B6932E900E235A3 /* libAsyncDisplayKit.a */ = { + isa = PBXReferenceProxy; + fileType = archive.ar; + path = libAsyncDisplayKit.a; + remoteRef = D05CC2AB1B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D05CC2AE1B6932E900E235A3 /* AsyncDisplayKitTests.xctest */ = { + isa = PBXReferenceProxy; + fileType = wrapper.cfbundle; + path = AsyncDisplayKitTests.xctest; + remoteRef = D05CC2AD1B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D05CC2B01B6932E900E235A3 /* AsyncDisplayKitTestHost.app */ = { + isa = PBXReferenceProxy; + fileType = wrapper.application; + path = AsyncDisplayKitTestHost.app; + remoteRef = D05CC2AF1B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; + D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */ = { + isa = PBXReferenceProxy; + fileType = wrapper.framework; + path = AsyncDisplayKit.framework; + remoteRef = D05CC2B11B6932E900E235A3 /* PBXContainerItemProxy */; + sourceTree = BUILT_PRODUCTS_DIR; + }; +/* End PBXReferenceProxy section */ + /* Begin PBXResourcesBuildPhase section */ D05CC2611B69316F00E235A3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -193,6 +441,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, + D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, + D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, + D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, + D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, + D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, + D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, + D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, + D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, + D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, + D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, + D05CC3071B69575900E235A3 /* NSBag.m in Sources */, + D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, + D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, + D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, + D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, + D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, + D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, + D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, + D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, + D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, + D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, + D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, + D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -212,6 +485,11 @@ target = D05CC2621B69316F00E235A3 /* Display */; targetProxy = D05CC26F1B69316F00E235A3 /* PBXContainerItemProxy */; }; + D05CC32B1B697C0900E235A3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + name = "AsyncDisplayKit-iOS"; + targetProxy = D05CC32A1B697C0900E235A3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -307,28 +585,35 @@ D05CC2781B69316F00E235A3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; INFOPLIST_FILE = Display/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; D05CC2791B69316F00E235A3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = NO; INFOPLIST_FILE = Display/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -375,6 +660,7 @@ D05CC2791B69316F00E235A3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; D05CC27A1B69316F00E235A3 /* Build configuration list for PBXNativeTarget "DisplayTests" */ = { isa = XCConfigurationList; @@ -383,6 +669,7 @@ D05CC27C1B69316F00E235A3 /* Release */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; }; /* End XCConfigurationList section */ }; diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift new file mode 100644 index 0000000000..fab8fa1752 --- /dev/null +++ b/Display/BarButtonItemWrapper.swift @@ -0,0 +1,49 @@ +import UIKit +import AsyncDisplayKit + +internal class BarButtonItemWrapper { + let parentNode: ASDisplayNode + let barButtonItem: UIBarButtonItem + let layoutNeeded: () -> () + + let buttonNode: NavigationButtonNode + + private var setEnabledListenerKey: Int! + private var setTitleListenerKey: Int! + + init(parentNode: ASDisplayNode, barButtonItem: UIBarButtonItem, layoutNeeded: () -> ()) { + self.parentNode = parentNode + self.barButtonItem = barButtonItem + self.layoutNeeded = layoutNeeded + + self.buttonNode = NavigationButtonNode() + self.buttonNode.pressed = { [weak self] in + self?.barButtonItem.performActionOnTarget() + return + } + self.parentNode.addSubnode(self.buttonNode) + + self.setEnabledListenerKey = barButtonItem.addSetEnabledListener({ [weak self] enabled in + self?.buttonNode.enabled = enabled + return + }) + + self.setTitleListenerKey = barButtonItem.addSetTitleListener({ [weak self] title in + self?.buttonNode.text = title + if let layoutNeeded = self?.layoutNeeded { + layoutNeeded() + } + return + }) + + self.buttonNode.text = barButtonItem.title ?? "" + self.buttonNode.enabled = barButtonItem.enabled ?? true + self.buttonNode.bold = (barButtonItem.style ?? UIBarButtonItemStyle.Plain) == UIBarButtonItemStyle.Done + } + + deinit { + self.barButtonItem.removeSetTitleListener(self.setTitleListenerKey) + self.barButtonItem.removeSetEnabledListener(self.setEnabledListenerKey) + self.buttonNode.removeFromSupernode() + } +} diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift new file mode 100644 index 0000000000..6e5efec0fa --- /dev/null +++ b/Display/CAAnimationUtils.swift @@ -0,0 +1,32 @@ +import UIKit + +extension CALayer { + internal func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval) { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.removedOnCompletion = true + animation.fillMode = kCAFillModeForwards + animation.speed = speed + + self.addAnimation(animation, forKey: keyPath) + + self.setValue(to, forKey: keyPath) + } + + internal func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) + } + + internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { + self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) + } +} diff --git a/Display/CALayer+ImplicitAnimations.h b/Display/CALayer+ImplicitAnimations.h new file mode 100644 index 0000000000..541efc951b --- /dev/null +++ b/Display/CALayer+ImplicitAnimations.h @@ -0,0 +1,20 @@ +#import + +@interface CALayer (ImplicitAnimations) + ++ (void)beginRecordingChanges; ++ (NSArray *)endRecordingChanges; + +@end + +@interface CALayerAnimation : NSObject + +@property (nonatomic, weak, readonly) CALayer *layer; + +@property (nonatomic, readonly) CGRect startBounds; +@property (nonatomic, readonly) CGRect endBounds; + +@property (nonatomic, readonly) CGPoint startPosition; +@property (nonatomic, readonly) CGPoint endPosition; + +@end diff --git a/Display/CALayer+ImplicitAnimations.m b/Display/CALayer+ImplicitAnimations.m new file mode 100644 index 0000000000..2229d1694f --- /dev/null +++ b/Display/CALayer+ImplicitAnimations.m @@ -0,0 +1,142 @@ +#import "CALayer+ImplicitAnimations.h" + +#import + +#import "RuntimeUtils.h" +#import + +static bool recordingChanges = false; +static NSMutableArray *currentLayerAnimations() +{ + static NSMutableArray *array = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + array = [[NSMutableArray alloc] init]; + }); + + return array; +} + +@implementation CALayerAnimation + +- (instancetype)initWithLayer:(CALayer *)layer +{ + self = [super init]; + if (self != nil) + { + _layer = layer; + + _startBounds = layer.bounds; + _startPosition = layer.position; + + _endBounds = _startBounds; + _endPosition = _startPosition; + } + return self; +} + +- (void)setEndBounds:(CGRect)endBounds +{ + _endBounds = endBounds; +} + +- (void)setEndPosition:(CGPoint)endPosition +{ + _endPosition = endPosition; +} + +@end + +@interface CALayer (_ca836a62_) + +@end + +@implementation CALayer (_ca836a62_) + +- (void)_ca836a62_setBounds:(CGRect)bounds +{ + if (recordingChanges && [self.delegate isKindOfClass:[ASDisplayNode class]]) + { + CALayerAnimation *animation = nil; + for (CALayerAnimation *listAnimation in currentLayerAnimations()) + { + if (listAnimation.layer == self) + { + animation = listAnimation; + break; + } + } + if (animation == nil) + { + animation = [[CALayerAnimation alloc] initWithLayer:self]; + [currentLayerAnimations() addObject:animation]; + } + [animation setEndBounds:bounds]; + } + + [self _ca836a62_setBounds:bounds]; +} + +- (void)_ca836a62_setPosition:(CGPoint)position +{ + if (recordingChanges && [self.delegate isKindOfClass:[ASDisplayNode class]]) + { + CALayerAnimation *animation = nil; + for (CALayerAnimation *listAnimation in currentLayerAnimations()) + { + if (listAnimation.layer == self) + { + animation = listAnimation; + break; + } + } + if (animation == nil) + { + animation = [[CALayerAnimation alloc] initWithLayer:self]; + [currentLayerAnimations() addObject:animation]; + } + [animation setEndPosition:position]; + } + + [self _ca836a62_setPosition:position]; +} + +@end + +@interface LayerAnimationExtensions : NSObject + +@end + +@implementation LayerAnimationExtensions + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setBounds:) newSelector:@selector(_ca836a62_setBounds:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setPosition:) newSelector:@selector(_ca836a62_setPosition:)]; + }); +} + +@end + +@implementation CALayer (ImplicitAnimations) + ++ (void)beginRecordingChanges +{ + recordingChanges = true; + [currentLayerAnimations() removeAllObjects]; +} + ++ (NSArray *)endRecordingChanges +{ + recordingChanges = false; + NSArray *array = [[NSArray alloc] initWithArray:currentLayerAnimations()]; + [currentLayerAnimations() removeAllObjects]; + + return array; +} + +@end diff --git a/Display/Display.h b/Display/Display.h index 94f35b8246..8cb75256da 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -16,4 +16,15 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; // In this header, you should import all the public headers of your framework using statements like #import - +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import +#import diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift new file mode 100644 index 0000000000..107d85bdf6 --- /dev/null +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -0,0 +1,44 @@ +import Foundation +import UIKit + +class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { + var validatedGesture = false + var firstLocation: CGPoint = CGPoint() + + override init(target: AnyObject?, action: Selector) { + super.init(target: target, action: action) + + self.maximumNumberOfTouches = 1 + } + + override func reset() { + super.reset() + + validatedGesture = false + } + + override func touchesBegan(touches: Set, withEvent event: UIEvent) { + super.touchesBegan(touches, withEvent: event) + + self.firstLocation = touches.first!.locationInView(self.view) + } + + override func touchesMoved(touches: Set, withEvent event: UIEvent) { + let location = touches.first!.locationInView(self.view) + let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) + + if !validatedGesture { + if translation.x < 0.0 { + self.state = .Failed + } else if abs(translation.y) >= 2.0 { + self.state = .Failed + } else if translation.x >= 3.0 && translation.x / 3.0 > translation.y { + validatedGesture = true + } + } + + if validatedGesture { + super.touchesMoved(touches, withEvent: event) + } + } +} diff --git a/Display/NSBag.h b/Display/NSBag.h new file mode 100644 index 0000000000..739fe92552 --- /dev/null +++ b/Display/NSBag.h @@ -0,0 +1,9 @@ +#import + +@interface NSBag : NSObject + +- (NSInteger)addItem:(id)item; +- (void)enumerateItems:(void (^)(id))block; +- (void)removeItem:(NSInteger)key; + +@end diff --git a/Display/NSBag.m b/Display/NSBag.m new file mode 100644 index 0000000000..4da04f5737 --- /dev/null +++ b/Display/NSBag.m @@ -0,0 +1,64 @@ +#import "NSBag.h" + +@interface NSBag () +{ + NSInteger _nextKey; + NSMutableArray *_items; + NSMutableArray *_itemKeys; +} + +@end + +@implementation NSBag + +- (instancetype)init +{ + self = [super init]; + if (self != nil) + { + _items = [[NSMutableArray alloc] init]; + _itemKeys = [[NSMutableArray alloc] init]; + } + return self; +} + +- (NSInteger)addItem:(id)item +{ + if (item == nil) + return -1; + + NSInteger key = _nextKey; + [_items addObject:item]; + [_itemKeys addObject:@(key)]; + _nextKey++; + + return key; +} + +- (void)enumerateItems:(void (^)(id))block +{ + if (block) + { + for (id item in _items) + { + block(item); + } + } +} + +- (void)removeItem:(NSInteger)key +{ + NSUInteger index = 0; + for (NSNumber *itemKey in _itemKeys) + { + if ([itemKey integerValue] == key) + { + [_items removeObjectAtIndex:index]; + [_itemKeys removeObjectAtIndex:index]; + break; + } + index++; + } +} + +@end diff --git a/Display/NavigationBackArrowLight@2x.png b/Display/NavigationBackArrowLight@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..ca8cabf210a6d5215672a1a362722e6dd805988a GIT binary patch literal 3096 zcmV+z4CnKSP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0003)Nkl!53{XkK(@d~YWrpu=mc2<3D})u z;nEEgkI0{@0-M~``w}a+#_<9iB9I2z52edq$c%U(7h;9nhy@BES}2TY zAPw>?ArhoT?j>XjDvDf!)W~q3xjuZeAm`9*2*8yR`37FS$OuWu5qJY0qT%EQXeOR{ zk0K-VDU{?6*p!M}Q!hCIPr@cK?K%nDMzG4TeM4AfJS%RjavV$-Ryl70GbVkHL@c%a zx&)-fno@0xgzY5El7d2*HP^W@Pf~SZ-t2A0BHe@ui}ptvY*hjxcl8t&?vFHBxIfZh m;r>X2-Ts%w(^9`^eg*(|^s UIFont { + return UIFont.systemFontOfSize(17.0) + } + + private func attributesForCurrentState() -> [String : AnyObject] { + return [ + NSFontAttributeName: self.fontForCurrentState(), + NSForegroundColorAttributeName: self.enabled ? UIColor.blueColor() : UIColor.grayColor() + ] + } + + var suspendLayout = false + + let arrow: ASDisplayNode + let label: ASTextNode + + private let arrowSpacing: CGFloat = 4.0 + + private var _text: String = "" + var text: String { + get { + return self._text + } + set(value) { + self._text = value + self.label.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.invalidateCalculatedLayout() + } + } + + private var touchCount = 0 + var pressed: () -> () = {} + + override init() { + self.arrow = ASDisplayNode() + self.label = ASTextNode() + + super.init() + + self.userInteractionEnabled = true + self.exclusiveTouch = true + self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) + self.displaysAsynchronously = false + + self.arrow.displaysAsynchronously = false + self.label.displaysAsynchronously = false + + self.addSubnode(self.arrow) + let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil) + self.arrow.contents = arrowImage?.CGImage + self.arrow.frame = CGRect(origin: CGPoint(), size: arrowImage?.size ?? CGSize()) + + self.addSubnode(self.label) + } + + public override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize { + self.label.measure(CGSize(width: max(0.0, constrainedSize.width - self.arrow.frame.size.width - self.arrowSpacing), height: constrainedSize.height)) + + return CGSize(width: self.arrow.frame.size.width + self.arrowSpacing + self.label.calculatedSize.width, height: max(self.arrow.frame.size.height, self.label.calculatedSize.height)) + } + + var labelFrame: CGRect { + get { + return CGRect(x: self.arrow.frame.size.width + self.arrowSpacing, y: floor((self.frame.size.height - self.label.calculatedSize.height) / 2.0), width: self.label.calculatedSize.width, height: self.label.calculatedSize.height) + } + } + + public override func layout() { + super.layout() + + if self.suspendLayout { + return + } + + self.arrow.frame = CGRect(x: 0.0, y: floor((self.frame.size.height - arrow.frame.size.height) / 2.0), width: self.arrow.frame.size.width, height: self.arrow.frame.size.height) + + self.label.frame = self.labelFrame + } + + private func touchInsideApparentBounds(touch: UITouch) -> Bool { + var apparentBounds = self.bounds + let hitTestSlop = self.hitTestSlop + apparentBounds.origin.x += hitTestSlop.left + apparentBounds.size.width -= hitTestSlop.left + hitTestSlop.right + apparentBounds.origin.y += hitTestSlop.top + apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom + + return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) + } + + public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + super.touchesBegan(touches, withEvent: event) + self.touchCount += touches.count + self.updateHighlightedState(true, animated: false) + } + + public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + super.touchesMoved(touches, withEvent: event) + + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + } + + public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + super.touchesEnded(touches, withEvent: event) + self.updateHighlightedState(false, animated: false) + + let previousTouchCount = self.touchCount + self.touchCount = max(0, self.touchCount - touches.count) + + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + self.pressed() + } + } + + public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + super.touchesCancelled(touches, withEvent: event) + + self.touchCount = max(0, self.touchCount - touches.count) + self.updateHighlightedState(false, animated: false) + } + + private var _highlighted = false + private func updateHighlightedState(highlighted: Bool, animated: Bool) { + if _highlighted != highlighted { + _highlighted = highlighted + + let alpha: CGFloat = !enabled ? 1.0 : (highlighted ? 0.4 : 1.0) + + if animated { + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: { () -> Void in + self.alpha = alpha + }, completion: nil) + } + else { + self.alpha = alpha + } + } + } +} diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift new file mode 100644 index 0000000000..186ef84e77 --- /dev/null +++ b/Display/NavigationBar.swift @@ -0,0 +1,120 @@ +import UIKit +import AsyncDisplayKit + +private enum ItemAnimation { + case None + case Push + case Pop +} + +public class NavigationBar: ASDisplayNode { + private var topItem: UINavigationItem? + private var topItemWrapper: NavigationItemWrapper? + + private var tempItem: UINavigationItem? + private var tempItemWrapper: NavigationItemWrapper? + + var backPressed: () -> () = { } + + private var collapsed: Bool { + get { + return self.frame.size.height < (20.0 + 44.0) + } + } + + var _proxy: NavigationBarProxy? + var proxy: NavigationBarProxy? { + get { + return self._proxy + } + set(value) { + self._proxy = value + self._proxy?.setItemsProxy = {[weak self] previousItems, items, animated in + if let strongSelf = self { + var animation = ItemAnimation.None + if animated && previousItems.count != 0 && items.count != 0 { + if previousItems.filter({element in element === items[items.count - 1]}).count != 0 { + animation = .Pop + } + else { + animation = .Push + } + } + + let count = items.count + if count != 0 { + strongSelf.updateTopItem(items[count - 1] as! UINavigationItem, previousItem: count >= 2 ? (items[count - 2] as! UINavigationItem) : nil, animation: animation) + } + } + return + } + } + } + let stripeView: UIView + + public override init() { + stripeView = UIView() + stripeView.backgroundColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) + + super.init() + + self.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0) + + self.view.addSubview(stripeView) + } + + private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { + if self.topItem !== item { + let previousTopItemWrapper = self.topItemWrapper + self.topItemWrapper = nil + + self.topItem = item + self.topItemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: previousItem) + self.topItemWrapper?.backPressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + + self.topItemWrapper?.layoutItems() + + switch animation { + case .None: + break + case .Push: + self.topItemWrapper?.animatePush(previousTopItemWrapper, duration: 0.3) + break + case .Pop: + self.topItemWrapper?.animatePop(previousTopItemWrapper, duration: 0.3) + break + } + } + } + + public func beginInteractivePopProgress(previousItem: UINavigationItem, evenMorePreviousItem: UINavigationItem?) { + self.tempItem = previousItem + self.tempItemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: previousItem, previousNavigationItem: evenMorePreviousItem) + + self.tempItemWrapper?.layoutItems() + + self.setInteractivePopProgress(0.0) + } + + public func endInteractivePopProgress() { + self.tempItem = nil + self.tempItemWrapper = nil + } + + public func setInteractivePopProgress(progress: CGFloat) { + if let topItemWrapper = self.topItemWrapper { + self.tempItemWrapper?.setInteractivePopProgress(progress, previousItemWrapper: topItemWrapper) + } + } + + public override func layout() { + self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height - 0.5, width: self.frame.size.width, height: 0.5) + + self.topItemWrapper?.layoutItems() + self.tempItemWrapper?.layoutItems() + } +} diff --git a/Display/NavigationBarProxy.h b/Display/NavigationBarProxy.h new file mode 100644 index 0000000000..9936b4726a --- /dev/null +++ b/Display/NavigationBarProxy.h @@ -0,0 +1,7 @@ +#import + +@interface NavigationBarProxy : UINavigationBar + +@property (nonatomic, copy) void (^setItemsProxy)(NSArray *, NSArray *, bool); + +@end diff --git a/Display/NavigationBarProxy.m b/Display/NavigationBarProxy.m new file mode 100644 index 0000000000..9f5700c13b --- /dev/null +++ b/Display/NavigationBarProxy.m @@ -0,0 +1,67 @@ +#import "NavigationBarProxy.h" + +@interface NavigationBarProxy () +{ + NSArray *_items; +} + +@end + +@implementation NavigationBarProxy + +- (instancetype)initWithFrame:(CGRect)frame +{ + self = [super initWithFrame:frame]; + if (self != nil) + { + } + return self; +} + +- (void)pushNavigationItem:(UINavigationItem *)item animated:(BOOL)animated +{ + [self setItems:[[self items] arrayByAddingObject:item] animated:animated]; +} + +- (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated +{ + NSMutableArray *items = [[NSMutableArray alloc] initWithArray:[self items]]; + UINavigationItem *lastItem = [items lastObject]; + [items removeLastObject]; + [self setItems:items animated:animated]; + return lastItem; +} + +- (UINavigationItem *)topItem +{ + return [[self items] lastObject]; +} + +- (UINavigationItem *)backItem +{ + NSLog(@"backItem"); + return nil; +} + +- (NSArray *)items +{ + if (_items == nil) + return @[]; + return _items; +} + +- (void)setItems:(NSArray *)items +{ + [self setItems:items animated:false]; +} + +- (void)setItems:(NSArray *)items animated:(BOOL)animated +{ + NSArray *previousItems = _items; + _items = items; + + if (_setItemsProxy) + _setItemsProxy(previousItems, items, animated); +} + +@end diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift new file mode 100644 index 0000000000..80c63b657a --- /dev/null +++ b/Display/NavigationButtonNode.swift @@ -0,0 +1,126 @@ +import UIKit +import AsyncDisplayKit + +public class NavigationButtonNode: ASTextNode { + private func fontForCurrentState() -> UIFont { + return self.bold ? UIFont.boldSystemFontOfSize(17.0) : UIFont.systemFontOfSize(17.0) + } + + private func attributesForCurrentState() -> [String : AnyObject] { + return [ + NSFontAttributeName: self.fontForCurrentState(), + NSForegroundColorAttributeName: self.enabled ? UIColor.blueColor() : UIColor.grayColor() + ] + } + + private var _text: String? + public var text: String { + get { + return _text ?? "" + } + set(value) { + _text = value + + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + + private var _bold: Bool = false + public var bold: Bool { + get { + return _bold + } + set(value) { + if _bold != value { + _bold = value + + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + + private var touchCount = 0 + public var pressed: () -> () = {} + + public override init() { + super.init() + + self.userInteractionEnabled = true + self.exclusiveTouch = true + self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) + self.displaysAsynchronously = false + } + + private func touchInsideApparentBounds(touch: UITouch) -> Bool { + var apparentBounds = self.bounds + let hitTestSlop = self.hitTestSlop + apparentBounds.origin.x += hitTestSlop.left + apparentBounds.size.width -= hitTestSlop.left + hitTestSlop.right + apparentBounds.origin.y += hitTestSlop.top + apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom + + return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) + } + + public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + super.touchesBegan(touches, withEvent: event) + self.touchCount += touches.count + self.updateHighlightedState(true, animated: false) + } + + public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + super.touchesMoved(touches, withEvent: event) + + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + } + + public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + super.touchesEnded(touches, withEvent: event) + self.updateHighlightedState(false, animated: false) + + let previousTouchCount = self.touchCount + self.touchCount = max(0, self.touchCount - touches.count) + + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + self.pressed() + } + } + + public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + super.touchesCancelled(touches, withEvent: event) + + self.touchCount = max(0, self.touchCount - touches.count) + self.updateHighlightedState(false, animated: false) + } + + private var _highlighted = false + private func updateHighlightedState(highlighted: Bool, animated: Bool) { + if _highlighted != highlighted { + _highlighted = highlighted + + let alpha: CGFloat = !enabled ? 1.0 : (highlighted ? 0.4 : 1.0) + + if animated { + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: { () -> Void in + self.alpha = alpha + }, completion: nil) + } + else { + self.alpha = alpha + } + } + } + + public override var enabled: Bool { + get { + return super.enabled + } + set(value) { + if self.enabled != value { + super.enabled = value + + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } +} diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift new file mode 100644 index 0000000000..a8d88014ba --- /dev/null +++ b/Display/NavigationController.swift @@ -0,0 +1,232 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate { + private var _navigationBar: NavigationBar? + private var navigationTransitionCoordinator: NavigationTransitionCoordinator? + + public override init() { + super.init() + self._navigationBar = NavigationBar() + self._navigationBar?.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) + self._navigationBar?.proxy = self.navigationBar as? NavigationBarProxy + self._navigationBar?.backPressed = { [weak self] in + if self?.viewControllers.count > 1 { + self?.popViewControllerAnimated(true) + } + return + } + } + + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func loadView() { + super.loadView() + + if let _navigationBar = self._navigationBar { + self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) + } + self.navigationBar.removeFromSuperview() + + self._navigationBar?.frame = navigationBarFrame(self.view.frame.size) + + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("panGesture:")) + panRecognizer.delegate = self + panRecognizer.cancelsTouchesInView = true + self.view.addGestureRecognizer(panRecognizer) + + if self.topViewController != nil { + self.topViewController?.view.frame = CGRect(origin: CGPoint(), size: self.view.frame.size) + } + } + + func panGesture(recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case UIGestureRecognizerState.Began: + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillDisappear(true) + let topView = topController.view + bottomController.viewWillAppear(true) + let bottomView = bottomController.view + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar!) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + self._navigationBar?.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil) + } + case UIGestureRecognizerState.Changed: + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + let translation = recognizer.translationInView(self.view).x + navigationTransitionCoordinator.progress = max(0.0, min(1.0, translation / self.view.frame.width)) + } + case UIGestureRecognizerState.Ended: + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + let velocity = recognizer.velocityInView(self.view).x + + if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { + navigationTransitionCoordinator.animateCompletion(velocity, completion: { + self.navigationTransitionCoordinator = nil + + self._navigationBar?.endInteractivePopProgress() + + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + self.popViewControllerAnimated(false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + topController.viewDidDisappear(true) + bottomController.viewDidAppear(true) + } + }) + } + else { + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + navigationTransitionCoordinator.animateCancel({ + self.navigationTransitionCoordinator = nil + + self._navigationBar?.endInteractivePopProgress() + + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + } + case .Cancelled: + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewWillAppear(true) + bottomController.viewWillDisappear(true) + } + + navigationTransitionCoordinator.animateCancel({ + self.navigationTransitionCoordinator = nil + + if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + topController.viewDidAppear(true) + bottomController.viewDidDisappear(true) + } + }) + } + default: + break + } + } + + public override func pushViewController(viewController: UIViewController, animated: Bool) { + var controllers = self.viewControllers + controllers.append(viewController) + self.setViewControllers(controllers, animated: animated) + } + + public override func popViewControllerAnimated(animated: Bool) -> UIViewController? { + var controller: UIViewController? + var controllers = self.viewControllers + if controllers.count != 0 { + controller = controllers[controllers.count - 1] as UIViewController + controllers.removeAtIndex(controllers.count - 1) + self.setViewControllers(controllers, animated: animated) + } + return controller + } + + public override func setViewControllers(viewControllers: [UIViewController], animated: Bool) { + if viewControllers.count > 0 { + let topViewController = viewControllers[viewControllers.count - 1] as UIViewController + + if let controller = topViewController as? WindowContentController { + controller.setViewSize(self.view.bounds.size, duration: 0.0) + } else { + topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) + } + } + + super.setViewControllers(viewControllers, animated: animated) + } + + private func navigationBarFrame(size: CGSize) -> CGRect { + let condensedBar = (size.height < size.width || size.height <= 320.0) && size.height < 768.0 + return CGRect(x: 0.0, y: 0.0, width: size.width, height: 20.0 + (size.height >= size.width ? 44.0 : 32.0)) + } + + public func setViewSize(toSize: CGSize, duration: NSTimeInterval) { + if duration > DBL_EPSILON { + animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height), duration: duration) + } + else { + self.view.frame = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height) + } + + if duration > DBL_EPSILON { + animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(toSize), duration: duration) + } + else { + self._navigationBar?.frame = self.navigationBarFrame(toSize) + } + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + + if self.viewControllers.count >= 2 { + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + if let controller = bottomController as? WindowContentController { + controller.setViewSize(toSize, duration: duration) + } + bottomController.view.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + } + } + + if let topViewController = self.topViewController { + if let controller = topViewController as? WindowContentController { + controller.setViewSize(toSize, duration: duration) + } else { + topViewController.view.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + } + } + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + navigationTransitionCoordinator.updateProgress() + } + } + + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } + + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } +} diff --git a/Display/NavigationControllerProxy.h b/Display/NavigationControllerProxy.h new file mode 100644 index 0000000000..02e4fb6208 --- /dev/null +++ b/Display/NavigationControllerProxy.h @@ -0,0 +1,7 @@ +#import + +@interface NavigationControllerProxy : UINavigationController + +- (instancetype)init; + +@end diff --git a/Display/NavigationControllerProxy.m b/Display/NavigationControllerProxy.m new file mode 100644 index 0000000000..a6e77f8a55 --- /dev/null +++ b/Display/NavigationControllerProxy.m @@ -0,0 +1,16 @@ +#import "NavigationControllerProxy.h" + +#import "NavigationBarProxy.h" + +@implementation NavigationControllerProxy + +- (instancetype)init +{ + self = [super initWithNavigationBarClass:[NavigationBarProxy class] toolbarClass:[UIToolbar class]]; + if (self != nil) + { + } + return self; +} + +@end diff --git a/Display/NavigationItemTransitionState.swift b/Display/NavigationItemTransitionState.swift new file mode 100644 index 0000000000..179e9a3821 --- /dev/null +++ b/Display/NavigationItemTransitionState.swift @@ -0,0 +1,4 @@ +struct NavigationItemTransitionState { + let backButtonPosition: CGPoint? + let titlePosition: CGPoint +} diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift new file mode 100644 index 0000000000..0ae268cc07 --- /dev/null +++ b/Display/NavigationItemWrapper.swift @@ -0,0 +1,332 @@ +import UIKit +import AsyncDisplayKit + +internal class NavigationItemWrapper { + let parentNode: ASDisplayNode + + private var navigationItem: UINavigationItem + private var setTitleListenerKey: Int! + private var setLeftBarButtonItemListenerKey: Int! + private var setRightBarButtonItemListenerKey: Int! + + private var previousNavigationItem: UINavigationItem? + private var previousItemSetTitleListenerKey: Int? + + private let titleNode: NavigationTitleNode + private var backButtonNode: NavigationBackButtonNode + private var leftBarButtonItem: UIBarButtonItem? + private var leftBarButtonItemWrapper: BarButtonItemWrapper? + private var rightBarButtonItem: UIBarButtonItem? + private var rightBarButtonItemWrapper: BarButtonItemWrapper? + + var backPressed: () -> () = { } + + var suspendLayout = false + + init(parentNode: ASDisplayNode, navigationItem: UINavigationItem, previousNavigationItem: UINavigationItem?) { + self.parentNode = parentNode + self.navigationItem = navigationItem + self.previousNavigationItem = previousNavigationItem + + self.titleNode = NavigationTitleNode(text: "") + self.parentNode.addSubnode(titleNode) + + self.backButtonNode = NavigationBackButtonNode() + backButtonNode.pressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + self.parentNode.addSubnode(self.backButtonNode) + + self.previousItemSetTitleListenerKey = previousNavigationItem?.addSetTitleListener({ [weak self] title in + self?.setBackButtonTitle(title) + return + }) + + self.setTitleListenerKey = navigationItem.addSetTitleListener({ [weak self] title in + self?.setTitle(title) + return + }) + + self.setLeftBarButtonItemListenerKey = navigationItem.addSetLeftBarButtonItemListener({ [weak self] barButtonItem, animated in + self?.setLeftBarButtonItem(barButtonItem, animated: animated) + return + }) + + self.setRightBarButtonItemListenerKey = navigationItem.addSetRightBarButtonItemListener({ [weak self] barButtonItem, animated in + self?.setRightBarButtonItem(barButtonItem, animated: animated) + return + }) + + self.setTitle(navigationItem.title ?? "") + self.setBackButtonTitle(previousNavigationItem?.title ?? "Back") + self.setLeftBarButtonItem(navigationItem.leftBarButtonItem, animated: false) + self.setRightBarButtonItem(navigationItem.rightBarButtonItem, animated: false) + } + + deinit { + self.navigationItem.removeSetTitleListener(self.setTitleListenerKey) + self.navigationItem.removeSetLeftBarButtonItemListener(self.setLeftBarButtonItemListenerKey) + self.navigationItem.removeSetRightBarButtonItemListener(self.setRightBarButtonItemListenerKey) + + if let previousItemSetTitleListenerKey = self.previousItemSetTitleListenerKey { + self.previousNavigationItem?.removeSetTitleListener(previousItemSetTitleListenerKey) + } + + self.titleNode.removeFromSupernode() + self.backButtonNode.removeFromSupernode() + } + + func setBackButtonTitle(backButtonTitle: String) { + self.backButtonNode.text = backButtonTitle + self.layoutItems() + } + + func setTitle(title: String) { + self.titleNode.text = title + self.layoutItems() + } + + func setLeftBarButtonItem(leftBarButtonItem: UIBarButtonItem?, animated: Bool) { + if self.leftBarButtonItem !== leftBarButtonItem { + self.leftBarButtonItem = leftBarButtonItem + + self.leftBarButtonItemWrapper = nil + + if let leftBarButtonItem = leftBarButtonItem { + self.leftBarButtonItemWrapper = BarButtonItemWrapper(parentNode: self.parentNode, barButtonItem: leftBarButtonItem, layoutNeeded: { [weak self] in + self?.layoutItems() + return + }) + } + } + + self.backButtonNode.hidden = self.previousNavigationItem == nil || self.leftBarButtonItemWrapper != nil + } + + func setRightBarButtonItem(rightBarButtonItem: UIBarButtonItem?, animated: Bool) { + if self.rightBarButtonItem !== rightBarButtonItem { + self.rightBarButtonItem = rightBarButtonItem + + self.rightBarButtonItemWrapper = nil + + if let rightBarButtonItem = rightBarButtonItem { + self.rightBarButtonItemWrapper = BarButtonItemWrapper(parentNode: self.parentNode, barButtonItem: rightBarButtonItem, layoutNeeded: { [weak self] in + self?.layoutItems() + return + }) + } + } + } + + private var collapsed: Bool { + get { + return self.parentNode.frame.size.height < (20.0 + 44.0) + } + } + + var titleFrame: CGRect { + get { + return CGRect(x: floor((self.parentNode.frame.size.width - self.titleNode.calculatedSize.width) / 2.0), y: self.collapsed ? 24.0 : 31.0, width: self.titleNode.calculatedSize.width, height: self.titleNode.calculatedSize.height) + } + } + + var titlePosition: CGPoint { + get { + let titleFrame = self.titleFrame + return CGPoint(x: CGRectGetMidX(titleFrame), y: CGRectGetMidY(titleFrame)) + } + } + + var backButtonFrame: CGRect { + get { + return CGRect(x: self.collapsed ? 15.0 : 8.0, y: self.collapsed ? 24.0 : 31.0, width: backButtonNode.calculatedSize.width, height: backButtonNode.calculatedSize.height) + } + } + + var backButtonLabelFrame: CGRect { + get { + let backButtonFrame = self.backButtonFrame + let labelFrame = self.backButtonNode.labelFrame + return CGRect(origin: CGPoint(x: backButtonFrame.origin.x + labelFrame.origin.x, y: backButtonFrame.origin.y + labelFrame.origin.y), size: labelFrame.size) + } + } + + var backButtonLabelPosition: CGPoint { + get { + let backButtonLabelFrame = self.backButtonLabelFrame + return CGPoint(x: CGRectGetMidX(backButtonLabelFrame), y: CGRectGetMidY(backButtonLabelFrame)) + } + } + + var leftButtonFrame: CGRect? { + get { + if let leftBarButtonItemWrapper = self.leftBarButtonItemWrapper { + return CGRect(x: self.collapsed ? 15.0 : 8.0, y: self.collapsed ? 24.0 : 31.0, width: leftBarButtonItemWrapper.buttonNode.calculatedSize.width, height: leftBarButtonItemWrapper.buttonNode.calculatedSize.height) + } + else { + return nil + } + } + } + + var rightButtonFrame: CGRect? { + get { + if let rightBarButtonItemWrapper = self.rightBarButtonItemWrapper { + return CGRect(x: self.parentNode.frame.size.width - rightBarButtonItemWrapper.buttonNode.calculatedSize.width - (self.collapsed ? 15.0 : 8.0), y: self.collapsed ? 24.0 : 31.0, width: rightBarButtonItemWrapper.buttonNode.calculatedSize.width, height: rightBarButtonItemWrapper.buttonNode.calculatedSize.height) + } + else { + return nil + } + } + } + + var transitionState: NavigationItemTransitionState { + get { + return NavigationItemTransitionState(backButtonPosition: self.backButtonNode.hidden ? nil : self.backButtonLabelPosition, titlePosition: self.titlePosition) + } + } + + func layoutItems() { + if suspendLayout { + return + } + self.titleNode.measure(self.parentNode.bounds.size) + self.titleNode.frame = self.titleFrame + + self.backButtonNode.measure(self.parentNode.frame.size) + self.backButtonNode.frame = self.backButtonFrame + self.backButtonNode.layout() + + if let leftBarButtonItemWrapper = self.leftBarButtonItemWrapper { + leftBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) + leftBarButtonItemWrapper.buttonNode.frame = self.leftButtonFrame! + } + + if let rightBarButtonItemWrapper = self.rightBarButtonItemWrapper { + rightBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) + rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! + } + } + + func interpolatePosition(from: CGPoint, _ to: CGPoint, value: CGFloat) -> CGPoint { + return CGPoint(x: from.x * (CGFloat(1.0) - value) + to.x * value, y: from.y * (CGFloat(1.0) - value) + to.y * value) + } + + func interpolateValue(from: CGFloat, _ to: CGFloat, value: CGFloat) -> CGFloat { + return (from * (CGFloat(1.0) - value)) + (to * value) + } + + func applyPushAnimationProgress(previousItemState previousItemState: NavigationItemTransitionState, value: CGFloat) { + let titleStartPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + let titleStartAlpha: CGFloat = 0.0 + let titleEndPosition = self.titlePosition + let titleEndAlpha: CGFloat = 1.0 + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: previousItemState.titlePosition.x - self.backButtonFrame.origin.x, y: previousItemState.titlePosition.y - self.backButtonFrame.origin.y), CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + } + + func applyPushAnimationProgress(nextItemState nextItemState: NavigationItemTransitionState, value: CGFloat) { + let titleStartPosition = self.titlePosition + let titleStartAlpha: CGFloat = 1.0 + var titleEndPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + if let nextItemBackButtonPosition = nextItemState.backButtonPosition { + titleEndPosition = nextItemBackButtonPosition + } + let titleEndAlpha: CGFloat = 0.0 + + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: -self.backButtonLabelFrame.size.width - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.label.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, nextItemState.backButtonPosition == nil ? 0.0 : 1.0, value: value) + } + + func applyPopAnimationProgress(previousItemState previousItemState: NavigationItemTransitionState, value: CGFloat) { + var titleStartPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + if let previousItemBackButtonPosition = previousItemState.backButtonPosition { + titleStartPosition = previousItemBackButtonPosition + } + let titleStartAlpha: CGFloat = 0.0 + let titleEndPosition = self.titlePosition + let titleEndAlpha: CGFloat = 1.0 + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: -self.backButtonLabelFrame.size.width - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.label.alpha = self.interpolateValue(0.0, 1.0, value: value) + self.backButtonNode.arrow.alpha = self.interpolateValue(previousItemState.backButtonPosition == nil ? 0.0 : 1.0, 1.0, value: value) + } + + func applyPopAnimationProgress(nextItemState nextItemState: NavigationItemTransitionState, value: CGFloat) { + let titleStartPosition = self.titlePosition + let titleStartAlpha: CGFloat = 1.0 + let titleEndPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) + let titleEndAlpha: CGFloat = 0.0 + self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) + self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) + + self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) + + self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: nextItemState.titlePosition.x - self.backButtonFrame.origin.x, y: nextItemState.titlePosition.y - self.backButtonFrame.origin.y), value: value) + self.backButtonNode.label.alpha = self.interpolateValue(1.0, 0.0, value: value) + self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, 0.0, value: value) + } + + func animatePush(previousItemWrapper: NavigationItemWrapper?, duration: NSTimeInterval) { + if let previousItemWrapper = previousItemWrapper { + self.suspendLayout = true + self.backButtonNode.suspendLayout = true + + let transitionState = self.transitionState + let previousItemState = previousItemWrapper.transitionState + + self.applyPushAnimationProgress(previousItemState: previousItemState, value: 0.0) + previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 0.0) + + UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in + self.applyPushAnimationProgress(previousItemState: previousItemState, value: 1.0) + previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 1.0) + }, completion: { completed in + self.suspendLayout = false + self.backButtonNode.suspendLayout = false + + previousItemWrapper.applyPushAnimationProgress(nextItemState: self.transitionState, value: 1.0) + }) + } + } + + func animatePop(previousItemWrapper: NavigationItemWrapper?, duration: NSTimeInterval) { + if let previousItemWrapper = previousItemWrapper { + self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 0.0) + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) + + UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in + self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 1.0) + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 1.0) + }, completion: { completed in + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) + }) + } + } + + func setInteractivePopProgress(progress: CGFloat, previousItemWrapper: NavigationItemWrapper) { + self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: progress) + previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: progress) + } +} diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift new file mode 100644 index 0000000000..f268dd3871 --- /dev/null +++ b/Display/NavigationTitleNode.swift @@ -0,0 +1,50 @@ +import UIKit +import AsyncDisplayKit + +public class NavigationTitleNode: ASDisplayNode { + private let label: ASTextNode + + private var _text: NSString = "" + public var text: NSString { + get { + return self._text + } + set(value) { + self._text = value + self.setText(value) + } + } + + public init(text: NSString) { + self.label = ASTextNode() + self.label.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.label) + + self.setText(text) + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private func setText(text: NSString) { + var titleAttributes = [String : AnyObject]() + titleAttributes[NSFontAttributeName] = UIFont.boldSystemFontOfSize(17.0) + titleAttributes[NSForegroundColorAttributeName] = UIColor.blackColor() + let titleString = NSAttributedString(string: text as String, attributes: titleAttributes) + self.label.attributedString = titleString + self.invalidateCalculatedLayout() + } + + public override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize { + self.label.measure(constrainedSize) + return self.label.calculatedSize + } + + public override func layout() { + self.label.frame = self.bounds + } +} diff --git a/Display/NavigationTransitionView.swift b/Display/NavigationTransitionView.swift new file mode 100644 index 0000000000..46d081c2d6 --- /dev/null +++ b/Display/NavigationTransitionView.swift @@ -0,0 +1,82 @@ +import UIKit + +class NavigationTransitionCoordinator { + private var _progress: CGFloat = 0.0 + var progress: CGFloat { + get { + return self._progress + } + set(value) { + self._progress = value + self.navigationBar.setInteractivePopProgress(value) + self.updateProgress() + } + } + + private let container: UIView + private let topView: UIView + private let topViewSuperview: UIView? + private let bottomView: UIView + private let dimView: UIView + private let navigationBar: NavigationBar + + init(container: UIView, topView: UIView, bottomView: UIView, navigationBar: NavigationBar) { + self.container = container + self.topView = topView + self.topViewSuperview = topView.superview + self.bottomView = bottomView + self.dimView = UIView() + self.dimView.backgroundColor = UIColor.blackColor() + self.navigationBar = navigationBar + + if let topViewSuperview = self.topViewSuperview { + topViewSuperview.insertSubview(bottomView, belowSubview: topView) + } + + self.updateProgress() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateProgress() { + self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.progress * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) + self.dimView.frame = self.container.bounds + self.dimView.alpha = (1.0 - self.progress) * 0.1 + self.bottomView.frame = CGRect(origin: CGPoint(x: ((self.progress - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) + } + + func animateCancel(completion: () -> ()) { + UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 0.0 + }) { (completed) -> Void in + if let topViewSuperview = self.topViewSuperview { + topViewSuperview.addSubview(self.topView) + } + else { + self.topView.removeFromSuperview() + } + self.bottomView.removeFromSuperview() + + completion() + } + } + + func animateCompletion(velocity: CGFloat, completion: () -> ()) { + let distance = (1.0 - self.progress) * self.container.bounds.size.width + UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 1.0 + }) { (completed) -> Void in + if let topViewSuperview = self.topViewSuperview { + topViewSuperview.addSubview(self.topView) + } + else { + self.topView.removeFromSuperview() + } + self.bottomView.removeFromSuperview() + + completion() + } + } +} \ No newline at end of file diff --git a/Display/NotificationCenterUtils.h b/Display/NotificationCenterUtils.h new file mode 100644 index 0000000000..09409f3fc7 --- /dev/null +++ b/Display/NotificationCenterUtils.h @@ -0,0 +1,9 @@ +#import + +typedef bool (^NotificationHandlerBlock)(NSString *, id, NSDictionary *); + +@interface NotificationCenterUtils : NSObject + ++ (void)addNotificationHandler:(NotificationHandlerBlock)handler; + +@end diff --git a/Display/NotificationCenterUtils.m b/Display/NotificationCenterUtils.m new file mode 100644 index 0000000000..7088967d19 --- /dev/null +++ b/Display/NotificationCenterUtils.m @@ -0,0 +1,51 @@ +#import "NotificationCenterUtils.h" + +#import "RuntimeUtils.h" + +static NSMutableArray *notificationHandlers() +{ + static NSMutableArray *array = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + array = [[NSMutableArray alloc] init]; + }); + return array; +} + +@interface NSNotificationCenter (_a65afc19) + +@end + +@implementation NSNotificationCenter (_a65afc19) + +- (void)_a65afc19_postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo +{ + for (NotificationHandlerBlock handler in notificationHandlers()) + { + if (handler(aName, anObject, aUserInfo)) + return; + } + + [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; +} + +@end + +@implementation NotificationCenterUtils + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[NSNotificationCenter class] currentSelector:@selector(postNotificationName:object:userInfo:) newSelector:@selector(_a65afc19_postNotificationName:object:userInfo:)]; + }); +} + ++ (void)addNotificationHandler:(bool (^)(NSString *, id, NSDictionary *))handler +{ + [notificationHandlers() addObject:[handler copy]]; +} + +@end diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h new file mode 100644 index 0000000000..8f6e180282 --- /dev/null +++ b/Display/RuntimeUtils.h @@ -0,0 +1,20 @@ +#import + +typedef enum { + NSObjectAssociationPolicyRetain = 0, + NSObjectAssociationPolicyCopy = 1 +} NSObjectAssociationPolicy; + +@interface RuntimeUtils : NSObject + ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; + +@end + +@interface NSObject (AssociatedObject) + +- (void)setAssociatedObject:(id)object forKey:(void const *)key; +- (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy; +- (id)associatedObjectForKey:(void const *)key; + +@end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m new file mode 100644 index 0000000000..da122a4a38 --- /dev/null +++ b/Display/RuntimeUtils.m @@ -0,0 +1,56 @@ +#import "RuntimeUtils.h" + +#import + +@implementation RuntimeUtils + ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector +{ + Method origMethod = nil, newMethod = nil; + + origMethod = class_getInstanceMethod(targetClass, currentSelector); + newMethod = class_getInstanceMethod(targetClass, newSelector); + if ((origMethod != nil) && (newMethod != nil)) + { + if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) + { + class_replaceMethod(targetClass, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); + } + else + method_exchangeImplementations(origMethod, newMethod); + } +} + +@end + +@implementation NSObject (AssociatedObject) + +- (void)setAssociatedObject:(id)object forKey:(void const *)key +{ + [self setAssociatedObject:object forKey:key associationPolicy:NSObjectAssociationPolicyRetain]; +} + +- (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy +{ + int policy = 0; + switch (associationPolicy) + { + case NSObjectAssociationPolicyRetain: + policy = OBJC_ASSOCIATION_RETAIN_NONATOMIC; + break; + case NSObjectAssociationPolicyCopy: + policy = OBJC_ASSOCIATION_COPY_NONATOMIC; + break; + default: + policy = OBJC_ASSOCIATION_RETAIN_NONATOMIC; + break; + } + objc_setAssociatedObject(self, key, object, policy); +} + +- (id)associatedObjectForKey:(void const *)key +{ + return objc_getAssociatedObject(self, key); +} + +@end diff --git a/Display/UIBarButtonItem+Proxy.h b/Display/UIBarButtonItem+Proxy.h new file mode 100644 index 0000000000..031bf15fb3 --- /dev/null +++ b/Display/UIBarButtonItem+Proxy.h @@ -0,0 +1,15 @@ +#import + +typedef void (^UIBarButtonItemSetTitleListener)(NSString *); +typedef void (^UIBarButtonItemSetEnabledListener)(BOOL); + +@interface UIBarButtonItem (Proxy) + +- (void)performActionOnTarget; + +- (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener; +- (void)removeSetTitleListener:(NSInteger)key; +- (NSInteger)addSetEnabledListener:(UIBarButtonItemSetEnabledListener)listener; +- (void)removeSetEnabledListener:(NSInteger)key; + +@end diff --git a/Display/UIBarButtonItem+Proxy.m b/Display/UIBarButtonItem+Proxy.m new file mode 100644 index 0000000000..01e6ad0e0c --- /dev/null +++ b/Display/UIBarButtonItem+Proxy.m @@ -0,0 +1,83 @@ +#import "UIBarButtonItem+Proxy.h" + +#import "NSBag.h" +#import "RuntimeUtils.h" + +static const void *setEnabledListenerBagKey = &setEnabledListenerBagKey; +static const void *setTitleListenerBagKey = &setTitleListenerBagKey; + +@implementation UIBarButtonItem (Proxy) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UIBarButtonItem class] currentSelector:@selector(setEnabled:) newSelector:@selector(_c1e56039_setEnabled:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIBarButtonItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_c1e56039_setTitle:)]; + }); +} + +- (void)_c1e56039_setEnabled:(BOOL)enabled +{ + [self _c1e56039_setEnabled:enabled]; + + [(NSBag *)[self associatedObjectForKey:setEnabledListenerBagKey] enumerateItems:^(UIBarButtonItemSetEnabledListener listener) + { + listener(enabled); + }]; +} + +- (void)_c1e56039_setTitle:(NSString *)title +{ + [self _c1e56039_setTitle:title]; + + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UIBarButtonItemSetTitleListener listener) + { + listener(title); + }]; +} + +- (void)performActionOnTarget +{ + NSAssert(self.target != nil, @"self.target != nil"); + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + [self.target performSelector:self.action]; +#pragma clang diagnostic pop +} + +- (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setTitleListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetTitleListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetEnabledListener:(UIBarButtonItemSetEnabledListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setEnabledListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setEnabledListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetEnabledListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setEnabledListenerBagKey] removeItem:key]; +} + +@end diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h new file mode 100644 index 0000000000..d53da326e3 --- /dev/null +++ b/Display/UIKitUtils.h @@ -0,0 +1,7 @@ +#import + +@interface UIView (AnimationUtils) + ++ (double)animationDurationFactor; + +@end diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m new file mode 100644 index 0000000000..c8cbb8aed8 --- /dev/null +++ b/Display/UIKitUtils.m @@ -0,0 +1,18 @@ +#import "UIKitUtils.h" + +#if TARGET_IPHONE_SIMULATOR +UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient, use judiciously +#endif + +@implementation UIView (AnimationUtils) + ++ (double)animationDurationFactor +{ +#if TARGET_IPHONE_SIMULATOR + return (double)UIAnimationDragCoefficient(); +#endif + + return 1.0f; +} + +@end diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift new file mode 100644 index 0000000000..46e1fc8a89 --- /dev/null +++ b/Display/UIKitUtils.swift @@ -0,0 +1,31 @@ +import UIKit + +public func dumpViews(view: UIView) { + dumpViews(view, indent: "") +} + +private func dumpViews(view: UIView, indent: String = "") { + print("\(indent)\(view)") + let nextIndent = indent + "-" + for subview in view.subviews { + dumpViews(subview as UIView, indent: nextIndent) + } +} + +public func dumpLayers(layer: CALayer) { + dumpLayers(layer, indent: "") +} + +private func dumpLayers(layer: CALayer, indent: String = "") { + print("\(indent)\(layer)(\(layer.frame))") + if layer.sublayers != nil { + let nextIndent = indent + ".." + for sublayer in layer.sublayers ?? [] { + dumpLayers(sublayer as CALayer, indent: nextIndent) + } + } +} + +public func floorToScreenPixels(value: CGFloat) -> CGFloat { + return floor(value * 2.0) / 2.0 +} diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h new file mode 100644 index 0000000000..0c2a5bd37a --- /dev/null +++ b/Display/UINavigationItem+Proxy.h @@ -0,0 +1,15 @@ +#import + +typedef void (^UINavigationItemSetTitleListener)(NSString *); +typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL); + +@interface UINavigationItem (Proxy) + +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; +- (void)removeSetTitleListener:(NSInteger)key; +- (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (void)removeSetLeftBarButtonItemListener:(NSInteger)key; +- (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (void)removeSetRightBarButtonItemListener:(NSInteger)key; + +@end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m new file mode 100644 index 0000000000..d6944828dc --- /dev/null +++ b/Display/UINavigationItem+Proxy.m @@ -0,0 +1,101 @@ +#import "UINavigationItem+Proxy.h" + +#import "NSBag.h" +#import "RuntimeUtils.h" + +static const void *setTitleListenerBagKey = &setTitleListenerBagKey; +static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemListenerBagKey; +static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; + +@implementation UINavigationItem (Proxy) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_ac91f40f_setTitle:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; + }); +} + +- (void)_ac91f40f_setTitle:(NSString *)title +{ + [self _ac91f40f_setTitle:title]; + + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) + { + listener(title); + }]; +} + +- (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated +{ + [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; + + [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) + { + listener(leftBarButtonItem, animated); + }]; +} + +- (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem animated:(BOOL)animated +{ + [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; + + [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) + { + listener(rightBarButtonItem, animated); + }]; +} + +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setTitleListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetTitleListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setLeftBarButtonItemListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setLeftBarButtonItemListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetLeftBarButtonItemListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setRightBarButtonItemListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setRightBarButtonItemListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetRightBarButtonItemListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] removeItem:key]; +} + +@end diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h new file mode 100644 index 0000000000..8f5a15d055 --- /dev/null +++ b/Display/UIViewController+Navigation.h @@ -0,0 +1,7 @@ +#import + +@interface UIViewController (Navigation) + +- (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; + +@end diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m new file mode 100644 index 0000000000..565c3ec385 --- /dev/null +++ b/Display/UIViewController+Navigation.m @@ -0,0 +1,55 @@ +#import "UIViewController+Navigation.h" + +#import "RuntimeUtils.h" + +static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIViewControllerIgnoreAppearanceMethodInvocationsKey; + +@implementation UIViewController (Navigation) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewWillAppear:) newSelector:@selector(_65087dc8_viewWillAppear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidAppear:) newSelector:@selector(_65087dc8_viewDidAppear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewWillDisappear:) newSelector:@selector(_65087dc8_viewWillDisappear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidDisappear:) newSelector:@selector(_65087dc8_viewDidDisappear:)]; + }); +} + +- (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations +{ + [self setAssociatedObject:@(ignoreAppearanceMethodInvocations) forKey:UIViewControllerIgnoreAppearanceMethodInvocationsKey]; +} + +- (BOOL)ignoreAppearanceMethodInvocations +{ + return [[self associatedObjectForKey:UIViewControllerIgnoreAppearanceMethodInvocationsKey] boolValue]; +} + +- (void)_65087dc8_viewWillAppear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewWillAppear:animated]; +} + +- (void)_65087dc8_viewDidAppear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewDidAppear:animated]; +} + +- (void)_65087dc8_viewWillDisappear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewWillDisappear:animated]; +} + +- (void)_65087dc8_viewDidDisappear:(BOOL)animated +{ + if (![self ignoreAppearanceMethodInvocations]) + [self _65087dc8_viewDidDisappear:animated]; +} + +@end diff --git a/Display/UIWindow+OrientationChange.h b/Display/UIWindow+OrientationChange.h new file mode 100644 index 0000000000..3a48232217 --- /dev/null +++ b/Display/UIWindow+OrientationChange.h @@ -0,0 +1,13 @@ +#import + +@interface UIWindow (OrientationChange) + +- (bool)isRotating; + +@end + +@interface UINavigationBar (Condensed) + +- (void)setCondensed:(BOOL)condensed; + +@end diff --git a/Display/UIWindow+OrientationChange.m b/Display/UIWindow+OrientationChange.m new file mode 100644 index 0000000000..1c7946f765 --- /dev/null +++ b/Display/UIWindow+OrientationChange.m @@ -0,0 +1,85 @@ +#import "UIWindow+OrientationChange.h" + +#import "RuntimeUtils.h" +#import "NotificationCenterUtils.h" + +static const void *isRotatingKey = &isRotatingKey; + +@implementation UIWindow (OrientationChange) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [NotificationCenterUtils addNotificationHandler:^bool(NSString *name, id object, NSDictionary *userInfo) + { + if ([name isEqualToString:@"UIWindowWillRotateNotification"]) + { + [(UIWindow *)object setRotating:true]; + + if (NSClassFromString(@"NSUserActivity") == NULL) + { + UIInterfaceOrientation orientation = [userInfo[@"UIWindowNewOrientationUserInfoKey"] integerValue]; + CGSize screenSize = [UIScreen mainScreen].bounds.size; + if (screenSize.width > screenSize.height) + { + CGFloat tmp = screenSize.height; + screenSize.height = screenSize.width; + screenSize.width = tmp; + } + CGSize windowSize = CGSizeZero; + CGFloat windowRotation = 0.0; + bool landscape = false; + switch (orientation) { + case UIInterfaceOrientationPortrait: + windowSize = screenSize; + break; + case UIInterfaceOrientationPortraitUpsideDown: + windowRotation = (CGFloat)(M_PI); + windowSize = screenSize; + break; + case UIInterfaceOrientationLandscapeLeft: + landscape = true; + windowRotation = (CGFloat)(-M_PI / 2.0); + windowSize = CGSizeMake(screenSize.height, screenSize.width); + break; + case UIInterfaceOrientationLandscapeRight: + landscape = true; + windowRotation = (CGFloat)(M_PI / 2.0); + windowSize = CGSizeMake(screenSize.height, screenSize.width); + break; + default: + break; + } + + [UIView animateWithDuration:0.3 animations:^ + { + CGAffineTransform transform = CGAffineTransformIdentity; + transform = CGAffineTransformRotate(transform, windowRotation); + ((UIWindow *)object).transform = transform; + ((UIWindow *)object).bounds = CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height); + }]; + } + } + else if ([name isEqualToString:@"UIWindowDidRotateNotification"]) + { + [(UIWindow *)object setRotating:false]; + } + + return false; + }]; + }); +} + +- (void)setRotating:(bool)rotating +{ + [self setAssociatedObject:@(rotating) forKey:isRotatingKey]; +} + +- (bool)isRotating +{ + return [[self associatedObjectForKey:isRotatingKey] boolValue]; +} + +@end diff --git a/Display/ViewController.swift b/Display/ViewController.swift new file mode 100644 index 0000000000..cd2d61e3f6 --- /dev/null +++ b/Display/ViewController.swift @@ -0,0 +1,51 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +@objc public class ViewController: UIViewController, WindowContentController { + private var _displayNode: ASDisplayNode? + public var displayNode: ASDisplayNode { + get { + if let value = self._displayNode { + return value + } + else { + self.loadDisplayNode() + if self._displayNode == nil { + fatalError("displayNode should be initialized after loadDisplayNode()") + } + return self._displayNode! + } + } + set(value) { + self._displayNode = value + } + } + + public init() { + super.init(nibName: nil, bundle: nil) + + self.automaticallyAdjustsScrollViewInsets = false + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func loadView() { + self.view = self.displayNode.view + } + + public func loadDisplayNode() { + self.displayNode = ASDisplayNode() + } + + public func setViewSize(toSize: CGSize, duration: NSTimeInterval) { + if duration > DBL_EPSILON { + animateRotation(self.displayNode, toFrame: CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height), duration: duration) + } + else { + self.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height) + } + } +} diff --git a/Display/Window.swift b/Display/Window.swift new file mode 100644 index 0000000000..0b2ae25ccd --- /dev/null +++ b/Display/Window.swift @@ -0,0 +1,134 @@ +import Foundation +import AsyncDisplayKit + +public class WindowRootViewController: UIViewController { + public override func preferredStatusBarStyle() -> UIStatusBarStyle { + return .Default + } + + public override func prefersStatusBarHidden() -> Bool { + return false + } +} + +@objc +public protocol WindowContentController { + func setViewSize(toSize: CGSize, duration: NSTimeInterval) + var view: UIView! { get } +} + +public func animateRotation(view: UIView?, toFrame: CGRect, duration: NSTimeInterval) { + if let view = view { + UIView.animateWithDuration(duration, animations: { () -> Void in + view.frame = toFrame + }) + } +} + +public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NSTimeInterval) { + if let view = view { + CALayer.beginRecordingChanges() + view.frame = toFrame + view.layout() + let states = CALayer.endRecordingChanges() as! [CALayerAnimation] + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + for state in states { + if let layer = state.layer { + if !CGRectEqualToRect(state.startBounds, state.endBounds) { + let boundsAnimation = CABasicAnimation(keyPath: "bounds") + boundsAnimation.fromValue = NSValue(CGRect: state.startBounds) + boundsAnimation.toValue = NSValue(CGRect: state.endBounds) + boundsAnimation.duration = duration + boundsAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + boundsAnimation.removedOnCompletion = true + boundsAnimation.fillMode = kCAFillModeForwards + boundsAnimation.speed = speed + layer.addAnimation(boundsAnimation, forKey: "_rotationBounds") + } + + if !CGPointEqualToPoint(state.startPosition, state.endPosition) { + let positionAnimation = CABasicAnimation(keyPath: "position") + positionAnimation.fromValue = NSValue(CGPoint: state.startPosition) + positionAnimation.toValue = NSValue(CGPoint: state.endPosition) + positionAnimation.duration = duration + positionAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + positionAnimation.removedOnCompletion = true + positionAnimation.fillMode = kCAFillModeForwards + positionAnimation.speed = speed + layer.addAnimation(positionAnimation, forKey: "_rotationPosition") + } + } + } + } +} + +public class Window: UIWindow { + public convenience init() { + self.init(frame: UIScreen.mainScreen().bounds) + } + + public override init(frame: CGRect) { + super.init(frame: frame) + + super.rootViewController = WindowRootViewController() + } + + public required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { + return self.viewController?.view.hitTest(point, withEvent: event) + } + + public override var frame: CGRect { + get { + return super.frame + } + set(value) { + let sizeUpdated = super.frame.size != value.size + super.frame = value + if sizeUpdated { + self.viewController?.setViewSize(value.size, duration: self.isRotating() ? 0.3 : 0.0) + } + } + } + + public override var bounds: CGRect { + get { + return super.frame + } + set(value) { + let sizeUpdated = super.bounds.size != value.size + super.bounds = value + if sizeUpdated { + self.viewController?.setViewSize(value.size, duration: self.isRotating() ? 0.3 : 0.0) + } + } + } + + private var _rootViewController: WindowContentController? + public var viewController: WindowContentController? { + get { + return _rootViewController + } + set(value) { + self._rootViewController?.view.removeFromSuperview() + self._rootViewController = value + self._rootViewController?.view.frame = self.bounds + /*if let reactiveController = self._rootViewController as? ReactiveViewController { + reactiveController.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: self.frame.size.width, height: self.frame.size.height) + self.addSubview(reactiveController.displayNode.view) + } + else {*/ + if let view = self._rootViewController?.view { + self.addSubview(view) + } + //} + } + } +} diff --git a/submodules/AsyncDisplayKit b/submodules/AsyncDisplayKit new file mode 160000 index 0000000000..5482213e2e --- /dev/null +++ b/submodules/AsyncDisplayKit @@ -0,0 +1 @@ +Subproject commit 5482213e2ee8b880ebc23024ccc4643e1491e071 From cacd41129984b1a1be098c5e99c05089a3f428e9 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 30 Jul 2015 01:20:03 +0300 Subject: [PATCH 003/245] no message --- .gitmodules | 3 - Display.xcodeproj/project.pbxproj | 93 ------------------------------- submodules/AsyncDisplayKit | 1 - 3 files changed, 97 deletions(-) delete mode 160000 submodules/AsyncDisplayKit diff --git a/.gitmodules b/.gitmodules index 661f0a52ac..e69de29bb2 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +0,0 @@ -[submodule "submodules/AsyncDisplayKit"] - path = submodules/AsyncDisplayKit - url = https://github.com/facebook/AsyncDisplayKit.git diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index b338b70377..a1a3df240e 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -13,7 +13,6 @@ D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */; }; D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; - D05CC2B61B69339A00E235A3 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */; }; D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */; }; @@ -59,41 +58,6 @@ remoteGlobalIDString = D05CC2621B69316F00E235A3; remoteInfo = Display; }; - D05CC2AB1B6932E900E235A3 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 058D09AC195D04C000B7D73C; - remoteInfo = AsyncDisplayKit; - }; - D05CC2AD1B6932E900E235A3 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 058D09BC195D04C000B7D73C; - remoteInfo = AsyncDisplayKitTests; - }; - D05CC2AF1B6932E900E235A3 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = 057D02BF1AC0A66700C7AC3C; - remoteInfo = AsyncDisplayKitTestHost; - }; - D05CC2B11B6932E900E235A3 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; - proxyType = 2; - remoteGlobalIDString = B35061DA1B010EDF0018CF92; - remoteInfo = "AsyncDisplayKit-iOS"; - }; - D05CC32A1B697C0900E235A3 /* PBXContainerItemProxy */ = { - isa = PBXContainerItemProxy; - containerPortal = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; - proxyType = 1; - remoteGlobalIDString = B35061D91B010EDF0018CF92; - remoteInfo = "AsyncDisplayKit-iOS"; - }; /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ @@ -106,7 +70,6 @@ D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = ""; }; D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; - D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = AsyncDisplayKit.xcodeproj; path = submodules/AsyncDisplayKit/AsyncDisplayKit.xcodeproj; sourceTree = ""; }; D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CALayer+ImplicitAnimations.m"; sourceTree = ""; }; @@ -149,7 +112,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D05CC2B61B69339A00E235A3 /* AsyncDisplayKit.framework in Frameworks */, D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -208,23 +170,11 @@ D05CC2A31B6932D500E235A3 /* Frameworks */ = { isa = PBXGroup; children = ( - D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */, D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */, ); name = Frameworks; sourceTree = ""; }; - D05CC2A51B6932E800E235A3 /* Products */ = { - isa = PBXGroup; - children = ( - D05CC2AC1B6932E900E235A3 /* libAsyncDisplayKit.a */, - D05CC2AE1B6932E900E235A3 /* AsyncDisplayKitTests.xctest */, - D05CC2B01B6932E900E235A3 /* AsyncDisplayKitTestHost.app */, - D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */, - ); - name = Products; - sourceTree = ""; - }; D05CC2E11B69534100E235A3 /* Supporting Files */ = { isa = PBXGroup; children = ( @@ -320,7 +270,6 @@ buildRules = ( ); dependencies = ( - D05CC32B1B697C0900E235A3 /* PBXTargetDependency */, ); name = Display; productName = Display; @@ -373,12 +322,6 @@ mainGroup = D05CC2591B69316F00E235A3; productRefGroup = D05CC2641B69316F00E235A3 /* Products */; projectDirPath = ""; - projectReferences = ( - { - ProductGroup = D05CC2A51B6932E800E235A3 /* Products */; - ProjectRef = D05CC2A41B6932E800E235A3 /* AsyncDisplayKit.xcodeproj */; - }, - ); projectRoot = ""; targets = ( D05CC2621B69316F00E235A3 /* Display */, @@ -387,37 +330,6 @@ }; /* End PBXProject section */ -/* Begin PBXReferenceProxy section */ - D05CC2AC1B6932E900E235A3 /* libAsyncDisplayKit.a */ = { - isa = PBXReferenceProxy; - fileType = archive.ar; - path = libAsyncDisplayKit.a; - remoteRef = D05CC2AB1B6932E900E235A3 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - D05CC2AE1B6932E900E235A3 /* AsyncDisplayKitTests.xctest */ = { - isa = PBXReferenceProxy; - fileType = wrapper.cfbundle; - path = AsyncDisplayKitTests.xctest; - remoteRef = D05CC2AD1B6932E900E235A3 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - D05CC2B01B6932E900E235A3 /* AsyncDisplayKitTestHost.app */ = { - isa = PBXReferenceProxy; - fileType = wrapper.application; - path = AsyncDisplayKitTestHost.app; - remoteRef = D05CC2AF1B6932E900E235A3 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; - D05CC2B21B6932E900E235A3 /* AsyncDisplayKit.framework */ = { - isa = PBXReferenceProxy; - fileType = wrapper.framework; - path = AsyncDisplayKit.framework; - remoteRef = D05CC2B11B6932E900E235A3 /* PBXContainerItemProxy */; - sourceTree = BUILT_PRODUCTS_DIR; - }; -/* End PBXReferenceProxy section */ - /* Begin PBXResourcesBuildPhase section */ D05CC2611B69316F00E235A3 /* Resources */ = { isa = PBXResourcesBuildPhase; @@ -485,11 +397,6 @@ target = D05CC2621B69316F00E235A3 /* Display */; targetProxy = D05CC26F1B69316F00E235A3 /* PBXContainerItemProxy */; }; - D05CC32B1B697C0900E235A3 /* PBXTargetDependency */ = { - isa = PBXTargetDependency; - name = "AsyncDisplayKit-iOS"; - targetProxy = D05CC32A1B697C0900E235A3 /* PBXContainerItemProxy */; - }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ diff --git a/submodules/AsyncDisplayKit b/submodules/AsyncDisplayKit deleted file mode 160000 index 5482213e2e..0000000000 --- a/submodules/AsyncDisplayKit +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 5482213e2ee8b880ebc23024ccc4643e1491e071 From 032a5ec6bdbe9d631a85bfc5188849e95b6df8de Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 10 Aug 2015 12:01:07 +0200 Subject: [PATCH 004/245] no message --- Display.xcodeproj/project.pbxproj | 44 ++++++++++++ Display/BarButtonItemWrapper.swift | 2 +- Display/CAAnimationUtils.swift | 23 +++++- Display/Font.swift | 20 ++++++ ...teractiveTransitionGestureRecognizer.swift | 8 ++- Display/KeyboardHostWindow.swift | 28 ++++++++ Display/NavigationController.swift | 72 ++++++++++++------- Display/NavigationItemWrapper.swift | 4 +- Display/RuntimeUtils.swift | 23 ++++++ Display/StatusBarHostWindow.swift | 29 ++++++++ Display/UIKitUtils.swift | 19 +++++ Display/ViewController.swift | 6 +- Display/Window.swift | 6 +- 13 files changed, 243 insertions(+), 41 deletions(-) create mode 100644 Display/Font.swift create mode 100644 Display/KeyboardHostWindow.swift create mode 100644 Display/RuntimeUtils.swift create mode 100644 Display/StatusBarHostWindow.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index a1a3df240e..93f7294fe1 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; @@ -48,6 +49,10 @@ D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */; }; D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; + D05CC3651B69960300E235A3 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC3641B69960300E235A3 /* AsyncDisplayKit.framework */; }; + D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; + D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; + D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -61,6 +66,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -105,6 +111,10 @@ D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationBarProxy.m; sourceTree = ""; }; D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; + D05CC3641B69960300E235A3 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/AsyncDisplayKit.framework"; sourceTree = ""; }; + D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; + D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; + D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -112,6 +122,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D05CC3651B69960300E235A3 /* AsyncDisplayKit.framework in Frameworks */, D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -127,6 +138,13 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D02BDAEC1B6A7053008AFAD2 /* Nodes */ = { + isa = PBXGroup; + children = ( + ); + name = Nodes; + sourceTree = ""; + }; D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( @@ -150,7 +168,10 @@ isa = PBXGroup; children = ( D05CC3001B6955D500E235A3 /* Utils */, + D07921AA1B6FC911005C23D9 /* Status Bar */, + D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, + D02BDAEC1B6A7053008AFAD2 /* Nodes */, D05CC2A11B69326C00E235A3 /* Window.swift */, D05CC2E21B69552C00E235A3 /* ViewController.swift */, D05CC2E11B69534100E235A3 /* Supporting Files */, @@ -170,6 +191,7 @@ D05CC2A31B6932D500E235A3 /* Frameworks */ = { isa = PBXGroup; children = ( + D05CC3641B69960300E235A3 /* AsyncDisplayKit.framework */, D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */, ); name = Frameworks; @@ -212,6 +234,8 @@ D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */, D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */, D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */, + D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */, + D06EE8441B7140FF00837186 /* Font.swift */, ); name = Utils; sourceTree = ""; @@ -233,6 +257,22 @@ name = Navigation; sourceTree = ""; }; + D07921A71B6FC0AE005C23D9 /* Keyboard */ = { + isa = PBXGroup; + children = ( + D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */, + ); + name = Keyboard; + sourceTree = ""; + }; + D07921AA1B6FC911005C23D9 /* Status Bar */ = { + isa = PBXGroup; + children = ( + D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */, + ); + name = "Status Bar"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -354,11 +394,15 @@ buildActionMask = 2147483647; files = ( D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, + D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, + D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, + D06EE8451B7140FF00837186 /* Font.swift in Sources */, + D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift index fab8fa1752..f529094e29 100644 --- a/Display/BarButtonItemWrapper.swift +++ b/Display/BarButtonItemWrapper.swift @@ -24,7 +24,7 @@ internal class BarButtonItemWrapper { self.parentNode.addSubnode(self.buttonNode) self.setEnabledListenerKey = barButtonItem.addSetEnabledListener({ [weak self] enabled in - self?.buttonNode.enabled = enabled + self?.buttonNode.enabled = enabled.boolValue return }) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 6e5efec0fa..5c9b095749 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,7 +1,21 @@ import UIKit -extension CALayer { - internal func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval) { +@objc private class CALayerAnimationDelegate: NSObject { + let completion: Bool -> Void + + init(completion: Bool -> Void) { + self.completion = completion + + super.init() + } + + @objc override func animationDidStop(anim: CAAnimation, finished flag: Bool) { + self.completion(flag) + } +} + +public extension CALayer { + public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 if k != 0 && k != 1 { @@ -16,13 +30,16 @@ extension CALayer { animation.removedOnCompletion = true animation.fillMode = kCAFillModeForwards animation.speed = speed + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } self.addAnimation(animation, forKey: keyPath) self.setValue(to, forKey: keyPath) } - internal func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) } diff --git a/Display/Font.swift b/Display/Font.swift new file mode 100644 index 0000000000..e908fac5f8 --- /dev/null +++ b/Display/Font.swift @@ -0,0 +1,20 @@ +import Foundation +import UIKit + +public struct Font { + public static func regular(size: CGFloat) -> UIFont { + if matchMinimumSystemVersion(9) { + return UIFont(name: ".SFUIDisplay-Regular", size: size)! + } else { + return UIFont(name: "HelveticaNeue", size: size)! + } + } + + public static func medium(size: CGFloat) -> UIFont { + if matchMinimumSystemVersion(9) { + return UIFont(name: ".SFUIDisplay-Medium", size: size)! + } else { + return UIFont(name: "HelveticaNeue-Medium", size: size)! + } + } +} diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index 107d85bdf6..4526e27c41 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -28,11 +28,13 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) if !validatedGesture { - if translation.x < 0.0 { + if self.firstLocation.x < 16.0 { + validatedGesture = true + } else if translation.x < 0.0 { self.state = .Failed - } else if abs(translation.y) >= 2.0 { + } else if abs(translation.y) > 2.0 && abs(translation.y) > abs(translation.x) * 2.0 { self.state = .Failed - } else if translation.x >= 3.0 && translation.x / 3.0 > translation.y { + } else if abs(translation.x) > 2.0 && abs(translation.y) * 2.0 < abs(translation.x) { validatedGesture = true } } diff --git a/Display/KeyboardHostWindow.swift b/Display/KeyboardHostWindow.swift new file mode 100644 index 0000000000..c6597c2a92 --- /dev/null +++ b/Display/KeyboardHostWindow.swift @@ -0,0 +1,28 @@ +import Foundation +import UIKit + +public class KeyboardHostWindow: UIWindow { + let textField: UITextField + + convenience public init() { + self.init(frame: CGRect()) + } + + override init(frame: CGRect) { + self.textField = UITextField(frame: CGRect(x: -110.0, y: 0.0, width: 100.0, height: 50.0)) + + super.init(frame: frame) + + self.windowLevel = 1000.0 + self.rootViewController = UIViewController() + self.addSubview(self.textField) + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func acquireFirstResponder() { + textField.becomeFirstResponder() + } +} diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index a8d88014ba..e6a852f8ba 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -1,19 +1,28 @@ import Foundation import UIKit import AsyncDisplayKit +import SwiftSignalKit public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate { - private var _navigationBar: NavigationBar? + private var _navigationBar: NavigationBar! private var navigationTransitionCoordinator: NavigationTransitionCoordinator? + private var currentPushDisposable = MetaDisposable() + public override init() { + self._navigationBar = nil + super.init() + self._navigationBar = NavigationBar() - self._navigationBar?.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) - self._navigationBar?.proxy = self.navigationBar as? NavigationBarProxy - self._navigationBar?.backPressed = { [weak self] in - if self?.viewControllers.count > 1 { - self?.popViewControllerAnimated(true) + + self._navigationBar.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) + self._navigationBar.proxy = self.navigationBar as? NavigationBarProxy + self._navigationBar.backPressed = { [weak self] in + if let strongSelf = self { + if strongSelf.viewControllers.count > 1 { + strongSelf.popViewControllerAnimated(true) + } } return } @@ -30,12 +39,10 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr public override func loadView() { super.loadView() - if let _navigationBar = self._navigationBar { - self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) - } + self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) self.navigationBar.removeFromSuperview() - self._navigationBar?.frame = navigationBarFrame(self.view.frame.size) + self._navigationBar.frame = navigationBarFrame(self.view.frame.size) let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("panGesture:")) panRecognizer.delegate = self @@ -59,10 +66,10 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr bottomController.viewWillAppear(true) let bottomView = bottomController.view - let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar!) + let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator - self._navigationBar?.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil) + self._navigationBar.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil) } case UIGestureRecognizerState.Changed: if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { @@ -77,7 +84,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr navigationTransitionCoordinator.animateCompletion(velocity, completion: { self.navigationTransitionCoordinator = nil - self._navigationBar?.endInteractivePopProgress() + self._navigationBar.endInteractivePopProgress() if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -106,7 +113,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr navigationTransitionCoordinator.animateCancel({ self.navigationTransitionCoordinator = nil - self._navigationBar?.endInteractivePopProgress() + self._navigationBar.endInteractivePopProgress() if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -145,7 +152,19 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } + public func pushViewController(signal: Signal) -> Disposable { + let disposable = (signal |> deliverOnMainQueue).start(next: {[weak self] controller in + if let strongSelf = self { + strongSelf.pushViewController(controller, animated: true) + } + }) + self.currentPushDisposable.set(disposable) + return disposable + } + public override func pushViewController(viewController: UIViewController, animated: Bool) { + self.currentPushDisposable.set(nil) + var controllers = self.viewControllers controllers.append(viewController) self.setViewControllers(controllers, animated: animated) @@ -167,7 +186,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr let topViewController = viewControllers[viewControllers.count - 1] as UIViewController if let controller = topViewController as? WindowContentController { - controller.setViewSize(self.view.bounds.size, duration: 0.0) + controller.setViewSize(self.view.bounds.size, insets: UIEdgeInsets(top: CGRectGetMaxY(self._navigationBar.frame), left: 0.0, bottom: 0.0, right: 0.0), duration: 0.0) } else { topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) } @@ -177,23 +196,23 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } private func navigationBarFrame(size: CGSize) -> CGRect { - let condensedBar = (size.height < size.width || size.height <= 320.0) && size.height < 768.0 + //let condensedBar = (size.height < size.width || size.height <= 320.0) && size.height < 768.0 return CGRect(x: 0.0, y: 0.0, width: size.width, height: 20.0 + (size.height >= size.width ? 44.0 : 32.0)) } - public func setViewSize(toSize: CGSize, duration: NSTimeInterval) { + public func setViewSize(size: CGSize, insets: UIEdgeInsets, duration: NSTimeInterval) { if duration > DBL_EPSILON { - animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height), duration: duration) + animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), duration: duration) } else { - self.view.frame = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height) + self.view.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) } if duration > DBL_EPSILON { - animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(toSize), duration: duration) + animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(size), duration: duration) } else { - self._navigationBar?.frame = self.navigationBarFrame(toSize) + self._navigationBar.frame = self.navigationBarFrame(size) } if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { @@ -203,17 +222,18 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController if let controller = bottomController as? WindowContentController { - controller.setViewSize(toSize, duration: duration) + controller.setViewSize(size, insets: UIEdgeInsets(top: CGRectGetMaxY(self._navigationBar.frame), left: 0.0, bottom: 0.0, right: 0.0), duration: duration) + } else { + bottomController.view.frame = CGRectMake(0.0, 0.0, size.width, size.height) } - bottomController.view.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) } } if let topViewController = self.topViewController { if let controller = topViewController as? WindowContentController { - controller.setViewSize(toSize, duration: duration) + controller.setViewSize(size, insets: UIEdgeInsets(top: CGRectGetMaxY(self._navigationBar.frame), left: 0.0, bottom: 0.0, right: 0.0), duration: duration) } else { - topViewController.view.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + topViewController.view.frame = CGRectMake(0.0, 0.0, size.width, size.height) } } @@ -227,6 +247,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return true + return otherGestureRecognizer is UIPanGestureRecognizer } } diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift index 0ae268cc07..47ed0bcba4 100644 --- a/Display/NavigationItemWrapper.swift +++ b/Display/NavigationItemWrapper.swift @@ -50,12 +50,12 @@ internal class NavigationItemWrapper { }) self.setLeftBarButtonItemListenerKey = navigationItem.addSetLeftBarButtonItemListener({ [weak self] barButtonItem, animated in - self?.setLeftBarButtonItem(barButtonItem, animated: animated) + self?.setLeftBarButtonItem(barButtonItem, animated: animated.boolValue) return }) self.setRightBarButtonItemListenerKey = navigationItem.addSetRightBarButtonItemListener({ [weak self] barButtonItem, animated in - self?.setRightBarButtonItem(barButtonItem, animated: animated) + self?.setRightBarButtonItem(barButtonItem, animated: animated.boolValue) return }) diff --git a/Display/RuntimeUtils.swift b/Display/RuntimeUtils.swift new file mode 100644 index 0000000000..178c77d13b --- /dev/null +++ b/Display/RuntimeUtils.swift @@ -0,0 +1,23 @@ +import Foundation +import UIKit + +private let systemVersion = { () -> (Int, Int) in + let string = UIDevice.currentDevice().systemVersion as NSString + var minor = 0 + let range = string.rangeOfString(".") + if range.location != NSNotFound { + minor = Int((string.substringFromIndex(range.location + 1) as NSString).intValue) + } + return (Int(string.intValue), minor) +}() + +public func matchMinimumSystemVersion(major: Int, minor: Int = 0) -> Bool { + let version = systemVersion + if version.0 == major { + return version.1 >= minor + } else if version.0 < major { + return false + } else { + return true + } +} diff --git a/Display/StatusBarHostWindow.swift b/Display/StatusBarHostWindow.swift new file mode 100644 index 0000000000..901b24de39 --- /dev/null +++ b/Display/StatusBarHostWindow.swift @@ -0,0 +1,29 @@ +import Foundation +import UIKit + +private class StatusBarHostWindowController: UIViewController { + override func preferredStatusBarStyle() -> UIStatusBarStyle { + return UIStatusBarStyle.Default + } + + override func prefersStatusBarHidden() -> Bool { + return false + } + + override func shouldAutorotate() -> Bool { + return true + } +} + +public class StatusBarHostWindow: UIWindow { + public init() { + super.init(frame: CGRect()) + + self.windowLevel = 10000.0 + self.rootViewController = StatusBarHostWindowController() + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} \ No newline at end of file diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 46e1fc8a89..0d6dace84b 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -29,3 +29,22 @@ private func dumpLayers(layer: CALayer, indent: String = "") { public func floorToScreenPixels(value: CGFloat) -> CGFloat { return floor(value * 2.0) / 2.0 } + +public extension UIColor { + convenience init(_ rgb: Int) { + self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) + } +} + +public extension CGSize { + public func fitted(size: CGSize) -> CGSize { + var fittedSize = self + if fittedSize.width > size.width { + fittedSize = CGSize(width: size.width, height: floor((fittedSize.height * size.width / max(fittedSize.width, 1.0)))) + } + if fittedSize.height > size.height { + fittedSize = CGSize(width: floor((fittedSize.width * size.height / max(fittedSize.height, 1.0))), height: size.height) + } + return fittedSize + } +} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index cd2d61e3f6..fc83869639 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -40,12 +40,12 @@ import AsyncDisplayKit self.displayNode = ASDisplayNode() } - public func setViewSize(toSize: CGSize, duration: NSTimeInterval) { + public func setViewSize(size: CGSize, insets: UIEdgeInsets, duration: NSTimeInterval) { if duration > DBL_EPSILON { - animateRotation(self.displayNode, toFrame: CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height), duration: duration) + animateRotation(self.displayNode, toFrame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), duration: duration) } else { - self.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: toSize.width, height: toSize.height) + self.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) } } } diff --git a/Display/Window.swift b/Display/Window.swift index 0b2ae25ccd..2fdf47ae8f 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -13,7 +13,7 @@ public class WindowRootViewController: UIViewController { @objc public protocol WindowContentController { - func setViewSize(toSize: CGSize, duration: NSTimeInterval) + func setViewSize(size: CGSize, insets: UIEdgeInsets, duration: NSTimeInterval) var view: UIView! { get } } @@ -93,7 +93,7 @@ public class Window: UIWindow { let sizeUpdated = super.frame.size != value.size super.frame = value if sizeUpdated { - self.viewController?.setViewSize(value.size, duration: self.isRotating() ? 0.3 : 0.0) + self.viewController?.setViewSize(value.size, insets: UIEdgeInsets(), duration: self.isRotating() ? 0.3 : 0.0) } } } @@ -106,7 +106,7 @@ public class Window: UIWindow { let sizeUpdated = super.bounds.size != value.size super.bounds = value if sizeUpdated { - self.viewController?.setViewSize(value.size, duration: self.isRotating() ? 0.3 : 0.0) + self.viewController?.setViewSize(value.size, insets: UIEdgeInsets(), duration: self.isRotating() ? 0.3 : 0.0) } } } From 20786560334a685eb3ee93c3d4fcaef43c1093f7 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 22 Sep 2015 15:23:16 +0300 Subject: [PATCH 005/245] no message --- Display.xcodeproj/project.pbxproj | 12 +++ Display/CAAnimationUtils.swift | 2 +- Display/ImageCache.swift | 154 ++++++++++++++++++++++++++++ Display/NavigationItemWrapper.swift | 5 +- Display/NavigationTitleNode.swift | 2 + Display/UIKitUtils.swift | 13 +++ 6 files changed, 185 insertions(+), 3 deletions(-) create mode 100644 Display/ImageCache.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 93f7294fe1..994d9c495f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -53,6 +53,7 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; + D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,6 +116,7 @@ D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; + D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -172,6 +174,7 @@ D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, + D0E49C861B83A1680099E553 /* Image Cache */, D05CC2A11B69326C00E235A3 /* Window.swift */, D05CC2E21B69552C00E235A3 /* ViewController.swift */, D05CC2E11B69534100E235A3 /* Supporting Files */, @@ -273,6 +276,14 @@ name = "Status Bar"; sourceTree = ""; }; + D0E49C861B83A1680099E553 /* Image Cache */ = { + isa = PBXGroup; + children = ( + D0E49C871B83A3580099E553 /* ImageCache.swift */, + ); + name = "Image Cache"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -393,6 +404,7 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 5c9b095749..7f4940375f 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -36,7 +36,7 @@ public extension CALayer { self.addAnimation(animation, forKey: keyPath) - self.setValue(to, forKey: keyPath) + //self.setValue(to, forKey: keyPath) } public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { diff --git a/Display/ImageCache.swift b/Display/ImageCache.swift new file mode 100644 index 0000000000..f78e841305 --- /dev/null +++ b/Display/ImageCache.swift @@ -0,0 +1,154 @@ +import Foundation + +private final class ImageCacheData { + let size: CGSize + let bytesPerRow: Int + var data: NSPurgeableData + + var isDiscarded: Bool { + return self.data.isContentDiscarded() + } + + var image: UIImage? { + if self.data.beginContentAccess() { + return self.createImage() + } + return nil + } + + init(size: CGSize, generator: CGContextRef -> Void, @noescape takenImage: UIImage -> Void) { + self.size = size + + self.bytesPerRow = (4 * Int(size.width) + 15) & (~15) + self.data = NSPurgeableData(length: self.bytesPerRow * Int(size.height))! + + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue + + if let context = CGBitmapContextCreate(self.data.mutableBytes, Int(size.width), Int(size.height), 8, bytesPerRow, colorSpace, bitmapInfo) + { + CGContextTranslateCTM(context, size.width / 2.0, size.height / 2.0) + CGContextScaleCTM(context, 1.0, -1.0) + CGContextTranslateCTM(context, -size.width / 2.0, -size.height / 2.0) + + UIGraphicsPushContext(context) + + generator(context) + + UIGraphicsPopContext() + } + + takenImage(self.createImage()) + } + + private func createImage() -> UIImage { + let colorSpace = CGColorSpaceCreateDeviceRGB() + let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue + + let unmanagedData = withUnsafePointer(&self.data, { pointer in + return Unmanaged.fromOpaque(COpaquePointer(pointer)) + }) + unmanagedData.retain() + let dataProvider = CGDataProviderCreateWithData(UnsafeMutablePointer(unmanagedData.toOpaque()), self.data.bytes, self.bytesPerRow, { info, _, _ in + let unmanagedData = Unmanaged.fromOpaque(COpaquePointer(info)) + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { + unmanagedData.takeUnretainedValue().endContentAccess() + unmanagedData.release() + }) + }) + + let image = CGImageCreate(Int(self.size.width), Int(self.size.height), 8, 32, self.bytesPerRow, colorSpace, CGBitmapInfo(rawValue: bitmapInfo), dataProvider, nil, false, CGColorRenderingIntent(rawValue: 0)!) + + let result = UIImage(CGImage: image!) + return result + } +} + +private final class ImageCacheResidentImage { + let key: String + let image: UIImage + var accessIndex: Int + + init(key: String, image: UIImage, accessIndex: Int) { + self.key = key + self.image = image + self.accessIndex = accessIndex + } +} + +public final class ImageCache { + let maxResidentSize: Int + var mutex = pthread_mutex_t() + + private var imageDatas: [String : ImageCacheData] = [:] + private var residentImages: [String : ImageCacheResidentImage] = [:] + var nextAccessIndex = 1 + var residentImagesSize = 0 + + public init(maxResidentSize: Int) { + self.maxResidentSize = maxResidentSize + pthread_mutex_init(&self.mutex, nil) + } + + deinit { + pthread_mutex_destroy(&self.mutex) + } + + public func addImageForKey(key: String, size: CGSize, generator: CGContextRef -> Void) { + var image: UIImage? + let imageData = ImageCacheData(size: size, generator: generator, takenImage: { image = $0 }) + + pthread_mutex_lock(&self.mutex) + self.imageDatas[key] = imageData + self.addResidentImage(image!, forKey: key) + pthread_mutex_unlock(&self.mutex) + } + + public func imageForKey(key: String) -> UIImage? { + var image: UIImage? + + pthread_mutex_lock(&self.mutex); + if let residentImage = self.residentImages[key] { + image = residentImage.image + self.nextAccessIndex++ + residentImage.accessIndex = self.nextAccessIndex + } else { + if let imageData = self.imageDatas[key] { + if let takenImage = imageData.image { + image = takenImage + self.addResidentImage(takenImage, forKey: key) + } else { + self.imageDatas.removeValueForKey(key) + } + } + } + pthread_mutex_unlock(&self.mutex) + + return image + } + + private func addResidentImage(image: UIImage, forKey key: String) { + let imageSize = Int(image.size.width * image.size.height * image.scale) * 4 + + if self.residentImagesSize + imageSize > self.maxResidentSize { + let sizeToRemove = self.residentImagesSize - (self.maxResidentSize - imageSize) + let sortedImages = self.residentImages.values.sort({ $0.accessIndex < $1.accessIndex }) + + var removedSize = 0 + var i = sortedImages.count - 1 + while i >= 0 && removedSize < sizeToRemove { + let currentImage = sortedImages[i] + let currentImageSize = Int(currentImage.image.size.width * currentImage.image.size.height * currentImage.image.scale) * 4 + removedSize += currentImageSize + self.residentImages.removeValueForKey(currentImage.key) + i-- + } + + self.residentImagesSize = max(0, self.residentImagesSize - removedSize) + } + + self.residentImagesSize += imageSize + self.nextAccessIndex++ + self.residentImages[key] = ImageCacheResidentImage(key: key, image: image, accessIndex: self.nextAccessIndex) + } +} diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift index 47ed0bcba4..4855745fb5 100644 --- a/Display/NavigationItemWrapper.swift +++ b/Display/NavigationItemWrapper.swift @@ -192,8 +192,6 @@ internal class NavigationItemWrapper { if suspendLayout { return } - self.titleNode.measure(self.parentNode.bounds.size) - self.titleNode.frame = self.titleFrame self.backButtonNode.measure(self.parentNode.frame.size) self.backButtonNode.frame = self.backButtonFrame @@ -208,6 +206,9 @@ internal class NavigationItemWrapper { rightBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! } + + self.titleNode.measure(CGSize(width: self.parentNode.bounds.size.width - 140.0, height: CGFloat.max)) + self.titleNode.frame = self.titleFrame } func interpolatePosition(from: CGPoint, _ to: CGPoint, value: CGFloat) -> CGPoint { diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index f268dd3871..4b6b98cec9 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -17,6 +17,8 @@ public class NavigationTitleNode: ASDisplayNode { public init(text: NSString) { self.label = ASTextNode() + self.label.maximumLineCount = 1 + self.label.truncationMode = .ByTruncatingTail self.label.displaysAsynchronously = false super.init() diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 0d6dace84b..82d70d8eea 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -34,6 +34,10 @@ public extension UIColor { convenience init(_ rgb: Int) { self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) } + + convenience init(_ rgb: Int, _ alpha: CGFloat) { + self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: alpha) + } } public extension CGSize { @@ -47,4 +51,13 @@ public extension CGSize { } return fittedSize } + + public func aspectFilled(size: CGSize) -> CGSize { + let scale = max(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) + return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) + } +} + +public func assertNotOnMainThread(file: String = __FILE__, line: Int = __LINE__) { + assert(!NSThread.isMainThread(), "\(file):\(line) running on main thread") } From 7cb2c49a4bc1a0a5243152d7638496728cd3f6b8 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 19 Nov 2015 14:37:53 +0300 Subject: [PATCH 006/245] no message --- Display.xcodeproj/project.pbxproj | 22 ++++ Display/CAAnimationUtils.swift | 28 ++++- Display/Font.swift | 18 ++-- Display/KeyboardHostWindow.swift | 4 +- Display/NavigationBar.swift | 5 +- Display/NavigationController.swift | 147 +++++++++++++++++++-------- Display/NotificationCenterUtils.h | 2 +- Display/NotificationCenterUtils.m | 39 +++++-- Display/RuntimeUtils.h | 1 + Display/RuntimeUtils.m | 30 ++++-- Display/TabBarContollerNode.swift | 37 +++++++ Display/TabBarController.swift | 101 ++++++++++++++++++ Display/TabBarNode.swift | 106 +++++++++++++++++++ Display/UIKitUtils.h | 6 ++ Display/UIKitUtils.m | 14 +++ Display/UIKitUtils.swift | 16 ++- Display/UIWindow+OrientationChange.h | 4 + Display/UIWindow+OrientationChange.m | 62 ++++++++--- Display/ViewController.swift | 120 ++++++++++++++++++++-- Display/Window.swift | 74 +++++++++++--- 20 files changed, 726 insertions(+), 110 deletions(-) create mode 100644 Display/TabBarContollerNode.swift create mode 100644 Display/TabBarController.swift create mode 100644 Display/TabBarNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 994d9c495f..ff3255d01f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -53,6 +53,9 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; + D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; + D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; + D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; /* End PBXBuildFile section */ @@ -116,6 +119,9 @@ D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; + D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; + D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; + D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -173,6 +179,7 @@ D07921AA1B6FC911005C23D9 /* Status Bar */, D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, + D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, D0E49C861B83A1680099E553 /* Image Cache */, D05CC2A11B69326C00E235A3 /* Window.swift */, @@ -276,6 +283,16 @@ name = "Status Bar"; sourceTree = ""; }; + D0DC48521BF93D7C00F672FD /* Tabs */ = { + isa = PBXGroup; + children = ( + D0DC48531BF93D8A00F672FD /* TabBarController.swift */, + D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */, + D0DC48551BF945DD00F672FD /* TabBarNode.swift */, + ); + name = Tabs; + sourceTree = ""; + }; D0E49C861B83A1680099E553 /* Image Cache */ = { isa = PBXGroup; children = ( @@ -419,13 +436,16 @@ D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, + D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, + D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, @@ -549,6 +569,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -569,6 +590,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 7f4940375f..d7232bd9db 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,16 +1,38 @@ import UIKit @objc private class CALayerAnimationDelegate: NSObject { - let completion: Bool -> Void + var completion: (Bool -> Void)? - init(completion: Bool -> Void) { + init(completion: (Bool -> Void)?) { self.completion = completion super.init() } @objc override func animationDidStop(anim: CAAnimation, finished flag: Bool) { - self.completion(flag) + if let completion = self.completion { + completion(flag) + } + } +} + +private let completionKey = "CAAnimationUtils_completion" + +public extension CAAnimation { + public var completion: (Bool -> Void)? { + get { + if let delegate = self.delegate as? CALayerAnimationDelegate { + return delegate.completion + } else { + return nil + } + } set(value) { + if let delegate = self.delegate as? CALayerAnimationDelegate { + delegate.completion = value + } else { + self.delegate = CALayerAnimationDelegate(completion: value) + } + } } } diff --git a/Display/Font.swift b/Display/Font.swift index e908fac5f8..577522a280 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -3,18 +3,16 @@ import UIKit public struct Font { public static func regular(size: CGFloat) -> UIFont { - if matchMinimumSystemVersion(9) { - return UIFont(name: ".SFUIDisplay-Regular", size: size)! - } else { - return UIFont(name: "HelveticaNeue", size: size)! - } + return UIFont.systemFontOfSize(size) } public static func medium(size: CGFloat) -> UIFont { - if matchMinimumSystemVersion(9) { - return UIFont(name: ".SFUIDisplay-Medium", size: size)! - } else { - return UIFont(name: "HelveticaNeue-Medium", size: size)! - } + return UIFont.boldSystemFontOfSize(size) + } +} + +public extension NSAttributedString { + convenience init(string: String, font: CTFontRef, textColor: UIColor = UIColor.blackColor()) { + self.init(string: string, attributes: [kCTFontAttributeName as String: font, kCTForegroundColorAttributeName as String: textColor.CGColor]) } } diff --git a/Display/KeyboardHostWindow.swift b/Display/KeyboardHostWindow.swift index c6597c2a92..114a07f70c 100644 --- a/Display/KeyboardHostWindow.swift +++ b/Display/KeyboardHostWindow.swift @@ -2,7 +2,7 @@ import Foundation import UIKit public class KeyboardHostWindow: UIWindow { - let textField: UITextField + public let textField: UITextField convenience public init() { self.init(frame: CGRect()) @@ -13,7 +13,7 @@ public class KeyboardHostWindow: UIWindow { super.init(frame: frame) - self.windowLevel = 1000.0 + self.windowLevel = -1.0 self.rootViewController = UIViewController() self.addSubview(self.textField) } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 186ef84e77..00552b5332 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -14,6 +14,8 @@ public class NavigationBar: ASDisplayNode { private var tempItem: UINavigationItem? private var tempItemWrapper: NavigationItemWrapper? + private let stripeHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale + var backPressed: () -> () = { } private var collapsed: Bool { @@ -112,7 +114,8 @@ public class NavigationBar: ASDisplayNode { } public override func layout() { - self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height - 0.5, width: self.frame.size.width, height: 0.5) + + self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height - stripeHeight, width: self.frame.size.width, height: stripeHeight) self.topItemWrapper?.layoutItems() self.tempItemWrapper?.layoutItems() diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index e6a852f8ba..e173ccce45 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -3,12 +3,22 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +private struct NavigationControllerLayout { + let layout: ViewControllerLayout + let statusBarHeight: CGFloat +} + public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate { private var _navigationBar: NavigationBar! private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() + private var statusBarChangeObserver: AnyObject? + + private var layout: NavigationControllerLayout? + private var pendingLayout: (NavigationControllerLayout, NSTimeInterval, Bool)? + public override init() { self._navigationBar = nil @@ -26,6 +36,23 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } return } + + self.statusBarChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationWillChangeStatusBarFrameNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak self] notification in + if let strongSelf = self { + let statusBarHeight: CGFloat = (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.CGRectValue().height ?? 20.0 + + let previousLayout: NavigationControllerLayout? + if let pendingLayout = strongSelf.pendingLayout { + previousLayout = pendingLayout.0 + } else { + previousLayout = strongSelf.layout + } + + strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true) + + strongSelf.view.setNeedsLayout() + } + }) } public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { @@ -42,8 +69,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) self.navigationBar.removeFromSuperview() - self._navigationBar.frame = navigationBarFrame(self.view.frame.size) - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("panGesture:")) panRecognizer.delegate = self panRecognizer.cancelsTouchesInView = true @@ -152,14 +177,19 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - public func pushViewController(signal: Signal) -> Disposable { - let disposable = (signal |> deliverOnMainQueue).start(next: {[weak self] controller in + public func pushViewController(controller: ViewController) { + let layout: NavigationControllerLayout + if let currentLayout = self.layout { + layout = currentLayout + } else { + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0), statusBarHeight: 20.0) + } + controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) + self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in if let strongSelf = self { strongSelf.pushViewController(controller, animated: true) } - }) - self.currentPushDisposable.set(disposable) - return disposable + })) } public override func pushViewController(viewController: UIViewController, animated: Bool) { @@ -186,7 +216,14 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr let topViewController = viewControllers[viewControllers.count - 1] as UIViewController if let controller = topViewController as? WindowContentController { - controller.setViewSize(self.view.bounds.size, insets: UIEdgeInsets(top: CGRectGetMaxY(self._navigationBar.frame), left: 0.0, bottom: 0.0, right: 0.0), duration: 0.0) + let layout: NavigationControllerLayout + if let currentLayout = self.layout { + layout = currentLayout + } else { + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0), statusBarHeight: 20.0) + } + + controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) } else { topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) } @@ -195,50 +232,78 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr super.setViewControllers(viewControllers, animated: animated) } - private func navigationBarFrame(size: CGSize) -> CGRect { - //let condensedBar = (size.height < size.width || size.height <= 320.0) && size.height < 768.0 - return CGRect(x: 0.0, y: 0.0, width: size.width, height: 20.0 + (size.height >= size.width ? 44.0 : 32.0)) + private func navigationBarFrame(layout: NavigationControllerLayout) -> CGRect { + return CGRect(x: 0.0, y: layout.statusBarHeight - 20.0, width: layout.layout.size.width, height: 20.0 + (layout.layout.size.height >= layout.layout.size.width ? 44.0 : 32.0)) } - public func setViewSize(size: CGSize, insets: UIEdgeInsets, duration: NSTimeInterval) { - if duration > DBL_EPSILON { - animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), duration: duration) - } - else { - self.view.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) + private func childControllerLayoutForLayout(layout: NavigationControllerLayout) -> ViewControllerLayout { + var insets = layout.layout.insets + insets.top = self.navigationBarFrame(layout).maxY + return ViewControllerLayout(size: layout.layout.size, insets: insets, inputViewHeight: 0.0) + } + + public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { + let previousLayout: NavigationControllerLayout? + if let pendingLayout = self.pendingLayout { + previousLayout = pendingLayout.0 + } else { + previousLayout = self.layout } - if duration > DBL_EPSILON { - animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(size), duration: duration) - } - else { - self._navigationBar.frame = self.navigationBarFrame(size) - } + self.pendingLayout = (NavigationControllerLayout(layout: layout, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false) - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) + self.view.setNeedsLayout() + } + + public override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + + if let pendingLayout = self.pendingLayout { + self.layout = pendingLayout.0 - if self.viewControllers.count >= 2 { - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + if pendingLayout.1 > DBL_EPSILON { + animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height), duration: pendingLayout.1) + } + else { + self.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) + } + + if pendingLayout.1 > DBL_EPSILON { + animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(pendingLayout.0), duration: pendingLayout.1) + } + else { + self._navigationBar.frame = self.navigationBarFrame(pendingLayout.0) + } + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) - if let controller = bottomController as? WindowContentController { - controller.setViewSize(size, insets: UIEdgeInsets(top: CGRectGetMaxY(self._navigationBar.frame), left: 0.0, bottom: 0.0, right: 0.0), duration: duration) + if self.viewControllers.count >= 2 { + let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + + if let controller = bottomController as? WindowContentController { + controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) + } else { + bottomController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) + } + } + + self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress) + } + + if let topViewController = self.topViewController { + if let controller = topViewController as? WindowContentController { + controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) } else { - bottomController.view.frame = CGRectMake(0.0, 0.0, size.width, size.height) + topViewController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) } } - } - - if let topViewController = self.topViewController { - if let controller = topViewController as? WindowContentController { - controller.setViewSize(size, insets: UIEdgeInsets(top: CGRectGetMaxY(self._navigationBar.frame), left: 0.0, bottom: 0.0, right: 0.0), duration: duration) - } else { - topViewController.view.frame = CGRectMake(0.0, 0.0, size.width, size.height) + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + navigationTransitionCoordinator.updateProgress() } - } - - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - navigationTransitionCoordinator.updateProgress() + + self.pendingLayout = nil } } diff --git a/Display/NotificationCenterUtils.h b/Display/NotificationCenterUtils.h index 09409f3fc7..6918561d91 100644 --- a/Display/NotificationCenterUtils.h +++ b/Display/NotificationCenterUtils.h @@ -1,6 +1,6 @@ #import -typedef bool (^NotificationHandlerBlock)(NSString *, id, NSDictionary *); +typedef bool (^NotificationHandlerBlock)(NSString *, id, NSDictionary *, void (^)()); @interface NotificationCenterUtils : NSObject diff --git a/Display/NotificationCenterUtils.m b/Display/NotificationCenterUtils.m index 7088967d19..d1b1aa9bff 100644 --- a/Display/NotificationCenterUtils.m +++ b/Display/NotificationCenterUtils.m @@ -1,13 +1,12 @@ #import "NotificationCenterUtils.h" #import "RuntimeUtils.h" +#import -static NSMutableArray *notificationHandlers() -{ +static NSMutableArray *notificationHandlers() { static NSMutableArray *array = nil; static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { + dispatch_once(&onceToken, ^{ array = [[NSMutableArray alloc] init]; }); return array; @@ -23,8 +22,11 @@ static NSMutableArray *notificationHandlers() { for (NotificationHandlerBlock handler in notificationHandlers()) { - if (handler(aName, anObject, aUserInfo)) + if (handler(aName, anObject, aUserInfo, ^{ + [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; + })) { return; + } } [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; @@ -32,19 +34,34 @@ static NSMutableArray *notificationHandlers() @end +@interface CATransaction (Swizzle) + ++ (void)swizzle_flush; + +@end + +@implementation CATransaction (Swizzle) + ++ (void)swizzle_flush { + //printf("===flush\n"); + + [self swizzle_flush]; +} + +@end + @implementation NotificationCenterUtils -+ (void)load -{ ++ (void)load { static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { + dispatch_once(&onceToken, ^{ [RuntimeUtils swizzleInstanceMethodOfClass:[NSNotificationCenter class] currentSelector:@selector(postNotificationName:object:userInfo:) newSelector:@selector(_a65afc19_postNotificationName:object:userInfo:)]; + + //[RuntimeUtils swizzleClassMethodOfClass:[CATransaction class] currentSelector:@selector(flush) newSelector:@selector(swizzle_flush)]; }); } -+ (void)addNotificationHandler:(bool (^)(NSString *, id, NSDictionary *))handler -{ ++ (void)addNotificationHandler:(NotificationHandlerBlock)handler { [notificationHandlers() addObject:[handler copy]]; } diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h index 8f6e180282..6742a0f49d 100644 --- a/Display/RuntimeUtils.h +++ b/Display/RuntimeUtils.h @@ -8,6 +8,7 @@ typedef enum { @interface RuntimeUtils : NSObject + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; ++ (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; @end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m index da122a4a38..3302e14754 100644 --- a/Display/RuntimeUtils.m +++ b/Display/RuntimeUtils.m @@ -4,20 +4,34 @@ @implementation RuntimeUtils -+ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector -{ ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector { Method origMethod = nil, newMethod = nil; origMethod = class_getInstanceMethod(targetClass, currentSelector); newMethod = class_getInstanceMethod(targetClass, newSelector); - if ((origMethod != nil) && (newMethod != nil)) - { - if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) - { + if ((origMethod != nil) && (newMethod != nil)) { + if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) { class_replaceMethod(targetClass, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); - } - else + } else { method_exchangeImplementations(origMethod, newMethod); + } + } +} + ++ (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector { + Method origMethod = nil, newMethod = nil; + + origMethod = class_getClassMethod(targetClass, currentSelector); + newMethod = class_getClassMethod(targetClass, newSelector); + + targetClass = object_getClass((id)targetClass); + + if ((origMethod != nil) && (newMethod != nil)) { + if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) { + class_replaceMethod(targetClass, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); + } else { + method_exchangeImplementations(origMethod, newMethod); + } } } diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift new file mode 100644 index 0000000000..336a063fe8 --- /dev/null +++ b/Display/TabBarContollerNode.swift @@ -0,0 +1,37 @@ +import Foundation +import AsyncDisplayKit + +class TabBarControllerNode: ASDisplayNode { + let tabBarNode: TabBarNode + + var currentControllerView: UIView? { + didSet { + oldValue?.removeFromSuperview() + + if let currentControllerView = self.currentControllerView { + self.view.insertSubview(currentControllerView, atIndex: 0) + } + } + } + + override init() { + self.tabBarNode = TabBarNode() + + super.init() + + self.addSubnode(self.tabBarNode) + } + + func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + let update = { + self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets.bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0)) + self.tabBarNode.layout() + } + + if duration > DBL_EPSILON { + UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: curve << 7), animations: update, completion: nil) + } else { + update() + } + } +} diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift new file mode 100644 index 0000000000..0afe63998f --- /dev/null +++ b/Display/TabBarController.swift @@ -0,0 +1,101 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +public class TabBarController: ViewController { + private var tabBarControllerNode: TabBarControllerNode { + get { + return super.displayNode as! TabBarControllerNode + } + } + + public var controllers: [ViewController] = [] { + didSet { + self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem }) + + if oldValue.count == 0 && self.controllers.count != 0 { + self.updateSelectedIndex() + } + } + } + + private var _selectedIndex: Int = 1 + public var selectedIndex: Int { + get { + return _selectedIndex + } set(value) { + let index = max(0, min(self.controllers.count - 1, value)) + if _selectedIndex != index { + _selectedIndex = index + + self.updateSelectedIndex() + } + } + } + + private var layout: ViewControllerLayout? + private var currentController: ViewController? + + override public init() { + super.init() + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func loadDisplayNode() { + self.displayNode = TabBarControllerNode() + + self.updateSelectedIndex() + } + + private func updateSelectedIndex() { + if !self.isNodeLoaded { + return + } + + if let currentController = self.currentController { + currentController.willMoveToParentViewController(nil) + self.tabBarControllerNode.currentControllerView = nil + currentController.removeFromParentViewController() + currentController.didMoveToParentViewController(nil) + + self.currentController = nil + } + + if self._selectedIndex < self.controllers.count { + self.currentController = self.controllers[self._selectedIndex] + } + + if let currentController = self.currentController { + currentController.willMoveToParentViewController(self) + if let layout = self.layout { + currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) + + currentController.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) + } + self.tabBarControllerNode.currentControllerView = currentController.view + self.addChildViewController(currentController) + currentController.didMoveToParentViewController(self) + } + } + + private func childControllerLayoutForLayout(layout: ViewControllerLayout) -> ViewControllerLayout { + var insets = layout.insets + insets.bottom += 49.0 + return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight) + } + + override public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + super.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) + + self.tabBarControllerNode.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) + + self.layout = layout + if let currentController = self.currentController { + currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) + currentController.setParentLayout(self.childControllerLayoutForLayout(layout), duration: duration, curve: curve) + } + } +} diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift new file mode 100644 index 0000000000..93031028ab --- /dev/null +++ b/Display/TabBarNode.swift @@ -0,0 +1,106 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +private let separatorHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale +private func tabBarItemImage(image: UIImage?, title: String, tintColor: UIColor) -> UIImage { + let font = Font.regular(10.0) + let titleSize = (title as NSString).boundingRectWithSize(CGSize(width: CGFloat.max, height: CGFloat.max), options: [.UsesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil).size + + let imageSize: CGSize + if let image = image { + imageSize = image.size + } else { + imageSize = CGSize() + } + + let size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + + UIGraphicsBeginImageContextWithOptions(size, true, 0.0) + let context = UIGraphicsGetCurrentContext() + + CGContextSetFillColorWithColor(context, UIColor(0xf7f7f7).CGColor) + CGContextFillRect(context, CGRect(origin: CGPoint(), size: size)) + + image?.drawAtPoint(CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0)) + + (title as NSString).drawAtPoint(CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font]) + + CGContextSetBlendMode(context, .SourceIn) + CGContextSetFillColorWithColor(context, tintColor.CGColor) + //CGContextFillRect(context, CGRect(origin: CGPoint(), size: size)) + + let image = UIGraphicsGetImageFromCurrentImageContext() + UIGraphicsEndImageContext() + + return image +} + +class TabBarNode: ASDisplayNode { + let separatorNode: ASDisplayNode + + var tabBarNodes: [ASImageNode] = [] + + var tabBarItems: [UITabBarItem] = [] { + didSet { + self.reloadTabBarItems() + } + } + + override init() { + self.separatorNode = ASDisplayNode() + self.separatorNode.backgroundColor = UIColor(0xb2b2b2) + self.separatorNode.opaque = true + self.separatorNode.layerBacked = true + + super.init() + + self.opaque = true + self.backgroundColor = UIColor(0xf7f7f7) + + self.addSubnode(self.separatorNode) + } + + private func reloadTabBarItems() { + for node in self.tabBarNodes { + node.removeFromSupernode() + } + + var tabBarNodes: [ASImageNode] = [] + for item in self.tabBarItems { + let node = ASImageNode() + node.displaysAsynchronously = false + node.displayWithoutProcessing = true + node.layerBacked = true + node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor.blueColor()) + tabBarNodes.append(node) + self.addSubnode(node) + } + + self.tabBarNodes = tabBarNodes + + self.setNeedsLayout() + } + + override func layout() { + super.layout() + + let size = self.bounds.size + + self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight), size: CGSize(width: size.width, height: separatorHeight)) + + if self.tabBarNodes.count != 0 { + let distanceBetweenNodes = size.width / CGFloat(self.tabBarNodes.count) + + let internalWidth = distanceBetweenNodes * CGFloat(self.tabBarNodes.count - 1) + let leftNodeOriginX = (size.width - internalWidth) / 2.0 + + for i in 0 ..< self.tabBarNodes.count { + let node = self.tabBarNodes[i] + node.measure(CGSize(width: internalWidth, height: size.height)) + + node.frame = CGRect(origin: CGPoint(x: floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - node.calculatedSize.width / 2.0), y: 4.0), size: node.calculatedSize) + } + } + } +} \ No newline at end of file diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h index d53da326e3..08dd3954dc 100644 --- a/Display/UIKitUtils.h +++ b/Display/UIKitUtils.h @@ -5,3 +5,9 @@ + (double)animationDurationFactor; @end + +@interface CASpringAnimation (AnimationUtils) + +- (CGFloat)valueAt:(CGFloat)t; + +@end \ No newline at end of file diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index c8cbb8aed8..2c9fef85ea 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -16,3 +16,17 @@ UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient } @end + +@interface CASpringAnimation () + +- (float)_solveForInput:(float)arg1; + +@end + +@implementation CASpringAnimation (AnimationUtils) + +- (CGFloat)valueAt:(CGFloat)t { + return [self _solveForInput:t]; +} + +@end \ No newline at end of file diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 82d70d8eea..94d576f34d 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -26,8 +26,9 @@ private func dumpLayers(layer: CALayer, indent: String = "") { } } +private let screenScale = UIScreen.mainScreen().scale public func floorToScreenPixels(value: CGFloat) -> CGFloat { - return floor(value * 2.0) / 2.0 + return floor(value * screenScale) / screenScale } public extension UIColor { @@ -61,3 +62,16 @@ public extension CGSize { public func assertNotOnMainThread(file: String = __FILE__, line: Int = __LINE__) { assert(!NSThread.isMainThread(), "\(file):\(line) running on main thread") } + +public extension UIImage { + public func precomposed() -> UIImage { + UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) + self.drawAtPoint(CGPoint()) + let result = UIGraphicsGetImageFromCurrentImageContext(); + UIGraphicsEndImageContext() + if !UIEdgeInsetsEqualToEdgeInsets(self.capInsets, UIEdgeInsetsZero) { + return result.resizableImageWithCapInsets(self.capInsets, resizingMode: self.resizingMode) + } + return result + } +} diff --git a/Display/UIWindow+OrientationChange.h b/Display/UIWindow+OrientationChange.h index 3a48232217..d985458ad0 100644 --- a/Display/UIWindow+OrientationChange.h +++ b/Display/UIWindow+OrientationChange.h @@ -3,6 +3,10 @@ @interface UIWindow (OrientationChange) - (bool)isRotating; ++ (void)addPostDeviceOrientationDidChangeBlock:(void (^)())block; ++ (bool)isDeviceRotating; + +- (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3; @end diff --git a/Display/UIWindow+OrientationChange.m b/Display/UIWindow+OrientationChange.m index 1c7946f765..f44d682e5e 100644 --- a/Display/UIWindow+OrientationChange.m +++ b/Display/UIWindow+OrientationChange.m @@ -5,21 +5,27 @@ static const void *isRotatingKey = &isRotatingKey; +static NSMutableArray *postDeviceDidChangeOrientationBlocks() { + static NSMutableArray *array = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + array = [[NSMutableArray alloc] init]; + }); + return array; +} + +static bool _isDeviceRotating = false; + @implementation UIWindow (OrientationChange) -+ (void)load -{ ++ (void)load { static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - [NotificationCenterUtils addNotificationHandler:^bool(NSString *name, id object, NSDictionary *userInfo) - { - if ([name isEqualToString:@"UIWindowWillRotateNotification"]) - { + dispatch_once(&onceToken, ^ { + [NotificationCenterUtils addNotificationHandler:^bool(NSString *name, id object, NSDictionary *userInfo, void (^passNotification)()) { + if ([name isEqualToString:@"UIWindowWillRotateNotification"]) { [(UIWindow *)object setRotating:true]; - if (NSClassFromString(@"NSUserActivity") == NULL) - { + if (NSClassFromString(@"NSUserActivity") == NULL) { UIInterfaceOrientation orientation = [userInfo[@"UIWindowNewOrientationUserInfoKey"] integerValue]; CGSize screenSize = [UIScreen mainScreen].bounds.size; if (screenSize.width > screenSize.height) @@ -61,10 +67,32 @@ static const void *isRotatingKey = &isRotatingKey; ((UIWindow *)object).bounds = CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height); }]; } - } - else if ([name isEqualToString:@"UIWindowDidRotateNotification"]) - { + + passNotification(); + + return true; + } else if ([name isEqualToString:@"UIWindowDidRotateNotification"]) { [(UIWindow *)object setRotating:false]; + } else if ([name isEqualToString:UIDeviceOrientationDidChangeNotification]) { + //NSLog(@"notification start: %@", name); + + _isDeviceRotating = true; + + passNotification(); + + if (postDeviceDidChangeOrientationBlocks().count != 0) { + NSArray *blocks = [postDeviceDidChangeOrientationBlocks() copy]; + [postDeviceDidChangeOrientationBlocks() removeAllObjects]; + for (dispatch_block_t block in blocks) { + block(); + } + } + + _isDeviceRotating = false; + + //NSLog(@"notification end: %@", name); + + return true; } return false; @@ -72,6 +100,10 @@ static const void *isRotatingKey = &isRotatingKey; }); } ++ (void)addPostDeviceOrientationDidChangeBlock:(void (^)())block { + [postDeviceDidChangeOrientationBlocks() addObject:[block copy]]; +} + - (void)setRotating:(bool)rotating { [self setAssociatedObject:@(rotating) forKey:isRotatingKey]; @@ -82,4 +114,8 @@ static const void *isRotatingKey = &isRotatingKey; return [[self associatedObjectForKey:isRotatingKey] boolValue]; } ++ (bool)isDeviceRotating { + return _isDeviceRotating; +} + @end diff --git a/Display/ViewController.swift b/Display/ViewController.swift index fc83869639..c74892c441 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -1,10 +1,15 @@ import Foundation import UIKit import AsyncDisplayKit +import SwiftSignalKit + +public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { + return lhs.size == rhs.size && lhs.insets == rhs.insets && lhs.inputViewHeight == rhs.inputViewHeight +} @objc public class ViewController: UIViewController, WindowContentController { private var _displayNode: ASDisplayNode? - public var displayNode: ASDisplayNode { + public final var displayNode: ASDisplayNode { get { if let value = self._displayNode { return value @@ -22,30 +27,133 @@ import AsyncDisplayKit } } + public final var isNodeLoaded: Bool { + return self._displayNode != nil + } + + private let _ready = Promise(true) + public var ready: Promise { + return self._ready + } + + private var updateLayoutOnLayout: (ViewControllerLayout, NSTimeInterval, UInt)? + private var layout: ViewControllerLayout? + + var keyboardFrameObserver: AnyObject? + public init() { super.init(nibName: nil, bundle: nil) self.automaticallyAdjustsScrollViewInsets = false + + self.keyboardFrameObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil, usingBlock: { [weak self] notification in + if let strongSelf = self, _ = strongSelf._displayNode { + let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() ?? CGRect() + let keyboardHeight = max(0.0, UIScreen.mainScreen().bounds.size.height - keyboardFrame.minY) + let duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 + let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.unsignedIntegerValue ?? UInt(7 << 16) + + let previousLayout: ViewControllerLayout? + var previousDurationAndCurve: (NSTimeInterval, UInt)? + if let updateLayoutOnLayout = strongSelf.updateLayoutOnLayout { + previousLayout = updateLayoutOnLayout.0 + previousDurationAndCurve = (updateLayoutOnLayout.1, updateLayoutOnLayout.2) + } else{ + previousLayout = strongSelf.layout + } + let layout = ViewControllerLayout(size: previousLayout?.size ?? CGSize(), insets: previousLayout?.insets ?? UIEdgeInsets(), inputViewHeight: keyboardHeight) + let updated: Bool + if let previousLayout = previousLayout { + updated = previousLayout != layout + } else { + updated = true + } + if updated { + print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") + + let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration > DBL_EPSILON ? 0.5 : 0.0, curve) + strongSelf.updateLayoutOnLayout = (layout, durationAndCurve.0, durationAndCurve.1) + strongSelf.view.setNeedsLayout() + } + } + }) } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + if let keyboardFrameObserver = keyboardFrameObserver { + NSNotificationCenter.defaultCenter().removeObserver(keyboardFrameObserver) + } + } + public override func loadView() { self.view = self.displayNode.view } public func loadDisplayNode() { + self.displayNode = ASDisplayNode() } - public func setViewSize(size: CGSize, insets: UIEdgeInsets, duration: NSTimeInterval) { - if duration > DBL_EPSILON { - animateRotation(self.displayNode, toFrame: CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height), duration: duration) + public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { + if self._displayNode == nil { + self.loadDisplayNode() } - else { - self.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: size.width, height: size.height) + let previousLayout: ViewControllerLayout? + if let updateLayoutOnLayout = self.updateLayoutOnLayout { + previousLayout = updateLayoutOnLayout.0 + } else { + previousLayout = self.layout + } + + let layout = ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: previousLayout?.inputViewHeight ?? 0.0) + let updated: Bool + if let previousLayout = previousLayout { + updated = previousLayout != layout + } else { + updated = true + } + if updated { + if previousLayout == nil { + self.layout = layout + self.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: 0) + } else { + self.updateLayoutOnLayout = (layout, duration, 0) + self.view.setNeedsLayout() + } + } + } + + public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + } + + override public func viewDidLayoutSubviews() { + if let updateLayoutOnLayout = self.updateLayoutOnLayout { + if !Window.isDeviceRotating() { + if !((self.view.window as? Window)?.isUpdatingOrientationLayout ?? false) { + //print("\(self) apply inputHeight: \(updateLayoutOnLayout.0.inputViewHeight)") + let previousLayout = self.layout + self.layout = updateLayoutOnLayout.0 + self.updateLayout(updateLayoutOnLayout.0, previousLayout: previousLayout, duration: updateLayoutOnLayout.1, curve: updateLayoutOnLayout.2) + + self.updateLayoutOnLayout = nil + } else { + (self.view.window as? Window)?.addPostUpdateToInterfaceOrientationBlock({ [weak self] in + if let strongSelf = self { + strongSelf.view.setNeedsLayout() + } + }) + } + } else { + Window.addPostDeviceOrientationDidChangeBlock({ [weak self] in + if let strongSelf = self { + strongSelf.view.setNeedsLayout() + } + }) + } } } } diff --git a/Display/Window.swift b/Display/Window.swift index 2fdf47ae8f..4fccbff987 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -11,9 +11,14 @@ public class WindowRootViewController: UIViewController { } } -@objc +public struct ViewControllerLayout: Equatable { + public let size: CGSize + public let insets: UIEdgeInsets + public let inputViewHeight: CGFloat +} + public protocol WindowContentController { - func setViewSize(size: CGSize, insets: UIEdgeInsets, duration: NSTimeInterval) + func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) var view: UIView! { get } } @@ -28,7 +33,9 @@ public func animateRotation(view: UIView?, toFrame: CGRect, duration: NSTimeInte public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NSTimeInterval) { if let view = view { CALayer.beginRecordingChanges() - view.frame = toFrame + UIView.animateWithDuration(duration, animations: { () -> Void in + view.frame = toFrame + }) view.layout() let states = CALayer.endRecordingChanges() as! [CALayerAnimation] let k = Float(UIView.animationDurationFactor()) @@ -67,13 +74,26 @@ public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NST } public class Window: UIWindow { + //public let textField: UITextField + + private var updateViewSizeOnLayout: (Bool, NSTimeInterval) = (false, 0.0) + public var isUpdatingOrientationLayout = false + + private let orientationChangeDuration: NSTimeInterval = { + UIDevice.currentDevice().userInterfaceIdiom == .Pad ? 0.4 : 0.3 + }() + public convenience init() { self.init(frame: UIScreen.mainScreen().bounds) } public override init(frame: CGRect) { + //self.textField = UITextField(frame: CGRect(x: -110.0, y: 0.0, width: 100.0, height: 50.0)) + super.init(frame: frame) + //self.addSubview(self.textField) + super.rootViewController = WindowRootViewController() } @@ -92,8 +112,10 @@ public class Window: UIWindow { set(value) { let sizeUpdated = super.frame.size != value.size super.frame = value + if sizeUpdated { - self.viewController?.setViewSize(value.size, insets: UIEdgeInsets(), duration: self.isRotating() ? 0.3 : 0.0) + self.updateViewSizeOnLayout = (true, self.isRotating() ? self.orientationChangeDuration : 0.0) + self.setNeedsLayout() } } } @@ -105,8 +127,10 @@ public class Window: UIWindow { set(value) { let sizeUpdated = super.bounds.size != value.size super.bounds = value + if sizeUpdated { - self.viewController?.setViewSize(value.size, insets: UIEdgeInsets(), duration: self.isRotating() ? 0.3 : 0.0) + self.updateViewSizeOnLayout = (true, self.isRotating() ? self.orientationChangeDuration : 0.0) + self.setNeedsLayout() } } } @@ -120,15 +144,39 @@ public class Window: UIWindow { self._rootViewController?.view.removeFromSuperview() self._rootViewController = value self._rootViewController?.view.frame = self.bounds - /*if let reactiveController = self._rootViewController as? ReactiveViewController { - reactiveController.displayNode.frame = CGRect(x: 0.0, y: 0.0, width: self.frame.size.width, height: self.frame.size.height) - self.addSubview(reactiveController.displayNode.view) + self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0), duration: 0.0, curve: 0) + + if let view = self._rootViewController?.view { + self.addSubview(view) } - else {*/ - if let view = self._rootViewController?.view { - self.addSubview(view) - } - //} } } + + override public func layoutSubviews() { + super.layoutSubviews() + + if self.updateViewSizeOnLayout.0 { + self.updateViewSizeOnLayout.0 = false + + self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0), duration: updateViewSizeOnLayout.1, curve: 0) + } + } + + var postUpdateToInterfaceOrientationBlocks: [Void -> Void] = [] + + override public func _updateToInterfaceOrientation(arg1: Int32, duration arg2: Double, force arg3: Bool) { + self.isUpdatingOrientationLayout = true + super._updateToInterfaceOrientation(arg1, duration: arg2, force: arg3) + self.isUpdatingOrientationLayout = false + + let blocks = self.postUpdateToInterfaceOrientationBlocks + self.postUpdateToInterfaceOrientationBlocks = [] + for f in blocks { + f() + } + } + + public func addPostUpdateToInterfaceOrientationBlock(f: Void -> Void) { + postUpdateToInterfaceOrientationBlocks.append(f) + } } From dce68131c7d994aaeb7091aca86e497e43012a33 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 26 Jan 2016 16:18:33 +0300 Subject: [PATCH 007/245] no message --- Display/CAAnimationUtils.swift | 13 ++-- Display/CALayer+ImplicitAnimations.m | 4 +- Display/NavigationBar.swift | 9 ++- Display/TabBarContollerNode.swift | 4 +- Display/TabBarController.swift | 16 ++++- Display/TabBarNode.swift | 92 ++++++++++++++++++++++++---- 6 files changed, 115 insertions(+), 23 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index d7232bd9db..61d643ceda 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -37,7 +37,7 @@ public extension CAAnimation { } public extension CALayer { - public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { + public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, additive: Bool, completion: (Bool -> Void)? = nil) { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 if k != 0 && k != 1 { @@ -52,6 +52,7 @@ public extension CALayer { animation.removedOnCompletion = true animation.fillMode = kCAFillModeForwards animation.speed = speed + animation.additive = additive if let completion = completion { animation.delegate = CALayerAnimationDelegate(completion: completion) } @@ -61,11 +62,15 @@ public extension CALayer { //self.setValue(to, forKey: keyPath) } - public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { - self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) + public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval, completion: ((Bool) -> ())? = nil) { + self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, additive: false, completion: completion) } internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { - self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration) + self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, additive: false) + } + + public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, additive: true) } } diff --git a/Display/CALayer+ImplicitAnimations.m b/Display/CALayer+ImplicitAnimations.m index 2229d1694f..ca7ec863e2 100644 --- a/Display/CALayer+ImplicitAnimations.m +++ b/Display/CALayer+ImplicitAnimations.m @@ -115,8 +115,8 @@ static NSMutableArray *currentLayerAnimations() static dispatch_once_t onceToken; dispatch_once(&onceToken, ^ { - [RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setBounds:) newSelector:@selector(_ca836a62_setBounds:)]; - [RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setPosition:) newSelector:@selector(_ca836a62_setPosition:)]; + //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setBounds:) newSelector:@selector(_ca836a62_setBounds:)]; + //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setPosition:) newSelector:@selector(_ca836a62_setPosition:)]; }); } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 00552b5332..faf10e441d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -16,6 +16,8 @@ public class NavigationBar: ASDisplayNode { private let stripeHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale + //private let effectView: UIVisualEffectView + var backPressed: () -> () = { } private var collapsed: Bool { @@ -58,9 +60,12 @@ public class NavigationBar: ASDisplayNode { stripeView = UIView() stripeView.backgroundColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) + //self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light)) + super.init() self.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0) + //self.view.addSubview(self.effectView) self.view.addSubview(stripeView) } @@ -115,9 +120,11 @@ public class NavigationBar: ASDisplayNode { public override func layout() { - self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height - stripeHeight, width: self.frame.size.width, height: stripeHeight) + self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height, width: self.frame.size.width, height: stripeHeight) self.topItemWrapper?.layoutItems() self.tempItemWrapper?.layoutItems() + + //self.effectView.frame = self.bounds } } diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 336a063fe8..ada54e63cf 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -14,8 +14,8 @@ class TabBarControllerNode: ASDisplayNode { } } - override init() { - self.tabBarNode = TabBarNode() + init(itemSelected: Int -> Void) { + self.tabBarNode = TabBarNode(itemSelected: itemSelected) super.init() diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 0afe63998f..e587d41674 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -45,7 +45,11 @@ public class TabBarController: ViewController { } override public func loadDisplayNode() { - self.displayNode = TabBarControllerNode() + self.displayNode = TabBarControllerNode(itemSelected: { [weak self] index in + if let strongSelf = self { + strongSelf.selectedIndex = index + } + }) self.updateSelectedIndex() } @@ -55,6 +59,8 @@ public class TabBarController: ViewController { return } + self.tabBarControllerNode.tabBarNode.selectedIndex = self.selectedIndex + if let currentController = self.currentController { currentController.willMoveToParentViewController(nil) self.tabBarControllerNode.currentControllerView = nil @@ -78,6 +84,14 @@ public class TabBarController: ViewController { self.tabBarControllerNode.currentControllerView = currentController.view self.addChildViewController(currentController) currentController.didMoveToParentViewController(self) + + self.navigationItem.title = currentController.navigationItem.title + self.navigationItem.leftBarButtonItem = currentController.navigationItem.leftBarButtonItem + self.navigationItem.rightBarButtonItem = currentController.navigationItem.rightBarButtonItem + } else { + self.navigationItem.title = nil + self.navigationItem.leftBarButtonItem = nil + self.navigationItem.rightBarButtonItem = nil } } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 93031028ab..0e7277ba4d 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -22,13 +22,19 @@ private func tabBarItemImage(image: UIImage?, title: String, tintColor: UIColor) CGContextSetFillColorWithColor(context, UIColor(0xf7f7f7).CGColor) CGContextFillRect(context, CGRect(origin: CGPoint(), size: size)) - image?.drawAtPoint(CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0)) + if let image = image { + let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) + CGContextSaveGState(context) + CGContextTranslateCTM(context, imageRect.midX, imageRect.midY) + CGContextScaleCTM(context, 1.0, -1.0) + CGContextTranslateCTM(context, -imageRect.midX, -imageRect.midY) + CGContextClipToMask(context, imageRect, image.CGImage) + CGContextSetFillColorWithColor(context, tintColor.CGColor) + CGContextFillRect(context, imageRect) + CGContextRestoreGState(context) + } - (title as NSString).drawAtPoint(CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font]) - - CGContextSetBlendMode(context, .SourceIn) - CGContextSetFillColorWithColor(context, tintColor.CGColor) - //CGContextFillRect(context, CGRect(origin: CGPoint(), size: size)) + (title as NSString).drawAtPoint(CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: tintColor]) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -37,17 +43,34 @@ private func tabBarItemImage(image: UIImage?, title: String, tintColor: UIColor) } class TabBarNode: ASDisplayNode { - let separatorNode: ASDisplayNode - - var tabBarNodes: [ASImageNode] = [] - var tabBarItems: [UITabBarItem] = [] { didSet { self.reloadTabBarItems() } } - override init() { + var selectedIndex: Int? { + didSet { + if self.selectedIndex != oldValue { + if let oldValue = oldValue { + self.updateNodeImage(oldValue) + } + + if let selectedIndex = self.selectedIndex { + self.updateNodeImage(selectedIndex) + } + } + } + } + + private let itemSelected: Int -> Void + + let separatorNode: ASDisplayNode + private var tabBarNodes: [ASImageNode] = [] + + init(itemSelected: Int -> Void) { + self.itemSelected = itemSelected + self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = UIColor(0xb2b2b2) self.separatorNode.opaque = true @@ -67,12 +90,17 @@ class TabBarNode: ASDisplayNode { } var tabBarNodes: [ASImageNode] = [] - for item in self.tabBarItems { + for i in 0 ..< self.tabBarItems.count { + let item = self.tabBarItems[i] let node = ASImageNode() node.displaysAsynchronously = false node.displayWithoutProcessing = true node.layerBacked = true - node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor.blueColor()) + if let selectedIndex = self.selectedIndex where selectedIndex == i { + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor.blueColor()) + } else { + node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) + } tabBarNodes.append(node) self.addSubnode(node) } @@ -82,6 +110,19 @@ class TabBarNode: ASDisplayNode { self.setNeedsLayout() } + private func updateNodeImage(index: Int) { + if index < self.tabBarNodes.count && index < self.tabBarItems.count { + let node = self.tabBarNodes[index] + let item = self.tabBarItems[index] + + if let selectedIndex = self.selectedIndex where selectedIndex == index { + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor.blueColor()) + } else { + node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) + } + } + } + override func layout() { super.layout() @@ -103,4 +144,29 @@ class TabBarNode: ASDisplayNode { } } } + + override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + super.touchesBegan(touches, withEvent: event) + + if let touch = touches.first as? UITouch { + let location = touch.locationInView(self.view) + var closestNode: (Int, CGFloat)? + + for i in 0 ..< self.tabBarNodes.count { + let node = self.tabBarNodes[i] + let distance = abs(location.x - node.position.x) + if let previousClosestNode = closestNode { + if previousClosestNode.1 > distance { + closestNode = (i, distance) + } + } else { + closestNode = (i, distance) + } + } + + if let closestNode = closestNode { + self.itemSelected(closestNode.0) + } + } + } } \ No newline at end of file From 02e78f9e81544d1f742210e79265e5371ef9118e Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 11 Feb 2016 21:44:15 +0300 Subject: [PATCH 008/245] no message --- Display/CAAnimationUtils.swift | 13 ++++++------- Display/Font.swift | 4 ++-- Display/UIKitUtils.h | 4 +++- Display/UIKitUtils.m | 2 +- Display/UIKitUtils.swift | 4 ++-- 5 files changed, 14 insertions(+), 13 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 61d643ceda..53c87297af 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -37,7 +37,7 @@ public extension CAAnimation { } public extension CALayer { - public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, additive: Bool, completion: (Bool -> Void)? = nil) { + public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 if k != 0 && k != 1 { @@ -49,10 +49,9 @@ public extension CALayer { animation.toValue = to animation.duration = duration animation.timingFunction = CAMediaTimingFunction(name: timingFunction) - animation.removedOnCompletion = true + animation.removedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards animation.speed = speed - animation.additive = additive if let completion = completion { animation.delegate = CALayerAnimationDelegate(completion: completion) } @@ -62,15 +61,15 @@ public extension CALayer { //self.setValue(to, forKey: keyPath) } - public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, additive: false, completion: completion) + public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { + self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { - self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, additive: false) + self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { - self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, additive: true) + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } } diff --git a/Display/Font.swift b/Display/Font.swift index 577522a280..58cd061118 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -12,7 +12,7 @@ public struct Font { } public extension NSAttributedString { - convenience init(string: String, font: CTFontRef, textColor: UIColor = UIColor.blackColor()) { - self.init(string: string, attributes: [kCTFontAttributeName as String: font, kCTForegroundColorAttributeName as String: textColor.CGColor]) + convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.blackColor()) { + self.init(string: string, attributes: [kCTFontAttributeName as String: font, kCTForegroundColorAttributeName as String: textColor]) } } diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h index 08dd3954dc..227be571a6 100644 --- a/Display/UIKitUtils.h +++ b/Display/UIKitUtils.h @@ -1,4 +1,5 @@ #import +#import @interface UIView (AnimationUtils) @@ -10,4 +11,5 @@ - (CGFloat)valueAt:(CGFloat)t; -@end \ No newline at end of file +@end + diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 2c9fef85ea..7fff07df92 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -29,4 +29,4 @@ UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient return [self _solveForInput:t]; } -@end \ No newline at end of file +@end diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 94d576f34d..e551655f77 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -26,9 +26,9 @@ private func dumpLayers(layer: CALayer, indent: String = "") { } } -private let screenScale = UIScreen.mainScreen().scale +public let UIScreenScale = UIScreen.mainScreen().scale public func floorToScreenPixels(value: CGFloat) -> CGFloat { - return floor(value * screenScale) / screenScale + return floor(value * UIScreenScale) / UIScreenScale } public extension UIColor { From 41aa541bbf6ab08ffbb7c6961a79d46121096a82 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 28 Mar 2016 17:13:25 +0300 Subject: [PATCH 009/245] no message --- Display.xcodeproj/project.pbxproj | 96 +++- Display/ActionSheet.swift | 7 + Display/ActionSheetItemNode.swift | 8 + Display/CAAnimationUtils.swift | 29 +- Display/DefaultDisplayTheme.swift | 5 + Display/Display.h | 3 + Display/DisplayLinkDispatcher.swift | 34 ++ Display/DisplayTheme.swift | 9 + Display/FBAnimationPerformanceTracker.h | 136 ++++++ Display/FBAnimationPerformanceTracker.mm | 412 ++++++++++++++++++ Display/GenerateImage.swift | 258 +++++++++++ Display/ImageCache.swift | 6 +- Display/NSWeakReference.h | 9 + Display/NSWeakReference.m | 13 + Display/NavigationBackButtonNode.swift | 16 +- Display/NavigationBar.swift | 78 ++-- .../NavigationBarTransitionContainer.swift | 6 + Display/NavigationButtonNode.swift | 14 +- Display/NavigationController.swift | 267 ++++++++++-- Display/NavigationItemWrapper.swift | 2 +- Display/NavigationShadow@2x.png | Bin 0 -> 1116 bytes Display/NavigationTitleNode.swift | 2 +- Display/NavigationTransitionCoordinator.swift | 158 +++++++ Display/NavigationTransitionView.swift | 82 ---- Display/RuntimeUtils.h | 1 + Display/RuntimeUtils.m | 10 + Display/StatusBar.swift | 59 +++ Display/StatusBarManager.swift | 151 +++++++ Display/StatusBarProxyNode.swift | 333 ++++++++++++++ Display/StatusBarSurfaceProvider.swift | 4 + Display/StatusBarUtils.h | 9 + Display/StatusBarUtils.m | 32 ++ Display/TabBarController.swift | 2 +- Display/TabBarNode.swift | 8 +- Display/UIKitUtils.swift | 2 +- Display/UIViewController+Navigation.h | 4 + Display/UIViewController+Navigation.m | 125 ++++++ Display/ViewController.swift | 33 +- Display/Window.swift | 17 +- 39 files changed, 2230 insertions(+), 210 deletions(-) create mode 100644 Display/ActionSheet.swift create mode 100644 Display/ActionSheetItemNode.swift create mode 100644 Display/DefaultDisplayTheme.swift create mode 100644 Display/DisplayLinkDispatcher.swift create mode 100644 Display/DisplayTheme.swift create mode 100644 Display/FBAnimationPerformanceTracker.h create mode 100644 Display/FBAnimationPerformanceTracker.mm create mode 100644 Display/GenerateImage.swift create mode 100644 Display/NSWeakReference.h create mode 100644 Display/NSWeakReference.m create mode 100644 Display/NavigationBarTransitionContainer.swift create mode 100644 Display/NavigationShadow@2x.png create mode 100644 Display/NavigationTransitionCoordinator.swift delete mode 100644 Display/NavigationTransitionView.swift create mode 100644 Display/StatusBar.swift create mode 100644 Display/StatusBarManager.swift create mode 100644 Display/StatusBarProxyNode.swift create mode 100644 Display/StatusBarSurfaceProvider.swift create mode 100644 Display/StatusBarUtils.h create mode 100644 Display/StatusBarUtils.m diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index ff3255d01f..3bab6c708f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,7 +7,19 @@ objects = { /* Begin PBXBuildFile section */ + D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; + D0078A6D1C92B3B900DF6D92 /* ActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */; }; + D0078A6F1C92B40300DF6D92 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */; }; + D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */; }; + D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; + D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */; }; + D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E7DF61C96C5F200C07816 /* NSWeakReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DF71C96C5F200C07816 /* NSWeakReference.m */; }; + D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */; }; + D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */; }; + D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; @@ -33,7 +45,7 @@ D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC3071B69575900E235A3 /* NSBag.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3051B69575900E235A3 /* NSBag.m */; }; D05CC3081B69575900E235A3 /* NSBag.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3061B69575900E235A3 /* NSBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */; }; + D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */; }; D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30A1B695A9500E235A3 /* NavigationBar.swift */; }; D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */; }; D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */; }; @@ -53,6 +65,12 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; + D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */; }; + D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; + D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; + D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; @@ -70,7 +88,19 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; + D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheet.swift; sourceTree = ""; }; + D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayTheme.swift; sourceTree = ""; }; + D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultDisplayTheme.swift; sourceTree = ""; }; + D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; + D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionContainer.swift; sourceTree = ""; }; + D03E7DF61C96C5F200C07816 /* NSWeakReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSWeakReference.h; sourceTree = ""; }; + D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = ""; }; + D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = ""; }; + D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; + D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarSurfaceProvider.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -99,7 +129,7 @@ D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NotificationCenterUtils.h; sourceTree = ""; }; D05CC3051B69575900E235A3 /* NSBag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSBag.m; sourceTree = ""; }; D05CC3061B69575900E235A3 /* NSBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBag.h; sourceTree = ""; }; - D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionView.swift; sourceTree = ""; }; + D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionCoordinator.swift; sourceTree = ""; }; D05CC30A1B695A9500E235A3 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = ""; }; D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemWrapper.swift; sourceTree = ""; }; D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemTransitionState.swift; sourceTree = ""; }; @@ -119,6 +149,12 @@ D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; + D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarUtils.h; sourceTree = ""; }; + D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarUtils.m; sourceTree = ""; }; + D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; + D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; + D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; + D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; @@ -146,6 +182,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D0078A6B1C92B3A600DF6D92 /* Action Sheet */ = { + isa = PBXGroup; + children = ( + D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */, + D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */, + ); + name = "Action Sheet"; + sourceTree = ""; + }; D02BDAEC1B6A7053008AFAD2 /* Nodes */ = { isa = PBXGroup; children = ( @@ -153,6 +198,15 @@ name = Nodes; sourceTree = ""; }; + D03BCCE91C72AE4B0097A291 /* Theme */ = { + isa = PBXGroup; + children = ( + D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */, + D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */, + ); + name = Theme; + sourceTree = ""; + }; D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( @@ -175,10 +229,12 @@ D05CC2651B69316F00E235A3 /* Display */ = { isa = PBXGroup; children = ( + D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, + D0078A6B1C92B3A600DF6D92 /* Action Sheet */, D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, D0E49C861B83A1680099E553 /* Image Cache */, @@ -210,6 +266,7 @@ D05CC2E11B69534100E235A3 /* Supporting Files */ = { isa = PBXGroup; children = ( + D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */, D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */, D05CC2661B69316F00E235A3 /* Display.h */, D05CC2681B69316F00E235A3 /* Info.plist */, @@ -243,9 +300,17 @@ D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */, D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */, D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */, + D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */, + D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */, D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */, D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */, D06EE8441B7140FF00837186 /* Font.swift */, + D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */, + D03E7DF61C96C5F200C07816 /* NSWeakReference.h */, + D03E7DF71C96C5F200C07816 /* NSWeakReference.m */, + D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */, + D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */, + D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */, ); name = Utils; sourceTree = ""; @@ -253,7 +318,8 @@ D05CC3211B695AA600E235A3 /* Navigation */ = { isa = PBXGroup; children = ( - D05CC3091B695A9500E235A3 /* NavigationTransitionView.swift */, + D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */, + D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */, D05CC30A1B695A9500E235A3 /* NavigationBar.swift */, D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */, D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, @@ -279,6 +345,10 @@ isa = PBXGroup; children = ( D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */, + D0078A671C92B21400DF6D92 /* StatusBar.swift */, + D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */, + D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */, + D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */, ); name = "Status Bar"; sourceTree = ""; @@ -311,13 +381,16 @@ D05CC3041B69568600E235A3 /* NotificationCenterUtils.h in Headers */, D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */, D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */, + D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */, D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */, + D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */, D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */, D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */, D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */, D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */, D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, D05CC3081B69575900E235A3 /* NSBag.h in Headers */, + D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */, D05CC2671B69316F00E235A3 /* Display.h in Headers */, D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */, ); @@ -403,6 +476,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */, D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -422,8 +496,11 @@ buildActionMask = 2147483647; files = ( D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, + D0078A6F1C92B40300DF6D92 /* ActionSheetItemNode.swift in Sources */, + D0078A6D1C92B3B900DF6D92 /* ActionSheet.swift in Sources */, D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */, + D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, @@ -433,12 +510,17 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */, D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, + D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, + D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, + D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, + D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, @@ -448,12 +530,18 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, + D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */, + D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */, + D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, + D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, + D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, + D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, - D05CC3151B695A9600E235A3 /* NavigationTransitionView.swift in Sources */, + D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Display/ActionSheet.swift b/Display/ActionSheet.swift new file mode 100644 index 0000000000..5feab284f9 --- /dev/null +++ b/Display/ActionSheet.swift @@ -0,0 +1,7 @@ +import Foundation + +public class ActionSheet { + public init() { + + } +} diff --git a/Display/ActionSheetItemNode.swift b/Display/ActionSheetItemNode.swift new file mode 100644 index 0000000000..bae40f9bf6 --- /dev/null +++ b/Display/ActionSheetItemNode.swift @@ -0,0 +1,8 @@ +import Foundation +import AsyncDisplayKit + +public class ActionSheetItemNode: ASDisplayNode { + override public init() { + super.init() + } +} \ No newline at end of file diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 53c87297af..f8aabe340d 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -61,15 +61,42 @@ public extension CALayer { //self.setValue(to, forKey: keyPath) } + public func animateAdditive(from from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.removedOnCompletion = removeOnCompletion + animation.fillMode = kCAFillModeForwards + animation.speed = speed + animation.additive = true + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + self.addAnimation(animation, forKey: key) + } + public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } + public func animateScale(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) + } + internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { - self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) + self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } } diff --git a/Display/DefaultDisplayTheme.swift b/Display/DefaultDisplayTheme.swift new file mode 100644 index 0000000000..74f95ebf48 --- /dev/null +++ b/Display/DefaultDisplayTheme.swift @@ -0,0 +1,5 @@ +import Foundation + +func defaultDisplayTheme() -> DisplayTheme { + return DisplayTheme(tintColor: UIColor.blueColor()) +} diff --git a/Display/Display.h b/Display/Display.h index 8cb75256da..72849d0699 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -27,4 +27,7 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import +#import #import +#import +#import diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift new file mode 100644 index 0000000000..5210d7fc9e --- /dev/null +++ b/Display/DisplayLinkDispatcher.swift @@ -0,0 +1,34 @@ +import Foundation + +public class DisplayLinkDispatcher: NSObject { + private var displayLink: CADisplayLink! + private var blocksToDispatch: [Void -> Void] = [] + private let limit: Int + + public init(limit: Int = 0) { + self.limit = limit + + super.init() + + self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) + self.displayLink.paused = true + self.displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) + } + + public func dispatch(f: Void -> Void) { + self.blocksToDispatch.append(f) + self.displayLink.paused = false + } + + @objc func run() { + for _ in 0 ..< (self.limit == 0 ? 1000 : self.limit) { + if self.blocksToDispatch.count == 0 { + self.displayLink.paused = true + break + } else { + let f = self.blocksToDispatch.removeFirst() + f() + } + } + } +} \ No newline at end of file diff --git a/Display/DisplayTheme.swift b/Display/DisplayTheme.swift new file mode 100644 index 0000000000..6a96470066 --- /dev/null +++ b/Display/DisplayTheme.swift @@ -0,0 +1,9 @@ +import Foundation + +public class DisplayTheme { + var tintColor: UIColor + + public init(tintColor: UIColor) { + self.tintColor = tintColor + } +} diff --git a/Display/FBAnimationPerformanceTracker.h b/Display/FBAnimationPerformanceTracker.h new file mode 100644 index 0000000000..f9cad92ae9 --- /dev/null +++ b/Display/FBAnimationPerformanceTracker.h @@ -0,0 +1,136 @@ +#import + +/* + * This is an example provided by Facebook are for non-commercial testing and + * evaluation purposes only. + * + * Facebook reserves all rights not expressly granted. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS + * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL + * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN + * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * + * FBAnimationPerformanceTracker + * ----------------------------------------------------------------------- + * + * This class provides animation performance tracking functionality. It basically + * measures the app's frame rate during an operation, and reports this information. + * + * 1) In Foo's designated initializer, construct a tracker object + * + * 2) Add calls to -start and -stop in appropriate places, e.g. for a ScrollView + * + * - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView { + * [_apTracker start]; + * } + * + * - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView + * { + * if (!scrollView.dragging) { + * [_apTracker stop]; + * } + * } + * + * - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate { + * if (!decelerate) { + * [_apTracker stop]; + * } + * } + * + * Notes + * ----- + * [] The tracker operates by creating a CADisplayLink object to measure the frame rate of the display + * during start/stop interval. + * + * [] Calls to -stop that were not preceded by a matching call to -start have no effect. + * + * [] 2 calls to -start in a row will trash the data accumulated so far and not log anything. + * + * + * Configuration object for the core tracker + * + * =============================================================================== + * I highly recommend for you to use the standard configuration provided + * These are essentially here so that the computation of the metric is transparent + * and you can feel confident in what the numbers mean. + * =============================================================================== + */ +struct FBAnimationPerformanceTrackerConfig +{ + // Number of frame drop that defines a "small" drop event. By default, 1. + NSInteger smallDropEventFrameNumber; + // Number of frame drop that defines a "large" drop event. By default, 4. + NSInteger largeDropEventFrameNumber; + // Number of maximum frame drops to which the drop will be trimmed down to. Currently 15. + NSInteger maxFrameDropAccount; + + // If YES, will report stack traces + BOOL reportStackTraces; +}; +typedef struct FBAnimationPerformanceTrackerConfig FBAnimationPerformanceTrackerConfig; + + +@protocol FBAnimationPerformanceTrackerDelegate + +/** + * Core Metric + * + * You are responsible for the aggregation of these metrics (it being on the client or the server). I recommend to implement both + * to limit the payload you are sending to the server. + * + * The final recommended metric being: - SUM(duration) / SUM(smallDropEvent) aka the number of seconds between one frame drop or more + * - SUM(duration) / SUM(largeDropEvent) aka the number of seconds between four frame drops or more + * + * The first metric will tell you how smooth is your scroll view. + * The second metric will tell you how clowny your scroll view can get. + * + * Every time stop is called, this event will fire reporting the performance. + * + * NOTE on this metric: + * - It has been tested at scale on many Facebook apps. + * - It follows the curves of devices. + * - You will need about 100K calls for the number to converge. + * - It is perfectly correlated to X = Percentage of time spent at 60fps. Number of seconds between one frame drop = 1 / ( 1 - Time spent at 60 fps) + * - We report fraction of drops. 7 frame drop = 1.75 of a large frame drop if a large drop is 4 frame drop. + * This is to preserve the correlation mentionned above. + */ +- (void)reportDurationInMS:(NSInteger)duration smallDropEvent:(double)smallDropEvent largeDropEvent:(double)largeDropEvent; + +/** + * Stack traces + * + * Dark magic of the animation tracker. In case of a frame drop, this will return a stack trace. + * This will NOT be reported on the main-thread, but off-main thread to save a few CPU cycles. + * + * The slide is constant value that needs to be reported with the stack for processing. + * This currently only allows for symbolication of your own image. + * + * Future work includes symbolicating all modules. I personnaly find it usually + * good enough to know the name of the module. + * + * The stack will have the following format: + * Foundation:0x123|MyApp:0x234|MyApp:0x345| + * + * The slide will have the following format: + * 0x456 + */ +- (void)reportStackTrace:(NSString *)stack withSlide:(NSString *)slide; + +@end + +@interface FBAnimationPerformanceTracker : NSObject + +- (instancetype)initWithConfig:(FBAnimationPerformanceTrackerConfig)config; + ++ (FBAnimationPerformanceTrackerConfig)standardConfig; + +@property (weak, nonatomic, readwrite) id delegate; + +- (void)start; +- (void)stop; + +@end diff --git a/Display/FBAnimationPerformanceTracker.mm b/Display/FBAnimationPerformanceTracker.mm new file mode 100644 index 0000000000..7257c338b8 --- /dev/null +++ b/Display/FBAnimationPerformanceTracker.mm @@ -0,0 +1,412 @@ +// +// FBAnimationPerformanceTracker.m +// Display +// +// Created by Peter on 3/16/16. +// Copyright © 2016 Telegram. All rights reserved. +// + +#import "FBAnimationPerformanceTracker.h" + +#import +#import +#import + +#import + +#import + +#import "execinfo.h" + +#include + +static BOOL _signalSetup; +static pthread_t _mainThread; +static NSThread *_trackerThread; + +static std::map> _imageNames; + +#ifdef __LP64__ +typedef mach_header_64 fb_mach_header; +typedef segment_command_64 fb_mach_segment_command; +#define LC_SEGMENT_ARCH LC_SEGMENT_64 +#else +typedef mach_header fb_mach_header; +typedef segment_command fb_mach_segment_command; +#define LC_SEGMENT_ARCH LC_SEGMENT +#endif + +static volatile BOOL _scrolling; +pthread_mutex_t _scrollingMutex; +pthread_cond_t _scrollingCondVariable; +dispatch_queue_t _symbolicationQueue; + +// We record at most 16 frames since I cap the number of frames dropped measured at 15. +// Past 15, something went very wrong (massive contention, priority inversion, rpc call going wrong...) . +// It will only pollute the data to get more. +static const int callstack_max_number = 16; + +static int callstack_i; +static bool callstack_dirty; +static int callstack_size[callstack_max_number]; +static void *callstacks[callstack_max_number][128]; +uint64_t callstack_time_capture; + +static void _callstack_signal_handler(int signr, siginfo_t *info, void *secret) +{ + // This is run on the main thread every 16 ms or so during scroll. + + // Signals are run one by one so there is no risk of concurrency of a signal + // by the same signal. + + // The backtrace call is technically signal-safe on Unix-based system + // See: http://www.unix.com/man-page/all/3c/walkcontext/ + + // WARNING: this is signal handler, no memory allocation is safe. + // Essentially nothing is safe unless specified it is. + callstack_size[callstack_i] = backtrace(callstacks[callstack_i], 128); + callstack_i = (callstack_i + 1) & (callstack_max_number - 1); // & is a cheap modulo (only works for power of 2) + callstack_dirty = true; +} + +@interface FBCallstack : NSObject +@property (nonatomic, readonly, assign) int size; +@property (nonatomic, readonly, assign) void **callstack; +- (instancetype)initWithSize:(int)size callstack:(void *)callstack; +@end + +@implementation FBCallstack +- (instancetype)initWithSize:(int)size callstack:(void *)callstack +{ + if (self = [super init]) { + _size = size; + _callstack = (void **)malloc(size * sizeof(void *)); + memcpy(_callstack, callstack, size * sizeof(void *)); + } + return self; +} + +- (void)dealloc +{ + free(_callstack); +} +@end + +@implementation FBAnimationPerformanceTracker +{ + FBAnimationPerformanceTrackerConfig _config; + + BOOL _tracking; + BOOL _firstUpdate; + NSTimeInterval _previousFrameTimestamp; + CADisplayLink *_displayLink; + BOOL _prepared; + + // numbers used to track the performance metrics + double _durationTotal; + double _maxFrameTime; + double _smallDrops; + double _largeDrops; +} + +- (instancetype)initWithConfig:(FBAnimationPerformanceTrackerConfig)config +{ + if (self = [super init]) { + // Stack trace logging is not working well in debug mode + // We don't want the data anyway. So let's bail. +#if defined(DEBUG) + config.reportStackTraces = NO; +#endif + _config = config; + if (config.reportStackTraces) { + [self _setupSignal]; + } + } + return self; +} + ++ (FBAnimationPerformanceTrackerConfig)standardConfig +{ + FBAnimationPerformanceTrackerConfig config = { + .smallDropEventFrameNumber = 1, + .largeDropEventFrameNumber = 4, + .maxFrameDropAccount = 15, + .reportStackTraces = NO + }; + return config; +} + ++ (void)_trackerLoop +{ + while (true) { + // If you are confused by this part, + // Check out https://computing.llnl.gov/tutorials/pthreads/#ConditionVariables + + // Lock the mutex + pthread_mutex_lock(&_scrollingMutex); + while (!_scrolling) { + // Unlock the mutex and sleep until the conditional variable is signaled + pthread_cond_wait(&_scrollingCondVariable, &_scrollingMutex); + // The conditional variable was signaled, but we need to check _scrolling + // As nothing guarantees that it is still true + } + // _scrolling is true, go ahead and capture traces for a while. + pthread_mutex_unlock(&_scrollingMutex); + + // We are scrolling, yay, capture traces + while (_scrolling) { + usleep(16000); + + // Here I use SIGPROF which is a signal supposed to be used for profiling + // I haven't stumbled upon any collision so far. + // There is no guarantee that it won't impact the system in unpredicted ways. + // Use wisely. + + pthread_kill(_mainThread, SIGPROF); + } + } +} + +- (void)_setupSignal +{ + if (!_signalSetup) { + // The signal hook should be setup once and only once + _signalSetup = YES; + + // I actually don't know if the main thread can die. If it does, well, + // this is not going to work. + // UPDATE 4/2015: on iOS8, it looks like the main-thread never dies, and this pointer is correct + _mainThread = pthread_self(); + + callstack_i = 0; + + // Setup the signal + struct sigaction sa; + sigfillset(&sa.sa_mask); + sa.sa_flags = SA_SIGINFO; + sa.sa_sigaction = _callstack_signal_handler; + sigaction(SIGPROF, &sa, NULL); + + pthread_mutex_init(&_scrollingMutex, NULL); + pthread_cond_init (&_scrollingCondVariable, NULL); + + // Setup the signal firing loop + _trackerThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(_trackerLoop) object:nil]; + // We wanna be higher priority than the main thread + // On iOS8 : this will roughly stick us at priority 61, while the main thread oscillates between 20 and 47 + _trackerThread.threadPriority = 1.0; + [_trackerThread start]; + + _symbolicationQueue = dispatch_queue_create("com.facebook.symbolication", DISPATCH_QUEUE_SERIAL); + dispatch_async(_symbolicationQueue, ^(void) {[self _setupSymbolication];}); + } +} + +- (void)_setupSymbolication +{ + // This extract the starting slide of every module in the app + // This is used to know which module an instruction pointer belongs to. + + // These operations is NOT thread-safe according to Apple docs + // Do not call this multiple times + int images = _dyld_image_count(); + + for (int i = 0; i < images; i ++) { + intptr_t imageSlide = _dyld_get_image_vmaddr_slide(i); + + // Here we extract the module name from the full path + // Typically it looks something like: /path/to/lib/UIKit + // And I just extract UIKit + NSString *fullName = [NSString stringWithUTF8String:_dyld_get_image_name(i)]; + NSRange range = [fullName rangeOfString:@"/" options:NSBackwardsSearch]; + NSUInteger startP = (range.location != NSNotFound) ? range.location + 1 : 0; + NSString *imageName = [fullName substringFromIndex:startP]; + + // This is parsing the mach header in order to extract the slide. + // See https://developer.apple.com/library/mac/documentation/DeveloperTools/Conceptual/MachORuntime/index.html + // For the structure of mach headers + fb_mach_header *header = (fb_mach_header*)_dyld_get_image_header(i); + if (!header) { + continue; + } + + const struct load_command *cmd = + reinterpret_cast(header + 1); + + for (unsigned int c = 0; cmd && (c < header->ncmds); c++) { + if (cmd->cmd == LC_SEGMENT_ARCH) { + const fb_mach_segment_command *seg = + reinterpret_cast(cmd); + + if (!strcmp(seg->segname, "__TEXT")) { + _imageNames[(void *)(seg->vmaddr + imageSlide)] = imageName; + break; + } + } + cmd = reinterpret_cast((char *)cmd + cmd->cmdsize); + } + } +} + +- (void)dealloc +{ + if (_prepared) { + [self _tearDownCADisplayLink]; + } +} + +#pragma mark - Tracking + +- (void)start +{ + if (!_tracking) { + if ([self prepare]) { + _displayLink.paused = NO; + _tracking = YES; + [self _reset]; + + if (_config.reportStackTraces) { + pthread_mutex_lock(&_scrollingMutex); + _scrolling = YES; + // Signal the tracker thread to start firing the signals + pthread_cond_signal(&_scrollingCondVariable); + pthread_mutex_unlock(&_scrollingMutex); + } + } + } +} + +- (void)stop +{ + if (_tracking) { + _tracking = NO; + _displayLink.paused = YES; + if (_durationTotal > 0) { + [_delegate reportDurationInMS:round(1000.0 * _durationTotal) smallDropEvent:_smallDrops largeDropEvent:_largeDrops]; + if (_config.reportStackTraces) { + pthread_mutex_lock(&_scrollingMutex); + _scrolling = NO; + pthread_mutex_unlock(&_scrollingMutex); + } + } + } +} + +- (BOOL)prepare +{ + if (_prepared) { + return YES; + } + + [self _setUpCADisplayLink]; + _prepared = YES; + + return YES; +} + +- (void)_setUpCADisplayLink +{ + _displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(_update)]; + [_displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes]; + _displayLink.paused = YES; +} + +- (void)_tearDownCADisplayLink +{ + [_displayLink invalidate]; + _displayLink = nil; +} + +- (void)_reset +{ + _firstUpdate = YES; + _previousFrameTimestamp = 0.0; + _durationTotal = 0; + _maxFrameTime = 0; + _largeDrops = 0; + _smallDrops = 0; +} + +- (void)_addFrameTime:(NSTimeInterval)actualFrameTime singleFrameTime:(NSTimeInterval)singleFrameTime +{ + _maxFrameTime = MAX(actualFrameTime, _maxFrameTime); + + NSInteger frameDropped = round(actualFrameTime / singleFrameTime) - 1; + frameDropped = MAX(frameDropped, 0); + // This is to reduce noise. Massive frame drops will just add noise to your data. + frameDropped = MIN(_config.maxFrameDropAccount, frameDropped); + + _durationTotal += (frameDropped + 1) * singleFrameTime; + // We account 2 frame drops as 2 small events. This way the metric correlates perfectly with Time at X fps. + _smallDrops += (frameDropped >= _config.smallDropEventFrameNumber) ? ((double) frameDropped) / (double)_config.smallDropEventFrameNumber : 0.0; + _largeDrops += (frameDropped >= _config.largeDropEventFrameNumber) ? ((double) frameDropped) / (double)_config.largeDropEventFrameNumber : 0.0; + + if (frameDropped >= 1) { + if (_config.reportStackTraces) { + callstack_dirty = false; + for (int ci = 0; ci <= frameDropped ; ci ++) { + // This is computing the previous indexes + // callstack - 1 - ci takes us back ci frames + // I want a positive number so I add callstack_max_number + // And then just modulo it, with & (callstack_max_number - 1) + int callstackPreviousIndex = ((callstack_i - 1 - ci) + callstack_max_number) & (callstack_max_number - 1); + FBCallstack *callstackCopy = [[FBCallstack alloc] initWithSize:callstack_size[callstackPreviousIndex] callstack:callstacks[callstackPreviousIndex]]; + // Check that in between the beginning and the end of the copy the signal did not fire + if (!callstack_dirty) { + // The copy has been made. We are now fine, let's punt the rest off main-thread. + __weak FBAnimationPerformanceTracker *weakSelf = self; + dispatch_async(_symbolicationQueue, ^(void) { + [weakSelf _reportStackTrace:callstackCopy]; + }); + } + } + } + } +} + +- (void)_update +{ + if (!_tracking) { + return; + } + + if (_firstUpdate) { + _firstUpdate = NO; + _previousFrameTimestamp = _displayLink.timestamp; + return; + } + + NSTimeInterval currentTimestamp = _displayLink.timestamp; + NSTimeInterval frameTime = currentTimestamp - _previousFrameTimestamp; + [self _addFrameTime:frameTime singleFrameTime:_displayLink.duration]; + _previousFrameTimestamp = currentTimestamp; +} + +- (void)_reportStackTrace:(FBCallstack *)callstack +{ + static NSString *slide; + static dispatch_once_t slide_predicate; + + dispatch_once(&slide_predicate, ^{ + slide = [NSString stringWithFormat:@"%p", (void *)_dyld_get_image_header(0)]; + }); + + @autoreleasepool { + NSMutableString *stack = [NSMutableString string]; + + for (int j = 2; j < callstack.size; j ++) { + void *instructionPointer = callstack.callstack[j]; + auto it = _imageNames.lower_bound(instructionPointer); + + NSString *imageName = (it != _imageNames.end()) ? it->second : @"???"; + + [stack appendString:imageName]; + [stack appendString:@":"]; + [stack appendString:[NSString stringWithFormat:@"%p", instructionPointer]]; + [stack appendString:@"|"]; + } + + [_delegate reportStackTrace:stack withSlide:slide]; + } +} +@end diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift new file mode 100644 index 0000000000..ff19c6a151 --- /dev/null +++ b/Display/GenerateImage.swift @@ -0,0 +1,258 @@ +import Foundation +import UIKit + +let deviceColorSpace = CGColorSpaceCreateDeviceRGB() +let deviceScale = UIScreen.mainScreen().scale + +public func generateImage(size: CGSize, generator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { + let scale = deviceScale + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = UnsafeMutablePointer(malloc(length)) + let provider: CGDataProvider? = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + free(bytes) + }) + + generator(scaledSize, bytes) + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + guard let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) + else { + return nil + } + + return UIImage(CGImage: image, scale: scale, orientation: .Up) +} + +public func generateImage(size: CGSize, generator: (CGSize, CGContextRef) -> Void) -> UIImage? { + let scale = deviceScale + let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = UnsafeMutablePointer(malloc(length)) + let provider: CGDataProvider? = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + free(bytes) + }) + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + + guard let context = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, bitmapInfo.rawValue) + else { + return nil + } + + CGContextScaleCTM(context, scale, scale) + + generator(size, context) + + guard let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) + else { + return nil + } + + return UIImage(CGImage: image, scale: scale, orientation: .Up) +} + +public enum DrawingContextBltMode { + case Alpha +} + +public class DrawingContext { + public let size: CGSize + public let scale: CGFloat + private let scaledSize: CGSize + public let bytesPerRow: Int + private let bitmapInfo: CGBitmapInfo + public let length: Int + public let bytes: UnsafeMutablePointer + let provider: CGDataProvider? + + private var _context: CGContext? + + public func withContext(@noescape f: (CGContext) -> ()) { + if self._context == nil { + let c = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, self.bitmapInfo.rawValue) + CGContextScaleCTM(c, scale, scale) + self._context = c + } + + if let _context = self._context { + CGContextTranslateCTM(_context, self.size.width / 2.0, self.size.height / 2.0) + CGContextScaleCTM(_context, 1.0, -1.0) + CGContextTranslateCTM(_context, -self.size.width / 2.0, -self.size.height / 2.0) + + f(_context) + + CGContextTranslateCTM(_context, self.size.width / 2.0, self.size.height / 2.0) + CGContextScaleCTM(_context, 1.0, -1.0) + CGContextTranslateCTM(_context, -self.size.width / 2.0, -self.size.height / 2.0) + } + } + + public func withFlippedContext(@noescape f: (CGContext) -> ()) { + if self._context == nil { + let c = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, self.bitmapInfo.rawValue) + CGContextScaleCTM(c, scale, scale) + self._context = c + } + + if let _context = self._context { + f(_context) + } + } + + public init(size: CGSize, clear: Bool = false) { + self.size = size + self.scale = deviceScale + self.scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + + self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + self.length = bytesPerRow * Int(scaledSize.height) + + self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + + self.bytes = UnsafeMutablePointer(malloc(length)) + if clear { + memset(self.bytes, 0, self.length) + } + self.provider = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + free(bytes) + }) + } + + public func generateImage() -> UIImage? { + if let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) { + return UIImage(CGImage: image, scale: scale, orientation: .Up) + } else { + return nil + } + } + + public func blt(other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { + if abs(other.scale - self.scale) < CGFloat(FLT_EPSILON) { + let srcX = 0 + var srcY = 0 + let dstX = Int(at.x * self.scale) + var dstY = Int(at.y * self.scale) + + let width = min(Int(self.size.width * self.scale) - dstX, Int(other.size.width * self.scale)) + let height = min(Int(self.size.height * self.scale) - dstY, Int(other.size.height * self.scale)) + + let maxDstX = dstX + width + let maxDstY = dstY + height + + switch mode { + case .Alpha: + while dstY < maxDstY { + let srcLine = UnsafeMutablePointer(other.bytes + srcY * other.bytesPerRow) + let dstLine = UnsafeMutablePointer(self.bytes + dstY * self.bytesPerRow) + + var dx = dstX + var sx = srcX + while dx < maxDstX { + let srcPixel = srcLine + sx + let dstPixel = dstLine + dx + + let baseColor = dstPixel.memory + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + let alpha = srcPixel.memory >> 24 + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + dstPixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + dx += 1 + sx += 1 + } + + dstY += 1 + srcY += 1 + } + } + } + } +} + +public enum ParsingError: ErrorType { + case Generic +} + +public func readCGFloat(inout index: UnsafePointer, end: UnsafePointer, separator: UInt8) throws -> CGFloat { + let begin = index + var seenPoint = false + while index <= end { + let c = index.memory + index = index.successor() + + if c == 46 { // . + if seenPoint { + throw ParsingError.Generic + } else { + seenPoint = true + } + } else if c == separator { + break + } else if c < 48 || c > 57 { + throw ParsingError.Generic + } + } + + if index == begin { + throw ParsingError.Generic + } + + if let value = NSString(bytes: UnsafePointer(begin), length: index - begin, encoding: NSUTF8StringEncoding)?.floatValue { + return CGFloat(value) + } else { + throw ParsingError.Generic + } +} + +public func drawSvgPath(context: CGContextRef, path: StaticString) throws { + var index: UnsafePointer = path.utf8Start + let end = path.utf8Start.advancedBy(path.byteSize) + while index < end { + let c = index.memory + index = index.successor() + + if c == 77 { // M + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + + //print("Move to \(x), \(y)") + CGContextMoveToPoint(context, x, y) + } else if c == 76 { // L + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + + //print("Line to \(x), \(y)") + CGContextAddLineToPoint(context, x, y) + } else if c == 67 { // C + let x1 = try readCGFloat(&index, end: end, separator: 44) + let y1 = try readCGFloat(&index, end: end, separator: 32) + let x2 = try readCGFloat(&index, end: end, separator: 44) + let y2 = try readCGFloat(&index, end: end, separator: 32) + let x = try readCGFloat(&index, end: end, separator: 44) + let y = try readCGFloat(&index, end: end, separator: 32) + CGContextAddCurveToPoint(context, x1, y1, x2, y2, x, y) + + //print("Line to \(x), \(y)") + + } else if c == 90 { // Z + if index != end && index.memory != 32 { + throw ParsingError.Generic + } + + //CGContextClosePath(context) + CGContextFillPath(context) + //CGContextBeginPath(context) + //print("Close") + } + } +} diff --git a/Display/ImageCache.swift b/Display/ImageCache.swift index f78e841305..de4913e500 100644 --- a/Display/ImageCache.swift +++ b/Display/ImageCache.swift @@ -110,7 +110,7 @@ public final class ImageCache { pthread_mutex_lock(&self.mutex); if let residentImage = self.residentImages[key] { image = residentImage.image - self.nextAccessIndex++ + self.nextAccessIndex += 1 residentImage.accessIndex = self.nextAccessIndex } else { if let imageData = self.imageDatas[key] { @@ -141,14 +141,14 @@ public final class ImageCache { let currentImageSize = Int(currentImage.image.size.width * currentImage.image.size.height * currentImage.image.scale) * 4 removedSize += currentImageSize self.residentImages.removeValueForKey(currentImage.key) - i-- + i -= 1 } self.residentImagesSize = max(0, self.residentImagesSize - removedSize) } self.residentImagesSize += imageSize - self.nextAccessIndex++ + self.nextAccessIndex += 1 self.residentImages[key] = ImageCacheResidentImage(key: key, image: image, accessIndex: self.nextAccessIndex) } } diff --git a/Display/NSWeakReference.h b/Display/NSWeakReference.h new file mode 100644 index 0000000000..8c762f1256 --- /dev/null +++ b/Display/NSWeakReference.h @@ -0,0 +1,9 @@ +#import + +@interface NSWeakReference : NSObject + +@property (nonatomic, weak) id value; + +- (instancetype)initWithValue:(id)value; + +@end diff --git a/Display/NSWeakReference.m b/Display/NSWeakReference.m new file mode 100644 index 0000000000..f9b714f3d7 --- /dev/null +++ b/Display/NSWeakReference.m @@ -0,0 +1,13 @@ +#import "NSWeakReference.h" + +@implementation NSWeakReference + +- (instancetype)initWithValue:(id)value { + self = [super init]; + if (self != nil) { + self.value = value; + } + return self; +} + +@end diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 6f483e09e1..16b8d70064 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -50,7 +50,7 @@ public class NavigationBackButtonNode: ASControlNode { self.label.displaysAsynchronously = false self.addSubnode(self.arrow) - let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil) + let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil)?.precomposed() self.arrow.contents = arrowImage?.CGImage self.arrow.frame = CGRect(origin: CGPoint(), size: arrowImage?.size ?? CGSize()) @@ -92,34 +92,34 @@ public class NavigationBackButtonNode: ASControlNode { return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) } - public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { super.touchesBegan(touches, withEvent: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } - public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { super.touchesMoved(touches, withEvent: event) - self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } - public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { super.touchesEnded(touches, withEvent: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) - if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } - public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { super.touchesCancelled(touches, withEvent: event) - self.touchCount = max(0, self.touchCount - touches.count) + self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index faf10e441d..4609490f9f 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1,23 +1,41 @@ import UIKit import AsyncDisplayKit -private enum ItemAnimation { - case None - case Push - case Pop -} - public class NavigationBar: ASDisplayNode { - private var topItem: UINavigationItem? - private var topItemWrapper: NavigationItemWrapper? + var item: UINavigationItem? { + didSet { + if let item = self.item { + self.itemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: self.previousItem) + self.itemWrapper?.backPressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + } else { + self.itemWrapper = nil + } + } + } - private var tempItem: UINavigationItem? - private var tempItemWrapper: NavigationItemWrapper? + var previousItem: UINavigationItem? { + didSet { + if let item = self.item { + self.itemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: self.previousItem) + self.itemWrapper?.backPressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + } else { + self.itemWrapper = nil + } + } + } + + private var itemWrapper: NavigationItemWrapper? private let stripeHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale - //private let effectView: UIVisualEffectView - var backPressed: () -> () = { } private var collapsed: Bool { @@ -26,34 +44,6 @@ public class NavigationBar: ASDisplayNode { } } - var _proxy: NavigationBarProxy? - var proxy: NavigationBarProxy? { - get { - return self._proxy - } - set(value) { - self._proxy = value - self._proxy?.setItemsProxy = {[weak self] previousItems, items, animated in - if let strongSelf = self { - var animation = ItemAnimation.None - if animated && previousItems.count != 0 && items.count != 0 { - if previousItems.filter({element in element === items[items.count - 1]}).count != 0 { - animation = .Pop - } - else { - animation = .Push - } - } - - let count = items.count - if count != 0 { - strongSelf.updateTopItem(items[count - 1] as! UINavigationItem, previousItem: count >= 2 ? (items[count - 2] as! UINavigationItem) : nil, animation: animation) - } - } - return - } - } - } let stripeView: UIView public override init() { @@ -70,7 +60,7 @@ public class NavigationBar: ASDisplayNode { self.view.addSubview(stripeView) } - private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { + /*private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { if self.topItem !== item { let previousTopItemWrapper = self.topItemWrapper self.topItemWrapper = nil @@ -116,14 +106,12 @@ public class NavigationBar: ASDisplayNode { if let topItemWrapper = self.topItemWrapper { self.tempItemWrapper?.setInteractivePopProgress(progress, previousItemWrapper: topItemWrapper) } - } + }*/ public override func layout() { - self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height, width: self.frame.size.width, height: stripeHeight) - self.topItemWrapper?.layoutItems() - self.tempItemWrapper?.layoutItems() + self.itemWrapper?.layoutItems() //self.effectView.frame = self.bounds } diff --git a/Display/NavigationBarTransitionContainer.swift b/Display/NavigationBarTransitionContainer.swift new file mode 100644 index 0000000000..fe65e3e534 --- /dev/null +++ b/Display/NavigationBarTransitionContainer.swift @@ -0,0 +1,6 @@ +import Foundation +import AsyncDisplayKit + +class NavigationBarTransitionContainer: ASDisplayNode { + +} diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 80c63b657a..8871bf83ed 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -62,34 +62,34 @@ public class NavigationButtonNode: ASTextNode { return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) } - public override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { super.touchesBegan(touches, withEvent: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } - public override func touchesMoved(touches: Set!, withEvent event: UIEvent!) { + public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { super.touchesMoved(touches, withEvent: event) - self.updateHighlightedState(self.touchInsideApparentBounds(touches.first as! UITouch), animated: true) + self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } - public override func touchesEnded(touches: Set!, withEvent event: UIEvent!) { + public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { super.touchesEnded(touches, withEvent: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) - if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first as! UITouch) { + if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } - public override func touchesCancelled(touches: Set!, withEvent event: UIEvent!) { + public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { super.touchesCancelled(touches, withEvent: event) - self.touchCount = max(0, self.touchCount - touches.count) + self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index e173ccce45..1eadd5a0e7 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -8,8 +8,9 @@ private struct NavigationControllerLayout { let statusBarHeight: CGFloat } -public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate { - private var _navigationBar: NavigationBar! +public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate, StatusBarSurfaceProvider { + + private let statusBarSurface: StatusBarSurface = StatusBarSurface() private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() @@ -19,27 +20,30 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr private var layout: NavigationControllerLayout? private var pendingLayout: (NavigationControllerLayout, NSTimeInterval, Bool)? - public override init() { - self._navigationBar = nil - - super.init() - - self._navigationBar = NavigationBar() + private var _presentedViewController: UIViewController? + public override var presentedViewController: UIViewController? { + return self._presentedViewController + } - self._navigationBar.frame = CGRect(x: 0.0, y: 0.0, width: 320.0, height: 44.0) - self._navigationBar.proxy = self.navigationBar as? NavigationBarProxy - self._navigationBar.backPressed = { [weak self] in - if let strongSelf = self { - if strongSelf.viewControllers.count > 1 { - strongSelf.popViewControllerAnimated(true) - } - } - return + private var _viewControllers: [UIViewController] = [] + public override var viewControllers: [UIViewController] { + get { + return self._viewControllers + } set(value) { + self.setViewControllers(_viewControllers, animated: false) } + } + + public override var topViewController: UIViewController? { + return self._viewControllers.last + } + + public override init() { + super.init() self.statusBarChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationWillChangeStatusBarFrameNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak self] notification in if let strongSelf = self { - let statusBarHeight: CGFloat = (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.CGRectValue().height ?? 20.0 + let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.CGRectValue().height ?? 20.0) let previousLayout: NavigationControllerLayout? if let pendingLayout = strongSelf.pendingLayout { @@ -48,7 +52,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr previousLayout = strongSelf.layout } - strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true) + strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: statusBarHeight), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true) strongSelf.view.setNeedsLayout() } @@ -64,12 +68,13 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } public override func loadView() { - super.loadView() + self.view = UIView() + //super.loadView() + self.view.clipsToBounds = true - self.navigationBar.superview?.insertSubview(_navigationBar.view, aboveSubview: self.navigationBar) self.navigationBar.removeFromSuperview() - let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: Selector("panGesture:")) + let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) panRecognizer.delegate = self panRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(panRecognizer) @@ -91,10 +96,19 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr bottomController.viewWillAppear(true) let bottomView = bottomController.view - let navigationTransitionCoordinator = NavigationTransitionCoordinator(container: self.view, topView: topView, bottomView: bottomView, navigationBar: self._navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator + var bottomStatusBar: StatusBar? + if let bottomController = bottomController as? ViewController { + bottomStatusBar = bottomController.statusBar + } - self._navigationBar.beginInteractivePopProgress(bottomController.navigationItem, evenMorePreviousItem: self.viewControllers.count >= 3 ? (self.viewControllers[self.viewControllers.count - 3] as UIViewController).navigationItem : nil) + if let bottomStatusBar = bottomStatusBar { + self.statusBarSurface.insertStatusBar(bottomStatusBar, atIndex: 0) + } + + (self.view.window as? Window)?.updateStatusBars() + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator } case UIGestureRecognizerState.Changed: if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { @@ -109,7 +123,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr navigationTransitionCoordinator.animateCompletion(velocity, completion: { self.navigationTransitionCoordinator = nil - self._navigationBar.endInteractivePopProgress() + //self._navigationBar.endInteractivePopProgress() if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -123,6 +137,12 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidDisappear(true) bottomController.viewDidAppear(true) + + if let topStatusBar = (topController as? ViewController)?.statusBar { + self.statusBarSurface.removeStatusBar(topStatusBar) + } + + (self.view.window as? Window)?.updateStatusBars() } }) } @@ -138,7 +158,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr navigationTransitionCoordinator.animateCancel({ self.navigationTransitionCoordinator = nil - self._navigationBar.endInteractivePopProgress() + //self._navigationBar.endInteractivePopProgress() if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -146,6 +166,11 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidAppear(true) bottomController.viewDidDisappear(true) + + if let bottomStatusBar = (bottomController as? ViewController)?.statusBar { + self.statusBarSurface.removeStatusBar(bottomStatusBar) + } + (self.view.window as? Window)?.updateStatusBars() } }) } @@ -169,6 +194,11 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidAppear(true) bottomController.viewDidDisappear(true) + + if let bottomStatusBar = (bottomController as? ViewController)?.statusBar { + self.statusBarSurface.removeStatusBar(bottomStatusBar) + } + (self.view.window as? Window)?.updateStatusBars() } }) } @@ -182,7 +212,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if let currentLayout = self.layout { layout = currentLayout } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0), statusBarHeight: 20.0) + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) } controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in @@ -220,7 +250,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if let currentLayout = self.layout { layout = currentLayout } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0), statusBarHeight: 20.0) + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) } controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) @@ -229,17 +259,83 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - super.setViewControllers(viewControllers, animated: animated) - } - - private func navigationBarFrame(layout: NavigationControllerLayout) -> CGRect { - return CGRect(x: 0.0, y: layout.statusBarHeight - 20.0, width: layout.layout.size.width, height: 20.0 + (layout.layout.size.height >= layout.layout.size.width ? 44.0 : 32.0)) + if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! { + let topController = viewControllers.last! as UIViewController + let bottomController = self.viewControllers.last! as UIViewController + + if let topController = topController as? ViewController { + topController.navigationBar.previousItem = bottomController.navigationItem + } + + bottomController.viewWillDisappear(true) + let bottomView = bottomController.view + topController.viewWillAppear(true) + let topView = topController.view + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + if let topController = topController as? ViewController { + self.statusBarSurface.addStatusBar(topController.statusBar) + } + (self.view.window as? Window)?.updateStatusBars() + + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.navigationTransitionCoordinator = nil + + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + strongSelf.setViewControllers(viewControllers, animated: false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + bottomController.viewDidDisappear(true) + topController.viewDidAppear(true) + + if let bottomController = bottomController as? ViewController { + strongSelf.statusBarSurface.removeStatusBar(bottomController.statusBar) + } + (strongSelf.view.window as? Window)?.updateStatusBars() + } + }) + } else { + var previousStatusBar: StatusBar? + if let previousController = self.viewControllers.last as? ViewController { + previousStatusBar = previousController.statusBar + } + var newStatusBar: StatusBar? + if let newController = viewControllers.last as? ViewController { + newStatusBar = newController.statusBar + } + + if previousStatusBar !== newStatusBar { + if let previousStatusBar = previousStatusBar { + self.statusBarSurface.removeStatusBar(previousStatusBar) + } + if let newStatusBar = newStatusBar { + self.statusBarSurface.addStatusBar(newStatusBar) + } + } + + if let topController = self.viewControllers.last where topController.isViewLoaded() { + topController.navigation_setNavigationController(nil) + topController.view.removeFromSuperview() + } + + self._viewControllers = viewControllers + + if let topController = viewControllers.last { + topController.navigation_setNavigationController(self) + self.view.addSubview(topController.view) + } + + //super.setViewControllers(viewControllers, animated: animated) + } } private func childControllerLayoutForLayout(layout: NavigationControllerLayout) -> ViewControllerLayout { - var insets = layout.layout.insets - insets.top = self.navigationBarFrame(layout).maxY - return ViewControllerLayout(size: layout.layout.size, insets: insets, inputViewHeight: 0.0) + return ViewControllerLayout(size: layout.layout.size, insets: layout.layout.insets, inputViewHeight: 0.0, statusBarHeight: layout.statusBarHeight) } public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { @@ -250,7 +346,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr previousLayout = self.layout } - self.pendingLayout = (NavigationControllerLayout(layout: layout, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false) + self.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false) self.view.setNeedsLayout() } @@ -268,12 +364,12 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr self.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) } - if pendingLayout.1 > DBL_EPSILON { + /*if pendingLayout.1 > DBL_EPSILON { animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(pendingLayout.0), duration: pendingLayout.1) } else { self._navigationBar.frame = self.navigationBarFrame(pendingLayout.0) - } + }*/ if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) @@ -282,13 +378,20 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController if let controller = bottomController as? WindowContentController { + let layout: NavigationControllerLayout + if let currentLayout = self.layout { + layout = currentLayout + } else { + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) + } + controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) } else { bottomController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) } } - self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress) + //self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress) } if let topViewController = self.topViewController { @@ -299,6 +402,14 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } + if let presentedViewController = self.presentedViewController { + if let controller = presentedViewController as? WindowContentController { + controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) + } else { + presentedViewController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) + } + } + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { navigationTransitionCoordinator.updateProgress() } @@ -307,6 +418,72 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } + override public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + if let controller = viewControllerToPresent as? NavigationController { + controller.navigation_setPresentingViewController(self) + self._presentedViewController = controller + + self.view.endEditing(true) + + let layout: NavigationControllerLayout + if let currentLayout = self.layout { + layout = currentLayout + } else { + layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) + } + + controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) + + if flag { + controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) + self.view.addSubview(controller.view) + (self.view.window as? Window)?.updateStatusBars() + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = self.view.bounds + (self.view.window as? Window)?.updateStatusBars() + }, completion: { _ in + if let completion = completion { + completion() + } + }) + } else { + self.view.addSubview(controller.view) + (self.view.window as? Window)?.updateStatusBars() + + if let completion = completion { + completion() + } + } + } else { + preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.") + } + } + + override public func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) { + if let controller = self.presentedViewController { + + if flag { + UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) + (self.view.window as? Window)?.updateStatusBars() + }, completion: { _ in + controller.view.removeFromSuperview() + self._presentedViewController = nil + (self.view.window as? Window)?.updateStatusBars() + if let completion = completion { + completion() + } + }) + } else { + self._presentedViewController = nil + (self.view.window as? Window)?.updateStatusBars() + if let completion = completion { + completion() + } + } + } + } + public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } @@ -314,4 +491,12 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return otherGestureRecognizer is UIPanGestureRecognizer } + + func statusBarSurfaces() -> [StatusBarSurface] { + var surfaces: [StatusBarSurface] = [self.statusBarSurface] + if let controller = self.presentedViewController as? StatusBarSurfaceProvider { + surfaces.appendContentsOf(controller.statusBarSurfaces()) + } + return surfaces + } } diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift index 4855745fb5..f7185c4166 100644 --- a/Display/NavigationItemWrapper.swift +++ b/Display/NavigationItemWrapper.swift @@ -207,7 +207,7 @@ internal class NavigationItemWrapper { rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! } - self.titleNode.measure(CGSize(width: self.parentNode.bounds.size.width - 140.0, height: CGFloat.max)) + self.titleNode.measure(CGSize(width: max(0.0, self.parentNode.bounds.size.width - 140.0), height: CGFloat.max)) self.titleNode.frame = self.titleFrame } diff --git a/Display/NavigationShadow@2x.png b/Display/NavigationShadow@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..3eecc232e55a751731d3dc7d6c3899239c03daf8 GIT binary patch literal 1116 zcmZ{j&x_MQ6vwBms2f>Og!Km^AznmiCO@_=4QX(<(Qd)6wN^+y$Y$H=8f-E#nWfo< zz39oKR~7N52U!muz1EAcqFw}L{{Rmnh*y8ir0q7wHZaM|d*9Ezd2jOWSC>mO+(`}q zFjHPKYxK*rad1C)N`k95vy`p zYhqWlTX+?Tk?T`60D7d+sf&osM{dUpwWuJ(3XRU0847$XA-4;HRjKkt93Va?&WVy> za6HfJL94CR%u55Dt_s2}LVOLvZnrCTRS^ekP|oM`P*R|xWT_$>Zg|9wvR-(mzsYbP z6NSyd@ri>yp6zR|;&oCG1QuxT8JyGg_X2t0AS;?5jBFpuq6Ei5#A%O?_YcK96EPGy z6ENoKV{tM1XVhs$wF+)I?G4i=$WSEtd{$Dk@?2e3G)dLw^EymvMj6I*F+euKK^^0c zQFS8JaoLhrY2B4Rn^YuVmWOsFa1bppE2AOn@IPBxU&O8-pfF_m+(4hSrS&xw;?6qT z{<`N7gPtf3kus;L#>j8X51FJf4OruVq%i@oi_qdqS*GJ6FV01!QN}vbU=eq4aLu=y zh}A1b5~j2|4%3(vCgB)>I%NNH;@|E!PHd%}V{r6Fhh1oJ-%k|p(8jEk&4s!({q6k% zN88=}@M3oNPcQf2?9Hb;$9h+9^j^)PBlzv>wVltuc7N1QKifL@Y3s?CkM~}_IdJ#_ b$m~nLR;Ef#K;Wa_Io9&!i_7Nr;>z8>-6&NQ literal 0 HcmV?d00001 diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 4b6b98cec9..537c45a4c9 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -17,7 +17,7 @@ public class NavigationTitleNode: ASDisplayNode { public init(text: NSString) { self.label = ASTextNode() - self.label.maximumLineCount = 1 + self.label.maximumNumberOfLines = 1 self.label.truncationMode = .ByTruncatingTail self.label.displaysAsynchronously = false diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift new file mode 100644 index 0000000000..c5fe59ba06 --- /dev/null +++ b/Display/NavigationTransitionCoordinator.swift @@ -0,0 +1,158 @@ +import UIKit + +enum NavigationTransition { + case Push + case Pop +} + +private let shadowWidth: CGFloat = 16.0 + +private func generateShadow() -> UIImage? { + return UIImage(named: "NavigationShadow", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil)?.precomposed().resizableImageWithCapInsets(UIEdgeInsetsZero, resizingMode: .Tile) +} + +private let shadowImage = generateShadow() + +class NavigationTransitionCoordinator { + private var _progress: CGFloat = 0.0 + var progress: CGFloat { + get { + return self._progress + } + set(value) { + self._progress = value + self.updateProgress() + } + } + + private let container: UIView + private let transition: NavigationTransition + private let topView: UIView + private let viewSuperview: UIView? + private let bottomView: UIView + private let topNavigationBar: NavigationBar? + private let bottomNavigationBar: NavigationBar? + private let dimView: UIView + private let shadowView: UIImageView + + init(transition: NavigationTransition, container: UIView, topView: UIView, topNavigationBar: NavigationBar?, bottomView: UIView, bottomNavigationBar: NavigationBar?) { + self.transition = transition + self.container = container + self.topView = topView + switch transition { + case .Push: + self.viewSuperview = bottomView.superview + case .Pop: + self.viewSuperview = topView.superview + } + self.bottomView = bottomView + self.topNavigationBar = topNavigationBar + self.bottomNavigationBar = bottomNavigationBar + self.dimView = UIView() + self.dimView.backgroundColor = UIColor.blackColor() + self.shadowView = UIImageView(image: shadowImage) + + switch transition { + case .Push: + self.viewSuperview?.insertSubview(topView, belowSubview: topView) + case .Pop: + self.viewSuperview?.insertSubview(bottomView, belowSubview: topView) + } + + self.viewSuperview?.insertSubview(self.dimView, belowSubview: topView) + self.viewSuperview?.insertSubview(self.shadowView, belowSubview: dimView) + + self.updateProgress() + } + + required init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + func updateProgress() { + let position: CGFloat + switch self.transition { + case .Push: + position = 1.0 - progress + case .Pop: + position = progress + } + self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) + self.dimView.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, self.topView.frame.minX), height: self.container.bounds.size.height)) + self.shadowView.frame = CGRect(origin: CGPoint(x: self.dimView.frame.maxX - shadowWidth, y: 0.0), size: CGSize(width: shadowWidth, height: self.container.bounds.size.height)) + self.dimView.alpha = (1.0 - position) * 0.15 + self.shadowView.alpha = (1.0 - position) * 0.9 + self.bottomView.frame = CGRect(origin: CGPoint(x: ((position - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) + + (self.container.window as? Window)?.updateStatusBars() + } + + func animateCancel(completion: () -> ()) { + UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 0.0 + }) { (completed) -> Void in + switch self.transition { + case .Push: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.bottomView) + } else { + self.bottomView.removeFromSuperview() + } + self.topView.removeFromSuperview() + case .Pop: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.topView) + } else { + self.topView.removeFromSuperview() + } + self.bottomView.removeFromSuperview() + } + + self.dimView.removeFromSuperview() + self.shadowView.removeFromSuperview() + + completion() + } + } + + func animateCompletion(velocity: CGFloat, completion: () -> ()) { + let distance = (1.0 - self.progress) * self.container.bounds.size.width + let f = { + switch self.transition { + case .Push: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.bottomView) + } else { + self.bottomView.removeFromSuperview() + } + //self.topView.removeFromSuperview() + case .Pop: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.topView) + } else { + self.topView.removeFromSuperview() + } + //self.bottomView.removeFromSuperview() + } + + self.dimView.removeFromSuperview() + self.shadowView.removeFromSuperview() + + completion() + } + + if abs(velocity) < CGFloat(FLT_EPSILON) && abs(self.progress) < CGFloat(FLT_EPSILON) { + UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + self.progress = 1.0 + }, completion: { _ in + f() + }) + } else { + UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + self.progress = 1.0 + }) { (completed) -> Void in + f() + } + } + } +} diff --git a/Display/NavigationTransitionView.swift b/Display/NavigationTransitionView.swift deleted file mode 100644 index 46d081c2d6..0000000000 --- a/Display/NavigationTransitionView.swift +++ /dev/null @@ -1,82 +0,0 @@ -import UIKit - -class NavigationTransitionCoordinator { - private var _progress: CGFloat = 0.0 - var progress: CGFloat { - get { - return self._progress - } - set(value) { - self._progress = value - self.navigationBar.setInteractivePopProgress(value) - self.updateProgress() - } - } - - private let container: UIView - private let topView: UIView - private let topViewSuperview: UIView? - private let bottomView: UIView - private let dimView: UIView - private let navigationBar: NavigationBar - - init(container: UIView, topView: UIView, bottomView: UIView, navigationBar: NavigationBar) { - self.container = container - self.topView = topView - self.topViewSuperview = topView.superview - self.bottomView = bottomView - self.dimView = UIView() - self.dimView.backgroundColor = UIColor.blackColor() - self.navigationBar = navigationBar - - if let topViewSuperview = self.topViewSuperview { - topViewSuperview.insertSubview(bottomView, belowSubview: topView) - } - - self.updateProgress() - } - - required init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - func updateProgress() { - self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(self.progress * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) - self.dimView.frame = self.container.bounds - self.dimView.alpha = (1.0 - self.progress) * 0.1 - self.bottomView.frame = CGRect(origin: CGPoint(x: ((self.progress - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) - } - - func animateCancel(completion: () -> ()) { - UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in - self.progress = 0.0 - }) { (completed) -> Void in - if let topViewSuperview = self.topViewSuperview { - topViewSuperview.addSubview(self.topView) - } - else { - self.topView.removeFromSuperview() - } - self.bottomView.removeFromSuperview() - - completion() - } - } - - func animateCompletion(velocity: CGFloat, completion: () -> ()) { - let distance = (1.0 - self.progress) * self.container.bounds.size.width - UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in - self.progress = 1.0 - }) { (completed) -> Void in - if let topViewSuperview = self.topViewSuperview { - topViewSuperview.addSubview(self.topView) - } - else { - self.topView.removeFromSuperview() - } - self.bottomView.removeFromSuperview() - - completion() - } - } -} \ No newline at end of file diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h index 6742a0f49d..1bd387edbf 100644 --- a/Display/RuntimeUtils.h +++ b/Display/RuntimeUtils.h @@ -8,6 +8,7 @@ typedef enum { @interface RuntimeUtils : NSObject + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector; + (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector; @end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m index 3302e14754..2a1b4376f2 100644 --- a/Display/RuntimeUtils.m +++ b/Display/RuntimeUtils.m @@ -18,6 +18,16 @@ } } ++ (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector { + Method origMethod = nil, newMethod = nil; + + origMethod = class_getInstanceMethod(targetClass, currentSelector); + newMethod = class_getInstanceMethod(anotherClass, newSelector); + if ((origMethod != nil) && (newMethod != nil)) { + method_exchangeImplementations(origMethod, newMethod); + } +} + + (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector { Method origMethod = nil, newMethod = nil; diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift new file mode 100644 index 0000000000..50fd47dd33 --- /dev/null +++ b/Display/StatusBar.swift @@ -0,0 +1,59 @@ +import Foundation +import AsyncDisplayKit + +public class StatusBarSurface { + var statusBars: [StatusBar] = [] + + func addStatusBar(statusBar: StatusBar) { + self.removeStatusBar(statusBar) + self.statusBars.append(statusBar) + } + + func insertStatusBar(statusBar: StatusBar, atIndex index: Int) { + self.removeStatusBar(statusBar) + self.statusBars.insert(statusBar, atIndex: index) + } + + func removeStatusBar(statusBar: StatusBar) { + for i in 0 ..< self.statusBars.count { + if self.statusBars[i] === statusBar { + self.statusBars.removeAtIndex(i) + break + } + } + } +} + +public class StatusBar: ASDisplayNode { + public var style: StatusBarStyle = .Black + var proxyNode: StatusBarProxyNode? + + override init() { + super.init() + + self.clipsToBounds = true + self.userInteractionEnabled = false + } + + func removeProxyNode() { + self.proxyNode?.hidden = true + self.proxyNode?.removeFromSupernode() + self.proxyNode = nil + } + + func updateProxyNode() { + let origin = self.view.convertPoint(CGPoint(), toView: nil) + if let proxyNode = proxyNode { + proxyNode.style = self.style + } else { + self.proxyNode = StatusBarProxyNode(style: self.style) + self.proxyNode!.hidden = false + self.addSubnode(self.proxyNode!) + } + + let frame = CGRect(origin: CGPoint(x: -origin.x, y: -origin.y), size: self.proxyNode!.frame.size) + self.proxyNode?.frame = frame + } + + +} diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift new file mode 100644 index 0000000000..a7bf31e6f5 --- /dev/null +++ b/Display/StatusBarManager.swift @@ -0,0 +1,151 @@ +import Foundation +import AsyncDisplayKit + +private struct MappedStatusBar { + let style: StatusBarStyle + let frame: CGRect + let statusBar: StatusBar? +} + +private struct MappedStatusBarSurface { + let statusBars: [MappedStatusBar] + let surface: StatusBarSurface +} + +private func mapStatusBar(statusBar: StatusBar) -> MappedStatusBar { + let frame = CGRect(origin: statusBar.view.convertPoint(CGPoint(), toView: nil), size: statusBar.frame.size) + return MappedStatusBar(style: statusBar.style, frame: frame, statusBar: statusBar) +} + +private func mappedSurface(surface: StatusBarSurface) -> MappedStatusBarSurface { + return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface) +} + +private func optimizeMappedSurface(surface: MappedStatusBarSurface) -> MappedStatusBarSurface { + if surface.statusBars.count > 1 { + for i in 1 ..< surface.statusBars.count { + if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { + return surface + } + } + let size = UIApplication.sharedApplication().statusBarFrame.size + return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) + } else { + return surface + } +} + +private func displayHiddenAnimation() -> CAAnimation { + let animation = CABasicAnimation(keyPath: "transform.translation.y") + animation.fromValue = NSNumber(float: -40.0) + animation.toValue = NSNumber(float: -40.0) + animation.fillMode = kCAFillModeBoth + animation.duration = 100000000.0 + animation.additive = true + animation.removedOnCompletion = false + + return animation +} + +class StatusBarManager { + var surfaces: [StatusBarSurface] = [] { + didSet { + self.updateSurfaces(oldValue) + } + } + + private func updateSurfaces(previousSurfaces: [StatusBarSurface]) { + let mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(mappedSurface($0)) }) + + var visibleStatusBars: [StatusBar] = [] + + var globalStatusBar: (StatusBarStyle, CGFloat)? + for i in 0 ..< mappedSurfaces.count { + if i == mappedSurfaces.count - 1 && mappedSurfaces[i].statusBars.count == 1 { + globalStatusBar = (mappedSurfaces[i].statusBars[0].style, mappedSurfaces[i].statusBars[0].frame.origin.y) + } else { + for mappedStatusBar in mappedSurfaces[i].statusBars { + if let statusBar = mappedStatusBar.statusBar { + visibleStatusBars.append(statusBar) + } + } + } + } + + for surface in previousSurfaces { + for statusBar in surface.statusBars { + if !visibleStatusBars.contains({$0 === statusBar}) { + statusBar.removeProxyNode() + } + } + } + + for surface in self.surfaces { + for statusBar in surface.statusBars { + if !visibleStatusBars.contains({$0 === statusBar}) { + statusBar.removeProxyNode() + } + } + } + + for statusBar in visibleStatusBars { + statusBar.updateProxyNode() + } + + if let globalStatusBar = globalStatusBar { + StatusBarUtils.statusBarWindow()!.hidden = false + let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .Default : .LightContent + if UIApplication.sharedApplication().statusBarStyle != statusBarStyle { + UIApplication.sharedApplication().setStatusBarStyle(statusBarStyle, animated: false) + } + StatusBarUtils.statusBarWindow()!.layer.removeAnimationForKey("displayHidden") + StatusBarUtils.statusBarWindow()!.transform = CGAffineTransformMakeTranslation(0.0, globalStatusBar.1) + } else { + if StatusBarUtils.statusBarWindow()!.layer.animationForKey("displayHidden") == nil { + StatusBarUtils.statusBarWindow()!.layer.addAnimation(displayHiddenAnimation(), forKey: "displayHidden") + } + } + + /*if self.items.count == 1 { + self.shouldHide = true + dispatch_async(dispatch_get_main_queue(), { + if self.shouldHide { + self.items[0].1.hidden = true + self.shouldHide = false + } + }) + //self.items[0].1.hidden = true + StatusBarUtils.statusBarWindow()!.hidden = false + } else if !self.items.isEmpty { + self.shouldHide = false + for (statusBar, node) in self.items { + node.hidden = false + var frame = statusBar.frame + frame.size.width = self.bounds.size.width + frame.size.height = 20.0 + node.frame = frame + + //print("origin: \(frame.origin.x)") + //print("style: \(node.style)") + + let bounds = frame + node.bounds = bounds + } + + UIView.performWithoutAnimation { + StatusBarUtils.statusBarWindow()!.hidden = true + } + } + + var statusBarStyle: UIStatusBarStyle = .Default + if let lastItem = self.items.last { + statusBarStyle = lastItem.0.style == .Black ? .Default : .LightContent + } + + if UIApplication.sharedApplication().statusBarStyle != statusBarStyle { + UIApplication.sharedApplication().setStatusBarStyle(statusBarStyle, animated: false) + } + + //print("window \(StatusBarUtils.statusBarWindow()!)")*/ + } +} \ No newline at end of file diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift new file mode 100644 index 0000000000..0e9f101970 --- /dev/null +++ b/Display/StatusBarProxyNode.swift @@ -0,0 +1,333 @@ +import Foundation +import AsyncDisplayKit + +public enum StatusBarStyle { + case Black + case White +} + +private enum StatusBarItemType { + case Generic + case Battery +} + +func makeStatusBarProxy(style: StatusBarStyle) -> StatusBarProxyNode { + return StatusBarProxyNode(style: style) +} + +private class StatusBarItemNode: ASDisplayNode { + var style: StatusBarStyle + var targetView: UIView + + init(style: StatusBarStyle, targetView: UIView) { + self.style = style + self.targetView = targetView + + super.init() + } + + func update() { + let context = DrawingContext(size: self.targetView.frame.size, clear: true) + + if let contents = self.targetView.layer.contents where (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents) == CGImageGetTypeID() && false { + let image = contents as! CGImageRef + context.withFlippedContext { c in + CGContextSetAlpha(c, CGFloat(self.targetView.layer.opacity)) + CGContextDrawImage(c, CGRect(origin: CGPoint(), size: context.size), image) + CGContextSetAlpha(c, 1.0) + } + + if let sublayers = self.targetView.layer.sublayers { + for sublayer in sublayers { + let origin = sublayer.frame.origin + if let contents = sublayer.contents where CFGetTypeID(contents) == CGImageGetTypeID() { + let image = contents as! CGImageRef + context.withFlippedContext { c in + CGContextTranslateCTM(c, origin.x, origin.y) + CGContextDrawImage(c, CGRect(origin: CGPoint(), size: context.size), image) + CGContextTranslateCTM(c, -origin.x, -origin.y) + } + } else { + context.withContext { c in + UIGraphicsPushContext(c) + CGContextTranslateCTM(c, origin.x, origin.y) + sublayer.renderInContext(c) + CGContextTranslateCTM(c, -origin.x, -origin.y) + UIGraphicsPopContext() + } + } + } + } + } else { + context.withContext { c in + UIGraphicsPushContext(c) + self.targetView.layer.renderInContext(c) + UIGraphicsPopContext() + } + } + + let type: StatusBarItemType = self.targetView.isKindOfClass(batteryItemClass!) ? .Battery : .Generic + tintStatusBarItem(context, type: type, style: style) + self.contents = context.generateImage()?.CGImage + + self.frame = self.targetView.frame + } +} + +private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, style: StatusBarStyle) { + switch type { + case .Battery: + let minY = 0 + let minX = 0 + let maxY = Int(context.size.height * context.scale) + let maxX = Int(context.size.width * context.scale) + if minY < maxY && minX < maxX { + let basePixel = UnsafeMutablePointer(context.bytes) + let pixelsPerRow = context.bytesPerRow / 4 + + let midX = (maxX + minX) / 2 + let midY = (maxY + minY) / 2 + let baseMidRow = basePixel + pixelsPerRow * midY + var baseX = minX + while baseX < maxX { + let pixel = baseMidRow + baseX + let alpha = pixel.memory & 0xff000000 + if alpha != 0 { + break + } + baseX += 1 + } + + baseX += 2 + + var targetX = baseX + while targetX < maxX { + let pixel = baseMidRow + targetX + let alpha = pixel.memory & 0xff000000 + if alpha == 0 { + break + } + + targetX += 1 + } + + let batteryColor = (baseMidRow + baseX).memory + let batteryR = (batteryColor >> 16) & 0xff + let batteryG = (batteryColor >> 8) & 0xff + let batteryB = batteryColor & 0xff + + var baseY = minY + while baseY < maxY { + let baseRow = basePixel + pixelsPerRow * baseY + let pixel = baseRow + midX + let alpha = pixel.memory & 0xff000000 + if alpha != 0 { + break + } + baseY += 1 + } + + var targetY = maxY - 1 + while targetY >= baseY { + let baseRow = basePixel + pixelsPerRow * targetY + let pixel = baseRow + midX + let alpha = pixel.memory & 0xff000000 + if alpha != 0 { + break + } + targetY -= 1 + } + + targetY -= 1 + + let baseColor: UInt32 + switch style { + case .Black: + baseColor = 0x000000 + case .White: + baseColor = 0xffffff + } + + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + var pixel = UnsafeMutablePointer(context.bytes) + let end = UnsafeMutablePointer(context.bytes + context.length) + while pixel != end { + let alpha = (pixel.memory & 0xff000000) >> 24 + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + pixel += 1 + } + + if batteryColor != 0xffffffff && batteryColor != 0xff000000 { + var y = baseY + 2 + while y < targetY { + let baseRow = basePixel + pixelsPerRow * y + var x = baseX + while x < targetX { + let pixel = baseRow + x + let alpha = (pixel.memory >> 24) & 0xff + + let r = (batteryR * alpha) / 255 + let g = (batteryG * alpha) / 255 + let b = (batteryB * alpha) / 255 + + pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + x += 1 + } + y += 1 + } + } + } + case .Generic: + var pixel = UnsafeMutablePointer(context.bytes) + let end = UnsafeMutablePointer(context.bytes + context.length) + + let baseColor: UInt32 + switch style { + case .Black: + baseColor = 0x000000 + case .White: + baseColor = 0xffffff + } + + let baseR = (baseColor >> 16) & 0xff + let baseG = (baseColor >> 8) & 0xff + let baseB = baseColor & 0xff + + while pixel != end { + let alpha = (pixel.memory & 0xff000000) >> 24 + + let r = (baseR * alpha) / 255 + let g = (baseG * alpha) / 255 + let b = (baseB * alpha) / 255 + + pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + + pixel += 1 + } + } +} + +private let batteryItemClass: AnyClass? = NSClassFromString("UIStatusBarBatteryItemView") + +private class StatusBarProxyNodeTimerTarget: NSObject { + let action: () -> Void + + init(action: () -> Void) { + self.action = action + } + + @objc func tick() { + action() + } +} + +class StatusBarProxyNode: ASDisplayNode { + var timer: NSTimer? + var style: StatusBarStyle { + didSet { + if oldValue != self.style { + if !self.hidden { + self.updateItems() + } + } + } + } + + private var itemNodes: [StatusBarItemNode] = [] + + override var hidden: Bool { + get { + return super.hidden + } set(value) { + if super.hidden != value { + super.hidden = value + + if !value { + self.updateItems() + self.timer = NSTimer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in + self?.updateItems() + }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) + NSRunLoop.mainRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes) + } else { + self.timer?.invalidate() + self.timer = nil + } + } + } + } + + init(style: StatusBarStyle) { + self.style = style + + super.init() + + self.hidden = true + + self.clipsToBounds = true + //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) + + let statusBar = StatusBarUtils.statusBar()! + + for subview in statusBar.subviews { + let itemNode = StatusBarItemNode(style: style, targetView: subview) + self.itemNodes.append(itemNode) + self.addSubnode(itemNode) + } + + self.frame = statusBar.bounds + } + + deinit { + self.timer?.invalidate() + } + + private func updateItems() { + let statusBar = StatusBarUtils.statusBar()! + + var i = 0 + while i < self.itemNodes.count { + var found = false + for subview in statusBar.subviews { + if self.itemNodes[i].targetView == subview { + found = true + break + } + } + if !found { + self.itemNodes[i].removeFromSupernode() + self.itemNodes.removeAtIndex(i) + } else { + self.itemNodes[i].style = self.style + self.itemNodes[i].update() + i += 1 + } + } + + for subview in statusBar.subviews { + var found = false + for itemNode in self.itemNodes { + if itemNode.targetView == subview { + found = true + break + } + } + + if !found { + let itemNode = StatusBarItemNode(style: self.style, targetView: subview) + itemNode.update() + self.itemNodes.append(itemNode) + self.addSubnode(itemNode) + } + } + } +} diff --git a/Display/StatusBarSurfaceProvider.swift b/Display/StatusBarSurfaceProvider.swift new file mode 100644 index 0000000000..25e3c9554a --- /dev/null +++ b/Display/StatusBarSurfaceProvider.swift @@ -0,0 +1,4 @@ + +protocol StatusBarSurfaceProvider { + func statusBarSurfaces() -> [StatusBarSurface] +} diff --git a/Display/StatusBarUtils.h b/Display/StatusBarUtils.h new file mode 100644 index 0000000000..45d160cca9 --- /dev/null +++ b/Display/StatusBarUtils.h @@ -0,0 +1,9 @@ +#import +#import + +@interface StatusBarUtils : NSObject + ++ (UIView * _Nullable)statusBarWindow; ++ (UIView * _Nullable)statusBar; + +@end diff --git a/Display/StatusBarUtils.m b/Display/StatusBarUtils.m new file mode 100644 index 0000000000..9890f83561 --- /dev/null +++ b/Display/StatusBarUtils.m @@ -0,0 +1,32 @@ +#import "StatusBarUtils.h" + +@implementation StatusBarUtils + ++ (UIView *)statusBarWindow { + UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; + UIView *view = window.subviews.firstObject; + return view; +} + ++ (UIView *)statusBar { + UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; + UIView *view = window.subviews.firstObject; + + static Class foregroundClass = nil; + static Class batteryClass = nil; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + foregroundClass = NSClassFromString(@"UIStatusBarForegroundView"); + batteryClass = NSClassFromString(@"UIStatusBarBatteryItemView"); + }); + + for (UIView *foreground in view.subviews) { + if ([foreground isKindOfClass:foregroundClass]) { + return foreground; + } + } + + return nil; +} + +@end diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index e587d41674..0d672e8c5e 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -98,7 +98,7 @@ public class TabBarController: ViewController { private func childControllerLayoutForLayout(layout: ViewControllerLayout) -> ViewControllerLayout { var insets = layout.insets insets.bottom += 49.0 - return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight) + return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: layout.statusBarHeight) } override public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 0e7277ba4d..a47407de06 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -97,7 +97,7 @@ class TabBarNode: ASDisplayNode { node.displayWithoutProcessing = true node.layerBacked = true if let selectedIndex = self.selectedIndex where selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor.blueColor()) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } @@ -116,7 +116,7 @@ class TabBarNode: ASDisplayNode { let item = self.tabBarItems[index] if let selectedIndex = self.selectedIndex where selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor.blueColor()) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } @@ -145,10 +145,10 @@ class TabBarNode: ASDisplayNode { } } - override func touchesBegan(touches: Set!, withEvent event: UIEvent!) { + override func touchesBegan(touches: Set, withEvent event: UIEvent?) { super.touchesBegan(touches, withEvent: event) - if let touch = touches.first as? UITouch { + if let touch = touches.first { let location = touch.locationInView(self.view) var closestNode: (Int, CGFloat)? diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index e551655f77..9d1aefdb6b 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -59,7 +59,7 @@ public extension CGSize { } } -public func assertNotOnMainThread(file: String = __FILE__, line: Int = __LINE__) { +public func assertNotOnMainThread(file: String = #file, line: Int = #line) { assert(!NSThread.isMainThread(), "\(file):\(line) running on main thread") } diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 8f5a15d055..55de13b6c3 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -3,5 +3,9 @@ @interface UIViewController (Navigation) - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; +- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; +- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; @end + +void applyKeyboardAutocorrection(); diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 565c3ec385..b0b98adac8 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -1,8 +1,59 @@ #import "UIViewController+Navigation.h" #import "RuntimeUtils.h" +#import + +#import "NSWeakReference.h" static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIViewControllerIgnoreAppearanceMethodInvocationsKey; +static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNavigationControllerKey; +static const void *UIViewControllerPresentingViewControllerKey = &UIViewControllerPresentingViewControllerKey; + +static bool notyfyingShiftState = false; + +@interface UIKeyboardImpl_65087dc8: UIView + +@end + +@implementation UIKeyboardImpl_65087dc8 + +- (void)notifyShiftState { + static void (*impl)(id, SEL) = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method m = class_getInstanceMethod([UIKeyboardImpl_65087dc8 class], @selector(notifyShiftState)); + impl = (typeof(impl))method_getImplementation(m); + }); + if (impl) { + notyfyingShiftState = true; + impl(self, @selector(notifyShiftState)); + notyfyingShiftState = false; + } +} + +@end + +@interface UIInputWindowController_65087dc8: UIViewController + +@end + +@implementation UIInputWindowController_65087dc8 + +- (void)updateViewConstraints { + static void (*impl)(id, SEL) = NULL; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + Method m = class_getInstanceMethod([UIInputWindowController_65087dc8 class], @selector(updateViewConstraints)); + impl = (typeof(impl))method_getImplementation(m); + }); + if (impl) { + if (!notyfyingShiftState) { + impl(self, @selector(updateViewConstraints)); + } + } +} + +@end @implementation UIViewController (Navigation) @@ -15,6 +66,11 @@ static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIVie [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidAppear:) newSelector:@selector(_65087dc8_viewDidAppear:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewWillDisappear:) newSelector:@selector(_65087dc8_viewWillDisappear:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidDisappear:) newSelector:@selector(_65087dc8_viewDidDisappear:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(navigationController) newSelector:@selector(_65087dc8_navigationController)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)]; + + //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)]; + //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)]; }); } @@ -52,4 +108,73 @@ static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIVie [self _65087dc8_viewDidDisappear:animated]; } +- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller { + [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:navigationControlller] forKey:UIViewControllerNavigationControllerKey]; +} + +- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController { + [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:presentingViewController] forKey:UIViewControllerPresentingViewControllerKey]; +} + +- (UINavigationController *)_65087dc8_navigationController { + UINavigationController *navigationController = self._65087dc8_navigationController; + if (navigationController != nil) { + return navigationController; + } + + navigationController = self.parentViewController.navigationController; + if (navigationController != nil) { + return navigationController; + } + + return ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerNavigationControllerKey]).value; +} + +- (UIViewController *)_65087dc8_presentingViewController { + UINavigationController *navigationController = self.navigationController; + if (navigationController.presentingViewController != nil) { + return navigationController.presentingViewController; + } + + return ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerPresentingViewControllerKey]).value; +} + @end + +static NSString *TGEncodeText(NSString *string, int key) +{ + NSMutableString *result = [[NSMutableString alloc] init]; + + for (int i = 0; i < (int)[string length]; i++) + { + unichar c = [string characterAtIndex:i]; + c += key; + [result appendString:[NSString stringWithCharacters:&c length:1]]; + } + + return result; +} + +void applyKeyboardAutocorrection() { + static Class keyboardClass = NULL; + static SEL currentInstanceSelector = NULL; + static SEL applyVariantSelector = NULL; + + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + keyboardClass = NSClassFromString(TGEncodeText(@"VJLfzcpbse", -1)); + + currentInstanceSelector = NSSelectorFromString(TGEncodeText(@"bdujwfLfzcpbse", -1)); + applyVariantSelector = NSSelectorFromString(TGEncodeText(@"bddfquBvupdpssfdujpo", -1)); + }); + + if ([keyboardClass respondsToSelector:currentInstanceSelector]) + { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + id currentInstance = [keyboardClass performSelector:currentInstanceSelector]; + if ([currentInstance respondsToSelector:applyVariantSelector]) + [currentInstance performSelector:applyVariantSelector]; +#pragma clang diagnostic pop + } +} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index c74892c441..26b71c7278 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit import SwiftSignalKit public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { - return lhs.size == rhs.size && lhs.insets == rhs.insets && lhs.inputViewHeight == rhs.inputViewHeight + return lhs.size == rhs.size && lhs.insets == rhs.insets && lhs.inputViewHeight == rhs.inputViewHeight && lhs.statusBarHeight == rhs.statusBarHeight } @objc public class ViewController: UIViewController, WindowContentController { @@ -31,6 +31,9 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { return self._displayNode != nil } + public let statusBar: StatusBar + public let navigationBar: NavigationBar + private let _ready = Promise(true) public var ready: Promise { return self._ready @@ -42,8 +45,12 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { var keyboardFrameObserver: AnyObject? public init() { + self.statusBar = StatusBar() + self.navigationBar = NavigationBar() + super.init(nibName: nil, bundle: nil) + self.navigationBar.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false self.keyboardFrameObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil, usingBlock: { [weak self] notification in @@ -61,7 +68,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } else{ previousLayout = strongSelf.layout } - let layout = ViewControllerLayout(size: previousLayout?.size ?? CGSize(), insets: previousLayout?.insets ?? UIEdgeInsets(), inputViewHeight: keyboardHeight) + let layout = ViewControllerLayout(size: previousLayout?.size ?? CGSize(), insets: previousLayout?.insets ?? UIEdgeInsets(), inputViewHeight: keyboardHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0) let updated: Bool if let previousLayout = previousLayout { updated = previousLayout != layout @@ -69,7 +76,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { updated = true } if updated { - print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") + //print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration > DBL_EPSILON ? 0.5 : 0.0, curve) strongSelf.updateLayoutOnLayout = (layout, durationAndCurve.0, durationAndCurve.1) @@ -91,10 +98,11 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { public override func loadView() { self.view = self.displayNode.view + self.displayNode.addSubnode(self.navigationBar) + self.view.addSubview(self.statusBar.view) } public func loadDisplayNode() { - self.displayNode = ASDisplayNode() } @@ -102,6 +110,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { if self._displayNode == nil { self.loadDisplayNode() } + let previousLayout: ViewControllerLayout? if let updateLayoutOnLayout = self.updateLayoutOnLayout { previousLayout = updateLayoutOnLayout.0 @@ -109,7 +118,10 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { previousLayout = self.layout } - let layout = ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: previousLayout?.inputViewHeight ?? 0.0) + var insets = layout.insets + insets.top += 22.0 + + let layout = ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: previousLayout?.inputViewHeight ?? 0.0, statusBarHeight: layout.statusBarHeight) let updated: Bool if let previousLayout = previousLayout { updated = previousLayout != layout @@ -128,6 +140,8 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + self.navigationBar.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 44.0 + 20.0)) } override public func viewDidLayoutSubviews() { @@ -138,6 +152,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { let previousLayout = self.layout self.layout = updateLayoutOnLayout.0 self.updateLayout(updateLayoutOnLayout.0, previousLayout: previousLayout, duration: updateLayoutOnLayout.1, curve: updateLayoutOnLayout.2) + self.view.frame = CGRect(origin: self.view.frame.origin, size: updateLayoutOnLayout.0.size) self.updateLayoutOnLayout = nil } else { @@ -156,4 +171,12 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } } } + + override public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + if let navigationController = self.navigationController as? NavigationController { + navigationController.presentViewController(viewControllerToPresent, animated: flag, completion: completion) + } else { + super.presentViewController(viewControllerToPresent, animated: flag, completion: completion) + } + } } diff --git a/Display/Window.swift b/Display/Window.swift index 4fccbff987..2ed18955cb 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -15,6 +15,7 @@ public struct ViewControllerLayout: Equatable { public let size: CGSize public let insets: UIEdgeInsets public let inputViewHeight: CGFloat + public let statusBarHeight: CGFloat } public protocol WindowContentController { @@ -74,7 +75,7 @@ public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NST } public class Window: UIWindow { - //public let textField: UITextField + private let statusBarManager: StatusBarManager private var updateViewSizeOnLayout: (Bool, NSTimeInterval) = (false, 0.0) public var isUpdatingOrientationLayout = false @@ -88,12 +89,10 @@ public class Window: UIWindow { } public override init(frame: CGRect) { - //self.textField = UITextField(frame: CGRect(x: -110.0, y: 0.0, width: 100.0, height: 50.0)) + self.statusBarManager = StatusBarManager() super.init(frame: frame) - //self.addSubview(self.textField) - super.rootViewController = WindowRootViewController() } @@ -144,11 +143,13 @@ public class Window: UIWindow { self._rootViewController?.view.removeFromSuperview() self._rootViewController = value self._rootViewController?.view.frame = self.bounds - self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0), duration: 0.0, curve: 0) + self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: 0.0), duration: 0.0, curve: 0) if let view = self._rootViewController?.view { self.addSubview(view) } + + self.updateStatusBars() } } @@ -158,7 +159,7 @@ public class Window: UIWindow { if self.updateViewSizeOnLayout.0 { self.updateViewSizeOnLayout.0 = false - self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0), duration: updateViewSizeOnLayout.1, curve: 0) + self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: 0.0), duration: updateViewSizeOnLayout.1, curve: 0) } } @@ -179,4 +180,8 @@ public class Window: UIWindow { public func addPostUpdateToInterfaceOrientationBlock(f: Void -> Void) { postUpdateToInterfaceOrientationBlocks.append(f) } + + func updateStatusBars() { + self.statusBarManager.surfaces = (self._rootViewController as? StatusBarSurfaceProvider)?.statusBarSurfaces() ?? [] + } } From 3ea022e8e24606a6e4d30e5ffbd2871b6a51eae8 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 18 Apr 2016 01:01:14 +0300 Subject: [PATCH 010/245] no message --- Display.xcodeproj/project.pbxproj | 92 +++++++++++++++++++++++++++---- Display/GenerateImage.swift | 16 +++++- Display/UIKitUtils.swift | 17 ++++++ 3 files changed, 113 insertions(+), 12 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 3bab6c708f..dcffe909d2 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -23,7 +23,6 @@ D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; - D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */; }; D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; @@ -61,7 +60,6 @@ D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */; }; D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; - D05CC3651B69960300E235A3 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC3641B69960300E235A3 /* AsyncDisplayKit.framework */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; @@ -74,6 +72,7 @@ D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; + D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; /* End PBXBuildFile section */ @@ -107,7 +106,6 @@ D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DisplayTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = ""; }; D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftSignalKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/SwiftSignalKit.framework"; sourceTree = ""; }; D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; @@ -145,7 +143,6 @@ D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationBarProxy.m; sourceTree = ""; }; D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; - D05CC3641B69960300E235A3 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-gbpsmqzuwcmmxadrqcwyrluaftwp/Build/Products/Debug-iphoneos/AsyncDisplayKit.framework"; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; @@ -158,6 +155,8 @@ D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; + D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; + D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/AsyncDisplayKit.framework"; sourceTree = ""; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -166,8 +165,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - D05CC3651B69960300E235A3 /* AsyncDisplayKit.framework in Frameworks */, - D05CC29A1B69323B00E235A3 /* SwiftSignalKit.framework in Frameworks */, + D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -257,8 +255,8 @@ D05CC2A31B6932D500E235A3 /* Frameworks */ = { isa = PBXGroup; children = ( - D05CC3641B69960300E235A3 /* AsyncDisplayKit.framework */, - D05CC2991B69323B00E235A3 /* SwiftSignalKit.framework */, + D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */, + D0E1D6351CBC159C00B04029 /* AVFoundation.framework */, ); name = Frameworks; sourceTree = ""; @@ -662,7 +660,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_BITCODE = NO; + ENABLE_BITCODE = YES; INFOPLIST_FILE = Display/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -683,7 +681,7 @@ DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_BITCODE = NO; + ENABLE_BITCODE = YES; INFOPLIST_FILE = Display/Info.plist; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; @@ -714,6 +712,77 @@ }; name = Release; }; + D086A56E1CC0115D00F08284 /* Hockeyapp */ = { + 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Hockeyapp; + }; + D086A56F1CC0115D00F08284 /* Hockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_ENABLE_MODULES = YES; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Hockeyapp; + }; + D086A5701CC0115D00F08284 /* Hockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Hockeyapp; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -722,6 +791,7 @@ buildConfigurations = ( D05CC2751B69316F00E235A3 /* Debug */, D05CC2761B69316F00E235A3 /* Release */, + D086A56E1CC0115D00F08284 /* Hockeyapp */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -731,6 +801,7 @@ buildConfigurations = ( D05CC2781B69316F00E235A3 /* Debug */, D05CC2791B69316F00E235A3 /* Release */, + D086A56F1CC0115D00F08284 /* Hockeyapp */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -740,6 +811,7 @@ buildConfigurations = ( D05CC27B1B69316F00E235A3 /* Debug */, D05CC27C1B69316F00E235A3 /* Release */, + D086A5701CC0115D00F08284 /* Hockeyapp */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index ff19c6a151..feae2911e6 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -102,9 +102,9 @@ public class DrawingContext { } } - public init(size: CGSize, clear: Bool = false) { + public init(size: CGSize, scale: CGFloat = deviceScale, clear: Bool = false) { self.size = size - self.scale = deviceScale + self.scale = scale self.scaledSize = CGSize(width: size.width * scale, height: size.height * scale) self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) @@ -129,6 +129,18 @@ public class DrawingContext { } } + public func colorAt(point: CGPoint) -> UIColor { + let x = Int(point.x * self.scale) + let y = Int(point.y * self.scale) + if x >= 0 && x < Int(self.scaledSize.width) && y >= 0 && y < Int(self.scaledSize.height) { + let srcLine = UnsafeMutablePointer(self.bytes + y * self.bytesPerRow) + let pixel = srcLine + x + return UIColor(Int(pixel.memory)) + } else { + return UIColor.clearColor() + } + } + public func blt(other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { if abs(other.scale - self.scale) < CGFloat(FLT_EPSILON) { let srcX = 0 diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 9d1aefdb6b..4d10c0873a 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -31,6 +31,8 @@ public func floorToScreenPixels(value: CGFloat) -> CGFloat { return floor(value * UIScreenScale) / UIScreenScale } +public let UIScreenPixel = 1.0 / UIScreenScale + public extension UIColor { convenience init(_ rgb: Int) { self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) @@ -53,10 +55,25 @@ public extension CGSize { return fittedSize } + public func fittedToArea(area: CGFloat) -> CGSize { + if self.height < 1.0 || self.width < 1.0 { + return CGSize() + } + let aspect = self.width / self.height + let height = sqrt(area / aspect) + let width = aspect * height + return CGSize(width: floor(width), height: floor(height)) + } + public func aspectFilled(size: CGSize) -> CGSize { let scale = max(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } + + public func aspectFitted(size: CGSize) -> CGSize { + let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) + return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) + } } public func assertNotOnMainThread(file: String = #file, line: Int = #line) { From cd9b012ccf27fb8677397493d8c4a840ed13d4c6 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 18 Apr 2016 01:30:55 +0300 Subject: [PATCH 011/245] no message --- Display.xcodeproj/project.pbxproj | 52 + Display/ASTransformLayerNode.swift | 62 + Display/AsyncLayoutable.swift | 6 + Display/ListView.swift | 1807 +++++++++++++++++++++++ Display/ListViewAccessoryItem.swift | 6 + Display/ListViewAccessoryItemNode.swift | 40 + Display/ListViewAnimation.swift | 136 ++ Display/ListViewItem.swift | 34 + Display/ListViewItemNode.swift | 390 +++++ Display/ListViewScroller.swift | 7 + Display/ListViewTransactionQueue.swift | 56 + Display/Spring.swift | 66 + 12 files changed, 2662 insertions(+) create mode 100644 Display/ASTransformLayerNode.swift create mode 100644 Display/AsyncLayoutable.swift create mode 100644 Display/ListView.swift create mode 100644 Display/ListViewAccessoryItem.swift create mode 100644 Display/ListViewAccessoryItemNode.swift create mode 100644 Display/ListViewAnimation.swift create mode 100644 Display/ListViewItem.swift create mode 100644 Display/ListViewItemNode.swift create mode 100644 Display/ListViewScroller.swift create mode 100644 Display/ListViewTransactionQueue.swift create mode 100644 Display/Spring.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index dcffe909d2..50ec9e07c2 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -69,6 +69,17 @@ D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; + D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; }; + D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */; }; + D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBD1CC4431D0044FF83 /* Spring.swift */; }; + D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; + D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */; }; + D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */; }; + D0C2DFCC1CC4431D0044FF83 /* AsyncLayoutable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC11CC4431D0044FF83 /* AsyncLayoutable.swift */; }; + D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */; }; + D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */; }; + D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */; }; + D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; @@ -152,6 +163,17 @@ D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; + D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTransformLayerNode.swift; sourceTree = ""; }; + D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemNode.swift; sourceTree = ""; }; + D0C2DFBD1CC4431D0044FF83 /* Spring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Spring.swift; sourceTree = ""; }; + D0C2DFBE1CC4431D0044FF83 /* ListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; }; + D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItem.swift; sourceTree = ""; }; + D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAnimation.swift; sourceTree = ""; }; + D0C2DFC11CC4431D0044FF83 /* AsyncLayoutable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncLayoutable.swift; sourceTree = ""; }; + D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewTransactionQueue.swift; sourceTree = ""; }; + D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAccessoryItem.swift; sourceTree = ""; }; + D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewScroller.swift; sourceTree = ""; }; + D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAccessoryItemNode.swift; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; @@ -227,6 +249,7 @@ D05CC2651B69316F00E235A3 /* Display */ = { isa = PBXGroup; children = ( + D0C2DFBA1CC443080044FF83 /* List View */, D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, @@ -351,6 +374,24 @@ name = "Status Bar"; sourceTree = ""; }; + D0C2DFBA1CC443080044FF83 /* List View */ = { + isa = PBXGroup; + children = ( + D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */, + D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */, + D0C2DFBD1CC4431D0044FF83 /* Spring.swift */, + D0C2DFBE1CC4431D0044FF83 /* ListView.swift */, + D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */, + D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */, + D0C2DFC11CC4431D0044FF83 /* AsyncLayoutable.swift */, + D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */, + D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */, + D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, + D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */, + ); + name = "List View"; + sourceTree = ""; + }; D0DC48521BF93D7C00F672FD /* Tabs */ = { isa = PBXGroup; children = ( @@ -504,35 +545,46 @@ D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, + D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */, + D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, + D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */, + D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, + D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */, + D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */, + D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, + D0C2DFCC1CC4431D0044FF83 /* AsyncLayoutable.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, + D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, + D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, diff --git a/Display/ASTransformLayerNode.swift b/Display/ASTransformLayerNode.swift new file mode 100644 index 0000000000..0978ddb5b7 --- /dev/null +++ b/Display/ASTransformLayerNode.swift @@ -0,0 +1,62 @@ +import Foundation +import AsyncDisplayKit + +class ASTransformLayer: CATransformLayer { + override var contents: AnyObject? { + get { + return nil + } set(value) { + + } + } + + override var backgroundColor: CGColor? { + get { + return nil + } set(value) { + + } + } + + override func setNeedsLayout() { + } + + override func layoutSublayers() { + } +} + +class ASTransformView: UIView { + override class func layerClass() -> AnyClass { + return ASTransformLayer.self + } +} + +public class ASTransformLayerNode: ASDisplayNode { + public override init() { + super.init(layerBlock: { + return ASTransformLayer() + }, didLoadBlock: nil) + } +} + +public class ASTransformViewNode: ASDisplayNode { + public override init() { + super.init(viewBlock: { + return ASTransformView() + }, didLoadBlock: nil) + } +} + +public class ASTransformNode: ASDisplayNode { + public init(layerBacked: Bool = true) { + if layerBacked { + super.init(layerBlock: { + return ASTransformLayer() + }, didLoadBlock: nil) + } else { + super.init(viewBlock: { + return ASTransformView() + }, didLoadBlock: nil) + } + } +} diff --git a/Display/AsyncLayoutable.swift b/Display/AsyncLayoutable.swift new file mode 100644 index 0000000000..0accb93230 --- /dev/null +++ b/Display/AsyncLayoutable.swift @@ -0,0 +1,6 @@ +import Foundation +import AsyncDisplayKit + +public protocol AsyncLayoutable: class { + static func asyncLayout(maybeNode: Self?) -> (constrainedSize: CGSize) -> (CGSize, () -> Self) +} diff --git a/Display/ListView.swift b/Display/ListView.swift new file mode 100644 index 0000000000..05370babc6 --- /dev/null +++ b/Display/ListView.swift @@ -0,0 +1,1807 @@ +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +private let usePerformanceTracker = false +private let useDynamicTuning = false + +public enum ListViewScrollPosition { + case Top + case Bottom + case Center +} + +public struct ListViewDeleteAndInsertOptions: OptionSetType { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1) + public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) +} + +public struct ListViewVisibleRange: Equatable { + public let firstIndex: Int + public let lastIndex: Int +} + +public func ==(lhs: ListViewVisibleRange, rhs: ListViewVisibleRange) -> Bool { + return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex +} + +private struct IndexRange { + let first: Int + let last: Int + + func contains(index: Int) -> Bool { + return index >= first && index <= last + } + + var empty: Bool { + return first > last + } +} + +private struct OffsetRanges { + var offsets: [(IndexRange, CGFloat)] = [] + + mutating func append(other: OffsetRanges) { + self.offsets.appendContentsOf(other.offsets) + } + + mutating func offset(indexRange: IndexRange, offset: CGFloat) { + self.offsets.append((indexRange, offset)) + } + + func offsetForIndex(index: Int) -> CGFloat { + var result: CGFloat = 0.0 + for offset in self.offsets { + if offset.0.contains(index) { + result += offset.1 + } + } + return result + } +} + +private func binarySearch(inputArr: [Int], searchItem: Int) -> Int? { + var lowerIndex = 0; + var upperIndex = inputArr.count - 1 + + if lowerIndex > upperIndex { + return nil + } + + while (true) { + let currentIndex = (lowerIndex + upperIndex) / 2 + if (inputArr[currentIndex] == searchItem) { + return currentIndex + } else if (lowerIndex > upperIndex) { + return nil + } else { + if (inputArr[currentIndex] > searchItem) { + upperIndex = currentIndex - 1 + } else { + lowerIndex = currentIndex + 1 + } + } + } +} + +private struct TransactionState { + let visibleSize: CGSize + let items: [ListViewItem] +} + +private struct PendingNode { + let index: Int + let node: ListViewItemNode + let apply: () -> () + let frame: CGRect + let apparentHeight: CGFloat +} + +private enum ListViewStateNode { + case Node(index: Int, frame: CGRect, referenceNode: ListViewItemNode?) + case Placeholder(frame: CGRect) + + var index: Int? { + switch self { + case .Node(let index, _, _): + return index + case .Placeholder(_): + return nil + } + } + + var frame: CGRect { + get { + switch self { + case .Node(_, let frame, _): + return frame + case .Placeholder(let frame): + return frame + } + } set(value) { + switch self { + case let .Node(index, _, referenceNode): + self = .Node(index: index, frame: value, referenceNode: referenceNode) + case .Placeholder(_): + self = .Placeholder(frame: value) + } + } + } +} + +private enum ListViewInsertionOffsetDirection { + case Up + case Down +} + +private struct ListViewState { + let insets: UIEdgeInsets + let visibleSize: CGSize + let invisibleInset: CGFloat + var nodes: [ListViewStateNode] + + func nodeInsertionPointAndIndex(itemIndex: Int) -> (CGPoint, Int) { + if self.nodes.count == 0 { + return (CGPoint(x: 0.0, y: self.insets.top), 0) + } else { + var index = 0 + var lastNodeWithIndex = -1 + for node in self.nodes { + if let nodeItemIndex = node.index { + if nodeItemIndex > itemIndex { + break + } + lastNodeWithIndex = index + } + index += 1 + } + lastNodeWithIndex += 1 + return (CGPoint(x: 0.0, y: lastNodeWithIndex == 0 ? self.nodes[0].frame.minY : self.nodes[lastNodeWithIndex - 1].frame.maxY), lastNodeWithIndex) + } + } + + mutating func insertNode(itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, inout operations: [ListViewStateOperation]) { + let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) + + let nodeOrigin: CGPoint + switch offsetDirection { + case .Up: + nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height)) + case .Down: + nodeOrigin = insertionOrigin + } + + let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) + + operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) + self.nodes.insert(.Node(index: node.index!, frame: nodeFrame, referenceNode: nil), atIndex: insertionIndex) + + if !animated { + switch offsetDirection { + case .Up: + var i = insertionIndex - 1 + while i >= 0 { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + i -= 1 + } + case .Down: + var i = insertionIndex + 1 + while i < self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + i += 1 + } + } + } + } + + mutating func removeNodeAtIndex(index: Int, animated: Bool, inout operations: [ListViewStateOperation]) { + let node = self.nodes[index] + if case let .Node(_, _, referenceNode) = node { + let nodeFrame = node.frame + self.nodes.removeAtIndex(index) + operations.append(.Remove(index: index)) + + if let referenceNode = referenceNode where animated { + self.nodes.insert(.Placeholder(frame: nodeFrame), atIndex: index) + operations.append(.InsertPlaceholder(index: index, referenceNode: referenceNode)) + } else { + for i in index ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + } + } + } else { + assertionFailure() + } + } +} + +private enum ListViewStateOperation { + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) + case InsertPlaceholder(index: Int, referenceNode: ListViewItemNode) + case Remove(index: Int) + case Remap([Int: Int]) + case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) +} + +private let infiniteScrollSize: CGFloat = 10000.0 +private let insertionAnimationDuration: Double = 0.4 + +private final class ListViewBackingLayer: CALayer { + override func setNeedsLayout() { + } + + override func layoutSublayers() { + } +} + +private final class ListViewBackingView: UIView { + weak var target: ASDisplayNode? + + override class func layerClass() -> AnyClass { + return ListViewBackingLayer.self + } + + override func setNeedsLayout() { + } + + override func layoutSubviews() { + } + + override func touchesBegan(touches: Set, withEvent event: UIEvent?) { + self.target?.touchesBegan(touches, withEvent: event) + } + + override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { + self.target?.touchesCancelled(touches, withEvent: event) + } + + override func touchesMoved(touches: Set, withEvent event: UIEvent?) { + self.target?.touchesMoved(touches, withEvent: event) + } + + override func touchesEnded(touches: Set, withEvent event: UIEvent?) { + self.target?.touchesEnded(touches, withEvent: event) + } +} + +private final class ListViewTimerProxy: NSObject { + private let action: () -> () + + init(_ action: () -> ()) { + self.action = action + super.init() + } + + @objc func timerEvent() { + self.action() + } +} + +public final class ListView: ASDisplayNode, UIScrollViewDelegate { + private final let scroller: ListViewScroller + private final var visibleSize: CGSize = CGSize() + private final var insets = UIEdgeInsets() + private final var lastContentOffset: CGPoint = CGPoint() + private final var lastContentOffsetTimestamp: CFAbsoluteTime = 0.0 + private final var ignoreScrollingEvents: Bool = false + + private final var displayLink: CADisplayLink! + private final var needsAnimations = false + + private final var invisibleInset: CGFloat = 500.0 + public var preloadPages: Bool = true { + didSet { + if self.preloadPages != oldValue { + self.invisibleInset = self.preloadPages ? 500.0 : 20.0 + self.enqueueUpdateVisibleItems() + } + } + } + + private var touchesPosition = CGPoint() + private var isTracking = false + + private final var transactionQueue: ListViewTransactionQueue + private final var transactionOffset: CGFloat = 0.0 + + private final var enqueuedUpdateVisibleItems = false + + private final var createdItemNodes = 0 + + public final var synchronousNodes = false + public final var debugInfo = false + + private final var items: [ListViewItem] = [] + private final var itemNodes: [ListViewItemNode] = [] + + public final var visibleItemRangeChanged: ListViewVisibleRange? -> Void = { _ in } + public final var visibleItemRange: ListViewVisibleRange? + + private final var animations: [ListViewAnimation] = [] + private final var actionsForVSync: [() -> ()] = [] + private final var inVSync = false + + private let frictionSlider = UISlider() + private let springSlider = UISlider() + private let freeResistanceSlider = UISlider() + private let scrollingResistanceSlider = UISlider() + + //let performanceTracker: FBAnimationPerformanceTracker + + private var selectionTouchLocation: CGPoint? + private var selectionTouchDelayTimer: NSTimer? + private var highlightedItemIndex: Int? + + public func reportDurationInMS(duration: Int, smallDropEvent: Double, largeDropEvent: Double) { + print("reportDurationInMS duration: \(duration), smallDropEvent: \(smallDropEvent), largeDropEvent: \(largeDropEvent)") + } + + public func reportStackTrace(stack: String!, withSlide slide: String!) { + NSLog("reportStackTrace stack: \(stack)\n\nslide: \(slide)") + } + + override public init() { + class DisplayLinkProxy: NSObject { + weak var target: ListView? + init(target: ListView) { + self.target = target + } + + @objc func displayLinkEvent() { + self.target?.displayLinkEvent() + } + } + + self.transactionQueue = ListViewTransactionQueue() + + self.scroller = ListViewScroller() + + /*var performanceTrackerConfig = FBAnimationPerformanceTracker.standardConfig() + performanceTrackerConfig.reportStackTraces = true + self.performanceTracker = FBAnimationPerformanceTracker(config: performanceTrackerConfig)*/ + + super.init(viewBlock: { Void -> UIView in + return ListViewBackingView() + }, didLoadBlock: nil) + + (self.view as! ListViewBackingView).target = self + + self.transactionQueue.transactionCompleted = { [weak self] in + if let strongSelf = self { + strongSelf.updateVisibleItemRange() + } + } + + //self.performanceTracker.delegate = self + + self.scroller.alwaysBounceVertical = true + self.scroller.contentSize = CGSize(width: 0.0, height: infiniteScrollSize * 2.0) + self.scroller.hidden = true + self.scroller.delegate = self + self.view.addSubview(self.scroller) + self.scroller.panGestureRecognizer.cancelsTouchesInView = false + self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) + + self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) + self.displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) + self.displayLink.paused = true + + if useDynamicTuning { + self.frictionSlider.addTarget(self, action: #selector(self.frictionSliderChanged(_:)), forControlEvents: .ValueChanged) + self.springSlider.addTarget(self, action: #selector(self.springSliderChanged(_:)), forControlEvents: .ValueChanged) + self.freeResistanceSlider.addTarget(self, action: #selector(self.freeResistanceSliderChanged(_:)), forControlEvents: .ValueChanged) + self.scrollingResistanceSlider.addTarget(self, action: #selector(self.scrollingResistanceSliderChanged(_:)), forControlEvents: .ValueChanged) + + self.frictionSlider.minimumValue = Float(testSpringFrictionLimits.0) + self.frictionSlider.maximumValue = Float(testSpringFrictionLimits.1) + self.frictionSlider.value = Float(testSpringFriction) + + self.springSlider.minimumValue = Float(testSpringConstantLimits.0) + self.springSlider.maximumValue = Float(testSpringConstantLimits.1) + self.springSlider.value = Float(testSpringConstant) + + self.freeResistanceSlider.minimumValue = Float(testSpringResistanceFreeLimits.0) + self.freeResistanceSlider.maximumValue = Float(testSpringResistanceFreeLimits.1) + self.freeResistanceSlider.value = Float(testSpringFreeResistance) + + self.scrollingResistanceSlider.minimumValue = Float(testSpringResistanceScrollingLimits.0) + self.scrollingResistanceSlider.maximumValue = Float(testSpringResistanceScrollingLimits.1) + self.scrollingResistanceSlider.value = Float(testSpringScrollingResistance) + + self.view.addSubview(self.frictionSlider) + self.view.addSubview(self.springSlider) + self.view.addSubview(self.freeResistanceSlider) + self.view.addSubview(self.scrollingResistanceSlider) + } + } + + deinit { + self.pauseAnimations() + } + + @objc func frictionSliderChanged(slider: UISlider) { + testSpringFriction = CGFloat(slider.value) + print("friction: \(testSpringFriction)") + } + + @objc func springSliderChanged(slider: UISlider) { + testSpringConstant = CGFloat(slider.value) + print("spring: \(testSpringConstant)") + } + + @objc func freeResistanceSliderChanged(slider: UISlider) { + testSpringFreeResistance = CGFloat(slider.value) + print("free resistance: \(testSpringFreeResistance)") + } + + @objc func scrollingResistanceSliderChanged(slider: UISlider) { + testSpringScrollingResistance = CGFloat(slider.value) + print("free resistance: \(testSpringScrollingResistance)") + } + + private func displayLinkEvent() { + self.updateAnimations() + } + + private func setNeedsAnimations() { + if !self.needsAnimations { + self.needsAnimations = true + self.displayLink.paused = false + } + } + + private func pauseAnimations() { + if self.needsAnimations { + self.needsAnimations = false + self.displayLink.paused = true + } + } + + private func dispatchOnVSync(forceNext: Bool = false, action: () -> ()) { + Queue.mainQueue().dispatch { + if !forceNext && self.inVSync { + action() + } else { + self.actionsForVSync.append(action) + self.setNeedsAnimations() + } + } + } + + public func scrollViewWillBeginDragging(scrollView: UIScrollView) { + self.lastContentOffsetTimestamp = 0.0 + + /*if usePerformanceTracker { + self.performanceTracker.start() + }*/ + } + + public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if decelerate { + self.lastContentOffsetTimestamp = CACurrentMediaTime() + } else { + self.lastContentOffsetTimestamp = 0.0 + /*if usePerformanceTracker { + self.performanceTracker.stop() + }*/ + } + } + + public func scrollViewDidEndDecelerating(scrollView: UIScrollView) { + self.lastContentOffsetTimestamp = 0.0 + /*if usePerformanceTracker { + self.performanceTracker.stop() + }*/ + } + + public func scrollViewDidScroll(scrollView: UIScrollView) { + if self.ignoreScrollingEvents || scroller !== self.scroller { + return + } + + CATransaction.begin() + CATransaction.setDisableActions(true) + + let deltaY = scrollView.contentOffset.y - self.lastContentOffset.y + + self.lastContentOffset = scrollView.contentOffset + if self.lastContentOffsetTimestamp > DBL_EPSILON { + self.lastContentOffsetTimestamp = CACurrentMediaTime() + } + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y - deltaY) + } + + self.transactionOffset += -deltaY + + self.enqueueUpdateVisibleItems() + self.updateScroller() + + var useScrollDynamics = false + + for itemNode in self.itemNodes { + if itemNode.wantsScrollDynamics { + useScrollDynamics = true + let anchor: CGFloat + if self.isTracking { + anchor = self.touchesPosition.y + } else if deltaY < 0.0 { + anchor = self.visibleSize.height + } else { + anchor = 0.0 + } + + var distance: CGFloat + let itemFrame = itemNode.apparentFrame + if anchor < itemFrame.origin.y { + distance = abs(itemFrame.origin.y - anchor) + } else if anchor > itemFrame.origin.y + itemFrame.size.height { + distance = abs(anchor - (itemFrame.origin.y + itemFrame.size.height)) + } else { + distance = 0.0 + } + + let factor: CGFloat = max(0.08, abs(distance) / self.visibleSize.height) + + let resistance: CGFloat = testSpringFreeResistance + + itemNode.addScrollingOffset(deltaY * factor * resistance) + } + } + + if useScrollDynamics { + self.setNeedsAnimations() + } + + self.updateVisibleNodes() + + CATransaction.commit() + } + + private func snapToBounds() { + if self.itemNodes.count == 0 { + return + } + + var overscroll: CGFloat = 0.0 + if self.scroller.contentOffset.y < 0.0 { + overscroll = self.scroller.contentOffset.y + } else if self.scroller.contentOffset.y > max(0.0, self.scroller.contentSize.height - self.scroller.bounds.size.height) { + overscroll = self.scroller.contentOffset.y - max(0.0, (self.scroller.contentSize.height - self.scroller.bounds.size.height)) + } + + var completeHeight: CGFloat = 0.0 + var topItemFound = false + var bottomItemFound = false + var topItemEdge: CGFloat = 0.0 + var bottomItemEdge: CGFloat = 0.0 + + if itemNodes[0].index == 0 { + topItemFound = true + topItemEdge = itemNodes[0].apparentFrame.origin.y + } + + if itemNodes[itemNodes.count - 1].index == self.items.count - 1 { + bottomItemFound = true + bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY + } + + if topItemFound && bottomItemFound { + for itemNode in self.itemNodes { + completeHeight += itemNode.apparentBounds.height + } + } + + var offset: CGFloat = 0.0 + if topItemFound && bottomItemFound { + let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) + if bottomItemEdge < self.insets.top + areaHeight - overscroll { + offset = self.insets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > self.insets.top - overscroll { + //offset = topItemEdge - (self.insets.top - overscroll) + } + } else if topItemFound { + if topItemEdge > self.insets.top - overscroll { + //offset = topItemEdge - (self.insets.top - overscroll) + } + } else if bottomItemFound { + if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { + offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge + } + } + + if abs(offset) > CGFloat(FLT_EPSILON) { + for itemNode in self.itemNodes { + var position = itemNode.position + position.y += offset + itemNode.position = position + } + } + } + + private func updateScroller() { + if itemNodes.count == 0 { + return + } + + var completeHeight = self.insets.top + self.insets.bottom + var topItemFound = false + var bottomItemFound = false + var topItemEdge: CGFloat = 0.0 + var bottomItemEdge: CGFloat = 0.0 + + if itemNodes[0].index == 0 { + topItemFound = true + topItemEdge = itemNodes[0].apparentFrame.origin.y + } + + if itemNodes[itemNodes.count - 1].index == self.items.count - 1 { + bottomItemFound = true + bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY + } + + if topItemFound && bottomItemFound { + for itemNode in self.itemNodes { + completeHeight += itemNode.apparentBounds.height + } + } + + topItemEdge -= self.insets.top + bottomItemEdge += self.insets.bottom + + self.ignoreScrollingEvents = true + if topItemFound && bottomItemFound { + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight) + self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) + self.scroller.contentOffset = self.lastContentOffset; + } else if topItemFound { + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) + self.scroller.contentOffset = self.lastContentOffset + } else if bottomItemFound { + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize * 2.0 - bottomItemEdge) + self.scroller.contentOffset = self.lastContentOffset + } + else + { + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) + self.scroller.contentOffset = self.lastContentOffset + } + + self.ignoreScrollingEvents = false + } + + private func nodeForItem(item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { + if let previousNode = previousNode { + item.updateNode(previousNode, width: width, previousItem: previousItem, nextItem: nextItem, completion: { (layout, apply) in + previousNode.index = index + completion(previousNode, layout, apply) + }) + } else { + let startTime = CACurrentMediaTime() + item.nodeConfiguredForWidth(width, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in + itemNode.index = index + if self.debugInfo { + print("[ListView] nodeConfiguredForWidth \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } + completion(itemNode, ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) + }) + } + } + + private func currentState() -> ListViewState { + var nodes: [ListViewStateNode] = [] + nodes.reserveCapacity(self.itemNodes.count) + for node in self.itemNodes { + if let index = node.index { + nodes.append(.Node(index: index, frame: node.apparentFrame, referenceNode: node)) + } else { + nodes.append(.Placeholder(frame: node.apparentFrame)) + } + } + return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes) + } + + public func deleteAndInsertItems(deleteIndices: [Int], insertIndicesAndItems: [(Int, ListViewItem, Int?)], offsetTopInsertedItems: Bool, options: ListViewDeleteAndInsertOptions, completion: Void -> Void = {}) { + if deleteIndices.count == 0 && insertIndicesAndItems.count == 0 { + completion() + return + } + + self.transactionQueue.addTransaction({ [weak self] transactionCompletion in + if let strongSelf = self { + strongSelf.transactionOffset = 0.0 + strongSelf.deleteAndInsertItemsTransaction(deleteIndices, insertIndicesAndItems: insertIndicesAndItems, offsetTopInsertedItems: offsetTopInsertedItems, options: options, completion: { + completion() + + transactionCompletion() + }) + } + }) + } + + private func deleteAndInsertItemsTransaction(deleteIndices: [Int], insertIndicesAndItems: [(Int, ListViewItem, Int?)], offsetTopInsertedItems: Bool, options: ListViewDeleteAndInsertOptions, completion: Void -> Void) { + var state = self.currentState() + + let sortedDeleteIndices = deleteIndices.sort() + for index in sortedDeleteIndices.reverse() { + self.items.removeAtIndex(index) + } + + let sortedIndicesAndItems = insertIndicesAndItems.sort { $0.0 < $1.0 } + if self.items.count == 0 { + if sortedIndicesAndItems[0].0 != 0 { + fatalError("deleteAndInsertItems: invalid insert into empty list") + } + } + + var previousNodes: [Int: ListViewItemNode] = [:] + for (index, item, previousIndex) in sortedIndicesAndItems { + self.items.insert(item, atIndex: index) + if let previousIndex = previousIndex { + for itemNode in self.itemNodes { + if itemNode.index == previousIndex { + previousNodes[index] = itemNode + } + } + } + } + + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { + var operations: [ListViewStateOperation] = [] + + let deleteIndexSet = Set(deleteIndices) + var insertedIndexSet = Set() + var moveMapping: [Int: Int] = [:] + for (index, _, previousIndex) in sortedIndicesAndItems { + insertedIndexSet.insert(index) + if let previousIndex = previousIndex { + moveMapping[previousIndex] = index + } + } + + let animated = options.contains(.AnimateInsertion) + + var remapDeletion: [Int: Int] = [:] + + var updateAdjacentItemsIndices = Set() + + var i = 0 + while i < state.nodes.count { + if let index = state.nodes[i].index { + var indexOffset = 0 + for deleteIndex in sortedDeleteIndices { + if deleteIndex < index { + indexOffset += 1 + } else { + break + } + } + + if deleteIndexSet.contains(index) { + state.removeNodeAtIndex(i, animated: animated, operations: &operations) + } else { + let updatedIndex = index - indexOffset + remapDeletion[index] = updatedIndex + if deleteIndexSet.contains(index - 1) || deleteIndexSet.contains(index + 1) { + updateAdjacentItemsIndices.insert(updatedIndex) + } + + switch state.nodes[i] { + case let .Node(_, frame, referenceNode): + state.nodes[i] = .Node(index: updatedIndex, frame: frame, referenceNode: referenceNode) + case .Placeholder: + break + } + i += 1 + } + } else { + i += 1 + } + } + + if !remapDeletion.isEmpty { + operations.append(.Remap(remapDeletion)) + } + + var remapInsertion: [Int: Int] = [:] + + for i in 0 ..< state.nodes.count { + if let index = state.nodes[i].index { + var indexOffset = 0 + for (insertIndex, _, _) in sortedIndicesAndItems { + if insertIndex <= index + indexOffset { + indexOffset += 1 + } + } + if indexOffset != 0 { + let updatedIndex = index + indexOffset + remapInsertion[index] = updatedIndex + switch state.nodes[i] { + case let .Node(_, frame, referenceNode): + state.nodes[i] = .Node(index: updatedIndex, frame: frame, referenceNode: referenceNode) + case .Placeholder: + break + } + } + } + } + + for node in state.nodes { + if let index = node.index { + if insertedIndexSet.contains(index - 1) || insertedIndexSet.contains(index + 1) { + updateAdjacentItemsIndices.insert(index) + } + } + } + + if !remapInsertion.isEmpty { + operations.append(.Remap(remapInsertion)) + } + + let startTime = CACurrentMediaTime() + + self.fillMissingNodes(animated, offsetTopInsertedItems: offsetTopInsertedItems, animatedInsertIndices: animated ? insertedIndexSet : Set(), state: state, previousNodes: previousNodes, operations: operations, completion: { updatedState, operations in + + var fixedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices + let maxIndex = updatedState.nodes.count - 1 + for nodeIndex in updateAdjacentItemsIndices { + if nodeIndex < 0 || nodeIndex > maxIndex { + fixedUpdateAdjacentItemsIndices.remove(nodeIndex) + } + } + + if self.debugInfo { + print("fillMissingNodes completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } + self.updateAdjacent(animated, state: updatedState, updateAdjacentItemsIndices: fixedUpdateAdjacentItemsIndices, operations: operations, completion: { operations in + if self.debugInfo { + print("updateAdjacent completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } + + let next = { + self.replayOperations(animated, operations: operations, completion: completion) + } + + self.dispatchOnVSync { + next() + } + }) + }) + }) + } + + private func updateAdjacent(animated: Bool, state: ListViewState, updateAdjacentItemsIndices: Set, operations: [ListViewStateOperation], completion: [ListViewStateOperation] -> Void) { + if updateAdjacentItemsIndices.isEmpty { + completion(operations) + } else { + var updatedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices + + let nodeIndex = updateAdjacentItemsIndices.first! + updatedUpdateAdjacentItemsIndices.remove(nodeIndex) + + var actualIndex = nodeIndex + /*for node in state.nodes { + if case let .Node(index, _, _) = node where index == nodeIndex { + break + } + actualIndex += 1 + }*/ + + var continueWithoutNode = true + + if actualIndex < state.nodes.count { + if case let .Node(index, _, referenceNode) = state.nodes[actualIndex] { + if let referenceNode = referenceNode { + continueWithoutNode = false + self.items[index].updateNode(referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], completion: { layout, apply in + var updatedState = state + var updatedOperations = operations + + for i in nodeIndex + 1 ..< updatedState.nodes.count { + let frame = updatedState.nodes[i].frame + updatedState.nodes[i].frame = frame.offsetBy(dx: 0.0, dy: frame.size.height) + updatedOperations.append(.UpdateLayout(index: nodeIndex, layout: layout, apply: apply)) + } + + self.updateAdjacent(animated, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion) + }) + } + } + } + + if continueWithoutNode { + updateAdjacent(animated, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion) + } + } + } + + private func fillMissingNodes(animated: Bool, offsetTopInsertedItems: Bool, animatedInsertIndices: Set, state: ListViewState, previousNodes: [Int: ListViewItemNode], operations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { + if self.items.count == 0 { + completion(state, operations) + } else { + var insertionItemIndexAndDirection: (Int, ListViewInsertionOffsetDirection)? + + if state.nodes.count == 0 { + insertionItemIndexAndDirection = (0, .Down) + } else { + var previousIndex: Int? + for node in state.nodes { + if let index = node.index { + if let previousIndex = previousIndex { + if previousIndex + 1 != index { + if state.nodeInsertionPointAndIndex(index - 1).0.y < state.insets.top { + insertionItemIndexAndDirection = (index - 1, .Up) + } else { + insertionItemIndexAndDirection = (previousIndex + 1, .Down) + } + break + } + } else if index != 0 { + let insertionPoint = state.nodeInsertionPointAndIndex(index - 1).0 + if insertionPoint.y >= -state.invisibleInset { + if !offsetTopInsertedItems || insertionPoint.y < state.insets.top { + insertionItemIndexAndDirection = (index - 1, .Up) + } else { + insertionItemIndexAndDirection = (0, .Down) + } + break + } + } + previousIndex = index + } + } + if let previousIndex = previousIndex where insertionItemIndexAndDirection == nil && previousIndex != self.items.count - 1 { + let insertionPoint = state.nodeInsertionPointAndIndex(previousIndex + 1).0 + if insertionPoint.y < state.visibleSize.height + state.invisibleInset { + insertionItemIndexAndDirection = (previousIndex + 1, .Down) + } + } + } + + if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { + let index = insertionItemIndexAndDirection.0 + self.nodeForItem(self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, completion: { (node, layout, apply) in + var updatedState = state + var updatedOperations = operations + updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations) + + self.fillMissingNodes(animated, offsetTopInsertedItems: offsetTopInsertedItems, animatedInsertIndices: animatedInsertIndices, state: updatedState, previousNodes: previousNodes, operations: updatedOperations, completion: completion) + }) + } else { + completion(state, operations) + } + } + } + + private func referencePointForInsertionAtIndex(nodeIndex: Int) -> CGPoint { + var index = 0 + for itemNode in self.itemNodes { + if index == nodeIndex { + return itemNode.apparentFrame.origin + } + index += 1 + } + if self.itemNodes.count == 0 { + return CGPoint(x: 0.0, y: self.insets.top) + } else { + return CGPoint(x: 0.0, y: self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY) + } + } + + private func updateVisibleNodes() { + /*let visibleRect = CGRect(origin: CGPoint(x: 0.0, y: -10.0), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height + 20)) + for itemNode in self.itemNodes { + if CGRectIntersectsRect(itemNode.apparentFrame, visibleRect) { + if useDynamicTuning { + self.insertSubnode(itemNode, atIndex: 0) + } else { + self.addSubnode(itemNode) + } + } else if itemNode.supernode != nil { + itemNode.removeFromSupernode() + } + }*/ + } + + private func insertNodeAtIndex(animated: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), timestamp: Double) { + let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) + + let nodeOrigin: CGPoint + switch offsetDirection { + case .Up: + nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height)) + case .Down: + nodeOrigin = insertionOrigin + } + + let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: layout.size.height)) + + let previousApparentHeight = node.apparentHeight + let previousInsets = node.insets + + node.contentSize = layout.contentSize + node.insets = layout.insets + node.apparentHeight = animated ? 0.0 : layout.size.height + node.frame = nodeFrame + apply() + self.itemNodes.insert(node, atIndex: nodeIndex) + + if useDynamicTuning { + self.insertSubnode(node, atIndex: 0) + } else { + //self.addSubnode(node) + } + + if previousFrame == nil { + node.setupGestures() + } + + var offsetHeight = node.apparentHeight + var takenAnimation = false + + if let _ = previousFrame where animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { + let nextNode = self.itemNodes[nodeIndex + 1] + if nextNode.index == nil { + let nextHeight = nextNode.apparentHeight + if abs(nextHeight - previousApparentHeight) < CGFloat(FLT_EPSILON) { + if let animation = node.animationForKey("apparentHeight") where abs(animation.to as! CGFloat - layout.size.height) < CGFloat(FLT_EPSILON) { + node.apparentHeight = previousApparentHeight + + offsetHeight = 0.0 + + var offsetPosition = nextNode.position + offsetPosition.y += nextHeight + nextNode.position = offsetPosition + nextNode.apparentHeight = 0.0 + + nextNode.removeApparentHeightAnimation() + + takenAnimation = true + } + } + } + } + + if node.index == nil { + node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } else if animated { + if !takenAnimation { + node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + + if let previousFrame = previousFrame { + node.transitionOffset += nodeFrame.origin.y - previousFrame.origin.y + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + if previousInsets != layout.insets { + node.insets = previousInsets + node.addInsetsAnimationToValue(layout.insets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + } else { + node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + } + } + } + + if node.apparentHeight > CGFloat(FLT_EPSILON) { + switch offsetDirection { + case .Up: + var i = nodeIndex - 1 + while i >= 0 { + var frame = self.itemNodes[i].frame + frame.origin.y -= offsetHeight + self.itemNodes[i].frame = frame + i -= 1 + } + case .Down: + var i = nodeIndex + 1 + while i < self.itemNodes.count { + var frame = self.itemNodes[i].frame + frame.origin.y += offsetHeight + self.itemNodes[i].frame = frame + i += 1 + } + } + } + } + + private func replayOperations(animated: Bool, operations: [ListViewStateOperation], completion: () -> Void) { + let timestamp = CACurrentMediaTime() + + var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] + for itemNode in self.itemNodes { + previousApparentFrames.append((itemNode, itemNode.apparentFrame)) + } + var insertedNodes: [ASDisplayNode] = [] + + for operation in operations { + switch operation { + case let .InsertNode(index, offsetDirection, node, layout, apply): + var previousFrame: CGRect? + for (previousNode, frame) in previousApparentFrames { + if previousNode === node { + previousFrame = frame + break + } + } + self.insertNodeAtIndex(animated, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + insertedNodes.append(node) + case let .InsertPlaceholder(index, referenceNode): + var height: CGFloat? + + for (node, previousFrame) in previousApparentFrames { + if node === referenceNode { + height = previousFrame.size.height + break + } + } + + if let height = height { + self.insertNodeAtIndex(false, previousFrame: nil, nodeIndex: index, offsetDirection: .Down, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + } else { + assertionFailure() + } + case let .Remap(mapping): + for node in self.itemNodes { + if let index = node.index { + if let mapped = mapping[index] { + node.index = mapped + } + } + } + case let .Remove(index): + let height = self.itemNodes[index].apparentHeight + if index != self.itemNodes.count - 1 { + for i in index + 1 ..< self.itemNodes.count { + var frame = self.itemNodes[i].frame + frame.origin.y -= height + self.itemNodes[i].frame = frame + } + } + self.removeItemNodeAtIndex(index) + case let .UpdateLayout(index, layout, apply): + let node = self.itemNodes[index] + + let previousApparentHeight = node.apparentHeight + let previousInsets = node.insets + + node.contentSize = layout.contentSize + node.insets = layout.insets + apply() + + let updatedApparentHeight = node.bounds.size.height + let updatedInsets = node.insets + + var offsetRanges = OffsetRanges() + + if animated { + if updatedInsets != previousInsets { + node.insets = previousInsets + node.addInsetsAnimationToValue(updatedInsets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + + if abs(updatedApparentHeight - previousApparentHeight) > CGFloat(FLT_EPSILON) { + node.apparentHeight = previousApparentHeight + node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + } else { + node.apparentHeight = updatedApparentHeight + + let apparentHeightDelta = updatedApparentHeight - previousApparentHeight + if apparentHeightDelta != 0.0 { + var apparentFrame = node.apparentFrame + apparentFrame.origin.y += offsetRanges.offsetForIndex(index) + if apparentFrame.maxY < self.insets.top { + offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta) + } else { + offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta) + } + } + } + + var index = 0 + for itemNode in self.itemNodes { + let offset = offsetRanges.offsetForIndex(index) + if offset != 0.0 { + var position = itemNode.position + position.y += offset + itemNode.position = position + } + + index += 1 + } + } + } + + self.insertNodesInBatches(insertedNodes, completion: { + self.debugCheckMonotonity() + self.removeInvisibleNodes() + self.updateAccessoryNodes(animated, currentTimestamp: timestamp) + self.snapToBounds() + self.updateVisibleNodes() + if animated { + self.setNeedsAnimations() + } + + completion() + }) + + /*let delta = CACurrentMediaTime() - timestamp + if delta > 1.0 / 60.0 { + print("replayOperations \(delta * 1000.0) ms \(nodeCreationDurations)") + }*/ + } + + private func insertNodesInBatches(nodes: [ASDisplayNode], completion: () -> Void) { + if nodes.count == 0 { + completion() + } else { + for node in nodes { + self.addSubnode(node) + } + completion() + /*self.dispatchOnVSync(true, action: { + self.addSubnode(nodes[0]) + var updatedNodes = nodes + updatedNodes.removeAtIndex(0) + self.insertNodesInBatches(updatedNodes, completion: completion) + })*/ + } + } + + private func debugCheckMonotonity() { + if self.debugInfo { + var previousMaxY: CGFloat? + for node in self.itemNodes { + if let previousMaxY = previousMaxY where abs(previousMaxY - node.apparentFrame.minY) > CGFloat(FLT_EPSILON) { + print("monotonity violated") + break + } + previousMaxY = node.apparentFrame.maxY + } + } + } + + private func removeItemNodeAtIndex(index: Int) { + let node = self.itemNodes[index] + self.itemNodes.removeAtIndex(index) + node.removeFromSupernode() + + node.accessoryItemNode?.removeFromSupernode() + node.accessoryItemNode = nil + node.accessoryHeaderItemNode?.removeFromSupernode() + node.accessoryHeaderItemNode = nil + } + + private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { + var index = -1 + let count = self.itemNodes.count + for itemNode in self.itemNodes { + index += 1 + + if let itemNodeIndex = itemNode.index { + if let accessoryItem = self.items[itemNodeIndex].accessoryItem { + let previousItem: ListViewItem? = itemNodeIndex == 0 ? nil : self.items[itemNodeIndex - 1] + let previousAccessoryItem = previousItem?.accessoryItem + + if (previousAccessoryItem == nil || !previousAccessoryItem!.isEqualToItem(accessoryItem)) { + if itemNode.accessoryItemNode == nil { + var didStealAccessoryNode = false + if index != count - 1 { + for i in index + 1 ..< count { + let nextItemNode = self.itemNodes[i] + if let nextItemNodeIndex = nextItemNode.index { + let nextItem = self.items[nextItemNodeIndex] + if let nextAccessoryItem = nextItem.accessoryItem where nextAccessoryItem.isEqualToItem(accessoryItem) { + if let nextAccessoryItemNode = nextItemNode.accessoryItemNode { + didStealAccessoryNode = true + + var previousAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin + let previousParentOrigin = nextItemNode.frame.origin + previousAccessoryItemNodeOrigin.x += previousParentOrigin.x + previousAccessoryItemNodeOrigin.y += previousParentOrigin.y + previousAccessoryItemNodeOrigin.y -= nextItemNode.bounds.origin.y + previousAccessoryItemNodeOrigin.y -= nextAccessoryItemNode.transitionOffset.y + nextAccessoryItemNode.transitionOffset = CGPoint() + + nextAccessoryItemNode.removeFromSupernode() + itemNode.addSubnode(nextAccessoryItemNode) + itemNode.accessoryItemNode = nextAccessoryItemNode + self.itemNodes[i].accessoryItemNode = nil + + var updatedAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin + let updatedParentOrigin = itemNode.frame.origin + updatedAccessoryItemNodeOrigin.x += updatedParentOrigin.x + updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y + updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y + + nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) + } + } else { + break + } + } + } + } + + if !didStealAccessoryNode { + let accessoryNode = accessoryItem.node() + itemNode.addSubnode(accessoryNode) + itemNode.accessoryItemNode = accessoryNode + } + } + } else { + itemNode.accessoryItemNode?.removeFromSupernode() + itemNode.accessoryItemNode = nil + } + } + } + } + } + + private func enqueueUpdateVisibleItems() { + if !self.enqueuedUpdateVisibleItems { + self.enqueuedUpdateVisibleItems = true + + self.transactionQueue.addTransaction({ [weak self] completion in + if let strongSelf = self { + strongSelf.transactionOffset = 0.0 + strongSelf.updateVisibleItemsTransaction({ + var repeatUpdate = false + if let strongSelf = self { + repeatUpdate = abs(strongSelf.transactionOffset) > 0.00001 + strongSelf.transactionOffset = 0.0 + strongSelf.enqueuedUpdateVisibleItems = false + } + + //dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { + completion() + + if repeatUpdate { + strongSelf.enqueueUpdateVisibleItems() + } + //}) + }) + } + }) + } + } + + private func updateVisibleItemsTransaction(completion: Void -> Void) { + var i = 0 + while i < self.itemNodes.count { + let node = self.itemNodes[i] + if node.index == nil && node.apparentHeight <= CGFloat(FLT_EPSILON) { + self.removeItemNodeAtIndex(i) + } else { + i += 1 + } + } + + self.fillMissingNodes(false, offsetTopInsertedItems: false, animatedInsertIndices: [], state: self.currentState(), previousNodes: [:], operations: []) { _, operations in + self.dispatchOnVSync { + self.replayOperations(false, operations: operations, completion: completion) + } + } + } + + private func removeInvisibleNodes() { + var i = 0 + var visibleItemNodeHeight: CGFloat = 0.0 + while i < self.itemNodes.count { + visibleItemNodeHeight += self.itemNodes[i].apparentBounds.height + i += 1 + } + + if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { + i = self.itemNodes.count - 1 + while i >= 0 { + let itemNode = self.itemNodes[i] + let apparentFrame = itemNode.apparentFrame + if apparentFrame.maxY < -self.invisibleInset || apparentFrame.origin.y > self.visibleSize.height + self.invisibleInset { + self.removeItemNodeAtIndex(i) + } + i -= 1 + } + } + } + + private func updateVisibleItemRange(force: Bool = false) { + let currentRange: ListViewVisibleRange? + if self.itemNodes.count != 0 { + var firstIndex: Int? + var lastIndex: Int? + var i = 0 + while i < self.itemNodes.count { + if let index = self.itemNodes[i].index { + firstIndex = index + break + } + i += 1 + } + i = self.itemNodes.count - 1 + while i >= 0 { + if let index = self.itemNodes[i].index { + lastIndex = index + break + } + i -= 1 + } + if let firstIndex = firstIndex, lastIndex = lastIndex { + currentRange = ListViewVisibleRange(firstIndex: firstIndex, lastIndex: lastIndex) + } else { + currentRange = nil + } + } else { + currentRange = nil + } + + if currentRange != self.visibleItemRange || force { + self.visibleItemRange = currentRange + self.visibleItemRangeChanged(currentRange) + } + } + + public func updateSizeAndInsets(size: CGSize, insets: UIEdgeInsets, duration: Double = 0.0, options: UIViewAnimationOptions = UIViewAnimationOptions()) { + self.transactionQueue.addTransaction({ [weak self] completion in + if let strongSelf = self { + strongSelf.transactionOffset = 0.0 + strongSelf.updateSizeAndInsetsTransaction(size, insets: insets, duration: duration, options: options, completion: { [weak self] in + if let strongSelf = self { + strongSelf.transactionOffset = 0.0 + strongSelf.updateVisibleItemsTransaction(completion) + } + }) + } + }) + + if useDynamicTuning { + self.frictionSlider.frame = CGRect(x: 10.0, y: size.height - insets.bottom - 10.0 - self.frictionSlider.bounds.height, width: size.width - 20.0, height: self.frictionSlider.bounds.height) + self.springSlider.frame = CGRect(x: 10.0, y: self.frictionSlider.frame.minY - self.springSlider.bounds.height, width: size.width - 20.0, height: self.springSlider.bounds.height) + self.freeResistanceSlider.frame = CGRect(x: 10.0, y: self.springSlider.frame.minY - self.freeResistanceSlider.bounds.height, width: size.width - 20.0, height: self.freeResistanceSlider.bounds.height) + self.scrollingResistanceSlider.frame = CGRect(x: 10.0, y: self.freeResistanceSlider.frame.minY - self.scrollingResistanceSlider.bounds.height, width: size.width - 20.0, height: self.scrollingResistanceSlider.bounds.height) + } + } + + private func updateSizeAndInsetsTransaction(size: CGSize, insets: UIEdgeInsets, duration: Double, options: UIViewAnimationOptions, completion: Void -> Void) { + if CGSizeEqualToSize(size, self.visibleSize) && UIEdgeInsetsEqualToEdgeInsets(self.insets, insets) { + completion() + } else { + if abs(size.width - self.visibleSize.width) > CGFloat(FLT_EPSILON) { + let itemNodes = self.itemNodes + for itemNode in itemNodes { + itemNode.removeAllAnimations() + itemNode.transitionOffset = 0.0 + if let index = itemNode.index { + itemNode.layoutForWidth(size.width, item: self.items[index], previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1]) + } + itemNode.apparentHeight = itemNode.bounds.height + } + + if itemNodes.count != 0 { + for i in 0 ..< itemNodes.count - 1 { + var nextFrame = itemNodes[i + 1].frame + nextFrame.origin.y = itemNodes[i].apparentFrame.maxY + itemNodes[i + 1].frame = nextFrame + } + } + } + + var offsetFix = insets.top - self.insets.top + + self.visibleSize = size + self.insets = insets + + var completeOffset = offsetFix + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) + } + + let completeDeltaHeight = offsetFix + offsetFix = 0.0 + + if Double(completeDeltaHeight) < DBL_EPSILON && self.itemNodes.count != 0 { + let firstItemNode = self.itemNodes[0] + let lastItemNode = self.itemNodes[self.itemNodes.count - 1] + + if lastItemNode.index == self.items.count - 1 { + if firstItemNode.index == 0 { + let topGap = firstItemNode.apparentFrame.origin.y - self.insets.top + let bottomGap = self.visibleSize.height - lastItemNode.apparentFrame.maxY - self.insets.bottom + if Double(bottomGap) > DBL_EPSILON { + offsetFix = -bottomGap + if topGap + bottomGap > 0.0 { + offsetFix = topGap + } + + let absOffsetFix = abs(offsetFix) + let absCompleteDeltaHeight = abs(completeDeltaHeight) + offsetFix = min(absOffsetFix, absCompleteDeltaHeight) * (offsetFix < 0 ? -1.0 : 1.0) + } + } else { + offsetFix = completeDeltaHeight + } + } + } + + if Double(abs(offsetFix)) > DBL_EPSILON { + completeOffset -= offsetFix + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y - offsetFix) + } + } + + self.snapToBounds() + + self.ignoreScrollingEvents = true + self.scroller.frame = CGRect(origin: CGPoint(), size: size) + self.scroller.contentSize = CGSizeMake(size.width, infiniteScrollSize * 2.0) + self.lastContentOffset = CGPointMake(0.0, infiniteScrollSize) + self.scroller.contentOffset = self.lastContentOffset + + self.updateScroller() + self.updateVisibleItemRange() + + let completion = { [weak self] (_: Bool) -> Void in + if let strongSelf = self { + strongSelf.updateVisibleItemsTransaction(completion) + strongSelf.ignoreScrollingEvents = false + } + } + + if duration > DBL_EPSILON { + let animation: CABasicAnimation + if (options.rawValue & UInt(7 << 16)) != 0 { + let springAnimation = CASpringAnimation(keyPath: "sublayerTransform") + springAnimation.mass = 3.0 + springAnimation.stiffness = 1000.0 + springAnimation.damping = 500.0 + springAnimation.initialVelocity = 0.0 + springAnimation.duration = duration * UIView.animationDurationFactor() + springAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) + springAnimation.removedOnCompletion = true + animation = springAnimation + } else { + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + basicAnimation.duration = duration * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + basicAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) + basicAnimation.removedOnCompletion = true + animation = basicAnimation + } + + animation.completion = completion + self.layer.addAnimation(animation, forKey: "sublayerTransform") + } else { + completion(true) + } + } + } + + private func updateAnimations() { + self.inVSync = true + let actionsForVSync = self.actionsForVSync + self.actionsForVSync.removeAll() + for action in actionsForVSync { + action() + } + self.inVSync = false + + let timestamp: Double = CACurrentMediaTime() + + var continueAnimations = false + + if !self.actionsForVSync.isEmpty { + continueAnimations = true + } + + var i = 0 + var animationCount = self.animations.count + while i < animationCount { + let animation = self.animations[i] + animation.applyAt(timestamp) + + if animation.completeAt(timestamp) { + animations.removeAtIndex(i) + animationCount -= 1 + i -= 1 + } else { + continueAnimations = true + } + + i += 1 + } + + var offsetRanges = OffsetRanges() + + var requestUpdateVisibleItems = false + var index = 0 + while index < self.itemNodes.count { + let itemNode = self.itemNodes[index] + + let previousApparentHeight = itemNode.apparentHeight + if itemNode.animate(timestamp) { + continueAnimations = true + } + let updatedApparentHeight = itemNode.apparentHeight + let apparentHeightDelta = updatedApparentHeight - previousApparentHeight + if abs(apparentHeightDelta) > CGFloat(FLT_EPSILON) { + if itemNode.apparentFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta) + } else { + offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta) + } + } + + if itemNode.index == nil && updatedApparentHeight <= CGFloat(FLT_EPSILON) { + requestUpdateVisibleItems = true + } + + index += 1 + } + + if !offsetRanges.offsets.isEmpty { + requestUpdateVisibleItems = true + var index = 0 + for itemNode in self.itemNodes { + let offset = offsetRanges.offsetForIndex(index) + if offset != 0.0 { + var position = itemNode.position + position.y += offset + itemNode.position = position + } + + index += 1 + } + + self.snapToBounds() + } + + self.debugCheckMonotonity() + + if !continueAnimations { + self.pauseAnimations() + } + + if requestUpdateVisibleItems { + self.updateVisibleNodes() + self.enqueueUpdateVisibleItems() + } + } + + override public func touchesBegan(touches: Set, withEvent event: UIEvent?) { + self.isTracking = true + self.touchesPosition = (touches.first!).locationInView(self.view) + self.selectionTouchLocation = self.touchesPosition + + self.selectionTouchDelayTimer?.invalidate() + let timer = NSTimer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in + if let strongSelf = self where strongSelf.selectionTouchLocation != nil { + strongSelf.clearHighlightAnimated(false) + let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) + + if let index = index { + if strongSelf.items[index].selectable { + strongSelf.highlightedItemIndex = index + for itemNode in strongSelf.itemNodes { + if itemNode.index == index { + if !itemNode.layerBacked { + strongSelf.view.bringSubviewToFront(itemNode.view) + } + itemNode.setHighlighted(true, animated: false) + break + } + } + } + } + } + }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) + self.selectionTouchDelayTimer = timer + NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes) + + super.touchesBegan(touches, withEvent: event) + + self.updateScroller() + } + + public func clearHighlightAnimated(animated: Bool) { + if let highlightedItemIndex = self.highlightedItemIndex { + for itemNode in self.itemNodes { + if itemNode.index == highlightedItemIndex { + itemNode.setHighlighted(false, animated: animated) + break + } + } + } + self.highlightedItemIndex = nil + } + + private func itemIndexAtPoint(point: CGPoint) -> Int? { + for itemNode in self.itemNodes { + if itemNode.apparentFrame.contains(point) { + return itemNode.index + } + } + return nil + } + + override public func touchesMoved(touches: Set, withEvent event: UIEvent?) { + self.touchesPosition = touches.first!.locationInView(self.view) + if let selectionTouchLocation = self.selectionTouchLocation { + let distance = CGPoint(x: selectionTouchLocation.x - self.touchesPosition.x, y: selectionTouchLocation.y - self.touchesPosition.y) + let maxMovementDistance: CGFloat = 4.0 + if distance.x * distance.x + distance.y * distance.y > maxMovementDistance * maxMovementDistance { + self.selectionTouchLocation = nil + self.selectionTouchDelayTimer?.invalidate() + self.selectionTouchDelayTimer = nil + self.clearHighlightAnimated(false) + } + } + + super.touchesMoved(touches, withEvent: event) + } + + override public func touchesEnded(touches: Set, withEvent event: UIEvent?) { + self.isTracking = false + + if let selectionTouchLocation = self.selectionTouchLocation { + let index = self.itemIndexAtPoint(selectionTouchLocation) + if index != self.highlightedItemIndex { + self.clearHighlightAnimated(false) + } + + if let index = index { + if self.items[index].selectable { + self.highlightedItemIndex = index + for itemNode in self.itemNodes { + if itemNode.index == index { + if !itemNode.layerBacked { + self.view.bringSubviewToFront(itemNode.view) + } + itemNode.setHighlighted(true, animated: false) + break + } + } + } + } + } + + if let highlightedItemIndex = self.highlightedItemIndex { + self.items[highlightedItemIndex].selected() + } + self.selectionTouchLocation = nil + + super.touchesEnded(touches, withEvent: event) + } + + override public func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { + self.isTracking = false + + self.selectionTouchLocation = nil + self.selectionTouchDelayTimer?.invalidate() + self.selectionTouchDelayTimer = nil + self.clearHighlightAnimated(false) + + super.touchesCancelled(touches, withEvent: event) + } +} diff --git a/Display/ListViewAccessoryItem.swift b/Display/ListViewAccessoryItem.swift new file mode 100644 index 0000000000..0a8837b83b --- /dev/null +++ b/Display/ListViewAccessoryItem.swift @@ -0,0 +1,6 @@ +import Foundation + +public protocol ListViewAccessoryItem { + func isEqualToItem(other: ListViewAccessoryItem) -> Bool + func node() -> ListViewAccessoryItemNode +} diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift new file mode 100644 index 0000000000..28f365ddd1 --- /dev/null +++ b/Display/ListViewAccessoryItemNode.swift @@ -0,0 +1,40 @@ +import Foundation +import AsyncDisplayKit + +public class ListViewAccessoryItemNode: ASDisplayNode { + var transitionOffset: CGPoint = CGPoint() { + didSet { + self.bounds = CGRect(origin: self.transitionOffset, size: self.bounds.size) + } + } + + private var transitionOffsetAnimation: ListViewAnimation? + + final func animateTransitionOffset(from: CGPoint, beginAt: Double, duration: Double, curve: CGFloat -> CGFloat) { + self.transitionOffset = from + self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] currentValue in + if let strongSelf = self { + strongSelf.transitionOffset = currentValue + } + }) + } + + final func removeAllAnimations() { + self.transitionOffsetAnimation = nil + self.transitionOffset = CGPoint() + } + + final func animate(timestamp: Double) -> Bool { + if let animation = self.transitionOffsetAnimation { + animation.applyAt(timestamp) + + if animation.completeAt(timestamp) { + self.transitionOffsetAnimation = nil + } else { + return true + } + } + + return false + } +} diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift new file mode 100644 index 0000000000..2eb56d288d --- /dev/null +++ b/Display/ListViewAnimation.swift @@ -0,0 +1,136 @@ +import Foundation +import Display + +public protocol Interpolatable { + static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> (Interpolatable) +} + +private func floorToPixels(value: CGFloat) -> CGFloat { + return round(value * 10.0) / 10.0 +} + +private func floorToPixels(value: CGPoint) -> CGPoint { + return CGPoint(x: round(value.x * 10.0) / 10.0, y: round(value.y * 10.0) / 10.0) +} + +private func floorToPixels(value: CGSize) -> CGSize { + return CGSize(width: round(value.width * 10.0) / 10.0, height: round(value.height * 10.0) / 10.0) +} + +private func floorToPixels(value: CGRect) -> CGRect { + return CGRect(origin: floorToPixels(value.origin), size: floorToPixels(value.size)) +} + +private func floorToPixels(value: UIEdgeInsets) -> UIEdgeInsets { + return UIEdgeInsets(top: round(value.top * 10.0) / 10.0, left: round(value.left * 10.0) / 10.0, bottom: round(value.bottom * 10.0) / 10.0, right: round(value.right * 10.0) / 10.0) +} + +extension CGFloat: Interpolatable { + public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable { + return { from, to, t -> Interpolatable in + return floorToPixels((to as! CGFloat) * t + (from as! CGFloat) * (1.0 - t)) + } + } +} + +extension UIEdgeInsets: Interpolatable { + public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable { + return { from, to, t -> Interpolatable in + let fromValue = from as! UIEdgeInsets + let toValue = to as! UIEdgeInsets + return floorToPixels(UIEdgeInsets(top: toValue.top * t + fromValue.top * (1.0 - t), left: toValue.left * t + fromValue.left * (1.0 - t), bottom: toValue.bottom * t + fromValue.bottom * (1.0 - t), right: toValue.right * t + fromValue.right * (1.0 - t))) + } + } +} + +extension CGPoint: Interpolatable { + public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable { + return { from, to, t -> Interpolatable in + let fromValue = from as! CGPoint + let toValue = to as! CGPoint + return floorToPixels(CGPoint(x: toValue.x * t + fromValue.x * (1.0 - t), y: toValue.y * t + fromValue.y * (1.0 - t))) + } + } +} + +private let springAnimationIn: CASpringAnimation = { + let animation = CASpringAnimation() + animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) + animation.duration = 0.6 + animation.damping = 500.0 + animation.stiffness = 1000.0 + animation.mass = 3.0 + return animation +}() + +public let listViewAnimationCurveSystem: CGFloat -> CGFloat = { t in + //return bezierPoint(0.23, 1.0, 0.32, 1.0, t) + return springAnimationIn.valueAt(t) +} + +public let listViewAnimationCurveLinear: CGFloat -> CGFloat = { t in + return t +} + +public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewAnimationOptions) -> CGFloat -> CGFloat { + if animationOptions.rawValue == UInt(7 << 16) { + return listViewAnimationCurveSystem + } else { + return listViewAnimationCurveLinear + } +} + +public final class ListViewAnimation { + let from: Interpolatable + let to: Interpolatable + let duration: Double + let startTime: Double + private let curve: CGFloat -> CGFloat + private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable + private let update: Interpolatable -> Void + private let completed: Bool -> Void + + public init(from: T, to: T, duration: Double, curve: CGFloat -> CGFloat, beginAt: Double, update: T -> Void, completed: Bool -> Void = { _ in }) { + self.from = from + self.to = to + self.duration = duration + self.curve = curve + self.startTime = beginAt + self.interpolator = T.interpolator() + self.update = { value in + update(value as! T) + } + self.completed = completed + } + + public func completeAt(timestamp: Double) -> Bool { + if timestamp >= self.startTime + self.duration { + self.completed(true) + return true + } else { + return false + } + } + + public func cancel() { + self.completed(false) + } + + private func valueAt(timestamp: Double) -> Interpolatable { + if timestamp < self.startTime { + return self.from + } + + let t = CGFloat((timestamp - self.startTime) / self.duration) + + if t >= 1.0 { + return self.to + } else { + return self.interpolator(self.from, self.to, self.curve(t)) + } + } + + public func applyAt(timestamp: Double) { + self.update(self.valueAt(timestamp)) + } +} diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift new file mode 100644 index 0000000000..f075b3b90d --- /dev/null +++ b/Display/ListViewItem.swift @@ -0,0 +1,34 @@ +import Foundation +import SwiftSignalKit + +public protocol ListViewItem { + func nodeConfiguredForWidth(width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNode, () -> Void) -> Void) + func updateNode(node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) + + var accessoryItem: ListViewAccessoryItem? { get } + var headerAccessoryItem: ListViewAccessoryItem? { get } + var selectable: Bool { get } + + func selected() +} + +public extension ListViewItem { + var accessoryItem: ListViewAccessoryItem? { + return nil + } + + var headerAccessoryItem: ListViewAccessoryItem? { + return nil + } + + var selectable: Bool { + return false + } + + func selected() { + } + + func updateNode(node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) { + completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {}) + } +} diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift new file mode 100644 index 0000000000..b8fe3a76e0 --- /dev/null +++ b/Display/ListViewItemNode.swift @@ -0,0 +1,390 @@ +import Foundation +import AsyncDisplayKit + +var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) +var testSpringFriction: CGFloat = 33.26 + +var testSpringConstantLimits: (CGFloat, CGFloat) = (3.0, 450.0) +var testSpringConstant: CGFloat = 450.0 + +var testSpringResistanceFreeLimits: (CGFloat, CGFloat) = (0.05, 1.0) +var testSpringFreeResistance: CGFloat = 1.0 + +var testSpringResistanceScrollingLimits: (CGFloat, CGFloat) = (0.1, 1.0) +var testSpringScrollingResistance: CGFloat = 0.4 + +struct ListViewItemSpring { + let stiffness: CGFloat + let damping: CGFloat + let mass: CGFloat + var velocity: CGFloat = 0.0 + + init(stiffness: CGFloat, damping: CGFloat, mass: CGFloat) { + self.stiffness = stiffness + self.damping = damping + self.mass = mass + } +} + +private class ListViewItemView: UIView { + override class func layerClass() -> AnyClass { + return ASTransformLayer.self + } +} + +public struct ListViewItemNodeLayout { + public let contentSize: CGSize + public let insets: UIEdgeInsets + + public init() { + self.contentSize = CGSize() + self.insets = UIEdgeInsets() + } + + public init(contentSize: CGSize, insets: UIEdgeInsets) { + self.contentSize = contentSize + self.insets = insets + } + + public var size: CGSize { + return CGSize(width: self.contentSize.width + self.insets.left + self.insets.right, height: self.contentSize.height + self.insets.top + self.insets.bottom) + } +} + +public class ListViewItemNode: ASDisplayNode { + final var index: Int? + + final var accessoryItemNode: ListViewAccessoryItemNode? { + didSet { + if let accessoryItemNode = self.accessoryItemNode { + self.layoutAccessoryItemNode(accessoryItemNode) + } + } + } + + final var accessoryHeaderItemNode: ListViewAccessoryItemNode? + + private final var spring: ListViewItemSpring? + private final var animations: [(String, ListViewAnimation)] = [] + + final let wantsScrollDynamics: Bool + + public final var insets: UIEdgeInsets = UIEdgeInsets() { + didSet { + let effectiveInsets = self.insets + self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: self.contentSize.width, height: self.contentSize.height + effectiveInsets.top + effectiveInsets.bottom)) + self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size) + + if oldValue != self.insets { + if let accessoryItemNode = self.accessoryItemNode { + self.layoutAccessoryItemNode(accessoryItemNode) + } + } + } + } + + private final var _contentSize: CGSize = CGSize() + public final var contentSize: CGSize { + get { + return self._contentSize + } set(value) { + let effectiveInsets = self.insets + self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: value.width, height: value.height + effectiveInsets.top + effectiveInsets.bottom)) + } + } + + private var contentOffset: CGFloat = 0.0 { + didSet { + let effectiveInsets = self.insets + self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size) + } + } + + public var transitionOffset: CGFloat = 0.0 { + didSet { + let effectiveInsets = self.insets + self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size) + } + } + + public var layout: ListViewItemNodeLayout { + var insets = self.insets + var contentSize = self.contentSize + + if let animation = self.animationForKey("insets") { + insets = animation.to as! UIEdgeInsets + } + + if let animation = self.animationForKey("apparentHeight") { + contentSize.height = (animation.to as! CGFloat) - insets.top - insets.bottom + } + + return ListViewItemNodeLayout(contentSize: contentSize, insets: insets) + } + + public init(layerBacked: Bool, dynamicBounce: Bool = true) { + if true { + if dynamicBounce { + self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) + } + self.wantsScrollDynamics = dynamicBounce + } else { + self.wantsScrollDynamics = false + } + + //super.init() + + //self.layerBacked = layerBacked + + if layerBacked { + super.init(layerBlock: { + return ASTransformLayer() + }, didLoadBlock: nil) + } else { + super.init(viewBlock: { + return ListViewItemView() + }, didLoadBlock: nil) + } + } + + var apparentHeight: CGFloat = 0.0 + private var _bounds: CGRect = CGRect() + private var _position: CGPoint = CGPoint() + + public override var frame: CGRect { + get { + return CGRect(origin: CGPoint(x: self._position.x - self._bounds.width / 2.0, y: self._position.y - self._bounds.height / 2.0), size: self._bounds.size) + } set(value) { + let previousSize = self._bounds.size + + super.frame = value + self._bounds.size = value.size + self._position = CGPoint(x: value.midX, y: value.midY) + let effectiveInsets = self.insets + self._contentSize = CGSize(width: value.size.width, height: value.size.height - effectiveInsets.top - effectiveInsets.bottom) + + if previousSize != value.size { + if let accessoryItemNode = self.accessoryItemNode { + self.layoutAccessoryItemNode(accessoryItemNode) + } + } + } + } + + public override var bounds: CGRect { + get { + return self._bounds + } set(value) { + let previousSize = self._bounds.size + + super.bounds = value + self._bounds = value + let effectiveInsets = self.insets + self._contentSize = CGSize(width: value.size.width, height: value.size.height - effectiveInsets.top - effectiveInsets.bottom) + + if previousSize != value.size { + if let accessoryItemNode = self.accessoryItemNode { + self.layoutAccessoryItemNode(accessoryItemNode) + } + } + } + } + + public override var position: CGPoint { + get { + return self._position + } set(value) { + super.position = value + self._position = value + } + } + + public final var apparentFrame: CGRect { + var frame = self.frame + frame.size.height = self.apparentHeight + return frame + } + + public final var apparentBounds: CGRect { + var bounds = self.bounds + bounds.size.height = self.apparentHeight + return bounds + } + + public func layoutAccessoryItemNode(accessoryItemNode: ListViewAccessoryItemNode) { + } + + public func reuse() { + } + + final func addScrollingOffset(scrollingOffset: CGFloat) { + if self.spring != nil { + self.contentOffset += scrollingOffset + } + } + + func initializeDynamicsFromSibling(itemView: ListViewItemNode, additionalOffset: CGFloat) { + if let itemViewSpring = itemView.spring { + self.contentOffset = itemView.contentOffset + additionalOffset + self.spring?.velocity = itemViewSpring.velocity + } + } + + public func animate(timestamp: Double) -> Bool { + var continueAnimations = false + + if let _ = self.spring { + var offset = self.contentOffset + + let frictionConstant: CGFloat = testSpringFriction + let springConstant: CGFloat = testSpringConstant + let time: CGFloat = 1.0 / 60.0 + + // friction force = velocity * friction constant + let frictionForce = self.spring!.velocity * frictionConstant + // spring force = (target point - current position) * spring constant + let springForce = -self.contentOffset * springConstant + // force = spring force - friction force + let force = springForce - frictionForce + + // velocity = current velocity + force * time / mass + self.spring!.velocity = self.spring!.velocity + force * time + // position = current position + velocity * time + offset = self.contentOffset + self.spring!.velocity * time + + offset = isnan(offset) ? 0.0 : offset + + let epsilon: CGFloat = 0.1 + if abs(offset) < epsilon && abs(self.spring!.velocity) < epsilon { + offset = 0.0 + self.spring!.velocity = 0.0 + } else { + continueAnimations = true + } + + if abs(offset) > 250.0 { + offset = offset < 0.0 ? -250.0 : 250.0 + } + self.contentOffset = offset + } + + var i = 0 + var animationCount = self.animations.count + while i < animationCount { + let (_, animation) = self.animations[i] + animation.applyAt(timestamp) + + if animation.completeAt(timestamp) { + animations.removeAtIndex(i) + animationCount -= 1 + i -= 1 + } else { + continueAnimations = true + } + + i += 1 + } + + if let accessoryItemNode = self.accessoryItemNode { + if (accessoryItemNode.animate(timestamp)) { + continueAnimations = true + } + } + + return continueAnimations + } + + public func layoutForWidth(width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + } + + public func animationForKey(key: String) -> ListViewAnimation? { + for (animationKey, animation) in self.animations { + if animationKey == key { + return animation + } + } + return nil + } + + public final func setAnimationForKey(key: String, animation: ListViewAnimation?) { + for i in 0 ..< self.animations.count { + let (currentKey, currentAnimation) = self.animations[i] + if currentKey == key { + self.animations.removeAtIndex(i) + currentAnimation.cancel() + break + } + } + if let animation = animation { + self.animations.append((key, animation)) + } + } + + public final func removeAllAnimations() { + let previousAnimations = self.animations + self.animations.removeAll() + + for (_, animation) in previousAnimations { + animation.cancel() + } + + self.accessoryItemNode?.removeAllAnimations() + } + + public func addInsetsAnimationToValue(value: UIEdgeInsets, duration: Double, beginAt: Double) { + let animation = ListViewAnimation(from: self.insets, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + if let strongSelf = self { + strongSelf.insets = currentValue + } + }) + self.setAnimationForKey("insets", animation: animation) + } + + public func addApparentHeightAnimation(value: CGFloat, duration: Double, beginAt: Double) { + let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + if let strongSelf = self { + strongSelf.apparentHeight = currentValue + } + }) + self.setAnimationForKey("apparentHeight", animation: animation) + } + + public func modifyApparentHeightAnimation(value: CGFloat, beginAt: Double) { + if let previousAnimation = self.animationForKey("apparentHeight") { + var duration = previousAnimation.startTime + previousAnimation.duration - beginAt + if abs(self.apparentHeight - value) < CGFloat(FLT_EPSILON) { + duration = 0.0 + } + + let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + if let strongSelf = self { + strongSelf.apparentHeight = currentValue + } + }) + + self.setAnimationForKey("apparentHeight", animation: animation) + } + } + + public func removeApparentHeightAnimation() { + self.setAnimationForKey("apparentHeight", animation: nil) + } + + public func addTransitionOffsetAnimation(value: CGFloat, duration: Double, beginAt: Double) { + let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + if let strongSelf = self { + strongSelf.transitionOffset = currentValue + } + }) + self.setAnimationForKey("transitionOffset", animation: animation) + } + + public func animateInsertion(currentTimestamp: Double, duration: Double) { + } + + public func setHighlighted(highlighted: Bool, animated: Bool) { + } + + public func setupGestures() { + } +} diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift new file mode 100644 index 0000000000..7a3e3376c7 --- /dev/null +++ b/Display/ListViewScroller.swift @@ -0,0 +1,7 @@ +import UIKit + +class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { + @objc func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } +} diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift new file mode 100644 index 0000000000..ed39a981cb --- /dev/null +++ b/Display/ListViewTransactionQueue.swift @@ -0,0 +1,56 @@ +import Foundation +import SwiftSignalKit + +public typealias ListViewTransaction = (Void -> Void) -> Void + +public final class ListViewTransactionQueue { + private var transactions: [ListViewTransaction] = [] + public final var transactionCompleted: Void -> Void = { } + + public init() { + } + + public func addTransaction(transaction: ListViewTransaction) { + let beginTransaction = self.transactions.count == 0 + self.transactions.append(transaction) + + if beginTransaction { + transaction({ [weak self] in + if NSThread.isMainThread() { + if let strongSelf = self { + strongSelf.endTransaction() + } + } else { + Queue.mainQueue().dispatch { + if let strongSelf = self { + strongSelf.endTransaction() + } + } + } + }) + } + } + + private func endTransaction() { + Queue.mainQueue().dispatch { + self.transactionCompleted() + let _ = self.transactions.removeFirst() + + if let nextTransaction = self.transactions.first { + nextTransaction({ [weak self] in + if NSThread.isMainThread() { + if let strongSelf = self { + strongSelf.endTransaction() + } + } else { + Queue.mainQueue().dispatch { + if let strongSelf = self { + strongSelf.endTransaction() + } + } + } + }) + } + } + } +} diff --git a/Display/Spring.swift b/Display/Spring.swift new file mode 100644 index 0000000000..8210a71fc6 --- /dev/null +++ b/Display/Spring.swift @@ -0,0 +1,66 @@ +import Foundation + +struct ViewportItemSpring { + let stiffness: CGFloat + let damping: CGFloat + let mass: CGFloat + var velocity: CGFloat = 0.0 + + init(stiffness: CGFloat, damping: CGFloat, mass: CGFloat) { + self.stiffness = stiffness + self.damping = damping + self.mass = mass + } +} + +private func a(a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 1.0 - 3.0 * a2 + 3.0 * a1 +} + +private func b(a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 3.0 * a2 - 6.0 * a1 +} + +private func c(a1: CGFloat) -> CGFloat +{ + return 3.0 * a1 +} + +private func calcBezier(t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return ((a(a1, a2)*t + b(a1, a2))*t + c(a1)) * t +} + +private func calcSlope(t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +{ + return 3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1) +} + +private func getTForX(x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat { + var t = x + var i = 0 + while i < 4 { + let currentSlope = calcSlope(t, x1, x2) + if currentSlope == 0.0 { + return t + } else { + let currentX = calcBezier(t, x1, x2) - x + t -= currentX / currentSlope + } + + i += 1 + } + + return t +} + +func bezierPoint(x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat +{ + var value = calcBezier(getTForX(x, x1, x2), y1, y2) + if value >= 0.997 { + value = 1.0 + } + return value +} From c3d1e3cfd3d04e017fd3f6f768eac7af74b0f093 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 5 Jun 2016 00:30:05 +0300 Subject: [PATCH 012/245] no message --- Display.xcodeproj/project.pbxproj | 10 +- Display/CAAnimationUtils.swift | 4 +- Display/CALayer+ImplicitAnimations.h | 2 + Display/CALayer+ImplicitAnimations.m | 21 + Display/GenerateImage.swift | 7 +- Display/ListView.swift | 1481 +++++++++++++++++++++----- Display/ListViewItem.swift | 6 +- Display/ListViewItemNode.swift | 5 + Display/ListViewScroller.swift | 10 + Display/ScrollToTopProxyView.swift | 32 + Display/TabBarController.swift | 3 +- Display/UIKitUtils.swift | 4 +- Display/ViewController.swift | 66 +- 13 files changed, 1380 insertions(+), 271 deletions(-) create mode 100644 Display/ScrollToTopProxyView.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 50ec9e07c2..edd93cb61c 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -80,6 +80,8 @@ D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */; }; D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */; }; D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */; }; + D0C2DFFC1CC528B70044FF83 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */; }; + D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; @@ -174,11 +176,13 @@ D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAccessoryItem.swift; sourceTree = ""; }; D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewScroller.swift; sourceTree = ""; }; D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAccessoryItemNode.swift; sourceTree = ""; }; + D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; - D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AsyncDisplayKit.framework; path = "../../../../Library/Developer/Xcode/DerivedData/Telegram-iOS-diblohvjozhgaifjcniwdlixlilx/Build/Products/Debug-iphonesimulator/AsyncDisplayKit.framework"; sourceTree = ""; }; + D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -187,6 +191,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D0C2DFFC1CC528B70044FF83 /* SwiftSignalKit.framework in Frameworks */, D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -214,6 +219,7 @@ D02BDAEC1B6A7053008AFAD2 /* Nodes */ = { isa = PBXGroup; children = ( + D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */, ); name = Nodes; sourceTree = ""; @@ -278,6 +284,7 @@ D05CC2A31B6932D500E235A3 /* Frameworks */ = { isa = PBXGroup; children = ( + D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */, D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */, D0E1D6351CBC159C00B04029 /* AVFoundation.framework */, ); @@ -582,6 +589,7 @@ D0C2DFCC1CC4431D0044FF83 /* AsyncLayoutable.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, + D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index f8aabe340d..0734a055a9 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -92,8 +92,8 @@ public extension CALayer { self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) } - internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval) { - self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) + internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { + self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: completion) } public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { diff --git a/Display/CALayer+ImplicitAnimations.h b/Display/CALayer+ImplicitAnimations.h index 541efc951b..0dc07cdb29 100644 --- a/Display/CALayer+ImplicitAnimations.h +++ b/Display/CALayer+ImplicitAnimations.h @@ -5,6 +5,8 @@ + (void)beginRecordingChanges; + (NSArray *)endRecordingChanges; ++ (void)overrideAnimationSpeed:(CGFloat)speed block:(void (^)())block; + @end @interface CALayerAnimation : NSObject diff --git a/Display/CALayer+ImplicitAnimations.m b/Display/CALayer+ImplicitAnimations.m index ca7ec863e2..42c6658de6 100644 --- a/Display/CALayer+ImplicitAnimations.m +++ b/Display/CALayer+ImplicitAnimations.m @@ -102,6 +102,22 @@ static NSMutableArray *currentLayerAnimations() [self _ca836a62_setPosition:position]; } +- (void)_ca836a62_addAnimation:(CAAnimation *)animation forKey:(NSString *)key { + if (speedOverride != 1.0f) { + animation.speed *= speedOverride; + } + [self _ca836a62_addAnimation:animation forKey:key]; +} + +static CGFloat speedOverride = 1.0f; + ++ (void)overrideAnimationSpeed:(CGFloat)speed block:(void (^)())block { + CGFloat previousOverride = speedOverride; + speedOverride = speed; + block(); + speedOverride = previousOverride; +} + @end @interface LayerAnimationExtensions : NSObject @@ -117,6 +133,7 @@ static NSMutableArray *currentLayerAnimations() { //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setBounds:) newSelector:@selector(_ca836a62_setBounds:)]; //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setPosition:) newSelector:@selector(_ca836a62_setPosition:)]; + //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(addAnimation:forKey:) newSelector:@selector(_ca836a62_addAnimation:forKey:)]; }); } @@ -139,4 +156,8 @@ static NSMutableArray *currentLayerAnimations() return array; } ++ (void)overrideAnimationSpeed:(CGFloat)speed block:(void (^)())block { + +} + @end diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index feae2911e6..410c5bc387 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -25,7 +25,7 @@ public func generateImage(size: CGSize, generator: (CGSize, UnsafeMutablePointer return UIImage(CGImage: image, scale: scale, orientation: .Up) } -public func generateImage(size: CGSize, generator: (CGSize, CGContextRef) -> Void) -> UIImage? { +public func generateImage(size: CGSize, opaque: Bool = false, generator: (CGSize, CGContextRef) -> Void) -> UIImage? { let scale = deviceScale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) @@ -35,7 +35,7 @@ public func generateImage(size: CGSize, generator: (CGSize, CGContextRef) -> Voi free(bytes) }) - let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.NoneSkipFirst.rawValue : CGImageAlphaInfo.PremultipliedFirst.rawValue)) guard let context = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, bitmapInfo.rawValue) else { @@ -135,7 +135,8 @@ public class DrawingContext { if x >= 0 && x < Int(self.scaledSize.width) && y >= 0 && y < Int(self.scaledSize.height) { let srcLine = UnsafeMutablePointer(self.bytes + y * self.bytesPerRow) let pixel = srcLine + x - return UIColor(Int(pixel.memory)) + let colorValue = pixel.memory + return UIColor(UInt32(colorValue)) } else { return UIColor.clearColor() } diff --git a/Display/ListView.swift b/Display/ListView.swift index 05370babc6..1f71c9a0a1 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -5,10 +5,96 @@ import SwiftSignalKit private let usePerformanceTracker = false private let useDynamicTuning = false -public enum ListViewScrollPosition { +public enum ListViewCenterScrollPositionOverflow { case Top case Bottom - case Center +} + +public enum ListViewScrollPosition: Equatable { + case Top + case Bottom + case Center(ListViewCenterScrollPositionOverflow) +} + +public func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { + switch lhs { + case .Top: + switch rhs { + case .Top: + return true + default: + return false + } + case .Bottom: + switch rhs { + case .Bottom: + return true + default: + return false + } + case let .Center(lhsOverflow): + switch rhs { + case let .Center(rhsOverflow) where lhsOverflow == rhsOverflow: + return true + default: + return false + } + } +} + +public enum ListViewScrollToItemDirectionHint { + case Up + case Down +} + +public enum ListViewAnimationCurve { + case Spring(speed: CGFloat) + case Default +} + +public struct ListViewScrollToItem { + public let index: Int + public let position: ListViewScrollPosition + public let animated: Bool + public let curve: ListViewAnimationCurve + public let directionHint: ListViewScrollToItemDirectionHint + + public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) { + self.index = index + self.position = position + self.animated = animated + self.curve = curve + self.directionHint = directionHint + } +} + +public enum ListViewItemOperationDirectionHint { + case Up + case Down +} + +public struct ListViewDeleteItem { + public let index: Int + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.directionHint = directionHint + } +} + +public struct ListViewInsertItem { + public let index: Int + public let previousIndex: Int? + public let item: ListViewItem + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.previousIndex = previousIndex + self.item = item + self.directionHint = directionHint + } } public struct ListViewDeleteAndInsertOptions: OptionSetType { @@ -20,17 +106,42 @@ public struct ListViewDeleteAndInsertOptions: OptionSetType { public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1) public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) + public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) + public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) } -public struct ListViewVisibleRange: Equatable { +public struct ListViewUpdateSizeAndInsets { + public let size: CGSize + public let insets: UIEdgeInsets + public let duration: Double + public let curve: ListViewAnimationCurve + + public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve) { + self.size = size + self.insets = insets + self.duration = duration + self.curve = curve + } +} + +public struct ListViewItemRange: Equatable { public let firstIndex: Int public let lastIndex: Int } -public func ==(lhs: ListViewVisibleRange, rhs: ListViewVisibleRange) -> Bool { +public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex } +public struct ListViewDisplayedItemRange: Equatable { + public let loadedRange: ListViewItemRange? + public let visibleRange: ListViewItemRange? +} + +public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { + return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange +} + private struct IndexRange { let first: Int let last: Int @@ -138,17 +249,392 @@ private enum ListViewStateNode { private enum ListViewInsertionOffsetDirection { case Up case Down + + init(_ hint: ListViewItemOperationDirectionHint) { + switch hint { + case .Up: + self = .Up + case .Down: + self = .Down + } + } + + func inverted() -> ListViewInsertionOffsetDirection { + switch self { + case .Up: + return .Down + case .Down: + return .Up + } + } +} + +private struct ListViewInsertionPoint { + let index: Int + let point: CGPoint + let direction: ListViewInsertionOffsetDirection } private struct ListViewState { - let insets: UIEdgeInsets - let visibleSize: CGSize + var insets: UIEdgeInsets + var visibleSize: CGSize let invisibleInset: CGFloat var nodes: [ListViewStateNode] + var scrollPosition: (Int, ListViewScrollPosition)? + var stationaryOffset: (Int, CGFloat)? + + mutating func fixScrollPostition(itemCount: Int) { + if let (fixedIndex, fixedPosition) = self.scrollPosition { + for node in self.nodes { + if let index = node.index where index == fixedIndex { + let offset: CGFloat + switch fixedPosition { + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + case .Top: + offset = self.insets.top - node.frame.minY + case let .Center(overflow): + let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top + if node.frame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { + offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY + } else { + switch overflow { + case .Top: + offset = self.insets.top - node.frame.minY + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + } + } + } + + var minY: CGFloat = CGFloat.max + var maxY: CGFloat = 0.0 + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.offsetInPlace(dx: 0.0, dy: offset) + self.nodes[i].frame = frame + + minY = min(minY, frame.minY) + maxY = max(maxY, frame.maxY) + } + + var additionalOffset: CGFloat = 0.0 + if minY > self.insets.top { + additionalOffset = self.insets.top - minY + } + + if abs(additionalOffset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.offsetInPlace(dx: 0.0, dy: additionalOffset) + self.nodes[i].frame = frame + } + } + + self.snapToBounds(itemCount, snapTopItem: true) + + break + } + } + } else if let (stationaryIndex, stationaryOffset) = self.stationaryOffset { + for node in self.nodes { + if node.index == stationaryIndex { + let offset = stationaryOffset - node.frame.minY + + if abs(offset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.offsetInPlace(dx: 0.0, dy: offset) + self.nodes[i].frame = frame + } + } + + break + } + } + + //self.snapToBounds(itemCount, snapTopItem: true) + } + } + + mutating func setupStationaryOffset(index: Int, boundary: Int, frames: [Int: CGRect]) { + if index < boundary { + for node in self.nodes { + if let nodeIndex = node.index where nodeIndex >= index { + if let frame = frames[nodeIndex] { + self.stationaryOffset = (nodeIndex, frame.minY) + break + } + } + } + } else { + for node in self.nodes.reverse() { + if let nodeIndex = node.index where nodeIndex <= index { + if let frame = frames[nodeIndex] { + self.stationaryOffset = (nodeIndex, frame.minY) + break + } + } + } + } + } + + mutating func snapToBounds(itemCount: Int, snapTopItem: Bool) { + var completeHeight: CGFloat = 0.0 + var topItemFound = false + var bottomItemFound = false + var topItemEdge: CGFloat = 0.0 + var bottomItemEdge: CGFloat = 0.0 + + for node in self.nodes { + if let index = node.index { + if index == 0 { + topItemFound = true + topItemEdge = node.frame.minY + } + break + } + } + + for node in self.nodes.reverse() { + if let index = node.index { + if index == itemCount - 1 { + bottomItemFound = true + bottomItemEdge = node.frame.maxY + } + break + } + } + + if topItemFound && bottomItemFound { + for node in self.nodes { + completeHeight += node.frame.size.height + } + } + + let overscroll: CGFloat = 0.0 + + var offset: CGFloat = 0.0 + if topItemFound && bottomItemFound { + let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) + if bottomItemEdge < self.insets.top + areaHeight - overscroll { + offset = self.insets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge + } + } else if topItemFound { + if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge + } + } else if bottomItemFound { + if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { + offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge + } + } + + if abs(offset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y += offset + self.nodes[i].frame = frame + } + } + } + + func insertionPoint(insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { + var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)? + + if let (fixedIndex, _) = self.scrollPosition { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index where index == fixedIndex { + fixedNode = (i, index, node.frame) + break + } + } + + if fixedNode == nil { + return ListViewInsertionPoint(index: fixedIndex, point: CGPoint(), direction: .Down) + } + } + + if fixedNode == nil { + if let (fixedIndex, _) = self.stationaryOffset { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index where index == fixedIndex { + fixedNode = (i, index, node.frame) + break + } + } + } + } + + if fixedNode == nil { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index where node.frame.maxY >= self.insets.top { + fixedNode = (i, index, node.frame) + break + } + } + } + + if fixedNode == nil && self.nodes.count != 0 { + for i in (0 ..< self.nodes.count).reverse() { + let node = self.nodes[i] + if let index = node.index { + fixedNode = (i, index, node.frame) + break + } + } + } + + if let fixedNode = fixedNode { + var currentUpperNode = fixedNode + for i in (0 ..< fixedNode.nodeIndex).reverse() { + let node = self.nodes[i] + if let index = node.index { + if index != currentUpperNode.index - 1 { + if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentUpperNode.index - 1] where currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) + } else { + break + } + } + currentUpperNode = (i, index, node.frame) + } + } + + if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentUpperNode.index - 1] where currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + + return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) + } + + var currentLowerNode = fixedNode + if fixedNode.nodeIndex + 1 < self.nodes.count { + for i in (fixedNode.nodeIndex + 1) ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index { + if index != currentLowerNode.index + 1 { + if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentLowerNode.index + 1] where currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) + } else { + break + } + } + currentLowerNode = (i, index, node.frame) + } + } + } + + if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentLowerNode.index + 1] where currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) + } + } else if itemCount != 0 { + return ListViewInsertionPoint(index: 0, point: CGPoint(x: 0.0, y: self.insets.top), direction: .Down) + } + + return nil + } + + mutating func removeInvisibleNodes(inout operations: [ListViewStateOperation]) { + var i = 0 + var visibleItemNodeHeight: CGFloat = 0.0 + while i < self.nodes.count { + visibleItemNodeHeight += self.nodes[i].frame.height + i += 1 + } + + if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { + i = self.nodes.count - 1 + while i >= 0 { + let itemNode = self.nodes[i] + let frame = itemNode.frame + if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { + //print("remove \(i)") + operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) + self.nodes.removeAtIndex(i) + } + + i -= 1 + } + } + + let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index where node.frame.maxY > upperBound { + if i != 0 { + var previousIndex = index + for j in (0 ..< i).reverse() { + if self.nodes[j].frame.maxY < upperBound { + if let index = self.nodes[j].index { + if index != previousIndex - 1 { + print("remove monotonity \(j) (\(index))") + operations.append(.Remove(index: j, offsetDirection: .Down)) + self.nodes.removeAtIndex(j) + } else { + previousIndex = index + } + } + } + } + } + break + } + } + + let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) + for i in (0 ..< self.nodes.count).reverse() { + let node = self.nodes[i] + if let index = node.index where node.frame.minY < lowerBound { + if i != self.nodes.count - 1 { + var previousIndex = index + var removeIndices: [Int] = [] + for j in (i + 1) ..< self.nodes.count { + if self.nodes[j].frame.minY > lowerBound { + if let index = self.nodes[j].index { + if index != previousIndex + 1 { + removeIndices.append(j) + } else { + previousIndex = index + } + } + } + } + if !removeIndices.isEmpty { + for i in removeIndices.reverse() { + print("remove monotonity \(i) (\(self.nodes[i].index!))") + operations.append(.Remove(index: i, offsetDirection: .Up)) + self.nodes.removeAtIndex(i) + } + } + } + break + } + } + } func nodeInsertionPointAndIndex(itemIndex: Int) -> (CGPoint, Int) { if self.nodes.count == 0 { - return (CGPoint(x: 0.0, y: self.insets.top), 0) + return (CGPoint(x: 0.0, y: self.insets.top), 0) } else { var index = 0 var lastNodeWithIndex = -1 @@ -166,7 +652,43 @@ private struct ListViewState { } } - mutating func insertNode(itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, inout operations: [ListViewStateOperation]) { + func continuousHeightRelativeToNodeIndex(fixedNodeIndex: Int) -> CGFloat { + let fixedIndex = self.nodes[fixedNodeIndex].index! + + var height: CGFloat = 0.0 + + if fixedNodeIndex != 0 { + var upperIndex = fixedIndex + for i in (0 ..< fixedNodeIndex).reverse() { + if let index = self.nodes[i].index { + if index == upperIndex - 1 { + height += self.nodes[i].frame.size.height + upperIndex = index + } else { + break + } + } + } + } + + if fixedNodeIndex != self.nodes.count - 1 { + var lowerIndex = fixedIndex + for i in (fixedNodeIndex + 1) ..< self.nodes.count { + if let index = self.nodes[i].index { + if index == lowerIndex + 1 { + height += self.nodes[i].frame.size.height + lowerIndex = index + } else { + break + } + } + } + } + + return height + } + + mutating func insertNode(itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, inout operations: [ListViewStateOperation], itemCount: Int) { let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) let nodeOrigin: CGPoint @@ -180,7 +702,7 @@ private struct ListViewState { let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) - self.nodes.insert(.Node(index: node.index!, frame: nodeFrame, referenceNode: nil), atIndex: insertionIndex) + self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), atIndex: insertionIndex) if !animated { switch offsetDirection { @@ -202,23 +724,53 @@ private struct ListViewState { } } } + + if let _ = self.scrollPosition { + self.fixScrollPostition(itemCount) + } } - mutating func removeNodeAtIndex(index: Int, animated: Bool, inout operations: [ListViewStateOperation]) { + mutating func removeNodeAtIndex(index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, inout operations: [ListViewStateOperation]) { let node = self.nodes[index] if case let .Node(_, _, referenceNode) = node { let nodeFrame = node.frame self.nodes.removeAtIndex(index) - operations.append(.Remove(index: index)) + let offsetDirection: ListViewInsertionOffsetDirection + if let direction = direction { + offsetDirection = ListViewInsertionOffsetDirection(direction) + } else { + if nodeFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetDirection = .Down + } else { + offsetDirection = .Up + } + } + operations.append(.Remove(index: index, offsetDirection: offsetDirection)) if let referenceNode = referenceNode where animated { self.nodes.insert(.Placeholder(frame: nodeFrame), atIndex: index) - operations.append(.InsertPlaceholder(index: index, referenceNode: referenceNode)) + operations.append(.InsertPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) } else { - for i in index ..< self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y -= nodeFrame.size.height - self.nodes[i].frame = frame + if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { + if let direction = direction where direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + for i in (0 ..< index).reverse() { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + } + } else { + for i in index ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + } + } + } else if index != 0 { + for i in (0 ..< index).reverse() { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + } } } } else { @@ -229,8 +781,8 @@ private struct ListViewState { private enum ListViewStateOperation { case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) - case InsertPlaceholder(index: Int, referenceNode: ListViewItemNode) - case Remove(index: Int) + case InsertPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) + case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) case Remap([Int: Int]) case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) } @@ -305,7 +857,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { didSet { if self.preloadPages != oldValue { self.invisibleInset = self.preloadPages ? 500.0 : 20.0 - self.enqueueUpdateVisibleItems() + //self.invisibleInset = self.preloadPages ? 20.0 : 20.0 + if self.preloadPages { + self.enqueueUpdateVisibleItems() + } } } } @@ -326,8 +881,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { private final var items: [ListViewItem] = [] private final var itemNodes: [ListViewItemNode] = [] - public final var visibleItemRangeChanged: ListViewVisibleRange? -> Void = { _ in } - public final var visibleItemRange: ListViewVisibleRange? + public final var displayedItemRangeChanged: ListViewDisplayedItemRange -> Void = { _ in } + public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) + + public final var visibleContentOffsetChanged: (CGFloat?) -> Void = { _ in } private final var animations: [ListViewAnimation] = [] private final var actionsForVSync: [() -> ()] = [] @@ -511,8 +1068,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return } - CATransaction.begin() - CATransaction.setDisableActions(true) + //CATransaction.begin() + //CATransaction.setDisableActions(true) let deltaY = scrollView.contentOffset.y - self.lastContentOffset.y @@ -567,12 +1124,13 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.setNeedsAnimations() } - self.updateVisibleNodes() + self.updateVisibleContentOffset() + self.updateVisibleItemRange() - CATransaction.commit() + //CATransaction.commit() } - private func snapToBounds() { + private func snapToBounds(snapTopItem: Bool = false) { if self.itemNodes.count == 0 { return } @@ -611,12 +1169,12 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) if bottomItemEdge < self.insets.top + areaHeight - overscroll { offset = self.insets.top + areaHeight - overscroll - bottomItemEdge - } else if topItemEdge > self.insets.top - overscroll { - //offset = topItemEdge - (self.insets.top - overscroll) + } else if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge } } else if topItemFound { - if topItemEdge > self.insets.top - overscroll { - //offset = topItemEdge - (self.insets.top - overscroll) + if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge } } else if bottomItemFound { if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { @@ -626,13 +1184,31 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if abs(offset) > CGFloat(FLT_EPSILON) { for itemNode in self.itemNodes { - var position = itemNode.position - position.y += offset - itemNode.position = position + var frame = itemNode.frame + frame.origin.y += offset + itemNode.frame = frame } + + self.updateVisibleContentOffset() } } + private func updateVisibleContentOffset() { + var offset: CGFloat? + if let itemNode = self.itemNodes.first, index = itemNode.index where index == 0 { + offset = -(itemNode.apparentFrame.minY - self.insets.top) + } + + self.visibleContentOffsetChanged(offset) + } + + private func stopScrolling() { + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents + self.ignoreScrollingEvents = true + self.scroller.setContentOffset(self.scroller.contentOffset, animated: false) + self.ignoreScrollingEvents = wasIgnoringScrollingEvents + } + private func updateScroller() { if itemNodes.count == 0 { return @@ -687,19 +1263,51 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.ignoreScrollingEvents = false } - private func nodeForItem(item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { + private func async(f: () -> Void) { + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { + f() + }) + } + + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { if let previousNode = previousNode { - item.updateNode(previousNode, width: width, previousItem: previousItem, nextItem: nextItem, completion: { (layout, apply) in - previousNode.index = index - completion(previousNode, layout, apply) + item.updateNode({ f in + if synchronous { + f() + } else { + self.async(f) + } + }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, completion: { (layout, apply) in + if NSThread.isMainThread() { + if synchronous { + completion(previousNode, layout, { + previousNode.index = index + apply() + }) + } else { + self.async { + completion(previousNode, layout, { + previousNode.index = index + apply() + }) + } + } + } else { + completion(previousNode, layout, { + previousNode.index = index + apply() + }) + } }) } else { - let startTime = CACurrentMediaTime() - item.nodeConfiguredForWidth(width, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in - itemNode.index = index - if self.debugInfo { - print("[ListView] nodeConfiguredForWidth \((CACurrentMediaTime() - startTime) * 1000.0) ms") + item.nodeConfiguredForWidth({ f in + if synchronous { + f() + } else { + self.async(f) } + }, width: width, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in + itemNode.index = index completion(itemNode, ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) }) } @@ -715,20 +1323,20 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { nodes.append(.Placeholder(frame: node.apparentFrame)) } } - return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes) + return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) } - public func deleteAndInsertItems(deleteIndices: [Int], insertIndicesAndItems: [(Int, ListViewItem, Int?)], offsetTopInsertedItems: Bool, options: ListViewDeleteAndInsertOptions, completion: Void -> Void = {}) { - if deleteIndices.count == 0 && insertIndicesAndItems.count == 0 { - completion() + public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: ListViewDisplayedItemRange -> Void = { _ in }) { + if deleteIndices.count == 0 && insertIndicesAndItems.count == 0 && scrollToItem == nil && updateSizeAndInsets == nil { + completion(self.immediateDisplayedItemRange()) return } self.transactionQueue.addTransaction({ [weak self] transactionCompletion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.deleteAndInsertItemsTransaction(deleteIndices, insertIndicesAndItems: insertIndicesAndItems, offsetTopInsertedItems: offsetTopInsertedItems, options: options, completion: { - completion() + strongSelf.deleteAndInsertItemsTransaction(deleteIndices, insertIndicesAndItems: insertIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, completion: { [weak strongSelf] in + completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)) transactionCompletion() }) @@ -736,50 +1344,110 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [Int], insertIndicesAndItems: [(Int, ListViewItem, Int?)], offsetTopInsertedItems: Bool, options: ListViewDeleteAndInsertOptions, completion: Void -> Void) { - var state = self.currentState() - - let sortedDeleteIndices = deleteIndices.sort() - for index in sortedDeleteIndices.reverse() { - self.items.removeAtIndex(index) + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: Void -> Void) { + if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && scrollToItem == nil { + if let updateSizeAndInsets = updateSizeAndInsets where self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { + self.visibleSize = updateSizeAndInsets.size + self.insets = updateSizeAndInsets.insets + + self.ignoreScrollingEvents = true + self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size) + self.scroller.contentSize = CGSizeMake(updateSizeAndInsets.size.width, infiniteScrollSize * 2.0) + self.lastContentOffset = CGPointMake(0.0, infiniteScrollSize) + self.scroller.contentOffset = self.lastContentOffset + + self.updateScroller() + + completion() + return + } } - let sortedIndicesAndItems = insertIndicesAndItems.sort { $0.0 < $1.0 } + let startTime = CACurrentMediaTime() + var state = self.currentState() + + let widthUpdated: Bool + if let updateSizeAndInsets = updateSizeAndInsets { + widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat(FLT_EPSILON) + + state.visibleSize = updateSizeAndInsets.size + state.insets = updateSizeAndInsets.insets + } else { + widthUpdated = false + } + + if let scrollToItem = scrollToItem { + state.scrollPosition = (scrollToItem.index, scrollToItem.position) + } + state.fixScrollPostition(self.items.count) + + let sortedDeleteIndices = deleteIndices.sort({$0.index < $1.index}) + for deleteItem in sortedDeleteIndices.reverse() { + self.items.removeAtIndex(deleteItem.index) + } + + let sortedIndicesAndItems = insertIndicesAndItems.sort { $0.index < $1.index } if self.items.count == 0 { - if sortedIndicesAndItems[0].0 != 0 { + if sortedIndicesAndItems[0].index != 0 { fatalError("deleteAndInsertItems: invalid insert into empty list") } } + if self.debugInfo { + //print("deleteAndInsertItemsTransaction deleteIndices: \(deleteIndices.map({$0.index})) insertIndicesAndItems: \(insertIndicesAndItems.map({"\($0.index) <- \($0.previousIndex)"}))") + } + + /*if scrollToItem != nil { + print("Current indices:") + for itemNode in self.itemNodes { + print(" \(itemNode.index)") + } + }*/ + var previousNodes: [Int: ListViewItemNode] = [:] - for (index, item, previousIndex) in sortedIndicesAndItems { - self.items.insert(item, atIndex: index) - if let previousIndex = previousIndex { + for insertedItem in sortedIndicesAndItems { + self.items.insert(insertedItem.item, atIndex: insertedItem.index) + if let previousIndex = insertedItem.previousIndex { for itemNode in self.itemNodes { if itemNode.index == previousIndex { - previousNodes[index] = itemNode + previousNodes[insertedItem.index] = itemNode } } } } - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { + let actions = { + var previousFrames: [Int: CGRect] = [:] + for i in 0 ..< state.nodes.count { + if let index = state.nodes[i].index { + previousFrames[index] = state.nodes[i].frame + } + } + var operations: [ListViewStateOperation] = [] - let deleteIndexSet = Set(deleteIndices) + var deleteDirectionHints: [Int: ListViewItemOperationDirectionHint] = [:] + var insertDirectionHints: [Int: ListViewItemOperationDirectionHint] = [:] + + var deleteIndexSet = Set() + for deleteItem in deleteIndices { + deleteIndexSet.insert(deleteItem.index) + if let directionHint = deleteItem.directionHint { + deleteDirectionHints[deleteItem.index] = directionHint + } + } + var insertedIndexSet = Set() - var moveMapping: [Int: Int] = [:] - for (index, _, previousIndex) in sortedIndicesAndItems { - insertedIndexSet.insert(index) - if let previousIndex = previousIndex { - moveMapping[previousIndex] = index + for insertedItem in sortedIndicesAndItems { + insertedIndexSet.insert(insertedItem.index) + if let directionHint = insertedItem.directionHint { + insertDirectionHints[insertedItem.index] = directionHint } } let animated = options.contains(.AnimateInsertion) var remapDeletion: [Int: Int] = [:] - var updateAdjacentItemsIndices = Set() var i = 0 @@ -787,7 +1455,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if let index = state.nodes[i].index { var indexOffset = 0 for deleteIndex in sortedDeleteIndices { - if deleteIndex < index { + if deleteIndex.index < index { indexOffset += 1 } else { break @@ -795,10 +1463,17 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if deleteIndexSet.contains(index) { - state.removeNodeAtIndex(i, animated: animated, operations: &operations) + previousFrames.removeValueForKey(index) + state.removeNodeAtIndex(i, direction: deleteDirectionHints[index], animated: animated, operations: &operations) } else { let updatedIndex = index - indexOffset - remapDeletion[index] = updatedIndex + if index != updatedIndex { + remapDeletion[index] = updatedIndex + } + if let previousFrame = previousFrames[index] { + previousFrames.removeValueForKey(index) + previousFrames[updatedIndex] = previousFrame + } if deleteIndexSet.contains(index - 1) || deleteIndexSet.contains(index + 1) { updateAdjacentItemsIndices.insert(updatedIndex) } @@ -817,6 +1492,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if !remapDeletion.isEmpty { + if self.debugInfo { + //print("remapDeletion \(remapDeletion)") + } operations.append(.Remap(remapDeletion)) } @@ -825,14 +1503,20 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { for i in 0 ..< state.nodes.count { if let index = state.nodes[i].index { var indexOffset = 0 - for (insertIndex, _, _) in sortedIndicesAndItems { - if insertIndex <= index + indexOffset { + for insertedItem in sortedIndicesAndItems { + if insertedItem.index <= index + indexOffset { indexOffset += 1 } } if indexOffset != 0 { let updatedIndex = index + indexOffset remapInsertion[index] = updatedIndex + + if let previousFrame = previousFrames[index] { + previousFrames.removeValueForKey(index) + previousFrames[updatedIndex] = previousFrame + } + switch state.nodes[i] { case let .Node(_, frame, referenceNode): state.nodes[i] = .Node(index: updatedIndex, frame: frame, referenceNode: referenceNode) @@ -843,6 +1527,27 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } + if !remapInsertion.isEmpty { + if self.debugInfo { + print("remapInsertion \(remapInsertion)") + } + operations.append(.Remap(remapInsertion)) + + var remappedUpdateAdjacentItemsIndices = Set() + for index in updateAdjacentItemsIndices { + if let remappedIndex = remapInsertion[index] { + remappedUpdateAdjacentItemsIndices.insert(remappedIndex) + } else { + remappedUpdateAdjacentItemsIndices.insert(index) + } + } + updateAdjacentItemsIndices = remappedUpdateAdjacentItemsIndices + } + + if self.debugInfo { + //print("state \(state.nodes.map({$0.index ?? -1}))") + } + for node in state.nodes { if let index = node.index { if insertedIndexSet.contains(index - 1) || insertedIndexSet.contains(index + 1) { @@ -851,141 +1556,164 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - if !remapInsertion.isEmpty { - operations.append(.Remap(remapInsertion)) + if let (index, boundary) = stationaryItemRange { + state.setupStationaryOffset(index, boundary: boundary, frames: previousFrames) } - let startTime = CACurrentMediaTime() + if self.debugInfo { + print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } - self.fillMissingNodes(animated, offsetTopInsertedItems: offsetTopInsertedItems, animatedInsertIndices: animated ? insertedIndexSet : Set(), state: state, previousNodes: previousNodes, operations: operations, completion: { updatedState, operations in - - var fixedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices - let maxIndex = updatedState.nodes.count - 1 - for nodeIndex in updateAdjacentItemsIndices { - if nodeIndex < 0 || nodeIndex > maxIndex { - fixedUpdateAdjacentItemsIndices.remove(nodeIndex) - } - } + self.fillMissingNodes(options.contains(.Synchronous), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in if self.debugInfo { print("fillMissingNodes completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") } - self.updateAdjacent(animated, state: updatedState, updateAdjacentItemsIndices: fixedUpdateAdjacentItemsIndices, operations: operations, completion: { operations in + + var updateIndices = updateAdjacentItemsIndices + if widthUpdated { + for case let .Node(index, _, _) in updatedState.nodes { + updateIndices.insert(index) + } + } + + self.updateAdjacent(options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in + var updatedState = state + var updatedOperations = operations + updatedState.removeInvisibleNodes(&updatedOperations) + if self.debugInfo { print("updateAdjacent completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") } + let stationaryItemIndex = updatedState.stationaryOffset?.0 + let next = { - self.replayOperations(animated, operations: operations, completion: completion) + self.replayOperations(animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, completion: completion) } - self.dispatchOnVSync { - next() + if options.contains(.LowLatency) || options.contains(.Synchronous) { + Queue.mainQueue().dispatch { + if self.debugInfo { + print("updateAdjacent LowLatency enqueue \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } + next() + } + } else { + self.dispatchOnVSync { + next() + } } }) }) - }) + } + + if options.contains(.Synchronous) { + actions() + } else { + self.async(actions) + } } - private func updateAdjacent(animated: Bool, state: ListViewState, updateAdjacentItemsIndices: Set, operations: [ListViewStateOperation], completion: [ListViewStateOperation] -> Void) { + private func updateAdjacent(synchronous: Bool, animated: Bool, state: ListViewState, updateAdjacentItemsIndices: Set, operations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { if updateAdjacentItemsIndices.isEmpty { - completion(operations) + completion(state, operations) } else { var updatedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices let nodeIndex = updateAdjacentItemsIndices.first! updatedUpdateAdjacentItemsIndices.remove(nodeIndex) - - var actualIndex = nodeIndex - /*for node in state.nodes { - if case let .Node(index, _, _) = node where index == nodeIndex { - break - } - actualIndex += 1 - }*/ var continueWithoutNode = true - if actualIndex < state.nodes.count { - if case let .Node(index, _, referenceNode) = state.nodes[actualIndex] { + var i = 0 + for node in state.nodes { + if case let .Node(index, _, referenceNode) = node where index == nodeIndex { if let referenceNode = referenceNode { continueWithoutNode = false - self.items[index].updateNode(referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], completion: { layout, apply in + self.items[index].updateNode({ f in + if synchronous { + f() + } else { + self.async(f) + } + }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], completion: { layout, apply in var updatedState = state var updatedOperations = operations - for i in nodeIndex + 1 ..< updatedState.nodes.count { - let frame = updatedState.nodes[i].frame - updatedState.nodes[i].frame = frame.offsetBy(dx: 0.0, dy: frame.size.height) - updatedOperations.append(.UpdateLayout(index: nodeIndex, layout: layout, apply: apply)) + updatedOperations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) + + if nodeIndex + 1 < updatedState.nodes.count { + for i in nodeIndex + 1 ..< updatedState.nodes.count { + let frame = updatedState.nodes[i].frame + updatedState.nodes[i].frame = frame.offsetBy(dx: 0.0, dy: frame.size.height) + } } - self.updateAdjacent(animated, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion) + self.updateAdjacent(synchronous, animated: animated, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion) }) } + break } + i += 1 } if continueWithoutNode { - updateAdjacent(animated, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion) + updateAdjacent(synchronous, animated: animated, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion) } } } - private func fillMissingNodes(animated: Bool, offsetTopInsertedItems: Bool, animatedInsertIndices: Set, state: ListViewState, previousNodes: [Int: ListViewItemNode], operations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { - if self.items.count == 0 { - completion(state, operations) - } else { - var insertionItemIndexAndDirection: (Int, ListViewInsertionOffsetDirection)? - - if state.nodes.count == 0 { - insertionItemIndexAndDirection = (0, .Down) - } else { - var previousIndex: Int? - for node in state.nodes { - if let index = node.index { - if let previousIndex = previousIndex { - if previousIndex + 1 != index { - if state.nodeInsertionPointAndIndex(index - 1).0.y < state.insets.top { - insertionItemIndexAndDirection = (index - 1, .Up) - } else { - insertionItemIndexAndDirection = (previousIndex + 1, .Down) - } - break - } - } else if index != 0 { - let insertionPoint = state.nodeInsertionPointAndIndex(index - 1).0 - if insertionPoint.y >= -state.invisibleInset { - if !offsetTopInsertedItems || insertionPoint.y < state.insets.top { - insertionItemIndexAndDirection = (index - 1, .Up) - } else { - insertionItemIndexAndDirection = (0, .Down) - } - break - } - } - previousIndex = index - } - } - if let previousIndex = previousIndex where insertionItemIndexAndDirection == nil && previousIndex != self.items.count - 1 { - let insertionPoint = state.nodeInsertionPointAndIndex(previousIndex + 1).0 - if insertionPoint.y < state.visibleSize.height + state.invisibleInset { - insertionItemIndexAndDirection = (previousIndex + 1, .Down) - } - } - } - - if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { - let index = insertionItemIndexAndDirection.0 - self.nodeForItem(self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, completion: { (node, layout, apply) in - var updatedState = state - var updatedOperations = operations - updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations) - - self.fillMissingNodes(animated, offsetTopInsertedItems: offsetTopInsertedItems, animatedInsertIndices: animatedInsertIndices, state: updatedState, previousNodes: previousNodes, operations: updatedOperations, completion: completion) - }) - } else { + private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], inputCompletion: (ListViewState, [ListViewStateOperation]) -> Void) { + let animatedInsertIndices = inputAnimatedInsertIndices + var state = inputState + var previousNodes = inputPreviousNodes + var operations = inputOperations + let completion = inputCompletion + + while true { + if self.items.count == 0 { completion(state, operations) + break + } else { + var insertionItemIndexAndDirection: (Int, ListViewInsertionOffsetDirection)? + + if self.debugInfo { + assert(true) + } + + if let insertionPoint = state.insertionPoint(insertDirectionHints, itemCount: self.items.count) { + insertionItemIndexAndDirection = (insertionPoint.index, insertionPoint.direction) + } + + if self.debugInfo { + print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") + } + + if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { + let index = insertionItemIndexAndDirection.0 + let threadId = pthread_self() + var tailRecurse = false + self.nodeForItem(synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, completion: { (node, layout, apply) in + + if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { + tailRecurse = true + state.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &operations, itemCount: self.items.count) + } else { + var updatedState = state + var updatedOperations = operations + updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations, itemCount: self.items.count) + self.fillMissingNodes(synchronous, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion) + } + }) + if !tailRecurse { + tailRecurse = true + break + } + } else { + completion(state, operations) + break + } } } } @@ -1005,22 +1733,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateVisibleNodes() { - /*let visibleRect = CGRect(origin: CGPoint(x: 0.0, y: -10.0), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height + 20)) - for itemNode in self.itemNodes { - if CGRectIntersectsRect(itemNode.apparentFrame, visibleRect) { - if useDynamicTuning { - self.insertSubnode(itemNode, atIndex: 0) - } else { - self.addSubnode(itemNode) - } - } else if itemNode.supernode != nil { - itemNode.removeFromSupernode() - } - }*/ - } - - private func insertNodeAtIndex(animated: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), timestamp: Double) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), timestamp: Double) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -1061,7 +1774,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if nextNode.index == nil { let nextHeight = nextNode.apparentHeight if abs(nextHeight - previousApparentHeight) < CGFloat(FLT_EPSILON) { - if let animation = node.animationForKey("apparentHeight") where abs(animation.to as! CGFloat - layout.size.height) < CGFloat(FLT_EPSILON) { + if let animation = nextNode.animationForKey("apparentHeight") { node.apparentHeight = previousApparentHeight offsetHeight = 0.0 @@ -1096,6 +1809,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } } + } else if animateAlpha && previousFrame == nil { + node.animateAdded(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } if node.apparentHeight > CGFloat(FLT_EPSILON) { @@ -1120,14 +1835,17 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func replayOperations(animated: Bool, operations: [ListViewStateOperation], completion: () -> Void) { + private func replayOperations(animated: Bool, animateAlpha: Bool, operations: [ListViewStateOperation], scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, completion: () -> Void) { let timestamp = CACurrentMediaTime() var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] for itemNode in self.itemNodes { previousApparentFrames.append((itemNode, itemNode.apparentFrame)) } - var insertedNodes: [ASDisplayNode] = [] + + if self.debugInfo { + //print("replay before \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))") + } for operation in operations { switch operation { @@ -1139,9 +1857,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { break } } - self.insertNodeAtIndex(animated, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) - insertedNodes.append(node) - case let .InsertPlaceholder(index, referenceNode): + self.insertNodeAtIndex(animated, animateAlpha: animateAlpha, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + self.addSubnode(node) + case let .InsertPlaceholder(index, referenceNode, offsetDirection): var height: CGFloat? for (node, previousFrame) in previousApparentFrames { @@ -1152,7 +1870,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if let height = height { - self.insertNodeAtIndex(false, previousFrame: nil, nodeIndex: index, offsetDirection: .Down, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + self.insertNodeAtIndex(false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) } else { assertionFailure() } @@ -1164,15 +1882,28 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } } - case let .Remove(index): - let height = self.itemNodes[index].apparentHeight - if index != self.itemNodes.count - 1 { - for i in index + 1 ..< self.itemNodes.count { - var frame = self.itemNodes[i].frame - frame.origin.y -= height - self.itemNodes[i].frame = frame - } + case let .Remove(index, offsetDirection): + let apparentFrame = self.itemNodes[index].apparentFrame + let height = apparentFrame.size.height + switch offsetDirection { + case .Up: + if index != self.itemNodes.count - 1 { + for i in index + 1 ..< self.itemNodes.count { + var frame = self.itemNodes[i].frame + frame.origin.y -= height + self.itemNodes[i].frame = frame + } + } + case .Down: + if index != 0 { + for i in (0 ..< index).reverse() { + var frame = self.itemNodes[i].frame + frame.origin.y += height + self.itemNodes[i].frame = frame + } + } } + self.removeItemNodeAtIndex(index) case let .UpdateLayout(index, layout, apply): let node = self.itemNodes[index] @@ -1218,49 +1949,273 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { for itemNode in self.itemNodes { let offset = offsetRanges.offsetForIndex(index) if offset != 0.0 { - var position = itemNode.position - position.y += offset - itemNode.position = position + var frame = itemNode.frame + frame.origin.y += offset + itemNode.frame = frame } index += 1 } } + + if self.debugInfo { + //print("operation \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))") + } } - self.insertNodesInBatches(insertedNodes, completion: { - self.debugCheckMonotonity() - self.removeInvisibleNodes() - self.updateAccessoryNodes(animated, currentTimestamp: timestamp) - self.snapToBounds() - self.updateVisibleNodes() - if animated { - self.setNeedsAnimations() + if self.debugInfo { + //print("replay after \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))") + } + + if let scrollToItem = scrollToItem { + self.stopScrolling() + + for itemNode in self.itemNodes { + if let index = itemNode.index where index == scrollToItem.index { + let offset: CGFloat + switch scrollToItem.position { + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + case .Top: + offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + case let .Center(overflow): + let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top + if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { + offset = self.insets.top + floor(((self.visibleSize.height - self.insets.bottom - self.insets.top) - itemNode.frame.size.height) / 2.0) - itemNode.apparentFrame.minY + } else { + switch overflow { + case .Top: + offset = self.insets.top - itemNode.apparentFrame.minY + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + } + } + } + + for itemNode in self.itemNodes { + var frame = itemNode.frame + frame.origin.y += offset + itemNode.frame = frame + } + + break + } } - completion() - }) + /*for itemNode in self.itemNodes { + print("item \(itemNode.index) frame \(itemNode.frame)") + }*/ + } else if let stationaryItemIndex = stationaryItemIndex { + for itemNode in self.itemNodes { + if let index = itemNode.index where index == stationaryItemIndex { + for (previousNode, previousFrame) in previousApparentFrames { + if previousNode === itemNode { + let offset = previousFrame.minY - itemNode.frame.minY + + if abs(offset) > CGFloat(FLT_EPSILON) { + for itemNode in self.itemNodes { + var frame = itemNode.frame + frame.origin.y += offset + itemNode.frame = frame + } + } + + break + } + } + break + } + } + } - /*let delta = CACurrentMediaTime() - timestamp - if delta > 1.0 / 60.0 { - print("replayOperations \(delta * 1000.0) ms \(nodeCreationDurations)") - }*/ + self.insertNodesInBatches([], completion: { + self.debugCheckMonotonity() + + var sizeAndInsetsOffset: CGFloat = 0.0 + + if let updateSizeAndInsets = updateSizeAndInsets { + self.visibleSize = updateSizeAndInsets.size + + if self.insets != updateSizeAndInsets.insets { + var offsetFix = updateSizeAndInsets.insets.top - self.insets.top + + self.insets = updateSizeAndInsets.insets + + var completeOffset = offsetFix + sizeAndInsetsOffset = offsetFix + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) + } + + if updateSizeAndInsets.duration > DBL_EPSILON { + let animation: CABasicAnimation + switch updateSizeAndInsets.curve { + case let .Spring(speed): + let springAnimation = CASpringAnimation(keyPath: "sublayerTransform") + springAnimation.mass = 3.0 + springAnimation.stiffness = 1000.0 + springAnimation.damping = 500.0 + springAnimation.initialVelocity = 0.0 + springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) + springAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) + springAnimation.removedOnCompletion = true + springAnimation.additive = true + springAnimation.duration = springAnimation.settlingDuration + animation = springAnimation + case .Default: + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + basicAnimation.duration = updateSizeAndInsets.duration * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + basicAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) + basicAnimation.removedOnCompletion = true + basicAnimation.additive = true + animation = basicAnimation + } + + self.layer.addAnimation(animation, forKey: nil) + } + } + + self.ignoreScrollingEvents = true + self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) + self.scroller.contentSize = CGSizeMake(self.visibleSize.width, infiniteScrollSize * 2.0) + self.lastContentOffset = CGPointMake(0.0, infiniteScrollSize) + self.scroller.contentOffset = self.lastContentOffset + } + + self.snapToBounds(scrollToItem != nil) + + if let scrollToItem = scrollToItem where scrollToItem.animated { + /*for itemNode in self.itemNodes { + print("item \(itemNode.index) frame \(itemNode.frame)") + }*/ + + self.updateAccessoryNodes(animated, currentTimestamp: timestamp) + + if self.itemNodes.count != 0 { + var offset: CGFloat? + + var temporaryPreviousNodes: [ListViewItemNode] = [] + var previousUpperBound: CGFloat? + var previousLowerBound: CGFloat? + for (previousNode, previousFrame) in previousApparentFrames { + if previousNode.supernode == nil { + temporaryPreviousNodes.append(previousNode) + previousNode.frame = previousFrame + if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { + previousUpperBound = previousFrame.minY + } + if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { + previousLowerBound = previousFrame.maxY + } + } else { + offset = previousNode.apparentFrame.minY - previousFrame.minY + } + } + + if offset == nil { + let updatedUpperBound = self.itemNodes[0].apparentFrame.minY + let updatedLowerBound = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY + + switch scrollToItem.directionHint { + case .Up: + offset = updatedLowerBound - (previousUpperBound ?? 0.0) + case .Down: + offset = updatedUpperBound - (previousLowerBound ?? self.visibleSize.height) + } + } + + if let offsetValue = offset { + offset = offsetValue - sizeAndInsetsOffset + } + + if let offset = offset where abs(offset) > CGFloat(FLT_EPSILON) { + for itemNode in temporaryPreviousNodes { + itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) + temporaryPreviousNodes.append(itemNode) + self.addSubnode(itemNode) + } + + let animation: CABasicAnimation + switch scrollToItem.curve { + case let .Spring(speed): + let springAnimation = CASpringAnimation(keyPath: "sublayerTransform") + springAnimation.mass = 3.0 + springAnimation.stiffness = 1000.0 + springAnimation.damping = 500.0 + springAnimation.initialVelocity = 0.0 + springAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + springAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) + springAnimation.removedOnCompletion = true + springAnimation.additive = true + springAnimation.fillMode = kCAFillModeForwards + springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) + springAnimation.duration = springAnimation.settlingDuration + animation = springAnimation + case .Default: + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) + //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + basicAnimation.duration = 0.5 * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) + basicAnimation.removedOnCompletion = true + basicAnimation.additive = true + animation = basicAnimation + } + animation.completion = { _ in + for itemNode in temporaryPreviousNodes { + itemNode.removeFromSupernode() + } + } + self.layer.addAnimation(animation, forKey: nil) + } + } + + self.setNeedsAnimations() + + self.updateVisibleContentOffset() + + if self.debugInfo { + let delta = CACurrentMediaTime() - timestamp + //print("replayOperations \(delta * 1000.0) ms") + } + + completion() + } else { + self.updateAccessoryNodes(animated, currentTimestamp: timestamp) + if animated { + self.setNeedsAnimations() + } + + self.updateVisibleContentOffset() + + if self.debugInfo { + let delta = CACurrentMediaTime() - timestamp + //print("replayOperations \(delta * 1000.0) ms") + } + + completion() + } + }) } private func insertNodesInBatches(nodes: [ASDisplayNode], completion: () -> Void) { if nodes.count == 0 { completion() } else { + let startTime = CFAbsoluteTimeGetCurrent() for node in nodes { self.addSubnode(node) } + if self.debugInfo { + //print("insertNodesInBatches \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0)") + } completion() - /*self.dispatchOnVSync(true, action: { - self.addSubnode(nodes[0]) - var updatedNodes = nodes - updatedNodes.removeAtIndex(0) - self.insertNodesInBatches(updatedNodes, completion: completion) - })*/ } } @@ -1383,6 +2338,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } private func updateVisibleItemsTransaction(completion: Void -> Void) { + if self.items.count == 0 && self.itemNodes.count == 0 { + completion() + return + } var i = 0 while i < self.itemNodes.count { let node = self.itemNodes[i] @@ -1393,43 +2352,37 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.fillMissingNodes(false, offsetTopInsertedItems: false, animatedInsertIndices: [], state: self.currentState(), previousNodes: [:], operations: []) { _, operations in + self.fillMissingNodes(false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: self.currentState(), inputPreviousNodes: [:], inputOperations: []) { state, operations in + + var updatedState = state + var updatedOperations = operations + updatedState.removeInvisibleNodes(&updatedOperations) self.dispatchOnVSync { - self.replayOperations(false, operations: operations, completion: completion) - } - } - } - - private func removeInvisibleNodes() { - var i = 0 - var visibleItemNodeHeight: CGFloat = 0.0 - while i < self.itemNodes.count { - visibleItemNodeHeight += self.itemNodes[i].apparentBounds.height - i += 1 - } - - if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { - i = self.itemNodes.count - 1 - while i >= 0 { - let itemNode = self.itemNodes[i] - let apparentFrame = itemNode.apparentFrame - if apparentFrame.maxY < -self.invisibleInset || apparentFrame.origin.y > self.visibleSize.height + self.invisibleInset { - self.removeItemNodeAtIndex(i) - } - i -= 1 + self.replayOperations(false, animateAlpha: false, operations: updatedOperations, scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, completion: completion) } } } private func updateVisibleItemRange(force: Bool = false) { - let currentRange: ListViewVisibleRange? + let currentRange = self.immediateDisplayedItemRange() + + if currentRange != self.displayedItemRange || force { + self.displayedItemRange = currentRange + self.displayedItemRangeChanged(currentRange) + } + } + + private func immediateDisplayedItemRange() -> ListViewDisplayedItemRange { + var loadedRange: ListViewItemRange? + var visibleRange: ListViewItemRange? if self.itemNodes.count != 0 { - var firstIndex: Int? - var lastIndex: Int? + var firstIndex: (nodeIndex: Int, index: Int)? + var lastIndex: (nodeIndex: Int, index: Int)? + var i = 0 while i < self.itemNodes.count { if let index = self.itemNodes[i].index { - firstIndex = index + firstIndex = (i, index) break } i += 1 @@ -1437,24 +2390,45 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { i = self.itemNodes.count - 1 while i >= 0 { if let index = self.itemNodes[i].index { - lastIndex = index + lastIndex = (i, index) break } i -= 1 } if let firstIndex = firstIndex, lastIndex = lastIndex { - currentRange = ListViewVisibleRange(firstIndex: firstIndex, lastIndex: lastIndex) - } else { - currentRange = nil + var firstVisibleIndex: Int? + for i in firstIndex.nodeIndex ... lastIndex.nodeIndex { + if let index = self.itemNodes[i].index { + let frame = self.itemNodes[i].apparentFrame + if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height + self.insets.bottom { + firstVisibleIndex = index + break + } + } + } + + if let firstVisibleIndex = firstVisibleIndex { + var lastVisibleIndex: Int? + for i in (firstIndex.nodeIndex ... lastIndex.nodeIndex).reverse() { + if let index = self.itemNodes[i].index { + let frame = self.itemNodes[i].apparentFrame + if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height - self.insets.bottom { + lastVisibleIndex = index + break + } + } + } + + if let lastVisibleIndex = lastVisibleIndex { + visibleRange = ListViewItemRange(firstIndex: firstVisibleIndex, lastIndex: lastVisibleIndex) + } + } + + loadedRange = ListViewItemRange(firstIndex: firstIndex.index, lastIndex: lastIndex.index) } - } else { - currentRange = nil } - if currentRange != self.visibleItemRange || force { - self.visibleItemRange = currentRange - self.visibleItemRangeChanged(currentRange) - } + return ListViewDisplayedItemRange(loadedRange: loadedRange, visibleRange: visibleRange) } public func updateSizeAndInsets(size: CGSize, insets: UIEdgeInsets, duration: Double = 0.0, options: UIViewAnimationOptions = UIViewAnimationOptions()) { @@ -1684,7 +2658,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if requestUpdateVisibleItems { - self.updateVisibleNodes() self.enqueueUpdateVisibleItems() } } diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index f075b3b90d..494cad4e2e 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -2,8 +2,8 @@ import Foundation import SwiftSignalKit public protocol ListViewItem { - func nodeConfiguredForWidth(width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNode, () -> Void) -> Void) - func updateNode(node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) + func nodeConfiguredForWidth(async: (() -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNode, () -> Void) -> Void) + func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } @@ -28,7 +28,7 @@ public extension ListViewItem { func selected() { } - func updateNode(node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) { + func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) { completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {}) } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index b8fe3a76e0..6aaa392f43 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -69,6 +69,8 @@ public class ListViewItemNode: ASDisplayNode { final let wantsScrollDynamics: Bool + public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets() + public final var insets: UIEdgeInsets = UIEdgeInsets() { didSet { let effectiveInsets = self.insets @@ -382,6 +384,9 @@ public class ListViewItemNode: ASDisplayNode { public func animateInsertion(currentTimestamp: Double, duration: Double) { } + public func animateAdded(currentTimestamp: Double, duration: Double) { + } + public func setHighlighted(highlighted: Bool, animated: Bool) { } diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 7a3e3376c7..782d332930 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -1,6 +1,16 @@ import UIKit class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { + override init(frame: CGRect) { + super.init(frame: frame) + + self.scrollsToTop = false + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + @objc func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } diff --git a/Display/ScrollToTopProxyView.swift b/Display/ScrollToTopProxyView.swift new file mode 100644 index 0000000000..cb72cc71be --- /dev/null +++ b/Display/ScrollToTopProxyView.swift @@ -0,0 +1,32 @@ +import UIKit + +class ScrollToTopView: UIScrollView, UIScrollViewDelegate { + var action: (() -> Void)? + + override init(frame: CGRect) { + super.init(frame: frame) + + self.delegate = self + self.scrollsToTop = true + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override var frame: CGRect { + didSet { + let frame = self.frame + self.contentSize = CGSize(width: frame.width, height: frame.height + 1.0) + self.contentOffset = CGPoint(x: 0.0, y: 1.0) + } + } + + @objc func scrollViewShouldScrollToTop(scrollView: UIScrollView) -> Bool { + if let action = self.action { + action() + } + + return false + } +} diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 0d672e8c5e..a375899a14 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -33,7 +33,6 @@ public class TabBarController: ViewController { } } - private var layout: ViewControllerLayout? private var currentController: ViewController? override public init() { @@ -52,6 +51,7 @@ public class TabBarController: ViewController { }) self.updateSelectedIndex() + self.displayNodeDidLoad() } private func updateSelectedIndex() { @@ -106,7 +106,6 @@ public class TabBarController: ViewController { self.tabBarControllerNode.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) - self.layout = layout if let currentController = self.currentController { currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) currentController.setParentLayout(self.childControllerLayoutForLayout(layout), duration: duration, curve: curve) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 4d10c0873a..e8d3be2849 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -34,11 +34,11 @@ public func floorToScreenPixels(value: CGFloat) -> CGFloat { public let UIScreenPixel = 1.0 / UIScreenScale public extension UIColor { - convenience init(_ rgb: Int) { + convenience init(_ rgb: UInt32) { self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) } - convenience init(_ rgb: Int, _ alpha: CGFloat) { + convenience init(_ rgb: UInt32, _ alpha: CGFloat) { self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: alpha) } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 26b71c7278..13decf1eb0 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -40,10 +40,37 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } private var updateLayoutOnLayout: (ViewControllerLayout, NSTimeInterval, UInt)? - private var layout: ViewControllerLayout? + public private(set) var layout: ViewControllerLayout? var keyboardFrameObserver: AnyObject? + private var scrollToTopView: ScrollToTopView? + public var scrollToTop: (() -> Void)? { + didSet { + if self.isViewLoaded() { + self.updateScrollToTopView() + } + } + } + + private func updateScrollToTopView() { + if let scrollToTop = self.scrollToTop { + if let displayNode = self._displayNode where self.scrollToTopView == nil { + let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.frame.size.width, height: 1.0)) + scrollToTopView.action = { [weak self] in + if let scrollToTop = self?.scrollToTop { + scrollToTop() + } + } + self.scrollToTopView = scrollToTopView + self.view.addSubview(scrollToTopView) + } + } else if let scrollToTopView = self.scrollToTopView { + scrollToTopView.removeFromSuperview() + self.scrollToTopView = nil + } + } + public init() { self.statusBar = StatusBar() self.navigationBar = NavigationBar() @@ -57,8 +84,11 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { if let strongSelf = self, _ = strongSelf._displayNode { let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() ?? CGRect() let keyboardHeight = max(0.0, UIScreen.mainScreen().bounds.size.height - keyboardFrame.minY) - let duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 - let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.unsignedIntegerValue ?? UInt(7 << 16) + var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 + if duration > DBL_EPSILON { + duration = 0.5 + } + var curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.unsignedIntegerValue ?? UInt(7 << 16) let previousLayout: ViewControllerLayout? var previousDurationAndCurve: (NSTimeInterval, UInt)? @@ -72,13 +102,17 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { let updated: Bool if let previousLayout = previousLayout { updated = previousLayout != layout + if duration < DBL_EPSILON && abs(min(previousLayout.inputViewHeight, layout.inputViewHeight) - 225.0) < CGFloat(FLT_EPSILON) && abs(max(previousLayout.inputViewHeight, layout.inputViewHeight) - 225.0 - 33.0) < CGFloat(FLT_EPSILON) { + duration = 0.1 + curve = 0 + } } else { updated = true } if updated { //print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") - let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration > DBL_EPSILON ? 0.5 : 0.0, curve) + let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration, curve) strongSelf.updateLayoutOnLayout = (layout, durationAndCurve.0, durationAndCurve.1) strongSelf.view.setNeedsLayout() } @@ -104,6 +138,11 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { public func loadDisplayNode() { self.displayNode = ASDisplayNode() + self.displayNodeDidLoad() + } + + public func displayNodeDidLoad() { + self.updateScrollToTopView() } public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { @@ -142,6 +181,25 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) self.navigationBar.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 44.0 + 20.0)) + if let scrollToTopView = self.scrollToTopView { + scrollToTopView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 10.0) + } + } + + public func setNeedsLayoutWithDuration(duration: Double, curve: UInt) { + let previousLayout: ViewControllerLayout? + var previousDurationAndCurve: (NSTimeInterval, UInt)? + if let updateLayoutOnLayout = self.updateLayoutOnLayout { + previousLayout = updateLayoutOnLayout.0 + previousDurationAndCurve = (updateLayoutOnLayout.1, updateLayoutOnLayout.2) + } else{ + previousLayout = self.layout + } + if let previousLayout = previousLayout { + let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration, curve) + self.updateLayoutOnLayout = (previousLayout, durationAndCurve.0, durationAndCurve.1) + self.view.setNeedsLayout() + } } override public func viewDidLayoutSubviews() { From 572a74e278d9e7ea02140f6bf3058f85e12cde59 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 14 Jun 2016 18:12:05 +0300 Subject: [PATCH 013/245] no message --- Display/CAAnimationUtils.swift | 84 ++++++++++++++++++------- Display/ListView.swift | 27 ++++++-- Display/ListViewAccessoryItemNode.swift | 2 +- Display/ListViewAnimation.swift | 46 +++++++++----- Display/ListViewItem.swift | 9 ++- Display/ListViewItemNode.swift | 17 +++-- 6 files changed, 136 insertions(+), 49 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 0734a055a9..d28b1d5df9 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -17,6 +17,7 @@ import UIKit } private let completionKey = "CAAnimationUtils_completion" +private let springKey = "CAAnimationUtilsSpringCurve" public extension CAAnimation { public var completion: (Bool -> Void)? { @@ -38,27 +39,50 @@ public extension CAAnimation { public extension CALayer { public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { - let k = Float(UIView.animationDurationFactor()) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k + if timingFunction == springKey { + let animation = CASpringAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.damping = 500.0 + animation.stiffness = 1000.0 + animation.mass = 3.0 + animation.duration = animation.settlingDuration + animation.removedOnCompletion = removeOnCompletion + animation.fillMode = kCAFillModeForwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + animation.speed = speed * Float(animation.duration / duration) + + self.addAnimation(animation, forKey: keyPath) + } else { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CABasicAnimation(keyPath: keyPath) + animation.fromValue = from + animation.toValue = to + animation.duration = duration + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + animation.removedOnCompletion = removeOnCompletion + animation.fillMode = kCAFillModeForwards + animation.speed = speed + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + self.addAnimation(animation, forKey: keyPath) } - - let animation = CABasicAnimation(keyPath: keyPath) - animation.fromValue = from - animation.toValue = to - animation.duration = duration - animation.timingFunction = CAMediaTimingFunction(name: timingFunction) - animation.removedOnCompletion = removeOnCompletion - animation.fillMode = kCAFillModeForwards - animation.speed = speed - if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) - } - - self.addAnimation(animation, forKey: keyPath) - - //self.setValue(to, forKey: keyPath) } public func animateAdditive(from from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { @@ -93,10 +117,28 @@ public extension CALayer { } internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { - self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: completion) + if from == to { + return + } + self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) + } + + internal func animateBounds(from from: CGRect, to: CGRect, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { + if from == to { + return + } + self.animate(from: NSValue(CGRect: from), to: NSValue(CGRect: to), keyPath: "bounds", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) } public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } + + public func animateFrame(from from: CGRect, to: CGRect, duration: NSTimeInterval, spring: Bool = false, completion: (Bool -> Void)? = nil) { + if from == to { + return + } + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, completion: nil) + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, completion: completion) + } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 1f71c9a0a1..8889e79b70 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1269,7 +1269,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { if let previousNode = previousNode { item.updateNode({ f in if synchronous { @@ -1277,7 +1277,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } else { self.async(f) } - }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, completion: { (layout, apply) in + }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in if NSThread.isMainThread() { if synchronous { completion(previousNode, layout, { @@ -1637,7 +1637,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } else { self.async(f) } - }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], completion: { layout, apply in + }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: .None, completion: { layout, apply in var updatedState = state var updatedOperations = operations @@ -1670,6 +1670,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var previousNodes = inputPreviousNodes var operations = inputOperations let completion = inputCompletion + let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None while true { if self.items.count == 0 { @@ -1694,7 +1695,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let index = insertionItemIndexAndDirection.0 let threadId = pthread_self() var tailRecurse = false - self.nodeForItem(synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, completion: { (node, layout, apply) in + self.nodeForItem(synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, updateAnimation: updateAnimation, completion: { (node, layout, apply) in if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { tailRecurse = true @@ -1749,6 +1750,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let previousApparentHeight = node.apparentHeight let previousInsets = node.insets + if node.wantsScrollDynamics && previousFrame != nil { + assert(true) + } + node.contentSize = layout.contentSize node.insets = layout.insets node.apparentHeight = animated ? 0.0 : layout.size.height @@ -1787,6 +1792,16 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { nextNode.removeApparentHeightAnimation() takenAnimation = true + + if abs(layout.size.height - previousApparentHeight) > CGFloat(FLT_EPSILON) { + node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration, beginAt: timestamp, update: { [weak node] progress in + if let node = node { + node.animateFrameTransition(progress) + } + }) + node.transitionOffset = previousApparentHeight - layout.size.height + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration, beginAt: timestamp) + } } } } @@ -2285,7 +2300,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y - nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) + let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height + + nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) } } else { break diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift index 28f365ddd1..b25d22757a 100644 --- a/Display/ListViewAccessoryItemNode.swift +++ b/Display/ListViewAccessoryItemNode.swift @@ -12,7 +12,7 @@ public class ListViewAccessoryItemNode: ASDisplayNode { final func animateTransitionOffset(from: CGPoint, beginAt: Double, duration: Double, curve: CGFloat -> CGFloat) { self.transitionOffset = from - self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] currentValue in + self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { strongSelf.transitionOffset = currentValue } diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 2eb56d288d..36d2a4167e 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -43,6 +43,16 @@ extension UIEdgeInsets: Interpolatable { } } +extension CGRect: Interpolatable { + public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable { + return { from, to, t -> Interpolatable in + let fromValue = from as! CGRect + let toValue = to as! CGRect + return floorToPixels(CGRect(x: toValue.origin.x * t + fromValue.origin.x * (1.0 - t), y: toValue.origin.y * t + fromValue.origin.y * (1.0 - t), width: toValue.size.width * t + fromValue.size.width * (1.0 - t), height: toValue.size.height * t + fromValue.size.height * (1.0 - t))) + } + } +} + extension CGPoint: Interpolatable { public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable { return { from, to, t -> Interpolatable in @@ -55,11 +65,10 @@ extension CGPoint: Interpolatable { private let springAnimationIn: CASpringAnimation = { let animation = CASpringAnimation() - animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear) - animation.duration = 0.6 animation.damping = 500.0 animation.stiffness = 1000.0 animation.mass = 3.0 + animation.duration = animation.settlingDuration return animation }() @@ -87,18 +96,18 @@ public final class ListViewAnimation { let startTime: Double private let curve: CGFloat -> CGFloat private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable - private let update: Interpolatable -> Void + private let update: (CGFloat, Interpolatable) -> Void private let completed: Bool -> Void - public init(from: T, to: T, duration: Double, curve: CGFloat -> CGFloat, beginAt: Double, update: T -> Void, completed: Bool -> Void = { _ in }) { + public init(from: T, to: T, duration: Double, curve: CGFloat -> CGFloat, beginAt: Double, update: (CGFloat, T) -> Void, completed: Bool -> Void = { _ in }) { self.from = from self.to = to self.duration = duration self.curve = curve self.startTime = beginAt self.interpolator = T.interpolator() - self.update = { value in - update(value as! T) + self.update = { progress, value in + update(progress, value as! T) } self.completed = completed } @@ -116,21 +125,28 @@ public final class ListViewAnimation { self.completed(false) } - private func valueAt(timestamp: Double) -> Interpolatable { - if timestamp < self.startTime { + private func valueAt(t: CGFloat) -> Interpolatable { + if t <= 0.0 { return self.from - } - - let t = CGFloat((timestamp - self.startTime) / self.duration) - - if t >= 1.0 { + } else if t >= 1.0 { return self.to } else { - return self.interpolator(self.from, self.to, self.curve(t)) + return self.interpolator(self.from, self.to, t) } } public func applyAt(timestamp: Double) { - self.update(self.valueAt(timestamp)) + var t = CGFloat((timestamp - self.startTime) / self.duration) + let ct: CGFloat + if t <= 0.0 + CGFloat(FLT_EPSILON) { + t = 0.0 + ct = 0.0 + } else if t >= 1.0 - CGFloat(FLT_EPSILON) { + t = 1.0 + ct = 1.0 + } else { + ct = self.curve(t) + } + self.update(ct, self.valueAt(ct)) } } diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 494cad4e2e..5fff85d05b 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -1,9 +1,14 @@ import Foundation import SwiftSignalKit +public enum ListViewItemUpdateAnimation { + case None + case System(duration: Double) +} + public protocol ListViewItem { func nodeConfiguredForWidth(async: (() -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNode, () -> Void) -> Void) - func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) + func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: (ListViewItemNodeLayout, () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } @@ -28,7 +33,7 @@ public extension ListViewItem { func selected() { } - func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNodeLayout, () -> Void) -> Void) { + func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: (ListViewItemNodeLayout, () -> Void) -> Void) { completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {}) } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 6aaa392f43..d3c0e373ea 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -334,7 +334,7 @@ public class ListViewItemNode: ASDisplayNode { } public func addInsetsAnimationToValue(value: UIEdgeInsets, duration: Double, beginAt: Double) { - let animation = ListViewAnimation(from: self.insets, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + let animation = ListViewAnimation(from: self.insets, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { strongSelf.insets = currentValue } @@ -342,10 +342,13 @@ public class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("insets", animation: animation) } - public func addApparentHeightAnimation(value: CGFloat, duration: Double, beginAt: Double) { - let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + public func addApparentHeightAnimation(value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat) -> Void)? = nil) { + let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { strongSelf.apparentHeight = currentValue + if let update = update { + update(progress) + } } }) self.setAnimationForKey("apparentHeight", animation: animation) @@ -358,7 +361,7 @@ public class ListViewItemNode: ASDisplayNode { duration = 0.0 } - let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { strongSelf.apparentHeight = currentValue } @@ -373,7 +376,7 @@ public class ListViewItemNode: ASDisplayNode { } public func addTransitionOffsetAnimation(value: CGFloat, duration: Double, beginAt: Double) { - let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] currentValue in + let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { strongSelf.transitionOffset = currentValue } @@ -392,4 +395,8 @@ public class ListViewItemNode: ASDisplayNode { public func setupGestures() { } + + public func animateFrameTransition(progress: CGFloat) { + + } } From f701b45c05a7ed5ec0e4c7cdfedcd7d49fdea7b2 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 17 Jun 2016 00:59:40 +0300 Subject: [PATCH 014/245] no message --- Display/ListView.swift | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 8889e79b70..34589c4cde 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1794,13 +1794,13 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { takenAnimation = true if abs(layout.size.height - previousApparentHeight) > CGFloat(FLT_EPSILON) { - node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration, beginAt: timestamp, update: { [weak node] progress in + node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in if let node = node { node.animateFrameTransition(progress) } }) - node.transitionOffset = previousApparentHeight - layout.size.height - node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration, beginAt: timestamp) + node.transitionOffset += previousApparentHeight - layout.size.height + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } } } @@ -1811,10 +1811,18 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } else if animated { if !takenAnimation { - node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in + if let node = node { + node.animateFrameTransition(progress) + } + }) if let previousFrame = previousFrame { - node.transitionOffset += nodeFrame.origin.y - previousFrame.origin.y + if self.debugInfo { + assert(true) + } + + node.transitionOffset += nodeFrame.origin.y - previousFrame.origin.y - previousApparentHeight + layout.size.height node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) if previousInsets != layout.insets { node.insets = previousInsets From c693212a772d8104e6414721f283aa8fec244871 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 19 Jun 2016 15:14:26 +0300 Subject: [PATCH 015/245] no message --- Display.xcodeproj/project.pbxproj | 4 +- .../xcschemes/DisplayTests.xcscheme | 56 +++ .../xcschemes/xcschememanagement.plist | 5 + Display/ASTransformLayerNode.swift | 8 +- Display/BarButtonItemWrapper.swift | 8 +- Display/CAAnimationUtils.swift | 52 ++- Display/DefaultDisplayTheme.swift | 2 +- Display/DisplayLinkDispatcher.swift | 14 +- Display/Font.swift | 12 +- Display/GenerateImage.swift | 130 ++++--- Display/ImageCache.swift | 8 +- ...teractiveTransitionGestureRecognizer.swift | 18 +- Display/ListView.swift | 342 +++++++++--------- Display/ListViewAccessoryItem.swift | 2 +- Display/ListViewAccessoryItemNode.swift | 4 +- Display/ListViewAnimation.swift | 39 +- Display/ListViewItemNode.swift | 40 +- Display/ListViewScroller.swift | 2 +- Display/ListViewTransactionQueue.swift | 16 +- Display/NavigationBackButtonNode.swift | 44 +-- Display/NavigationBar.swift | 2 +- Display/NavigationButtonNode.swift | 50 +-- Display/NavigationController.swift | 69 ++-- Display/NavigationItemWrapper.swift | 42 +-- Display/NavigationTitleNode.swift | 12 +- Display/NavigationTransitionCoordinator.swift | 14 +- Display/RuntimeUtils.h | 1 + Display/RuntimeUtils.m | 4 + Display/RuntimeUtils.swift | 6 +- Display/ScrollToTopProxyView.swift | 2 +- Display/Spring.swift | 14 +- Display/StatusBar.swift | 20 +- Display/StatusBarHostWindow.swift | 4 +- Display/StatusBarManager.swift | 38 +- Display/StatusBarProxyNode.swift | 78 ++-- Display/TabBarContollerNode.swift | 8 +- Display/TabBarController.swift | 12 +- Display/TabBarNode.swift | 62 ++-- Display/UIKitUtils.h | 7 +- Display/UIKitUtils.m | 14 + Display/UIKitUtils.swift | 30 +- Display/ViewController.swift | 38 +- Display/Window.swift | 52 +-- DisplayTests/DisplayTests.swift | 2 +- 44 files changed, 722 insertions(+), 665 deletions(-) create mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index edd93cb61c..264c2fb095 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -488,7 +488,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0700; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = Telegram; TargetAttributes = { D05CC2621B69316F00E235A3 = { @@ -749,6 +749,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; @@ -769,6 +770,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; name = Release; }; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme new file mode 100644 index 0000000000..d9e9893d68 --- /dev/null +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 29bf3de275..687ccdfc6e 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -9,6 +9,11 @@ orderHint 0 + DisplayTests.xcscheme + + orderHint + 18 + SuppressBuildableAutocreation diff --git a/Display/ASTransformLayerNode.swift b/Display/ASTransformLayerNode.swift index 0978ddb5b7..14c16e15c8 100644 --- a/Display/ASTransformLayerNode.swift +++ b/Display/ASTransformLayerNode.swift @@ -35,7 +35,7 @@ public class ASTransformLayerNode: ASDisplayNode { public override init() { super.init(layerBlock: { return ASTransformLayer() - }, didLoadBlock: nil) + }, didLoad: nil) } } @@ -43,7 +43,7 @@ public class ASTransformViewNode: ASDisplayNode { public override init() { super.init(viewBlock: { return ASTransformView() - }, didLoadBlock: nil) + }, didLoad: nil) } } @@ -52,11 +52,11 @@ public class ASTransformNode: ASDisplayNode { if layerBacked { super.init(layerBlock: { return ASTransformLayer() - }, didLoadBlock: nil) + }, didLoad: nil) } else { super.init(viewBlock: { return ASTransformView() - }, didLoadBlock: nil) + }, didLoad: nil) } } } diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift index f529094e29..de62a54dee 100644 --- a/Display/BarButtonItemWrapper.swift +++ b/Display/BarButtonItemWrapper.swift @@ -24,12 +24,12 @@ internal class BarButtonItemWrapper { self.parentNode.addSubnode(self.buttonNode) self.setEnabledListenerKey = barButtonItem.addSetEnabledListener({ [weak self] enabled in - self?.buttonNode.enabled = enabled.boolValue + self?.buttonNode.isEnabled = enabled.boolValue return }) self.setTitleListenerKey = barButtonItem.addSetTitleListener({ [weak self] title in - self?.buttonNode.text = title + self?.buttonNode.text = title ?? "" if let layoutNeeded = self?.layoutNeeded { layoutNeeded() } @@ -37,8 +37,8 @@ internal class BarButtonItemWrapper { }) self.buttonNode.text = barButtonItem.title ?? "" - self.buttonNode.enabled = barButtonItem.enabled ?? true - self.buttonNode.bold = (barButtonItem.style ?? UIBarButtonItemStyle.Plain) == UIBarButtonItemStyle.Done + self.buttonNode.isEnabled = barButtonItem.isEnabled ?? true + self.buttonNode.bold = (barButtonItem.style ?? UIBarButtonItemStyle.plain) == UIBarButtonItemStyle.done } deinit { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index d28b1d5df9..8547b44f3c 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,15 +1,15 @@ import UIKit @objc private class CALayerAnimationDelegate: NSObject { - var completion: (Bool -> Void)? + var completion: ((Bool) -> Void)? - init(completion: (Bool -> Void)?) { + init(completion: ((Bool) -> Void)?) { self.completion = completion super.init() } - @objc override func animationDidStop(anim: CAAnimation, finished flag: Bool) { + @objc override func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if let completion = self.completion { completion(flag) } @@ -20,7 +20,7 @@ private let completionKey = "CAAnimationUtils_completion" private let springKey = "CAAnimationUtilsSpringCurve" public extension CAAnimation { - public var completion: (Bool -> Void)? { + public var completion: ((Bool) -> Void)? { get { if let delegate = self.delegate as? CALayerAnimationDelegate { return delegate.completion @@ -38,16 +38,12 @@ public extension CAAnimation { } public extension CALayer { - public func animate(from from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { + public func animate(from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { if timingFunction == springKey { - let animation = CASpringAnimation(keyPath: keyPath) + let animation = makeSpringAnimation(keyPath) animation.fromValue = from animation.toValue = to - animation.damping = 500.0 - animation.stiffness = 1000.0 - animation.mass = 3.0 - animation.duration = animation.settlingDuration - animation.removedOnCompletion = removeOnCompletion + animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards if let completion = completion { animation.delegate = CALayerAnimationDelegate(completion: completion) @@ -61,7 +57,7 @@ public extension CALayer { animation.speed = speed * Float(animation.duration / duration) - self.addAnimation(animation, forKey: keyPath) + self.add(animation, forKey: keyPath) } else { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 @@ -74,18 +70,18 @@ public extension CALayer { animation.toValue = to animation.duration = duration animation.timingFunction = CAMediaTimingFunction(name: timingFunction) - animation.removedOnCompletion = removeOnCompletion + animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards animation.speed = speed if let completion = completion { animation.delegate = CALayerAnimationDelegate(completion: completion) } - self.addAnimation(animation, forKey: keyPath) + self.add(animation, forKey: keyPath) } } - public func animateAdditive(from from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: (Bool -> Void)? = nil) { + public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 if k != 0 && k != 1 { @@ -97,44 +93,44 @@ public extension CALayer { animation.toValue = to animation.duration = duration animation.timingFunction = CAMediaTimingFunction(name: timingFunction) - animation.removedOnCompletion = removeOnCompletion + animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards animation.speed = speed - animation.additive = true + animation.isAdditive = true if let completion = completion { animation.delegate = CALayerAnimationDelegate(completion: completion) } - self.addAnimation(animation, forKey: key) + self.add(animation, forKey: key) } - public func animateAlpha(from from: CGFloat, to: CGFloat, duration: NSTimeInterval, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - public func animateScale(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { - self.animate(from: NSNumber(float: Float(from)), to: NSNumber(float: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) + public func animateScale(from: CGFloat, to: CGFloat, duration: Double) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) } - internal func animatePosition(from from: CGPoint, to: CGPoint, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { + internal func animatePosition(from: CGPoint, to: CGPoint, duration: Double, completion: ((Bool) -> Void)? = nil) { if from == to { return } - self.animate(from: NSValue(CGPoint: from), to: NSValue(CGPoint: to), keyPath: "position", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) + self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) } - internal func animateBounds(from from: CGRect, to: CGRect, duration: NSTimeInterval, completion: (Bool -> Void)? = nil) { + internal func animateBounds(from: CGRect, to: CGRect, duration: Double, completion: ((Bool) -> Void)? = nil) { if from == to { return } - self.animate(from: NSValue(CGRect: from), to: NSValue(CGRect: to), keyPath: "bounds", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) + self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) } - public func animateBoundsOriginYAdditive(from from: CGFloat, to: CGFloat, duration: NSTimeInterval) { + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double) { self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } - public func animateFrame(from from: CGRect, to: CGRect, duration: NSTimeInterval, spring: Bool = false, completion: (Bool -> Void)? = nil) { + public func animateFrame(from: CGRect, to: CGRect, duration: Double, spring: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { return } diff --git a/Display/DefaultDisplayTheme.swift b/Display/DefaultDisplayTheme.swift index 74f95ebf48..b77e9745b5 100644 --- a/Display/DefaultDisplayTheme.swift +++ b/Display/DefaultDisplayTheme.swift @@ -1,5 +1,5 @@ import Foundation func defaultDisplayTheme() -> DisplayTheme { - return DisplayTheme(tintColor: UIColor.blueColor()) + return DisplayTheme(tintColor: UIColor.blue()) } diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index 5210d7fc9e..5efaa305bf 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -2,7 +2,7 @@ import Foundation public class DisplayLinkDispatcher: NSObject { private var displayLink: CADisplayLink! - private var blocksToDispatch: [Void -> Void] = [] + private var blocksToDispatch: [(Void) -> Void] = [] private let limit: Int public init(limit: Int = 0) { @@ -11,19 +11,19 @@ public class DisplayLinkDispatcher: NSObject { super.init() self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) - self.displayLink.paused = true - self.displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) + self.displayLink.isPaused = true + self.displayLink.add(to: RunLoop.main(), forMode: RunLoopMode.commonModes.rawValue) } - public func dispatch(f: Void -> Void) { + public func dispatch(f: (Void) -> Void) { self.blocksToDispatch.append(f) - self.displayLink.paused = false + self.displayLink.isPaused = false } @objc func run() { for _ in 0 ..< (self.limit == 0 ? 1000 : self.limit) { if self.blocksToDispatch.count == 0 { - self.displayLink.paused = true + self.displayLink.isPaused = true break } else { let f = self.blocksToDispatch.removeFirst() @@ -31,4 +31,4 @@ public class DisplayLinkDispatcher: NSObject { } } } -} \ No newline at end of file +} diff --git a/Display/Font.swift b/Display/Font.swift index 58cd061118..00ae0dda14 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -2,17 +2,17 @@ import Foundation import UIKit public struct Font { - public static func regular(size: CGFloat) -> UIFont { - return UIFont.systemFontOfSize(size) + public static func regular(_ size: CGFloat) -> UIFont { + return UIFont.systemFont(ofSize: size) } - public static func medium(size: CGFloat) -> UIFont { - return UIFont.boldSystemFontOfSize(size) + public static func medium(_ size: CGFloat) -> UIFont { + return UIFont.boldSystemFont(ofSize: size) } } -public extension NSAttributedString { - convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.blackColor()) { +public extension AttributedString { + convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black()) { self.init(string: string, attributes: [kCTFontAttributeName as String: font, kCTForegroundColorAttributeName as String: textColor]) } } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 410c5bc387..969ec517e5 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -2,56 +2,64 @@ import Foundation import UIKit let deviceColorSpace = CGColorSpaceCreateDeviceRGB() -let deviceScale = UIScreen.mainScreen().scale +let deviceScale = UIScreen.main().scale -public func generateImage(size: CGSize, generator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { +public func generateImage(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { let scale = deviceScale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) let length = bytesPerRow * Int(scaledSize.height) - let bytes = UnsafeMutablePointer(malloc(length)) - let provider: CGDataProvider? = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + let bytes = UnsafeMutablePointer(malloc(length)!) + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) }) - - generator(scaledSize, bytes) - - let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) - guard let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) - else { - return nil + else { + return nil } - return UIImage(CGImage: image, scale: scale, orientation: .Up) + pixelGenerator(scaledSize, bytes) + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + + return UIImage(cgImage: image, scale: scale, orientation: .up) } -public func generateImage(size: CGSize, opaque: Bool = false, generator: (CGSize, CGContextRef) -> Void) -> UIImage? { +public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false) -> UIImage? { let scale = deviceScale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) let length = bytesPerRow * Int(scaledSize.height) - let bytes = UnsafeMutablePointer(malloc(length)) - let provider: CGDataProvider? = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + let bytes = UnsafeMutablePointer(malloc(length)!) + + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) }) + else { + return nil + } - let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.NoneSkipFirst.rawValue : CGImageAlphaInfo.PremultipliedFirst.rawValue)) + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) - guard let context = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, bitmapInfo.rawValue) + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { return nil } - CGContextScaleCTM(context, scale, scale) + context.scale(x: scale, y: scale) - generator(size, context) + contextGenerator(size, context) - guard let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) - else { - return nil + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil } - return UIImage(CGImage: image, scale: scale, orientation: .Up) + return UIImage(cgImage: image, scale: scale, orientation: .up) } public enum DrawingContextBltMode { @@ -70,31 +78,33 @@ public class DrawingContext { private var _context: CGContext? - public func withContext(@noescape f: (CGContext) -> ()) { + public func withContext(_ f: @noescape(CGContext) -> ()) { if self._context == nil { - let c = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, self.bitmapInfo.rawValue) - CGContextScaleCTM(c, scale, scale) - self._context = c + if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { + c.scale(x: scale, y: scale) + self._context = c + } } if let _context = self._context { - CGContextTranslateCTM(_context, self.size.width / 2.0, self.size.height / 2.0) - CGContextScaleCTM(_context, 1.0, -1.0) - CGContextTranslateCTM(_context, -self.size.width / 2.0, -self.size.height / 2.0) + _context.translate(x: self.size.width / 2.0, y: self.size.height / 2.0) + _context.scale(x: 1.0, y: -1.0) + _context.translate(x: -self.size.width / 2.0, y: -self.size.height / 2.0) f(_context) - CGContextTranslateCTM(_context, self.size.width / 2.0, self.size.height / 2.0) - CGContextScaleCTM(_context, 1.0, -1.0) - CGContextTranslateCTM(_context, -self.size.width / 2.0, -self.size.height / 2.0) + _context.translate(x: self.size.width / 2.0, y: self.size.height / 2.0) + _context.scale(x: 1.0, y: -1.0) + _context.translate(x: -self.size.width / 2.0, y: -self.size.height / 2.0) } } - public func withFlippedContext(@noescape f: (CGContext) -> ()) { + public func withFlippedContext(_ f: @noescape(CGContext) -> ()) { if self._context == nil { - let c = CGBitmapContextCreate(bytes, Int(scaledSize.width), Int(scaledSize.height), 8, bytesPerRow, deviceColorSpace, self.bitmapInfo.rawValue) - CGContextScaleCTM(c, scale, scale) - self._context = c + if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { + c.scale(x: scale, y: scale) + self._context = c + } } if let _context = self._context { @@ -110,39 +120,39 @@ public class DrawingContext { self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) self.length = bytesPerRow * Int(scaledSize.height) - self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.ByteOrder32Little.rawValue | CGImageAlphaInfo.PremultipliedFirst.rawValue) + self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) self.bytes = UnsafeMutablePointer(malloc(length)) if clear { memset(self.bytes, 0, self.length) } - self.provider = CGDataProviderCreateWithData(bytes, bytes, length, { bytes, _, _ in + self.provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) }) } public func generateImage() -> UIImage? { - if let image = CGImageCreate(Int(scaledSize.width), Int(scaledSize.height), 8, 32, bytesPerRow, deviceColorSpace, bitmapInfo, provider, nil, false, .RenderingIntentDefault) { - return UIImage(CGImage: image, scale: scale, orientation: .Up) + if let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) { + return UIImage(cgImage: image, scale: scale, orientation: .up) } else { return nil } } - public func colorAt(point: CGPoint) -> UIColor { + public func colorAt(_ point: CGPoint) -> UIColor { let x = Int(point.x * self.scale) let y = Int(point.y * self.scale) if x >= 0 && x < Int(self.scaledSize.width) && y >= 0 && y < Int(self.scaledSize.height) { let srcLine = UnsafeMutablePointer(self.bytes + y * self.bytesPerRow) let pixel = srcLine + x - let colorValue = pixel.memory + let colorValue = pixel.pointee return UIColor(UInt32(colorValue)) } else { - return UIColor.clearColor() + return UIColor.clear() } } - public func blt(other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { + public func blt(_ other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { if abs(other.scale - self.scale) < CGFloat(FLT_EPSILON) { let srcX = 0 var srcY = 0 @@ -167,18 +177,18 @@ public class DrawingContext { let srcPixel = srcLine + sx let dstPixel = dstLine + dx - let baseColor = dstPixel.memory + let baseColor = dstPixel.pointee let baseR = (baseColor >> 16) & 0xff let baseG = (baseColor >> 8) & 0xff let baseB = baseColor & 0xff - let alpha = srcPixel.memory >> 24 + let alpha = srcPixel.pointee >> 24 let r = (baseR * alpha) / 255 let g = (baseG * alpha) / 255 let b = (baseB * alpha) / 255 - dstPixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + dstPixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b dx += 1 sx += 1 @@ -192,15 +202,15 @@ public class DrawingContext { } } -public enum ParsingError: ErrorType { +public enum ParsingError: ErrorProtocol { case Generic } -public func readCGFloat(inout index: UnsafePointer, end: UnsafePointer, separator: UInt8) throws -> CGFloat { +public func readCGFloat(_ index: inout UnsafePointer, end: UnsafePointer, separator: UInt8) throws -> CGFloat { let begin = index var seenPoint = false while index <= end { - let c = index.memory + let c = index.pointee index = index.successor() if c == 46 { // . @@ -220,18 +230,18 @@ public func readCGFloat(inout index: UnsafePointer, end: UnsafePointer(begin), length: index - begin, encoding: NSUTF8StringEncoding)?.floatValue { + if let value = NSString(bytes: UnsafePointer(begin), length: index - begin, encoding: String.Encoding.utf8.rawValue)?.floatValue { return CGFloat(value) } else { throw ParsingError.Generic } } -public func drawSvgPath(context: CGContextRef, path: StaticString) throws { +public func drawSvgPath(_ context: CGContext, path: StaticString) throws { var index: UnsafePointer = path.utf8Start - let end = path.utf8Start.advancedBy(path.byteSize) + let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount) while index < end { - let c = index.memory + let c = index.pointee index = index.successor() if c == 77 { // M @@ -239,13 +249,13 @@ public func drawSvgPath(context: CGContextRef, path: StaticString) throws { let y = try readCGFloat(&index, end: end, separator: 32) //print("Move to \(x), \(y)") - CGContextMoveToPoint(context, x, y) + context.moveTo(x: x, y: y) } else if c == 76 { // L let x = try readCGFloat(&index, end: end, separator: 44) let y = try readCGFloat(&index, end: end, separator: 32) //print("Line to \(x), \(y)") - CGContextAddLineToPoint(context, x, y) + context.addLineTo(x: x, y: y) } else if c == 67 { // C let x1 = try readCGFloat(&index, end: end, separator: 44) let y1 = try readCGFloat(&index, end: end, separator: 32) @@ -253,17 +263,17 @@ public func drawSvgPath(context: CGContextRef, path: StaticString) throws { let y2 = try readCGFloat(&index, end: end, separator: 32) let x = try readCGFloat(&index, end: end, separator: 44) let y = try readCGFloat(&index, end: end, separator: 32) - CGContextAddCurveToPoint(context, x1, y1, x2, y2, x, y) + context.addCurveTo(cp1x: x1, cp1y: y1, cp2x: x2, cp2y: y2, endingAtX: x, y: y) //print("Line to \(x), \(y)") } else if c == 90 { // Z - if index != end && index.memory != 32 { + if index != end && index.pointee != 32 { throw ParsingError.Generic } //CGContextClosePath(context) - CGContextFillPath(context) + context.fillPath() //CGContextBeginPath(context) //print("Close") } diff --git a/Display/ImageCache.swift b/Display/ImageCache.swift index de4913e500..6e1d6efd85 100644 --- a/Display/ImageCache.swift +++ b/Display/ImageCache.swift @@ -1,6 +1,6 @@ import Foundation -private final class ImageCacheData { +/*private final class ImageCacheData { let size: CGSize let bytesPerRow: Int var data: NSPurgeableData @@ -16,7 +16,7 @@ private final class ImageCacheData { return nil } - init(size: CGSize, generator: CGContextRef -> Void, @noescape takenImage: UIImage -> Void) { + init(size: CGSize, generator: (CGContext) -> Void, takenImage: @noescape(UIImage) -> Void) { self.size = size self.bytesPerRow = (4 * Int(size.width) + 15) & (~15) @@ -43,7 +43,7 @@ private final class ImageCacheData { private func createImage() -> UIImage { let colorSpace = CGColorSpaceCreateDeviceRGB() - let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue + let bitmapInfo = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue let unmanagedData = withUnsafePointer(&self.data, { pointer in return Unmanaged.fromOpaque(COpaquePointer(pointer)) @@ -151,4 +151,4 @@ public final class ImageCache { self.nextAccessIndex += 1 self.residentImages[key] = ImageCacheResidentImage(key: key, image: image, accessIndex: self.nextAccessIndex) } -} +}*/ diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index 4526e27c41..e15db8edc5 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -5,7 +5,7 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { var validatedGesture = false var firstLocation: CGPoint = CGPoint() - override init(target: AnyObject?, action: Selector) { + override init(target: AnyObject?, action: Selector?) { super.init(target: target, action: action) self.maximumNumberOfTouches = 1 @@ -17,30 +17,30 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { validatedGesture = false } - override func touchesBegan(touches: Set, withEvent event: UIEvent) { - super.touchesBegan(touches, withEvent: event) + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) - self.firstLocation = touches.first!.locationInView(self.view) + self.firstLocation = touches.first!.location(in: self.view) } - override func touchesMoved(touches: Set, withEvent event: UIEvent) { - let location = touches.first!.locationInView(self.view) + override func touchesMoved(_ touches: Set, with event: UIEvent) { + let location = touches.first!.location(in: self.view) let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) if !validatedGesture { if self.firstLocation.x < 16.0 { validatedGesture = true } else if translation.x < 0.0 { - self.state = .Failed + self.state = .failed } else if abs(translation.y) > 2.0 && abs(translation.y) > abs(translation.x) * 2.0 { - self.state = .Failed + self.state = .failed } else if abs(translation.x) > 2.0 && abs(translation.y) * 2.0 < abs(translation.x) { validatedGesture = true } } if validatedGesture { - super.touchesMoved(touches, withEvent: event) + super.touchesMoved(touches, with: event) } } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 34589c4cde..78c409800e 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -97,7 +97,7 @@ public struct ListViewInsertItem { } } -public struct ListViewDeleteAndInsertOptions: OptionSetType { +public struct ListViewDeleteAndInsertOptions: OptionSet { public let rawValue: Int public init(rawValue: Int) { @@ -146,7 +146,7 @@ private struct IndexRange { let first: Int let last: Int - func contains(index: Int) -> Bool { + func contains(_ index: Int) -> Bool { return index >= first && index <= last } @@ -158,15 +158,15 @@ private struct IndexRange { private struct OffsetRanges { var offsets: [(IndexRange, CGFloat)] = [] - mutating func append(other: OffsetRanges) { - self.offsets.appendContentsOf(other.offsets) + mutating func append(_ other: OffsetRanges) { + self.offsets.append(contentsOf: other.offsets) } - mutating func offset(indexRange: IndexRange, offset: CGFloat) { + mutating func offset(_ indexRange: IndexRange, offset: CGFloat) { self.offsets.append((indexRange, offset)) } - func offsetForIndex(index: Int) -> CGFloat { + func offsetForIndex(_ index: Int) -> CGFloat { var result: CGFloat = 0.0 for offset in self.offsets { if offset.0.contains(index) { @@ -177,7 +177,7 @@ private struct OffsetRanges { } } -private func binarySearch(inputArr: [Int], searchItem: Int) -> Int? { +private func binarySearch(_ inputArr: [Int], searchItem: Int) -> Int? { var lowerIndex = 0; var upperIndex = inputArr.count - 1 @@ -283,7 +283,7 @@ private struct ListViewState { var scrollPosition: (Int, ListViewScrollPosition)? var stationaryOffset: (Int, CGFloat)? - mutating func fixScrollPostition(itemCount: Int) { + mutating func fixScrollPostition(_ itemCount: Int) { if let (fixedIndex, fixedPosition) = self.scrollPosition { for node in self.nodes { if let index = node.index where index == fixedIndex { @@ -307,7 +307,7 @@ private struct ListViewState { } } - var minY: CGFloat = CGFloat.max + var minY: CGFloat = CGFloat.greatestFiniteMagnitude var maxY: CGFloat = 0.0 for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame @@ -357,7 +357,7 @@ private struct ListViewState { } } - mutating func setupStationaryOffset(index: Int, boundary: Int, frames: [Int: CGRect]) { + mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) { if index < boundary { for node in self.nodes { if let nodeIndex = node.index where nodeIndex >= index { @@ -368,7 +368,7 @@ private struct ListViewState { } } } else { - for node in self.nodes.reverse() { + for node in self.nodes.reversed() { if let nodeIndex = node.index where nodeIndex <= index { if let frame = frames[nodeIndex] { self.stationaryOffset = (nodeIndex, frame.minY) @@ -379,7 +379,7 @@ private struct ListViewState { } } - mutating func snapToBounds(itemCount: Int, snapTopItem: Bool) { + mutating func snapToBounds(_ itemCount: Int, snapTopItem: Bool) { var completeHeight: CGFloat = 0.0 var topItemFound = false var bottomItemFound = false @@ -396,7 +396,7 @@ private struct ListViewState { } } - for node in self.nodes.reverse() { + for node in self.nodes.reversed() { if let index = node.index { if index == itemCount - 1 { bottomItemFound = true @@ -441,7 +441,7 @@ private struct ListViewState { } } - func insertionPoint(insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { + func insertionPoint(_ insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)? if let (fixedIndex, _) = self.scrollPosition { @@ -481,7 +481,7 @@ private struct ListViewState { } if fixedNode == nil && self.nodes.count != 0 { - for i in (0 ..< self.nodes.count).reverse() { + for i in (0 ..< self.nodes.count).reversed() { let node = self.nodes[i] if let index = node.index { fixedNode = (i, index, node.frame) @@ -492,7 +492,7 @@ private struct ListViewState { if let fixedNode = fixedNode { var currentUpperNode = fixedNode - for i in (0 ..< fixedNode.nodeIndex).reverse() { + for i in (0 ..< fixedNode.nodeIndex).reversed() { let node = self.nodes[i] if let index = node.index { if index != currentUpperNode.index - 1 { @@ -554,7 +554,7 @@ private struct ListViewState { return nil } - mutating func removeInvisibleNodes(inout operations: [ListViewStateOperation]) { + mutating func removeInvisibleNodes(_ operations: inout [ListViewStateOperation]) { var i = 0 var visibleItemNodeHeight: CGFloat = 0.0 while i < self.nodes.count { @@ -570,7 +570,7 @@ private struct ListViewState { if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { //print("remove \(i)") operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) - self.nodes.removeAtIndex(i) + self.nodes.remove(at: i) } i -= 1 @@ -583,13 +583,13 @@ private struct ListViewState { if let index = node.index where node.frame.maxY > upperBound { if i != 0 { var previousIndex = index - for j in (0 ..< i).reverse() { + for j in (0 ..< i).reversed() { if self.nodes[j].frame.maxY < upperBound { if let index = self.nodes[j].index { if index != previousIndex - 1 { print("remove monotonity \(j) (\(index))") operations.append(.Remove(index: j, offsetDirection: .Down)) - self.nodes.removeAtIndex(j) + self.nodes.remove(at: j) } else { previousIndex = index } @@ -602,7 +602,7 @@ private struct ListViewState { } let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) - for i in (0 ..< self.nodes.count).reverse() { + for i in (0 ..< self.nodes.count).reversed() { let node = self.nodes[i] if let index = node.index where node.frame.minY < lowerBound { if i != self.nodes.count - 1 { @@ -620,10 +620,10 @@ private struct ListViewState { } } if !removeIndices.isEmpty { - for i in removeIndices.reverse() { + for i in removeIndices.reversed() { print("remove monotonity \(i) (\(self.nodes[i].index!))") operations.append(.Remove(index: i, offsetDirection: .Up)) - self.nodes.removeAtIndex(i) + self.nodes.remove(at: i) } } } @@ -632,7 +632,7 @@ private struct ListViewState { } } - func nodeInsertionPointAndIndex(itemIndex: Int) -> (CGPoint, Int) { + func nodeInsertionPointAndIndex(_ itemIndex: Int) -> (CGPoint, Int) { if self.nodes.count == 0 { return (CGPoint(x: 0.0, y: self.insets.top), 0) } else { @@ -652,14 +652,14 @@ private struct ListViewState { } } - func continuousHeightRelativeToNodeIndex(fixedNodeIndex: Int) -> CGFloat { + func continuousHeightRelativeToNodeIndex(_ fixedNodeIndex: Int) -> CGFloat { let fixedIndex = self.nodes[fixedNodeIndex].index! var height: CGFloat = 0.0 if fixedNodeIndex != 0 { var upperIndex = fixedIndex - for i in (0 ..< fixedNodeIndex).reverse() { + for i in (0 ..< fixedNodeIndex).reversed() { if let index = self.nodes[i].index { if index == upperIndex - 1 { height += self.nodes[i].frame.size.height @@ -688,7 +688,7 @@ private struct ListViewState { return height } - mutating func insertNode(itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, inout operations: [ListViewStateOperation], itemCount: Int) { + mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) let nodeOrigin: CGPoint @@ -702,7 +702,7 @@ private struct ListViewState { let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) - self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), atIndex: insertionIndex) + self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex) if !animated { switch offsetDirection { @@ -730,11 +730,11 @@ private struct ListViewState { } } - mutating func removeNodeAtIndex(index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, inout operations: [ListViewStateOperation]) { + mutating func removeNodeAtIndex(_ index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, operations: inout [ListViewStateOperation]) { let node = self.nodes[index] if case let .Node(_, _, referenceNode) = node { let nodeFrame = node.frame - self.nodes.removeAtIndex(index) + self.nodes.remove(at: index) let offsetDirection: ListViewInsertionOffsetDirection if let direction = direction { offsetDirection = ListViewInsertionOffsetDirection(direction) @@ -748,12 +748,12 @@ private struct ListViewState { operations.append(.Remove(index: index, offsetDirection: offsetDirection)) if let referenceNode = referenceNode where animated { - self.nodes.insert(.Placeholder(frame: nodeFrame), atIndex: index) + self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) operations.append(.InsertPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) } else { if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { if let direction = direction where direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - for i in (0 ..< index).reverse() { + for i in (0 ..< index).reversed() { var frame = self.nodes[i].frame frame.origin.y += nodeFrame.size.height self.nodes[i].frame = frame @@ -766,7 +766,7 @@ private struct ListViewState { } } } else if index != 0 { - for i in (0 ..< index).reverse() { + for i in (0 ..< index).reversed() { var frame = self.nodes[i].frame frame.origin.y += nodeFrame.size.height self.nodes[i].frame = frame @@ -811,20 +811,20 @@ private final class ListViewBackingView: UIView { override func layoutSubviews() { } - override func touchesBegan(touches: Set, withEvent event: UIEvent?) { - self.target?.touchesBegan(touches, withEvent: event) + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.target?.touchesBegan(touches, with: event) } - override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { - self.target?.touchesCancelled(touches, withEvent: event) + override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + self.target?.touchesCancelled(touches, with: event) } - override func touchesMoved(touches: Set, withEvent event: UIEvent?) { - self.target?.touchesMoved(touches, withEvent: event) + override func touchesMoved(_ touches: Set, with event: UIEvent?) { + self.target?.touchesMoved(touches, with: event) } - override func touchesEnded(touches: Set, withEvent event: UIEvent?) { - self.target?.touchesEnded(touches, withEvent: event) + override func touchesEnded(_ touches: Set, with event: UIEvent?) { + self.target?.touchesEnded(touches, with: event) } } @@ -881,7 +881,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { private final var items: [ListViewItem] = [] private final var itemNodes: [ListViewItemNode] = [] - public final var displayedItemRangeChanged: ListViewDisplayedItemRange -> Void = { _ in } + public final var displayedItemRangeChanged: (ListViewDisplayedItemRange) -> Void = { _ in } public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) public final var visibleContentOffsetChanged: (CGFloat?) -> Void = { _ in } @@ -898,7 +898,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { //let performanceTracker: FBAnimationPerformanceTracker private var selectionTouchLocation: CGPoint? - private var selectionTouchDelayTimer: NSTimer? + private var selectionTouchDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? public func reportDurationInMS(duration: Int, smallDropEvent: Double, largeDropEvent: Double) { @@ -931,7 +931,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { super.init(viewBlock: { Void -> UIView in return ListViewBackingView() - }, didLoadBlock: nil) + }, didLoad: nil) (self.view as! ListViewBackingView).target = self @@ -945,21 +945,21 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.scroller.alwaysBounceVertical = true self.scroller.contentSize = CGSize(width: 0.0, height: infiniteScrollSize * 2.0) - self.scroller.hidden = true + self.scroller.isHidden = true self.scroller.delegate = self self.view.addSubview(self.scroller) self.scroller.panGestureRecognizer.cancelsTouchesInView = false self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) - self.displayLink.addToRunLoop(NSRunLoop.mainRunLoop(), forMode: NSRunLoopCommonModes) - self.displayLink.paused = true + self.displayLink.add(to: RunLoop.main(), forMode: RunLoopMode.commonModes.rawValue) + self.displayLink.isPaused = true if useDynamicTuning { - self.frictionSlider.addTarget(self, action: #selector(self.frictionSliderChanged(_:)), forControlEvents: .ValueChanged) - self.springSlider.addTarget(self, action: #selector(self.springSliderChanged(_:)), forControlEvents: .ValueChanged) - self.freeResistanceSlider.addTarget(self, action: #selector(self.freeResistanceSliderChanged(_:)), forControlEvents: .ValueChanged) - self.scrollingResistanceSlider.addTarget(self, action: #selector(self.scrollingResistanceSliderChanged(_:)), forControlEvents: .ValueChanged) + self.frictionSlider.addTarget(self, action: #selector(self.frictionSliderChanged(_:)), for: .valueChanged) + self.springSlider.addTarget(self, action: #selector(self.springSliderChanged(_:)), for: .valueChanged) + self.freeResistanceSlider.addTarget(self, action: #selector(self.freeResistanceSliderChanged(_:)), for: .valueChanged) + self.scrollingResistanceSlider.addTarget(self, action: #selector(self.scrollingResistanceSliderChanged(_:)), for: .valueChanged) self.frictionSlider.minimumValue = Float(testSpringFrictionLimits.0) self.frictionSlider.maximumValue = Float(testSpringFrictionLimits.1) @@ -988,22 +988,22 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.pauseAnimations() } - @objc func frictionSliderChanged(slider: UISlider) { + @objc func frictionSliderChanged(_ slider: UISlider) { testSpringFriction = CGFloat(slider.value) print("friction: \(testSpringFriction)") } - @objc func springSliderChanged(slider: UISlider) { + @objc func springSliderChanged(_ slider: UISlider) { testSpringConstant = CGFloat(slider.value) print("spring: \(testSpringConstant)") } - @objc func freeResistanceSliderChanged(slider: UISlider) { + @objc func freeResistanceSliderChanged(_ slider: UISlider) { testSpringFreeResistance = CGFloat(slider.value) print("free resistance: \(testSpringFreeResistance)") } - @objc func scrollingResistanceSliderChanged(slider: UISlider) { + @objc func scrollingResistanceSliderChanged(_ slider: UISlider) { testSpringScrollingResistance = CGFloat(slider.value) print("free resistance: \(testSpringScrollingResistance)") } @@ -1015,19 +1015,19 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { private func setNeedsAnimations() { if !self.needsAnimations { self.needsAnimations = true - self.displayLink.paused = false + self.displayLink.isPaused = false } } private func pauseAnimations() { if self.needsAnimations { self.needsAnimations = false - self.displayLink.paused = true + self.displayLink.isPaused = true } } private func dispatchOnVSync(forceNext: Bool = false, action: () -> ()) { - Queue.mainQueue().dispatch { + Queue.mainQueue().async { if !forceNext && self.inVSync { action() } else { @@ -1037,7 +1037,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - public func scrollViewWillBeginDragging(scrollView: UIScrollView) { + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.lastContentOffsetTimestamp = 0.0 /*if usePerformanceTracker { @@ -1045,7 +1045,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }*/ } - public func scrollViewDidEndDragging(scrollView: UIScrollView, willDecelerate decelerate: Bool) { + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if decelerate { self.lastContentOffsetTimestamp = CACurrentMediaTime() } else { @@ -1056,14 +1056,14 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - public func scrollViewDidEndDecelerating(scrollView: UIScrollView) { + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.lastContentOffsetTimestamp = 0.0 /*if usePerformanceTracker { self.performanceTracker.stop() }*/ } - public func scrollViewDidScroll(scrollView: UIScrollView) { + public func scrollViewDidScroll(_ scrollView: UIScrollView) { if self.ignoreScrollingEvents || scroller !== self.scroller { return } @@ -1130,7 +1130,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { //CATransaction.commit() } - private func snapToBounds(snapTopItem: Bool = false) { + private func snapToBounds(_ snapTopItem: Bool = false) { if self.itemNodes.count == 0 { return } @@ -1263,22 +1263,20 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.ignoreScrollingEvents = false } - private func async(f: () -> Void) { - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), { - f() - }) + private func async(_ f: () -> Void) { + DispatchQueue.global().async(execute: f) } private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { if let previousNode = previousNode { - item.updateNode({ f in + item.updateNode(async: { f in if synchronous { f() } else { self.async(f) } }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in - if NSThread.isMainThread() { + if Thread.isMainThread() { if synchronous { completion(previousNode, layout, { previousNode.index = index @@ -1300,7 +1298,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } }) } else { - item.nodeConfiguredForWidth({ f in + item.nodeConfiguredForWidth(async: { f in if synchronous { f() } else { @@ -1326,7 +1324,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) } - public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: ListViewDisplayedItemRange -> Void = { _ in }) { + public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.count == 0 && insertIndicesAndItems.count == 0 && scrollToItem == nil && updateSizeAndInsets == nil { completion(self.immediateDisplayedItemRange()) return @@ -1335,7 +1333,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.transactionQueue.addTransaction({ [weak self] transactionCompletion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.deleteAndInsertItemsTransaction(deleteIndices, insertIndicesAndItems: insertIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, completion: { [weak strongSelf] in + strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, completion: { [weak strongSelf] in completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)) transactionCompletion() @@ -1344,7 +1342,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: Void -> Void) { + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: (Void) -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && scrollToItem == nil { if let updateSizeAndInsets = updateSizeAndInsets where self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { self.visibleSize = updateSizeAndInsets.size @@ -1352,8 +1350,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size) - self.scroller.contentSize = CGSizeMake(updateSizeAndInsets.size.width, infiniteScrollSize * 2.0) - self.lastContentOffset = CGPointMake(0.0, infiniteScrollSize) + self.scroller.contentSize = CGSize(width: updateSizeAndInsets.size.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset self.updateScroller() @@ -1381,12 +1379,12 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } state.fixScrollPostition(self.items.count) - let sortedDeleteIndices = deleteIndices.sort({$0.index < $1.index}) - for deleteItem in sortedDeleteIndices.reverse() { - self.items.removeAtIndex(deleteItem.index) + let sortedDeleteIndices = deleteIndices.sorted(isOrderedBefore: {$0.index < $1.index}) + for deleteItem in sortedDeleteIndices.reversed() { + self.items.remove(at: deleteItem.index) } - let sortedIndicesAndItems = insertIndicesAndItems.sort { $0.index < $1.index } + let sortedIndicesAndItems = insertIndicesAndItems.sorted(isOrderedBefore: { $0.index < $1.index }) if self.items.count == 0 { if sortedIndicesAndItems[0].index != 0 { fatalError("deleteAndInsertItems: invalid insert into empty list") @@ -1406,7 +1404,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var previousNodes: [Int: ListViewItemNode] = [:] for insertedItem in sortedIndicesAndItems { - self.items.insert(insertedItem.item, atIndex: insertedItem.index) + self.items.insert(insertedItem.item, at: insertedItem.index) if let previousIndex = insertedItem.previousIndex { for itemNode in self.itemNodes { if itemNode.index == previousIndex { @@ -1463,7 +1461,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if deleteIndexSet.contains(index) { - previousFrames.removeValueForKey(index) + previousFrames.removeValue(forKey: index) state.removeNodeAtIndex(i, direction: deleteDirectionHints[index], animated: animated, operations: &operations) } else { let updatedIndex = index - indexOffset @@ -1471,7 +1469,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { remapDeletion[index] = updatedIndex } if let previousFrame = previousFrames[index] { - previousFrames.removeValueForKey(index) + previousFrames.removeValue(forKey: index) previousFrames[updatedIndex] = previousFrame } if deleteIndexSet.contains(index - 1) || deleteIndexSet.contains(index + 1) { @@ -1513,7 +1511,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { remapInsertion[index] = updatedIndex if let previousFrame = previousFrames[index] { - previousFrames.removeValueForKey(index) + previousFrames.removeValue(forKey: index) previousFrames[updatedIndex] = previousFrame } @@ -1564,7 +1562,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms") } - self.fillMissingNodes(options.contains(.Synchronous), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in + self.fillMissingNodes(synchronous: options.contains(.Synchronous), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in if self.debugInfo { print("fillMissingNodes completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") @@ -1577,7 +1575,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.updateAdjacent(options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in + self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in var updatedState = state var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) @@ -1589,11 +1587,11 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let stationaryItemIndex = updatedState.stationaryOffset?.0 let next = { - self.replayOperations(animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, completion: completion) + self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, completion: completion) } if options.contains(.LowLatency) || options.contains(.Synchronous) { - Queue.mainQueue().dispatch { + Queue.mainQueue().async { if self.debugInfo { print("updateAdjacent LowLatency enqueue \((CACurrentMediaTime() - startTime) * 1000.0) ms") } @@ -1631,7 +1629,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if case let .Node(index, _, referenceNode) = node where index == nodeIndex { if let referenceNode = referenceNode { continueWithoutNode = false - self.items[index].updateNode({ f in + self.items[index].updateNode(async: { f in if synchronous { f() } else { @@ -1650,7 +1648,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.updateAdjacent(synchronous, animated: animated, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion) + self.updateAdjacent(synchronous: synchronous, animated: animated, state: updatedState, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: updatedOperations, completion: completion) }) } break @@ -1659,7 +1657,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if continueWithoutNode { - updateAdjacent(synchronous, animated: animated, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion) + updateAdjacent(synchronous: synchronous, animated: animated, state: state, updateAdjacentItemsIndices: updatedUpdateAdjacentItemsIndices, operations: operations, completion: completion) } } } @@ -1695,7 +1693,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let index = insertionItemIndexAndDirection.0 let threadId = pthread_self() var tailRecurse = false - self.nodeForItem(synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, updateAnimation: updateAnimation, completion: { (node, layout, apply) in + self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, updateAnimation: updateAnimation, completion: { (node, layout, apply) in if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { tailRecurse = true @@ -1704,7 +1702,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var updatedState = state var updatedOperations = operations updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations, itemCount: self.items.count) - self.fillMissingNodes(synchronous, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion) + self.fillMissingNodes(synchronous: synchronous, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion) } }) if !tailRecurse { @@ -1719,7 +1717,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func referencePointForInsertionAtIndex(nodeIndex: Int) -> CGPoint { + private func referencePointForInsertionAtIndex(_ nodeIndex: Int) -> CGPoint { var index = 0 for itemNode in self.itemNodes { if index == nodeIndex { @@ -1759,10 +1757,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { node.apparentHeight = animated ? 0.0 : layout.size.height node.frame = nodeFrame apply() - self.itemNodes.insert(node, atIndex: nodeIndex) + self.itemNodes.insert(node, at: nodeIndex) if useDynamicTuning { - self.insertSubnode(node, atIndex: 0) + self.insertSubnode(node, at: 0) } else { //self.addSubnode(node) } @@ -1880,7 +1878,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { break } } - self.insertNodeAtIndex(animated, animateAlpha: animateAlpha, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) self.addSubnode(node) case let .InsertPlaceholder(index, referenceNode, offsetDirection): var height: CGFloat? @@ -1893,7 +1891,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if let height = height { - self.insertNodeAtIndex(false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) } else { assertionFailure() } @@ -1919,7 +1917,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } case .Down: if index != 0 { - for i in (0 ..< index).reverse() { + for i in (0 ..< index).reversed() { var frame = self.itemNodes[i].frame frame.origin.y += height self.itemNodes[i].frame = frame @@ -2051,7 +2049,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.insertNodesInBatches([], completion: { + self.insertNodesInBatches(nodes: [], completion: { self.debugCheckMonotonity() var sizeAndInsetsOffset: CGFloat = 0.0 @@ -2076,37 +2074,32 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let animation: CABasicAnimation switch updateSizeAndInsets.curve { case let .Spring(speed): - let springAnimation = CASpringAnimation(keyPath: "sublayerTransform") - springAnimation.mass = 3.0 - springAnimation.stiffness = 1000.0 - springAnimation.damping = 500.0 - springAnimation.initialVelocity = 0.0 + let springAnimation = makeSpringAnimation("sublayerTransform") springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) - springAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - springAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) - springAnimation.removedOnCompletion = true - springAnimation.additive = true - springAnimation.duration = springAnimation.settlingDuration + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + springAnimation.isAdditive = true animation = springAnimation case .Default: let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) basicAnimation.duration = updateSizeAndInsets.duration * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - basicAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) - basicAnimation.removedOnCompletion = true - basicAnimation.additive = true + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true animation = basicAnimation } - self.layer.addAnimation(animation, forKey: nil) + self.layer.add(animation, forKey: nil) } } self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) - self.scroller.contentSize = CGSizeMake(self.visibleSize.width, infiniteScrollSize * 2.0) - self.lastContentOffset = CGPointMake(0.0, infiniteScrollSize) + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset } @@ -2117,7 +2110,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { print("item \(itemNode.index) frame \(itemNode.frame)") }*/ - self.updateAccessoryNodes(animated, currentTimestamp: timestamp) + self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) if self.itemNodes.count != 0 { var offset: CGFloat? @@ -2166,28 +2159,23 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let animation: CABasicAnimation switch scrollToItem.curve { case let .Spring(speed): - let springAnimation = CASpringAnimation(keyPath: "sublayerTransform") - springAnimation.mass = 3.0 - springAnimation.stiffness = 1000.0 - springAnimation.damping = 500.0 - springAnimation.initialVelocity = 0.0 - springAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) - springAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) - springAnimation.removedOnCompletion = true - springAnimation.additive = true + let springAnimation = makeSpringAnimation("sublayerTransform") + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + springAnimation.isAdditive = true springAnimation.fillMode = kCAFillModeForwards springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) - springAnimation.duration = springAnimation.settlingDuration animation = springAnimation case .Default: let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) basicAnimation.duration = 0.5 * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) - basicAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) - basicAnimation.removedOnCompletion = true - basicAnimation.additive = true + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true animation = basicAnimation } animation.completion = { _ in @@ -2195,7 +2183,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { itemNode.removeFromSupernode() } } - self.layer.addAnimation(animation, forKey: nil) + self.layer.add(animation, forKey: nil) } } @@ -2210,7 +2198,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { completion() } else { - self.updateAccessoryNodes(animated, currentTimestamp: timestamp) + self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) if animated { self.setNeedsAnimations() } @@ -2231,13 +2219,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if nodes.count == 0 { completion() } else { - let startTime = CFAbsoluteTimeGetCurrent() for node in nodes { self.addSubnode(node) } - if self.debugInfo { - //print("insertNodesInBatches \((CFAbsoluteTimeGetCurrent() - startTime) * 1000.0)") - } completion() } } @@ -2255,9 +2239,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func removeItemNodeAtIndex(index: Int) { + private func removeItemNodeAtIndex(_ index: Int) { let node = self.itemNodes[index] - self.itemNodes.removeAtIndex(index) + self.itemNodes.remove(at: index) node.removeFromSupernode() node.accessoryItemNode?.removeFromSupernode() @@ -2341,7 +2325,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.transactionQueue.addTransaction({ [weak self] completion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.updateVisibleItemsTransaction({ + strongSelf.updateVisibleItemsTransaction(completion: { var repeatUpdate = false if let strongSelf = self { repeatUpdate = abs(strongSelf.transactionOffset) > 0.00001 @@ -2377,13 +2361,13 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.fillMissingNodes(false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: self.currentState(), inputPreviousNodes: [:], inputOperations: []) { state, operations in + self.fillMissingNodes(synchronous: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: self.currentState(), inputPreviousNodes: [:], inputOperations: []) { state, operations in var updatedState = state var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) self.dispatchOnVSync { - self.replayOperations(false, animateAlpha: false, operations: updatedOperations, scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, operations: updatedOperations, scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, completion: completion) } } } @@ -2434,7 +2418,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if let firstVisibleIndex = firstVisibleIndex { var lastVisibleIndex: Int? - for i in (firstIndex.nodeIndex ... lastIndex.nodeIndex).reverse() { + for i in (firstIndex.nodeIndex ... lastIndex.nodeIndex).reversed() { if let index = self.itemNodes[i].index { let frame = self.itemNodes[i].apparentFrame if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height - self.insets.bottom { @@ -2460,10 +2444,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.transactionQueue.addTransaction({ [weak self] completion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.updateSizeAndInsetsTransaction(size, insets: insets, duration: duration, options: options, completion: { [weak self] in + strongSelf.updateSizeAndInsetsTransaction(size: size, insets: insets, duration: duration, options: options, completion: { [weak self] in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.updateVisibleItemsTransaction(completion) + strongSelf.updateVisibleItemsTransaction(completion: completion) } }) } @@ -2477,8 +2461,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateSizeAndInsetsTransaction(size: CGSize, insets: UIEdgeInsets, duration: Double, options: UIViewAnimationOptions, completion: Void -> Void) { - if CGSizeEqualToSize(size, self.visibleSize) && UIEdgeInsetsEqualToEdgeInsets(self.insets, insets) { + private func updateSizeAndInsetsTransaction(size: CGSize, insets: UIEdgeInsets, duration: Double, options: UIViewAnimationOptions, completion: (Void) -> Void) { + if size.equalTo(self.visibleSize) && UIEdgeInsetsEqualToEdgeInsets(self.insets, insets) { completion() } else { if abs(size.width - self.visibleSize.width) > CGFloat(FLT_EPSILON) { @@ -2552,8 +2536,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: size) - self.scroller.contentSize = CGSizeMake(size.width, infiniteScrollSize * 2.0) - self.lastContentOffset = CGPointMake(0.0, infiniteScrollSize) + self.scroller.contentSize = CGSize(width: size.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset self.updateScroller() @@ -2561,7 +2545,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let completion = { [weak self] (_: Bool) -> Void in if let strongSelf = self { - strongSelf.updateVisibleItemsTransaction(completion) + strongSelf.updateVisibleItemsTransaction(completion: completion) strongSelf.ignoreScrollingEvents = false } } @@ -2569,28 +2553,24 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if duration > DBL_EPSILON { let animation: CABasicAnimation if (options.rawValue & UInt(7 << 16)) != 0 { - let springAnimation = CASpringAnimation(keyPath: "sublayerTransform") - springAnimation.mass = 3.0 - springAnimation.stiffness = 1000.0 - springAnimation.damping = 500.0 - springAnimation.initialVelocity = 0.0 + let springAnimation = makeSpringAnimation("sublayerTransform") springAnimation.duration = duration * UIView.animationDurationFactor() - springAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - springAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) - springAnimation.removedOnCompletion = true + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true animation = springAnimation } else { let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) basicAnimation.duration = duration * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(CATransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - basicAnimation.toValue = NSValue(CATransform3D: CATransform3DIdentity) - basicAnimation.removedOnCompletion = true + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true animation = basicAnimation } animation.completion = completion - self.layer.addAnimation(animation, forKey: "sublayerTransform") + self.layer.add(animation, forKey: "sublayerTransform") } else { completion(true) } @@ -2621,7 +2601,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { animation.applyAt(timestamp) if animation.completeAt(timestamp) { - animations.removeAtIndex(i) + animations.remove(at: i) animationCount -= 1 i -= 1 } else { @@ -2687,13 +2667,13 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - override public func touchesBegan(touches: Set, withEvent event: UIEvent?) { + override public func touchesBegan(_ touches: Set, with event: UIEvent?) { self.isTracking = true - self.touchesPosition = (touches.first!).locationInView(self.view) + self.touchesPosition = (touches.first!).location(in: self.view) self.selectionTouchLocation = self.touchesPosition self.selectionTouchDelayTimer?.invalidate() - let timer = NSTimer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in + let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in if let strongSelf = self where strongSelf.selectionTouchLocation != nil { strongSelf.clearHighlightAnimated(false) let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) @@ -2703,8 +2683,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { if itemNode.index == index { - if !itemNode.layerBacked { - strongSelf.view.bringSubviewToFront(itemNode.view) + if !itemNode.isLayerBacked { + strongSelf.view.bringSubview(toFront: itemNode.view) } itemNode.setHighlighted(true, animated: false) break @@ -2715,14 +2695,14 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) self.selectionTouchDelayTimer = timer - NSRunLoop.mainRunLoop().addTimer(timer, forMode: NSRunLoopCommonModes) + RunLoop.main().add(timer, forMode: RunLoopMode.commonModes) - super.touchesBegan(touches, withEvent: event) + super.touchesBegan(touches, with: event) self.updateScroller() } - public func clearHighlightAnimated(animated: Bool) { + public func clearHighlightAnimated(_ animated: Bool) { if let highlightedItemIndex = self.highlightedItemIndex { for itemNode in self.itemNodes { if itemNode.index == highlightedItemIndex { @@ -2734,7 +2714,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.highlightedItemIndex = nil } - private func itemIndexAtPoint(point: CGPoint) -> Int? { + private func itemIndexAtPoint(_ point: CGPoint) -> Int? { for itemNode in self.itemNodes { if itemNode.apparentFrame.contains(point) { return itemNode.index @@ -2743,8 +2723,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return nil } - override public func touchesMoved(touches: Set, withEvent event: UIEvent?) { - self.touchesPosition = touches.first!.locationInView(self.view) + override public func touchesMoved(_ touches: Set, with event: UIEvent?) { + self.touchesPosition = touches.first!.location(in: self.view) if let selectionTouchLocation = self.selectionTouchLocation { let distance = CGPoint(x: selectionTouchLocation.x - self.touchesPosition.x, y: selectionTouchLocation.y - self.touchesPosition.y) let maxMovementDistance: CGFloat = 4.0 @@ -2756,10 +2736,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - super.touchesMoved(touches, withEvent: event) + super.touchesMoved(touches, with: event) } - override public func touchesEnded(touches: Set, withEvent event: UIEvent?) { + override public func touchesEnded(_ touches: Set, with event: UIEvent?) { self.isTracking = false if let selectionTouchLocation = self.selectionTouchLocation { @@ -2773,8 +2753,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.highlightedItemIndex = index for itemNode in self.itemNodes { if itemNode.index == index { - if !itemNode.layerBacked { - self.view.bringSubviewToFront(itemNode.view) + if !itemNode.isLayerBacked { + self.view.bringSubview(toFront: itemNode.view) } itemNode.setHighlighted(true, animated: false) break @@ -2789,10 +2769,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } self.selectionTouchLocation = nil - super.touchesEnded(touches, withEvent: event) + super.touchesEnded(touches, with: event) } - override public func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { + override public func touchesCancelled(_ touches: Set?, with event: UIEvent?) { self.isTracking = false self.selectionTouchLocation = nil @@ -2800,6 +2780,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.selectionTouchDelayTimer = nil self.clearHighlightAnimated(false) - super.touchesCancelled(touches, withEvent: event) + super.touchesCancelled(touches, with: event) } } diff --git a/Display/ListViewAccessoryItem.swift b/Display/ListViewAccessoryItem.swift index 0a8837b83b..b2e067b4c8 100644 --- a/Display/ListViewAccessoryItem.swift +++ b/Display/ListViewAccessoryItem.swift @@ -1,6 +1,6 @@ import Foundation public protocol ListViewAccessoryItem { - func isEqualToItem(other: ListViewAccessoryItem) -> Bool + func isEqualToItem(_ other: ListViewAccessoryItem) -> Bool func node() -> ListViewAccessoryItemNode } diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift index b25d22757a..284bfcc13e 100644 --- a/Display/ListViewAccessoryItemNode.swift +++ b/Display/ListViewAccessoryItemNode.swift @@ -10,7 +10,7 @@ public class ListViewAccessoryItemNode: ASDisplayNode { private var transitionOffsetAnimation: ListViewAnimation? - final func animateTransitionOffset(from: CGPoint, beginAt: Double, duration: Double, curve: CGFloat -> CGFloat) { + final func animateTransitionOffset(_ from: CGPoint, beginAt: Double, duration: Double, curve: (CGFloat) -> CGFloat) { self.transitionOffset = from self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { @@ -24,7 +24,7 @@ public class ListViewAccessoryItemNode: ASDisplayNode { self.transitionOffset = CGPoint() } - final func animate(timestamp: Double) -> Bool { + final func animate(_ timestamp: Double) -> Bool { if let animation = self.transitionOffsetAnimation { animation.applyAt(timestamp) diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 36d2a4167e..a0e032ed5f 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -1,27 +1,26 @@ import Foundation -import Display public protocol Interpolatable { static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> (Interpolatable) } -private func floorToPixels(value: CGFloat) -> CGFloat { +private func floorToPixels(_ value: CGFloat) -> CGFloat { return round(value * 10.0) / 10.0 } -private func floorToPixels(value: CGPoint) -> CGPoint { +private func floorToPixels(_ value: CGPoint) -> CGPoint { return CGPoint(x: round(value.x * 10.0) / 10.0, y: round(value.y * 10.0) / 10.0) } -private func floorToPixels(value: CGSize) -> CGSize { +private func floorToPixels(_ value: CGSize) -> CGSize { return CGSize(width: round(value.width * 10.0) / 10.0, height: round(value.height * 10.0) / 10.0) } -private func floorToPixels(value: CGRect) -> CGRect { +private func floorToPixels(_ value: CGRect) -> CGRect { return CGRect(origin: floorToPixels(value.origin), size: floorToPixels(value.size)) } -private func floorToPixels(value: UIEdgeInsets) -> UIEdgeInsets { +private func floorToPixels(_ value: UIEdgeInsets) -> UIEdgeInsets { return UIEdgeInsets(top: round(value.top * 10.0) / 10.0, left: round(value.left * 10.0) / 10.0, bottom: round(value.bottom * 10.0) / 10.0, right: round(value.right * 10.0) / 10.0) } @@ -63,25 +62,21 @@ extension CGPoint: Interpolatable { } } -private let springAnimationIn: CASpringAnimation = { - let animation = CASpringAnimation() - animation.damping = 500.0 - animation.stiffness = 1000.0 - animation.mass = 3.0 - animation.duration = animation.settlingDuration +private let springAnimationIn: CABasicAnimation = { + let animation = makeSpringAnimation("") return animation }() -public let listViewAnimationCurveSystem: CGFloat -> CGFloat = { t in +public let listViewAnimationCurveSystem: (CGFloat) -> CGFloat = { t in //return bezierPoint(0.23, 1.0, 0.32, 1.0, t) - return springAnimationIn.valueAt(t) + return springAnimationValueAt(springAnimationIn, t) } -public let listViewAnimationCurveLinear: CGFloat -> CGFloat = { t in +public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in return t } -public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewAnimationOptions) -> CGFloat -> CGFloat { +public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewAnimationOptions) -> (CGFloat) -> CGFloat { if animationOptions.rawValue == UInt(7 << 16) { return listViewAnimationCurveSystem } else { @@ -94,12 +89,12 @@ public final class ListViewAnimation { let to: Interpolatable let duration: Double let startTime: Double - private let curve: CGFloat -> CGFloat + private let curve: (CGFloat) -> CGFloat private let interpolator: (Interpolatable, Interpolatable, CGFloat) -> Interpolatable private let update: (CGFloat, Interpolatable) -> Void - private let completed: Bool -> Void + private let completed: (Bool) -> Void - public init(from: T, to: T, duration: Double, curve: CGFloat -> CGFloat, beginAt: Double, update: (CGFloat, T) -> Void, completed: Bool -> Void = { _ in }) { + public init(from: T, to: T, duration: Double, curve: (CGFloat) -> CGFloat, beginAt: Double, update: (CGFloat, T) -> Void, completed: (Bool) -> Void = { _ in }) { self.from = from self.to = to self.duration = duration @@ -112,7 +107,7 @@ public final class ListViewAnimation { self.completed = completed } - public func completeAt(timestamp: Double) -> Bool { + public func completeAt(_ timestamp: Double) -> Bool { if timestamp >= self.startTime + self.duration { self.completed(true) return true @@ -125,7 +120,7 @@ public final class ListViewAnimation { self.completed(false) } - private func valueAt(t: CGFloat) -> Interpolatable { + private func valueAt(_ t: CGFloat) -> Interpolatable { if t <= 0.0 { return self.from } else if t >= 1.0 { @@ -135,7 +130,7 @@ public final class ListViewAnimation { } } - public func applyAt(timestamp: Double) { + public func applyAt(_ timestamp: Double) { var t = CGFloat((timestamp - self.startTime) / self.duration) let ct: CGFloat if t <= 0.0 + CGFloat(FLT_EPSILON) { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index d3c0e373ea..7e2010d469 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -141,11 +141,11 @@ public class ListViewItemNode: ASDisplayNode { if layerBacked { super.init(layerBlock: { return ASTransformLayer() - }, didLoadBlock: nil) + }, didLoad: nil) } else { super.init(viewBlock: { return ListViewItemView() - }, didLoadBlock: nil) + }, didLoad: nil) } } @@ -213,26 +213,26 @@ public class ListViewItemNode: ASDisplayNode { return bounds } - public func layoutAccessoryItemNode(accessoryItemNode: ListViewAccessoryItemNode) { + public func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { } public func reuse() { } - final func addScrollingOffset(scrollingOffset: CGFloat) { + final func addScrollingOffset(_ scrollingOffset: CGFloat) { if self.spring != nil { self.contentOffset += scrollingOffset } } - func initializeDynamicsFromSibling(itemView: ListViewItemNode, additionalOffset: CGFloat) { + func initializeDynamicsFromSibling(_ itemView: ListViewItemNode, additionalOffset: CGFloat) { if let itemViewSpring = itemView.spring { self.contentOffset = itemView.contentOffset + additionalOffset self.spring?.velocity = itemViewSpring.velocity } } - public func animate(timestamp: Double) -> Bool { + public func animate(_ timestamp: Double) -> Bool { var continueAnimations = false if let _ = self.spring { @@ -254,7 +254,7 @@ public class ListViewItemNode: ASDisplayNode { // position = current position + velocity * time offset = self.contentOffset + self.spring!.velocity * time - offset = isnan(offset) ? 0.0 : offset + offset = offset.isNaN ? 0.0 : offset let epsilon: CGFloat = 0.1 if abs(offset) < epsilon && abs(self.spring!.velocity) < epsilon { @@ -277,7 +277,7 @@ public class ListViewItemNode: ASDisplayNode { animation.applyAt(timestamp) if animation.completeAt(timestamp) { - animations.removeAtIndex(i) + animations.remove(at: i) animationCount -= 1 i -= 1 } else { @@ -296,10 +296,10 @@ public class ListViewItemNode: ASDisplayNode { return continueAnimations } - public func layoutForWidth(width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + public func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { } - public func animationForKey(key: String) -> ListViewAnimation? { + public func animationForKey(_ key: String) -> ListViewAnimation? { for (animationKey, animation) in self.animations { if animationKey == key { return animation @@ -308,11 +308,11 @@ public class ListViewItemNode: ASDisplayNode { return nil } - public final func setAnimationForKey(key: String, animation: ListViewAnimation?) { + public final func setAnimationForKey(_ key: String, animation: ListViewAnimation?) { for i in 0 ..< self.animations.count { let (currentKey, currentAnimation) = self.animations[i] if currentKey == key { - self.animations.removeAtIndex(i) + self.animations.remove(at: i) currentAnimation.cancel() break } @@ -333,7 +333,7 @@ public class ListViewItemNode: ASDisplayNode { self.accessoryItemNode?.removeAllAnimations() } - public func addInsetsAnimationToValue(value: UIEdgeInsets, duration: Double, beginAt: Double) { + public func addInsetsAnimationToValue(_ value: UIEdgeInsets, duration: Double, beginAt: Double) { let animation = ListViewAnimation(from: self.insets, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { strongSelf.insets = currentValue @@ -342,7 +342,7 @@ public class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("insets", animation: animation) } - public func addApparentHeightAnimation(value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat) -> Void)? = nil) { + public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat) -> Void)? = nil) { let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { strongSelf.apparentHeight = currentValue @@ -354,7 +354,7 @@ public class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("apparentHeight", animation: animation) } - public func modifyApparentHeightAnimation(value: CGFloat, beginAt: Double) { + public func modifyApparentHeightAnimation(_ value: CGFloat, beginAt: Double) { if let previousAnimation = self.animationForKey("apparentHeight") { var duration = previousAnimation.startTime + previousAnimation.duration - beginAt if abs(self.apparentHeight - value) < CGFloat(FLT_EPSILON) { @@ -375,7 +375,7 @@ public class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("apparentHeight", animation: nil) } - public func addTransitionOffsetAnimation(value: CGFloat, duration: Double, beginAt: Double) { + public func addTransitionOffsetAnimation(_ value: CGFloat, duration: Double, beginAt: Double) { let animation = ListViewAnimation(from: self.transitionOffset, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { strongSelf.transitionOffset = currentValue @@ -384,19 +384,19 @@ public class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("transitionOffset", animation: animation) } - public func animateInsertion(currentTimestamp: Double, duration: Double) { + public func animateInsertion(_ currentTimestamp: Double, duration: Double) { } - public func animateAdded(currentTimestamp: Double, duration: Double) { + public func animateAdded(_ currentTimestamp: Double, duration: Double) { } - public func setHighlighted(highlighted: Bool, animated: Bool) { + public func setHighlighted(_ highlighted: Bool, animated: Bool) { } public func setupGestures() { } - public func animateFrameTransition(progress: CGFloat) { + public func animateFrameTransition(_ progress: CGFloat) { } } diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 782d332930..73d8ff4b1a 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -11,7 +11,7 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { fatalError("init(coder:) has not been implemented") } - @objc func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } } diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index ed39a981cb..8b55aabf7d 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -1,27 +1,27 @@ import Foundation import SwiftSignalKit -public typealias ListViewTransaction = (Void -> Void) -> Void +public typealias ListViewTransaction = ((Void) -> Void) -> Void public final class ListViewTransactionQueue { private var transactions: [ListViewTransaction] = [] - public final var transactionCompleted: Void -> Void = { } + public final var transactionCompleted: (Void) -> Void = { } public init() { } - public func addTransaction(transaction: ListViewTransaction) { + public func addTransaction(_ transaction: ListViewTransaction) { let beginTransaction = self.transactions.count == 0 self.transactions.append(transaction) if beginTransaction { transaction({ [weak self] in - if NSThread.isMainThread() { + if Thread.isMainThread() { if let strongSelf = self { strongSelf.endTransaction() } } else { - Queue.mainQueue().dispatch { + Queue.mainQueue().async { if let strongSelf = self { strongSelf.endTransaction() } @@ -32,18 +32,18 @@ public final class ListViewTransactionQueue { } private func endTransaction() { - Queue.mainQueue().dispatch { + Queue.mainQueue().async { self.transactionCompleted() let _ = self.transactions.removeFirst() if let nextTransaction = self.transactions.first { nextTransaction({ [weak self] in - if NSThread.isMainThread() { + if Thread.isMainThread() { if let strongSelf = self { strongSelf.endTransaction() } } else { - Queue.mainQueue().dispatch { + Queue.mainQueue().async { if let strongSelf = self { strongSelf.endTransaction() } diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 16b8d70064..e3a302e6e4 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -3,13 +3,13 @@ import AsyncDisplayKit public class NavigationBackButtonNode: ASControlNode { private func fontForCurrentState() -> UIFont { - return UIFont.systemFontOfSize(17.0) + return UIFont.systemFont(ofSize: 17.0) } private func attributesForCurrentState() -> [String : AnyObject] { return [ NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.enabled ? UIColor.blueColor() : UIColor.grayColor() + NSForegroundColorAttributeName: self.isEnabled ? UIColor.blue() : UIColor.gray() ] } @@ -27,7 +27,7 @@ public class NavigationBackButtonNode: ASControlNode { } set(value) { self._text = value - self.label.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.label.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) self.invalidateCalculatedLayout() } } @@ -41,8 +41,8 @@ public class NavigationBackButtonNode: ASControlNode { super.init() - self.userInteractionEnabled = true - self.exclusiveTouch = true + self.isUserInteractionEnabled = true + self.isExclusiveTouch = true self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) self.displaysAsynchronously = false @@ -50,14 +50,14 @@ public class NavigationBackButtonNode: ASControlNode { self.label.displaysAsynchronously = false self.addSubnode(self.arrow) - let arrowImage = UIImage(named: "NavigationBackArrowLight", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil)?.precomposed() - self.arrow.contents = arrowImage?.CGImage + let arrowImage = UIImage(named: "NavigationBackArrowLight", in: Bundle(for: NavigationBackButtonNode.self), compatibleWith: nil)?.precomposed() + self.arrow.contents = arrowImage?.cgImage self.arrow.frame = CGRect(origin: CGPoint(), size: arrowImage?.size ?? CGSize()) self.addSubnode(self.label) } - public override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize { + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { self.label.measure(CGSize(width: max(0.0, constrainedSize.width - self.arrow.frame.size.width - self.arrowSpacing), height: constrainedSize.height)) return CGSize(width: self.arrow.frame.size.width + self.arrowSpacing + self.label.calculatedSize.width, height: max(self.arrow.frame.size.height, self.label.calculatedSize.height)) @@ -81,7 +81,7 @@ public class NavigationBackButtonNode: ASControlNode { self.label.frame = self.labelFrame } - private func touchInsideApparentBounds(touch: UITouch) -> Bool { + private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop apparentBounds.origin.x += hitTestSlop.left @@ -89,49 +89,49 @@ public class NavigationBackButtonNode: ASControlNode { apparentBounds.origin.y += hitTestSlop.top apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom - return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) + return apparentBounds.contains(touch.location(in: self.view)) } - public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { - super.touchesBegan(touches, withEvent: event) + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } - public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { - super.touchesMoved(touches, withEvent: event) + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } - public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { - super.touchesEnded(touches, withEvent: event) + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) - if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first!) { + if previousTouchCount != 0 && self.touchCount == 0 && self.isEnabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } - public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { - super.touchesCancelled(touches, withEvent: event) + public override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } private var _highlighted = false - private func updateHighlightedState(highlighted: Bool, animated: Bool) { + private func updateHighlightedState(_ highlighted: Bool, animated: Bool) { if _highlighted != highlighted { _highlighted = highlighted - let alpha: CGFloat = !enabled ? 1.0 : (highlighted ? 0.4 : 1.0) + let alpha: CGFloat = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) if animated { - UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: { () -> Void in + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.beginFromCurrentState, animations: { () -> Void in self.alpha = alpha }, completion: nil) } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 4609490f9f..320394d7aa 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -34,7 +34,7 @@ public class NavigationBar: ASDisplayNode { private var itemWrapper: NavigationItemWrapper? - private let stripeHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale + private let stripeHeight: CGFloat = 1.0 / UIScreen.main().scale var backPressed: () -> () = { } diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 8871bf83ed..bcbc3afa05 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -3,13 +3,13 @@ import AsyncDisplayKit public class NavigationButtonNode: ASTextNode { private func fontForCurrentState() -> UIFont { - return self.bold ? UIFont.boldSystemFontOfSize(17.0) : UIFont.systemFontOfSize(17.0) + return self.bold ? UIFont.boldSystemFont(ofSize: 17.0) : UIFont.systemFont(ofSize: 17.0) } private func attributesForCurrentState() -> [String : AnyObject] { return [ NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.enabled ? UIColor.blueColor() : UIColor.grayColor() + NSForegroundColorAttributeName: self.isEnabled ? UIColor.blue() : UIColor.gray() ] } @@ -21,7 +21,7 @@ public class NavigationButtonNode: ASTextNode { set(value) { _text = value - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) } } @@ -34,7 +34,7 @@ public class NavigationButtonNode: ASTextNode { if _bold != value { _bold = value - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) } } } @@ -45,13 +45,13 @@ public class NavigationButtonNode: ASTextNode { public override init() { super.init() - self.userInteractionEnabled = true - self.exclusiveTouch = true + self.isUserInteractionEnabled = true + self.isExclusiveTouch = true self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) self.displaysAsynchronously = false } - private func touchInsideApparentBounds(touch: UITouch) -> Bool { + private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop apparentBounds.origin.x += hitTestSlop.left @@ -59,49 +59,49 @@ public class NavigationButtonNode: ASTextNode { apparentBounds.origin.y += hitTestSlop.top apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom - return CGRectContainsPoint(apparentBounds, touch.locationInView(self.view)) + return apparentBounds.contains(touch.location(in: self.view)) } - public override func touchesBegan(touches: Set, withEvent event: UIEvent?) { - super.touchesBegan(touches, withEvent: event) + public override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) self.touchCount += touches.count self.updateHighlightedState(true, animated: false) } - public override func touchesMoved(touches: Set, withEvent event: UIEvent?) { - super.touchesMoved(touches, withEvent: event) + public override func touchesMoved(_ touches: Set, with event: UIEvent?) { + super.touchesMoved(touches, with: event) self.updateHighlightedState(self.touchInsideApparentBounds(touches.first!), animated: true) } - public override func touchesEnded(touches: Set, withEvent event: UIEvent?) { - super.touchesEnded(touches, withEvent: event) + public override func touchesEnded(_ touches: Set, with event: UIEvent?) { + super.touchesEnded(touches, with: event) self.updateHighlightedState(false, animated: false) let previousTouchCount = self.touchCount self.touchCount = max(0, self.touchCount - touches.count) - if previousTouchCount != 0 && self.touchCount == 0 && self.enabled && self.touchInsideApparentBounds(touches.first!) { + if previousTouchCount != 0 && self.touchCount == 0 && self.isEnabled && self.touchInsideApparentBounds(touches.first!) { self.pressed() } } - public override func touchesCancelled(touches: Set?, withEvent event: UIEvent?) { - super.touchesCancelled(touches, withEvent: event) + public override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) self.touchCount = max(0, self.touchCount - (touches?.count ?? 0)) self.updateHighlightedState(false, animated: false) } private var _highlighted = false - private func updateHighlightedState(highlighted: Bool, animated: Bool) { + private func updateHighlightedState(_ highlighted: Bool, animated: Bool) { if _highlighted != highlighted { _highlighted = highlighted - let alpha: CGFloat = !enabled ? 1.0 : (highlighted ? 0.4 : 1.0) + let alpha: CGFloat = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) if animated { - UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions.BeginFromCurrentState, animations: { () -> Void in + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.beginFromCurrentState, animations: { () -> Void in self.alpha = alpha }, completion: nil) } @@ -111,15 +111,15 @@ public class NavigationButtonNode: ASTextNode { } } - public override var enabled: Bool { + public override var isEnabled: Bool { get { - return super.enabled + return super.isEnabled } set(value) { - if self.enabled != value { - super.enabled = value + if self.isEnabled != value { + super.isEnabled = value - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) } } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 1eadd5a0e7..ed9b9a8cdd 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -18,7 +18,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr private var statusBarChangeObserver: AnyObject? private var layout: NavigationControllerLayout? - private var pendingLayout: (NavigationControllerLayout, NSTimeInterval, Bool)? + private var pendingLayout: (NavigationControllerLayout, Double, Bool)? private var _presentedViewController: UIViewController? public override var presentedViewController: UIViewController? { @@ -41,9 +41,9 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr public override init() { super.init() - self.statusBarChangeObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIApplicationWillChangeStatusBarFrameNotification, object: nil, queue: NSOperationQueue.mainQueue(), usingBlock: { [weak self] notification in + self.statusBarChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main(), using: { [weak self] notification in if let strongSelf = self { - let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.CGRectValue().height ?? 20.0) + let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue().height ?? 20.0) let previousLayout: NavigationControllerLayout? if let pendingLayout = strongSelf.pendingLayout { @@ -59,7 +59,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr }) } - public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) { + public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) } @@ -84,17 +84,17 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - func panGesture(recognizer: UIPanGestureRecognizer) { + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { - case UIGestureRecognizerState.Began: + case UIGestureRecognizerState.began: if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController topController.viewWillDisappear(true) - let topView = topController.view + let topView = topController.view! bottomController.viewWillAppear(true) - let bottomView = bottomController.view + let bottomView = bottomController.view! var bottomStatusBar: StatusBar? if let bottomController = bottomController as? ViewController { @@ -110,14 +110,14 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator } - case UIGestureRecognizerState.Changed: + case UIGestureRecognizerState.changed: if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - let translation = recognizer.translationInView(self.view).x + let translation = recognizer.translation(in: self.view).x navigationTransitionCoordinator.progress = max(0.0, min(1.0, translation / self.view.frame.width)) } - case UIGestureRecognizerState.Ended: + case UIGestureRecognizerState.ended: if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - let velocity = recognizer.velocityInView(self.view).x + let velocity = recognizer.velocity(in: self.view).x if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { navigationTransitionCoordinator.animateCompletion(velocity, completion: { @@ -131,7 +131,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.setIgnoreAppearanceMethodInvocations(true) bottomController.setIgnoreAppearanceMethodInvocations(true) - self.popViewControllerAnimated(false) + let _ = self.popViewController(animated: false) topController.setIgnoreAppearanceMethodInvocations(false) bottomController.setIgnoreAppearanceMethodInvocations(false) @@ -175,7 +175,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr }) } } - case .Cancelled: + case .cancelled: if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -207,7 +207,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - public func pushViewController(controller: ViewController) { + public func pushViewController(_ controller: ViewController) { let layout: NavigationControllerLayout if let currentLayout = self.layout { layout = currentLayout @@ -222,7 +222,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr })) } - public override func pushViewController(viewController: UIViewController, animated: Bool) { + public override func pushViewController(_ viewController: UIViewController, animated: Bool) { self.currentPushDisposable.set(nil) var controllers = self.viewControllers @@ -230,18 +230,18 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr self.setViewControllers(controllers, animated: animated) } - public override func popViewControllerAnimated(animated: Bool) -> UIViewController? { + public override func popViewController(animated: Bool) -> UIViewController? { var controller: UIViewController? var controllers = self.viewControllers if controllers.count != 0 { controller = controllers[controllers.count - 1] as UIViewController - controllers.removeAtIndex(controllers.count - 1) + controllers.remove(at: controllers.count - 1) self.setViewControllers(controllers, animated: animated) } return controller } - public override func setViewControllers(viewControllers: [UIViewController], animated: Bool) { + public override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { if viewControllers.count > 0 { let topViewController = viewControllers[viewControllers.count - 1] as UIViewController @@ -268,9 +268,9 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } bottomController.viewWillDisappear(true) - let bottomView = bottomController.view + let bottomView = bottomController.view! topController.viewWillAppear(true) - let topView = topController.view + let topView = topController.view! let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator @@ -334,11 +334,11 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - private func childControllerLayoutForLayout(layout: NavigationControllerLayout) -> ViewControllerLayout { + private func childControllerLayoutForLayout(_ layout: NavigationControllerLayout) -> ViewControllerLayout { return ViewControllerLayout(size: layout.layout.size, insets: layout.layout.insets, inputViewHeight: 0.0, statusBarHeight: layout.statusBarHeight) } - public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { + public func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) { let previousLayout: NavigationControllerLayout? if let pendingLayout = self.pendingLayout { previousLayout = pendingLayout.0 @@ -387,7 +387,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) } else { - bottomController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) + bottomController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) } } @@ -398,7 +398,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if let controller = topViewController as? WindowContentController { controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) } else { - topViewController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) + topViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) } } @@ -406,7 +406,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if let controller = presentedViewController as? WindowContentController { controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) } else { - presentedViewController.view.frame = CGRectMake(0.0, 0.0, pendingLayout.0.layout.size.width, pendingLayout.0.layout.size.height) + presentedViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) } } @@ -418,9 +418,9 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - override public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = viewControllerToPresent as? NavigationController { - controller.navigation_setPresentingViewController(self) + controller.navigation_setPresenting(self) self._presentedViewController = controller self.view.endEditing(true) @@ -438,7 +438,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) self.view.addSubview(controller.view) (self.view.window as? Window)?.updateStatusBars() - UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { controller.view.frame = self.view.bounds (self.view.window as? Window)?.updateStatusBars() }, completion: { _ in @@ -459,11 +459,10 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - override public func dismissViewControllerAnimated(flag: Bool, completion: (() -> Void)?) { + override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = self.presentedViewController { - if flag { - UIView.animateWithDuration(0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) (self.view.window as? Window)?.updateStatusBars() }, completion: { _ in @@ -484,18 +483,18 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } } - public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWithGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } - public func gestureRecognizer(gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailByGestureRecognizer otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { return otherGestureRecognizer is UIPanGestureRecognizer } func statusBarSurfaces() -> [StatusBarSurface] { var surfaces: [StatusBarSurface] = [self.statusBarSurface] if let controller = self.presentedViewController as? StatusBarSurfaceProvider { - surfaces.appendContentsOf(controller.statusBarSurfaces()) + surfaces.append(contentsOf: controller.statusBarSurfaces()) } return surfaces } diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift index f7185c4166..725a569394 100644 --- a/Display/NavigationItemWrapper.swift +++ b/Display/NavigationItemWrapper.swift @@ -40,12 +40,12 @@ internal class NavigationItemWrapper { self.parentNode.addSubnode(self.backButtonNode) self.previousItemSetTitleListenerKey = previousNavigationItem?.addSetTitleListener({ [weak self] title in - self?.setBackButtonTitle(title) + self?.setBackButtonTitle(title ?? "") return }) self.setTitleListenerKey = navigationItem.addSetTitleListener({ [weak self] title in - self?.setTitle(title) + self?.setTitle(title ?? "") return }) @@ -78,17 +78,17 @@ internal class NavigationItemWrapper { self.backButtonNode.removeFromSupernode() } - func setBackButtonTitle(backButtonTitle: String) { + func setBackButtonTitle(_ backButtonTitle: String) { self.backButtonNode.text = backButtonTitle self.layoutItems() } - func setTitle(title: String) { + func setTitle(_ title: String) { self.titleNode.text = title self.layoutItems() } - func setLeftBarButtonItem(leftBarButtonItem: UIBarButtonItem?, animated: Bool) { + func setLeftBarButtonItem(_ leftBarButtonItem: UIBarButtonItem?, animated: Bool) { if self.leftBarButtonItem !== leftBarButtonItem { self.leftBarButtonItem = leftBarButtonItem @@ -102,10 +102,10 @@ internal class NavigationItemWrapper { } } - self.backButtonNode.hidden = self.previousNavigationItem == nil || self.leftBarButtonItemWrapper != nil + self.backButtonNode.isHidden = self.previousNavigationItem == nil || self.leftBarButtonItemWrapper != nil } - func setRightBarButtonItem(rightBarButtonItem: UIBarButtonItem?, animated: Bool) { + func setRightBarButtonItem(_ rightBarButtonItem: UIBarButtonItem?, animated: Bool) { if self.rightBarButtonItem !== rightBarButtonItem { self.rightBarButtonItem = rightBarButtonItem @@ -135,7 +135,7 @@ internal class NavigationItemWrapper { var titlePosition: CGPoint { get { let titleFrame = self.titleFrame - return CGPoint(x: CGRectGetMidX(titleFrame), y: CGRectGetMidY(titleFrame)) + return CGPoint(x: titleFrame.midX, y: titleFrame.midY) } } @@ -156,7 +156,7 @@ internal class NavigationItemWrapper { var backButtonLabelPosition: CGPoint { get { let backButtonLabelFrame = self.backButtonLabelFrame - return CGPoint(x: CGRectGetMidX(backButtonLabelFrame), y: CGRectGetMidY(backButtonLabelFrame)) + return CGPoint(x: backButtonLabelFrame.midX, y: backButtonLabelFrame.midY) } } @@ -184,7 +184,7 @@ internal class NavigationItemWrapper { var transitionState: NavigationItemTransitionState { get { - return NavigationItemTransitionState(backButtonPosition: self.backButtonNode.hidden ? nil : self.backButtonLabelPosition, titlePosition: self.titlePosition) + return NavigationItemTransitionState(backButtonPosition: self.backButtonNode.isHidden ? nil : self.backButtonLabelPosition, titlePosition: self.titlePosition) } } @@ -207,19 +207,19 @@ internal class NavigationItemWrapper { rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! } - self.titleNode.measure(CGSize(width: max(0.0, self.parentNode.bounds.size.width - 140.0), height: CGFloat.max)) + self.titleNode.measure(CGSize(width: max(0.0, self.parentNode.bounds.size.width - 140.0), height: CGFloat.greatestFiniteMagnitude)) self.titleNode.frame = self.titleFrame } - func interpolatePosition(from: CGPoint, _ to: CGPoint, value: CGFloat) -> CGPoint { + func interpolatePosition(_ from: CGPoint, _ to: CGPoint, value: CGFloat) -> CGPoint { return CGPoint(x: from.x * (CGFloat(1.0) - value) + to.x * value, y: from.y * (CGFloat(1.0) - value) + to.y * value) } - func interpolateValue(from: CGFloat, _ to: CGFloat, value: CGFloat) -> CGFloat { + func interpolateValue(_ from: CGFloat, _ to: CGFloat, value: CGFloat) -> CGFloat { return (from * (CGFloat(1.0) - value)) + (to * value) } - func applyPushAnimationProgress(previousItemState previousItemState: NavigationItemTransitionState, value: CGFloat) { + func applyPushAnimationProgress(previousItemState: NavigationItemTransitionState, value: CGFloat) { let titleStartPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) let titleStartAlpha: CGFloat = 0.0 let titleEndPosition = self.titlePosition @@ -234,7 +234,7 @@ internal class NavigationItemWrapper { self.backButtonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) } - func applyPushAnimationProgress(nextItemState nextItemState: NavigationItemTransitionState, value: CGFloat) { + func applyPushAnimationProgress(nextItemState: NavigationItemTransitionState, value: CGFloat) { let titleStartPosition = self.titlePosition let titleStartAlpha: CGFloat = 1.0 var titleEndPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) @@ -254,7 +254,7 @@ internal class NavigationItemWrapper { self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, nextItemState.backButtonPosition == nil ? 0.0 : 1.0, value: value) } - func applyPopAnimationProgress(previousItemState previousItemState: NavigationItemTransitionState, value: CGFloat) { + func applyPopAnimationProgress(previousItemState: NavigationItemTransitionState, value: CGFloat) { var titleStartPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) if let previousItemBackButtonPosition = previousItemState.backButtonPosition { titleStartPosition = previousItemBackButtonPosition @@ -273,7 +273,7 @@ internal class NavigationItemWrapper { self.backButtonNode.arrow.alpha = self.interpolateValue(previousItemState.backButtonPosition == nil ? 0.0 : 1.0, 1.0, value: value) } - func applyPopAnimationProgress(nextItemState nextItemState: NavigationItemTransitionState, value: CGFloat) { + func applyPopAnimationProgress(nextItemState: NavigationItemTransitionState, value: CGFloat) { let titleStartPosition = self.titlePosition let titleStartAlpha: CGFloat = 1.0 let titleEndPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) @@ -289,7 +289,7 @@ internal class NavigationItemWrapper { self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, 0.0, value: value) } - func animatePush(previousItemWrapper: NavigationItemWrapper?, duration: NSTimeInterval) { + func animatePush(previousItemWrapper: NavigationItemWrapper?, duration: Double) { if let previousItemWrapper = previousItemWrapper { self.suspendLayout = true self.backButtonNode.suspendLayout = true @@ -300,7 +300,7 @@ internal class NavigationItemWrapper { self.applyPushAnimationProgress(previousItemState: previousItemState, value: 0.0) previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 0.0) - UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in + UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in self.applyPushAnimationProgress(previousItemState: previousItemState, value: 1.0) previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 1.0) }, completion: { completed in @@ -312,12 +312,12 @@ internal class NavigationItemWrapper { } } - func animatePop(previousItemWrapper: NavigationItemWrapper?, duration: NSTimeInterval) { + func animatePop(previousItemWrapper: NavigationItemWrapper?, duration: Double) { if let previousItemWrapper = previousItemWrapper { self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 0.0) previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) - UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in + UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 1.0) previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 1.0) }, completion: { completed in diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 537c45a4c9..2e63cf1524 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -18,7 +18,7 @@ public class NavigationTitleNode: ASDisplayNode { public init(text: NSString) { self.label = ASTextNode() self.label.maximumNumberOfLines = 1 - self.label.truncationMode = .ByTruncatingTail + self.label.truncationMode = .byTruncatingTail self.label.displaysAsynchronously = false super.init() @@ -32,16 +32,16 @@ public class NavigationTitleNode: ASDisplayNode { fatalError("init(coder:) has not been implemented") } - private func setText(text: NSString) { + private func setText(_ text: NSString) { var titleAttributes = [String : AnyObject]() - titleAttributes[NSFontAttributeName] = UIFont.boldSystemFontOfSize(17.0) - titleAttributes[NSForegroundColorAttributeName] = UIColor.blackColor() - let titleString = NSAttributedString(string: text as String, attributes: titleAttributes) + titleAttributes[NSFontAttributeName] = UIFont.boldSystemFont(ofSize: 17.0) + titleAttributes[NSForegroundColorAttributeName] = UIColor.black() + let titleString = AttributedString(string: text as String, attributes: titleAttributes) self.label.attributedString = titleString self.invalidateCalculatedLayout() } - public override func calculateSizeThatFits(constrainedSize: CGSize) -> CGSize { + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { self.label.measure(constrainedSize) return self.label.calculatedSize } diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index c5fe59ba06..83b1f7d321 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -8,7 +8,7 @@ enum NavigationTransition { private let shadowWidth: CGFloat = 16.0 private func generateShadow() -> UIImage? { - return UIImage(named: "NavigationShadow", inBundle: NSBundle(forClass: NavigationBackButtonNode.self), compatibleWithTraitCollection: nil)?.precomposed().resizableImageWithCapInsets(UIEdgeInsetsZero, resizingMode: .Tile) + return UIImage(named: "NavigationShadow", in: Bundle(for: NavigationBackButtonNode.self), compatibleWith: nil)?.precomposed().resizableImage(withCapInsets: UIEdgeInsetsZero, resizingMode: .tile) } private let shadowImage = generateShadow() @@ -49,7 +49,7 @@ class NavigationTransitionCoordinator { self.topNavigationBar = topNavigationBar self.bottomNavigationBar = bottomNavigationBar self.dimView = UIView() - self.dimView.backgroundColor = UIColor.blackColor() + self.dimView.backgroundColor = UIColor.black() self.shadowView = UIImageView(image: shadowImage) switch transition { @@ -87,8 +87,8 @@ class NavigationTransitionCoordinator { (self.container.window as? Window)?.updateStatusBars() } - func animateCancel(completion: () -> ()) { - UIView.animateWithDuration(0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + func animateCancel(_ completion: () -> ()) { + UIView.animate(withDuration: 0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in self.progress = 0.0 }) { (completed) -> Void in switch self.transition { @@ -115,7 +115,7 @@ class NavigationTransitionCoordinator { } } - func animateCompletion(velocity: CGFloat, completion: () -> ()) { + func animateCompletion(_ velocity: CGFloat, completion: () -> ()) { let distance = (1.0 - self.progress) * self.container.bounds.size.width let f = { switch self.transition { @@ -142,13 +142,13 @@ class NavigationTransitionCoordinator { } if abs(velocity) < CGFloat(FLT_EPSILON) && abs(self.progress) < CGFloat(FLT_EPSILON) { - UIView.animateWithDuration(0.5, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { self.progress = 1.0 }, completion: { _ in f() }) } else { - UIView.animateWithDuration(NSTimeInterval(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in + UIView.animate(withDuration: Double(max(0.05, min(0.2, abs(distance / velocity)))), delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in self.progress = 1.0 }) { (completed) -> Void in f() diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h index 1bd387edbf..e7c4cb7a6b 100644 --- a/Display/RuntimeUtils.h +++ b/Display/RuntimeUtils.h @@ -18,5 +18,6 @@ typedef enum { - (void)setAssociatedObject:(id)object forKey:(void const *)key; - (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy; - (id)associatedObjectForKey:(void const *)key; +- (bool)checkObjectIsKindOfClass:(Class)targetClass; @end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m index 2a1b4376f2..444adb0329 100644 --- a/Display/RuntimeUtils.m +++ b/Display/RuntimeUtils.m @@ -77,4 +77,8 @@ return objc_getAssociatedObject(self, key); } +- (bool)checkObjectIsKindOfClass:(Class)targetClass { + return [self isKindOfClass:targetClass]; +} + @end diff --git a/Display/RuntimeUtils.swift b/Display/RuntimeUtils.swift index 178c77d13b..54cd75f979 100644 --- a/Display/RuntimeUtils.swift +++ b/Display/RuntimeUtils.swift @@ -2,11 +2,11 @@ import Foundation import UIKit private let systemVersion = { () -> (Int, Int) in - let string = UIDevice.currentDevice().systemVersion as NSString + let string = UIDevice.current().systemVersion as NSString var minor = 0 - let range = string.rangeOfString(".") + let range = string.range(of: ".") if range.location != NSNotFound { - minor = Int((string.substringFromIndex(range.location + 1) as NSString).intValue) + minor = Int((string.substring(from: range.location + 1) as NSString).intValue) } return (Int(string.intValue), minor) }() diff --git a/Display/ScrollToTopProxyView.swift b/Display/ScrollToTopProxyView.swift index cb72cc71be..c4ad8aed44 100644 --- a/Display/ScrollToTopProxyView.swift +++ b/Display/ScrollToTopProxyView.swift @@ -22,7 +22,7 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate { } } - @objc func scrollViewShouldScrollToTop(scrollView: UIScrollView) -> Bool { + @objc func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool { if let action = self.action { action() } diff --git a/Display/Spring.swift b/Display/Spring.swift index 8210a71fc6..44fba08d85 100644 --- a/Display/Spring.swift +++ b/Display/Spring.swift @@ -13,32 +13,32 @@ struct ViewportItemSpring { } } -private func a(a1: CGFloat, _ a2: CGFloat) -> CGFloat +private func a(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat { return 1.0 - 3.0 * a2 + 3.0 * a1 } -private func b(a1: CGFloat, _ a2: CGFloat) -> CGFloat +private func b(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat { return 3.0 * a2 - 6.0 * a1 } -private func c(a1: CGFloat) -> CGFloat +private func c(_ a1: CGFloat) -> CGFloat { return 3.0 * a1 } -private func calcBezier(t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +private func calcBezier(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat { return ((a(a1, a2)*t + b(a1, a2))*t + c(a1)) * t } -private func calcSlope(t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat +private func calcSlope(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat { return 3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1) } -private func getTForX(x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat { +private func getTForX(_ x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat { var t = x var i = 0 while i < 4 { @@ -56,7 +56,7 @@ private func getTForX(x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat { return t } -func bezierPoint(x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat +func bezierPoint(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat { var value = calcBezier(getTForX(x, x1, x2), y1, y2) if value >= 0.997 { diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 50fd47dd33..a09fb71b0e 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -4,20 +4,20 @@ import AsyncDisplayKit public class StatusBarSurface { var statusBars: [StatusBar] = [] - func addStatusBar(statusBar: StatusBar) { + func addStatusBar(_ statusBar: StatusBar) { self.removeStatusBar(statusBar) self.statusBars.append(statusBar) } - func insertStatusBar(statusBar: StatusBar, atIndex index: Int) { + func insertStatusBar(_ statusBar: StatusBar, atIndex index: Int) { self.removeStatusBar(statusBar) - self.statusBars.insert(statusBar, atIndex: index) + self.statusBars.insert(statusBar, at: index) } - func removeStatusBar(statusBar: StatusBar) { + func removeStatusBar(_ statusBar: StatusBar) { for i in 0 ..< self.statusBars.count { if self.statusBars[i] === statusBar { - self.statusBars.removeAtIndex(i) + self.statusBars.remove(at: i) break } } @@ -32,28 +32,26 @@ public class StatusBar: ASDisplayNode { super.init() self.clipsToBounds = true - self.userInteractionEnabled = false + self.isUserInteractionEnabled = false } func removeProxyNode() { - self.proxyNode?.hidden = true + self.proxyNode?.isHidden = true self.proxyNode?.removeFromSupernode() self.proxyNode = nil } func updateProxyNode() { - let origin = self.view.convertPoint(CGPoint(), toView: nil) + let origin = self.view.convert(CGPoint(), to: nil) if let proxyNode = proxyNode { proxyNode.style = self.style } else { self.proxyNode = StatusBarProxyNode(style: self.style) - self.proxyNode!.hidden = false + self.proxyNode!.isHidden = false self.addSubnode(self.proxyNode!) } let frame = CGRect(origin: CGPoint(x: -origin.x, y: -origin.y), size: self.proxyNode!.frame.size) self.proxyNode?.frame = frame } - - } diff --git a/Display/StatusBarHostWindow.swift b/Display/StatusBarHostWindow.swift index 901b24de39..02dd884e7a 100644 --- a/Display/StatusBarHostWindow.swift +++ b/Display/StatusBarHostWindow.swift @@ -3,7 +3,7 @@ import UIKit private class StatusBarHostWindowController: UIViewController { override func preferredStatusBarStyle() -> UIStatusBarStyle { - return UIStatusBarStyle.Default + return UIStatusBarStyle.default } override func prefersStatusBarHidden() -> Bool { @@ -26,4 +26,4 @@ public class StatusBarHostWindow: UIWindow { required public init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } -} \ No newline at end of file +} diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index a7bf31e6f5..1bf0ef2b55 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -12,23 +12,23 @@ private struct MappedStatusBarSurface { let surface: StatusBarSurface } -private func mapStatusBar(statusBar: StatusBar) -> MappedStatusBar { - let frame = CGRect(origin: statusBar.view.convertPoint(CGPoint(), toView: nil), size: statusBar.frame.size) +private func mapStatusBar(_ statusBar: StatusBar) -> MappedStatusBar { + let frame = CGRect(origin: statusBar.view.convert(CGPoint(), to: nil), size: statusBar.frame.size) return MappedStatusBar(style: statusBar.style, frame: frame, statusBar: statusBar) } -private func mappedSurface(surface: StatusBarSurface) -> MappedStatusBarSurface { +private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurface { return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface) } -private func optimizeMappedSurface(surface: MappedStatusBarSurface) -> MappedStatusBarSurface { +private func optimizeMappedSurface(_ surface: MappedStatusBarSurface) -> MappedStatusBarSurface { if surface.statusBars.count > 1 { for i in 1 ..< surface.statusBars.count { if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { return surface } } - let size = UIApplication.sharedApplication().statusBarFrame.size + let size = UIApplication.shared().statusBarFrame.size return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) } else { return surface @@ -37,12 +37,12 @@ private func optimizeMappedSurface(surface: MappedStatusBarSurface) -> MappedSta private func displayHiddenAnimation() -> CAAnimation { let animation = CABasicAnimation(keyPath: "transform.translation.y") - animation.fromValue = NSNumber(float: -40.0) - animation.toValue = NSNumber(float: -40.0) + animation.fromValue = NSNumber(value: Float(-40.0)) + animation.toValue = NSNumber(value: Float(-40.0)) animation.fillMode = kCAFillModeBoth animation.duration = 100000000.0 - animation.additive = true - animation.removedOnCompletion = false + animation.isAdditive = true + animation.isRemovedOnCompletion = false return animation } @@ -54,7 +54,7 @@ class StatusBarManager { } } - private func updateSurfaces(previousSurfaces: [StatusBarSurface]) { + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface]) { let mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(mappedSurface($0)) }) var visibleStatusBars: [StatusBar] = [] @@ -93,16 +93,16 @@ class StatusBarManager { } if let globalStatusBar = globalStatusBar { - StatusBarUtils.statusBarWindow()!.hidden = false - let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .Default : .LightContent - if UIApplication.sharedApplication().statusBarStyle != statusBarStyle { - UIApplication.sharedApplication().setStatusBarStyle(statusBarStyle, animated: false) + StatusBarUtils.statusBarWindow()!.isHidden = false + let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent + if UIApplication.shared().statusBarStyle != statusBarStyle { + UIApplication.shared().setStatusBarStyle(statusBarStyle, animated: false) } - StatusBarUtils.statusBarWindow()!.layer.removeAnimationForKey("displayHidden") - StatusBarUtils.statusBarWindow()!.transform = CGAffineTransformMakeTranslation(0.0, globalStatusBar.1) + StatusBarUtils.statusBarWindow()!.layer.removeAnimation(forKey: "displayHidden") + StatusBarUtils.statusBarWindow()!.transform = CGAffineTransform(translationX: 0.0, y: globalStatusBar.1) } else { - if StatusBarUtils.statusBarWindow()!.layer.animationForKey("displayHidden") == nil { - StatusBarUtils.statusBarWindow()!.layer.addAnimation(displayHiddenAnimation(), forKey: "displayHidden") + if StatusBarUtils.statusBarWindow()!.layer.animation(forKey: "displayHidden") == nil { + StatusBarUtils.statusBarWindow()!.layer.add(displayHiddenAnimation(), forKey: "displayHidden") } } @@ -148,4 +148,4 @@ class StatusBarManager { //print("window \(StatusBarUtils.statusBarWindow()!)")*/ } -} \ No newline at end of file +} diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 0e9f101970..75e178fcbe 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -11,7 +11,7 @@ private enum StatusBarItemType { case Battery } -func makeStatusBarProxy(style: StatusBarStyle) -> StatusBarProxyNode { +func makeStatusBarProxy(_ style: StatusBarStyle) -> StatusBarProxyNode { return StatusBarProxyNode(style: style) } @@ -29,30 +29,30 @@ private class StatusBarItemNode: ASDisplayNode { func update() { let context = DrawingContext(size: self.targetView.frame.size, clear: true) - if let contents = self.targetView.layer.contents where (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents) == CGImageGetTypeID() && false { - let image = contents as! CGImageRef + if let contents = self.targetView.layer.contents where (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents) == CGImage.typeID && false { + let image = contents as! CGImage context.withFlippedContext { c in - CGContextSetAlpha(c, CGFloat(self.targetView.layer.opacity)) - CGContextDrawImage(c, CGRect(origin: CGPoint(), size: context.size), image) - CGContextSetAlpha(c, 1.0) + c.setAlpha(CGFloat(self.targetView.layer.opacity)) + c.draw(in: CGRect(origin: CGPoint(), size: context.size), image: image) + c.setAlpha(1.0) } if let sublayers = self.targetView.layer.sublayers { for sublayer in sublayers { let origin = sublayer.frame.origin - if let contents = sublayer.contents where CFGetTypeID(contents) == CGImageGetTypeID() { - let image = contents as! CGImageRef + if let contents = sublayer.contents where CFGetTypeID(contents) == CGImage.typeID { + let image = contents as! CGImage context.withFlippedContext { c in - CGContextTranslateCTM(c, origin.x, origin.y) - CGContextDrawImage(c, CGRect(origin: CGPoint(), size: context.size), image) - CGContextTranslateCTM(c, -origin.x, -origin.y) + c.translate(x: origin.x, y: origin.y) + c.draw(in: CGRect(origin: CGPoint(), size: context.size), image: image) + c.translate(x: -origin.x, y: -origin.y) } } else { context.withContext { c in UIGraphicsPushContext(c) - CGContextTranslateCTM(c, origin.x, origin.y) - sublayer.renderInContext(c) - CGContextTranslateCTM(c, -origin.x, -origin.y) + c.translate(x: origin.x, y: origin.y) + sublayer.render(in: c) + c.translate(x: -origin.x, y: -origin.y) UIGraphicsPopContext() } } @@ -61,20 +61,20 @@ private class StatusBarItemNode: ASDisplayNode { } else { context.withContext { c in UIGraphicsPushContext(c) - self.targetView.layer.renderInContext(c) + self.targetView.layer.render(in: c) UIGraphicsPopContext() } } - let type: StatusBarItemType = self.targetView.isKindOfClass(batteryItemClass!) ? .Battery : .Generic + let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic tintStatusBarItem(context, type: type, style: style) - self.contents = context.generateImage()?.CGImage + self.contents = context.generateImage()?.cgImage self.frame = self.targetView.frame } } -private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, style: StatusBarStyle) { +private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemType, style: StatusBarStyle) { switch type { case .Battery: let minY = 0 @@ -91,7 +91,7 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, var baseX = minX while baseX < maxX { let pixel = baseMidRow + baseX - let alpha = pixel.memory & 0xff000000 + let alpha = pixel.pointee & 0xff000000 if alpha != 0 { break } @@ -103,7 +103,7 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, var targetX = baseX while targetX < maxX { let pixel = baseMidRow + targetX - let alpha = pixel.memory & 0xff000000 + let alpha = pixel.pointee & 0xff000000 if alpha == 0 { break } @@ -111,7 +111,7 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, targetX += 1 } - let batteryColor = (baseMidRow + baseX).memory + let batteryColor = (baseMidRow + baseX).pointee let batteryR = (batteryColor >> 16) & 0xff let batteryG = (batteryColor >> 8) & 0xff let batteryB = batteryColor & 0xff @@ -120,7 +120,7 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, while baseY < maxY { let baseRow = basePixel + pixelsPerRow * baseY let pixel = baseRow + midX - let alpha = pixel.memory & 0xff000000 + let alpha = pixel.pointee & 0xff000000 if alpha != 0 { break } @@ -131,7 +131,7 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, while targetY >= baseY { let baseRow = basePixel + pixelsPerRow * targetY let pixel = baseRow + midX - let alpha = pixel.memory & 0xff000000 + let alpha = pixel.pointee & 0xff000000 if alpha != 0 { break } @@ -155,13 +155,13 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, var pixel = UnsafeMutablePointer(context.bytes) let end = UnsafeMutablePointer(context.bytes + context.length) while pixel != end { - let alpha = (pixel.memory & 0xff000000) >> 24 + let alpha = (pixel.pointee & 0xff000000) >> 24 let r = (baseR * alpha) / 255 let g = (baseG * alpha) / 255 let b = (baseB * alpha) / 255 - pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + pixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b pixel += 1 } @@ -173,13 +173,13 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, var x = baseX while x < targetX { let pixel = baseRow + x - let alpha = (pixel.memory >> 24) & 0xff + let alpha = (pixel.pointee >> 24) & 0xff let r = (batteryR * alpha) / 255 let g = (batteryG * alpha) / 255 let b = (batteryB * alpha) / 255 - pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + pixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b x += 1 } @@ -204,13 +204,13 @@ private func tintStatusBarItem(context: DrawingContext, type: StatusBarItemType, let baseB = baseColor & 0xff while pixel != end { - let alpha = (pixel.memory & 0xff000000) >> 24 + let alpha = (pixel.pointee & 0xff000000) >> 24 let r = (baseR * alpha) / 255 let g = (baseG * alpha) / 255 let b = (baseB * alpha) / 255 - pixel.memory = (alpha << 24) | (r << 16) | (g << 8) | b + pixel.pointee = (alpha << 24) | (r << 16) | (g << 8) | b pixel += 1 } @@ -232,11 +232,11 @@ private class StatusBarProxyNodeTimerTarget: NSObject { } class StatusBarProxyNode: ASDisplayNode { - var timer: NSTimer? + var timer: Timer? var style: StatusBarStyle { didSet { if oldValue != self.style { - if !self.hidden { + if !self.isHidden { self.updateItems() } } @@ -245,19 +245,19 @@ class StatusBarProxyNode: ASDisplayNode { private var itemNodes: [StatusBarItemNode] = [] - override var hidden: Bool { + override var isHidden: Bool { get { - return super.hidden + return super.isHidden } set(value) { - if super.hidden != value { - super.hidden = value + if super.isHidden != value { + super.isHidden = value if !value { self.updateItems() - self.timer = NSTimer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in + self.timer = Timer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in self?.updateItems() }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) - NSRunLoop.mainRunLoop().addTimer(self.timer!, forMode: NSRunLoopCommonModes) + RunLoop.main().add(self.timer!, forMode: .commonModes) } else { self.timer?.invalidate() self.timer = nil @@ -271,7 +271,7 @@ class StatusBarProxyNode: ASDisplayNode { super.init() - self.hidden = true + self.isHidden = true self.clipsToBounds = true //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) @@ -305,7 +305,7 @@ class StatusBarProxyNode: ASDisplayNode { } if !found { self.itemNodes[i].removeFromSupernode() - self.itemNodes.removeAtIndex(i) + self.itemNodes.remove(at: i) } else { self.itemNodes[i].style = self.style self.itemNodes[i].update() diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index ada54e63cf..69909ed983 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -9,12 +9,12 @@ class TabBarControllerNode: ASDisplayNode { oldValue?.removeFromSuperview() if let currentControllerView = self.currentControllerView { - self.view.insertSubview(currentControllerView, atIndex: 0) + self.view.insertSubview(currentControllerView, at: 0) } } } - init(itemSelected: Int -> Void) { + init(itemSelected: (Int) -> Void) { self.tabBarNode = TabBarNode(itemSelected: itemSelected) super.init() @@ -22,14 +22,14 @@ class TabBarControllerNode: ASDisplayNode { self.addSubnode(self.tabBarNode) } - func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + func updateLayout(_ layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { let update = { self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets.bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0)) self.tabBarNode.layout() } if duration > DBL_EPSILON { - UIView.animateWithDuration(duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: curve << 7), animations: update, completion: nil) + UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: curve << 7), animations: update, completion: nil) } else { update() } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index a375899a14..283b6829db 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -62,10 +62,10 @@ public class TabBarController: ViewController { self.tabBarControllerNode.tabBarNode.selectedIndex = self.selectedIndex if let currentController = self.currentController { - currentController.willMoveToParentViewController(nil) + currentController.willMove(toParentViewController: nil) self.tabBarControllerNode.currentControllerView = nil currentController.removeFromParentViewController() - currentController.didMoveToParentViewController(nil) + currentController.didMove(toParentViewController: nil) self.currentController = nil } @@ -75,7 +75,7 @@ public class TabBarController: ViewController { } if let currentController = self.currentController { - currentController.willMoveToParentViewController(self) + currentController.willMove(toParentViewController: self) if let layout = self.layout { currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -83,7 +83,7 @@ public class TabBarController: ViewController { } self.tabBarControllerNode.currentControllerView = currentController.view self.addChildViewController(currentController) - currentController.didMoveToParentViewController(self) + currentController.didMove(toParentViewController: self) self.navigationItem.title = currentController.navigationItem.title self.navigationItem.leftBarButtonItem = currentController.navigationItem.leftBarButtonItem @@ -95,13 +95,13 @@ public class TabBarController: ViewController { } } - private func childControllerLayoutForLayout(layout: ViewControllerLayout) -> ViewControllerLayout { + private func childControllerLayoutForLayout(_ layout: ViewControllerLayout) -> ViewControllerLayout { var insets = layout.insets insets.bottom += 49.0 return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: layout.statusBarHeight) } - override public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + override public func updateLayout(_ layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { super.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) self.tabBarControllerNode.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index a47407de06..9f314aff8b 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -2,10 +2,10 @@ import Foundation import UIKit import AsyncDisplayKit -private let separatorHeight: CGFloat = 1.0 / UIScreen.mainScreen().scale -private func tabBarItemImage(image: UIImage?, title: String, tintColor: UIColor) -> UIImage { +private let separatorHeight: CGFloat = 1.0 / UIScreen.main().scale +private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColor) -> UIImage { let font = Font.regular(10.0) - let titleSize = (title as NSString).boundingRectWithSize(CGSize(width: CGFloat.max, height: CGFloat.max), options: [.UsesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil).size + let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil).size let imageSize: CGSize if let image = image { @@ -17,26 +17,26 @@ private func tabBarItemImage(image: UIImage?, title: String, tintColor: UIColor) let size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) UIGraphicsBeginImageContextWithOptions(size, true, 0.0) - let context = UIGraphicsGetCurrentContext() - - CGContextSetFillColorWithColor(context, UIColor(0xf7f7f7).CGColor) - CGContextFillRect(context, CGRect(origin: CGPoint(), size: size)) - - if let image = image { - let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) - CGContextSaveGState(context) - CGContextTranslateCTM(context, imageRect.midX, imageRect.midY) - CGContextScaleCTM(context, 1.0, -1.0) - CGContextTranslateCTM(context, -imageRect.midX, -imageRect.midY) - CGContextClipToMask(context, imageRect, image.CGImage) - CGContextSetFillColorWithColor(context, tintColor.CGColor) - CGContextFillRect(context, imageRect) - CGContextRestoreGState(context) + if let context = UIGraphicsGetCurrentContext() { + context.setFillColor(UIColor(0xf7f7f7).cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + + if let image = image { + let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) + context.saveGState() + context.translate(x: imageRect.midX, y: imageRect.midY) + context.scale(x: 1.0, y: -1.0) + context.translate(x: -imageRect.midX, y: -imageRect.midY) + context.clipToMask(imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + context.restoreGState() + } } - (title as NSString).drawAtPoint(CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: tintColor]) + (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: tintColor]) - let image = UIGraphicsGetImageFromCurrentImageContext() + let image = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() return image @@ -63,22 +63,22 @@ class TabBarNode: ASDisplayNode { } } - private let itemSelected: Int -> Void + private let itemSelected: (Int) -> Void let separatorNode: ASDisplayNode private var tabBarNodes: [ASImageNode] = [] - init(itemSelected: Int -> Void) { + init(itemSelected: (Int) -> Void) { self.itemSelected = itemSelected self.separatorNode = ASDisplayNode() self.separatorNode.backgroundColor = UIColor(0xb2b2b2) - self.separatorNode.opaque = true - self.separatorNode.layerBacked = true + self.separatorNode.isOpaque = true + self.separatorNode.isLayerBacked = true super.init() - self.opaque = true + self.isOpaque = true self.backgroundColor = UIColor(0xf7f7f7) self.addSubnode(self.separatorNode) @@ -95,7 +95,7 @@ class TabBarNode: ASDisplayNode { let node = ASImageNode() node.displaysAsynchronously = false node.displayWithoutProcessing = true - node.layerBacked = true + node.isLayerBacked = true if let selectedIndex = self.selectedIndex where selectedIndex == i { node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { @@ -110,7 +110,7 @@ class TabBarNode: ASDisplayNode { self.setNeedsLayout() } - private func updateNodeImage(index: Int) { + private func updateNodeImage(_ index: Int) { if index < self.tabBarNodes.count && index < self.tabBarItems.count { let node = self.tabBarNodes[index] let item = self.tabBarItems[index] @@ -145,11 +145,11 @@ class TabBarNode: ASDisplayNode { } } - override func touchesBegan(touches: Set, withEvent event: UIEvent?) { - super.touchesBegan(touches, withEvent: event) + override func touchesBegan(_ touches: Set, with event: UIEvent?) { + super.touchesBegan(touches, with: event) if let touch = touches.first { - let location = touch.locationInView(self.view) + let location = touch.location(in: self.view) var closestNode: (Int, CGFloat)? for i in 0 ..< self.tabBarNodes.count { @@ -169,4 +169,4 @@ class TabBarNode: ASDisplayNode { } } } -} \ No newline at end of file +} diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h index 227be571a6..6278f5f316 100644 --- a/Display/UIKitUtils.h +++ b/Display/UIKitUtils.h @@ -7,9 +7,6 @@ @end -@interface CASpringAnimation (AnimationUtils) - -- (CGFloat)valueAt:(CGFloat)t; - -@end +CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath); +CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t); diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 7fff07df92..256b17d629 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -30,3 +30,17 @@ UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient } @end + +CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { + CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath]; + springAnimation.mass = 3.0f; + springAnimation.stiffness = 1000.0f; + springAnimation.damping = 500.0f; + springAnimation.initialVelocity = 0.0f; + springAnimation.duration = springAnimation.settlingDuration; + return springAnimation; +} + +CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t) { + return [(CASpringAnimation *)animation _solveForInput:t]; +} diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index e8d3be2849..b361beee46 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -1,10 +1,10 @@ import UIKit -public func dumpViews(view: UIView) { +public func dumpViews(_ view: UIView) { dumpViews(view, indent: "") } -private func dumpViews(view: UIView, indent: String = "") { +private func dumpViews(_ view: UIView, indent: String = "") { print("\(indent)\(view)") let nextIndent = indent + "-" for subview in view.subviews { @@ -12,11 +12,11 @@ private func dumpViews(view: UIView, indent: String = "") { } } -public func dumpLayers(layer: CALayer) { +public func dumpLayers(_ layer: CALayer) { dumpLayers(layer, indent: "") } -private func dumpLayers(layer: CALayer, indent: String = "") { +private func dumpLayers(_ layer: CALayer, indent: String = "") { print("\(indent)\(layer)(\(layer.frame))") if layer.sublayers != nil { let nextIndent = indent + ".." @@ -26,8 +26,8 @@ private func dumpLayers(layer: CALayer, indent: String = "") { } } -public let UIScreenScale = UIScreen.mainScreen().scale -public func floorToScreenPixels(value: CGFloat) -> CGFloat { +public let UIScreenScale = UIScreen.main().scale +public func floorToScreenPixels(_ value: CGFloat) -> CGFloat { return floor(value * UIScreenScale) / UIScreenScale } @@ -44,7 +44,7 @@ public extension UIColor { } public extension CGSize { - public func fitted(size: CGSize) -> CGSize { + public func fitted(_ size: CGSize) -> CGSize { var fittedSize = self if fittedSize.width > size.width { fittedSize = CGSize(width: size.width, height: floor((fittedSize.height * size.width / max(fittedSize.width, 1.0)))) @@ -55,7 +55,7 @@ public extension CGSize { return fittedSize } - public func fittedToArea(area: CGFloat) -> CGSize { + public func fittedToArea(_ area: CGFloat) -> CGSize { if self.height < 1.0 || self.width < 1.0 { return CGSize() } @@ -65,29 +65,29 @@ public extension CGSize { return CGSize(width: floor(width), height: floor(height)) } - public func aspectFilled(size: CGSize) -> CGSize { + public func aspectFilled(_ size: CGSize) -> CGSize { let scale = max(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } - public func aspectFitted(size: CGSize) -> CGSize { + public func aspectFitted(_ size: CGSize) -> CGSize { let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } } -public func assertNotOnMainThread(file: String = #file, line: Int = #line) { - assert(!NSThread.isMainThread(), "\(file):\(line) running on main thread") +public func assertNotOnMainThread(_ file: String = #file, line: Int = #line) { + assert(!Thread.isMainThread(), "\(file):\(line) running on main thread") } public extension UIImage { public func precomposed() -> UIImage { UIGraphicsBeginImageContextWithOptions(self.size, false, self.scale) - self.drawAtPoint(CGPoint()) - let result = UIGraphicsGetImageFromCurrentImageContext(); + self.draw(at: CGPoint()) + let result = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() if !UIEdgeInsetsEqualToEdgeInsets(self.capInsets, UIEdgeInsetsZero) { - return result.resizableImageWithCapInsets(self.capInsets, resizingMode: self.resizingMode) + return result.resizableImage(withCapInsets: self.capInsets, resizingMode: self.resizingMode) } return result } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 13decf1eb0..f775677e1d 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -39,7 +39,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { return self._ready } - private var updateLayoutOnLayout: (ViewControllerLayout, NSTimeInterval, UInt)? + private var updateLayoutOnLayout: (ViewControllerLayout, Double, UInt)? public private(set) var layout: ViewControllerLayout? var keyboardFrameObserver: AnyObject? @@ -54,7 +54,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } private func updateScrollToTopView() { - if let scrollToTop = self.scrollToTop { + if self.scrollToTop != nil { if let displayNode = self._displayNode where self.scrollToTopView == nil { let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.frame.size.width, height: 1.0)) scrollToTopView.action = { [weak self] in @@ -80,18 +80,18 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { self.navigationBar.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false - self.keyboardFrameObserver = NSNotificationCenter.defaultCenter().addObserverForName(UIKeyboardWillChangeFrameNotification, object: nil, queue: nil, usingBlock: { [weak self] notification in + self.keyboardFrameObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in if let strongSelf = self, _ = strongSelf._displayNode { - let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.CGRectValue() ?? CGRect() - let keyboardHeight = max(0.0, UIScreen.mainScreen().bounds.size.height - keyboardFrame.minY) + let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue() ?? CGRect() + let keyboardHeight = max(0.0, UIScreen.main().bounds.size.height - keyboardFrame.minY) var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 if duration > DBL_EPSILON { duration = 0.5 } - var curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.unsignedIntegerValue ?? UInt(7 << 16) + var curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? UInt(7 << 16) let previousLayout: ViewControllerLayout? - var previousDurationAndCurve: (NSTimeInterval, UInt)? + var previousDurationAndCurve: (Double, UInt)? if let updateLayoutOnLayout = strongSelf.updateLayoutOnLayout { previousLayout = updateLayoutOnLayout.0 previousDurationAndCurve = (updateLayoutOnLayout.1, updateLayoutOnLayout.2) @@ -112,7 +112,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { if updated { //print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") - let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration, curve) + let durationAndCurve: (Double, UInt) = previousDurationAndCurve ?? (duration, curve) strongSelf.updateLayoutOnLayout = (layout, durationAndCurve.0, durationAndCurve.1) strongSelf.view.setNeedsLayout() } @@ -126,7 +126,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { deinit { if let keyboardFrameObserver = keyboardFrameObserver { - NSNotificationCenter.defaultCenter().removeObserver(keyboardFrameObserver) + NotificationCenter.default().removeObserver(keyboardFrameObserver) } } @@ -145,7 +145,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { self.updateScrollToTopView() } - public func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) { + public func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) { if self._displayNode == nil { self.loadDisplayNode() } @@ -178,7 +178,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } } - public func updateLayout(layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + public func updateLayout(_ layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) self.navigationBar.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 44.0 + 20.0)) if let scrollToTopView = self.scrollToTopView { @@ -186,9 +186,9 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } } - public func setNeedsLayoutWithDuration(duration: Double, curve: UInt) { + public func setNeedsLayoutWithDuration(_ duration: Double, curve: UInt) { let previousLayout: ViewControllerLayout? - var previousDurationAndCurve: (NSTimeInterval, UInt)? + var previousDurationAndCurve: (Double, UInt)? if let updateLayoutOnLayout = self.updateLayoutOnLayout { previousLayout = updateLayoutOnLayout.0 previousDurationAndCurve = (updateLayoutOnLayout.1, updateLayoutOnLayout.2) @@ -196,7 +196,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { previousLayout = self.layout } if let previousLayout = previousLayout { - let durationAndCurve: (NSTimeInterval, UInt) = previousDurationAndCurve ?? (duration, curve) + let durationAndCurve: (Double, UInt) = previousDurationAndCurve ?? (duration, curve) self.updateLayoutOnLayout = (previousLayout, durationAndCurve.0, durationAndCurve.1) self.view.setNeedsLayout() } @@ -214,14 +214,14 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { self.updateLayoutOnLayout = nil } else { - (self.view.window as? Window)?.addPostUpdateToInterfaceOrientationBlock({ [weak self] in + (self.view.window as? Window)?.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in if let strongSelf = self { strongSelf.view.setNeedsLayout() } }) } } else { - Window.addPostDeviceOrientationDidChangeBlock({ [weak self] in + Window.addPostDeviceOrientationDidChange({ [weak self] in if let strongSelf = self { strongSelf.view.setNeedsLayout() } @@ -230,11 +230,11 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } } - override public func presentViewController(viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)?) { + override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { - navigationController.presentViewController(viewControllerToPresent, animated: flag, completion: completion) + navigationController.present(viewControllerToPresent, animated: flag, completion: completion) } else { - super.presentViewController(viewControllerToPresent, animated: flag, completion: completion) + super.present(viewControllerToPresent, animated: flag, completion: completion) } } } diff --git a/Display/Window.swift b/Display/Window.swift index 2ed18955cb..fdfa019949 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -3,7 +3,7 @@ import AsyncDisplayKit public class WindowRootViewController: UIViewController { public override func preferredStatusBarStyle() -> UIStatusBarStyle { - return .Default + return .default } public override func prefersStatusBarHidden() -> Bool { @@ -19,22 +19,22 @@ public struct ViewControllerLayout: Equatable { } public protocol WindowContentController { - func setParentLayout(layout: ViewControllerLayout, duration: NSTimeInterval, curve: UInt) + func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) var view: UIView! { get } } -public func animateRotation(view: UIView?, toFrame: CGRect, duration: NSTimeInterval) { +public func animateRotation(_ view: UIView?, toFrame: CGRect, duration: Double) { if let view = view { - UIView.animateWithDuration(duration, animations: { () -> Void in + UIView.animate(withDuration: duration, animations: { () -> Void in view.frame = toFrame }) } } -public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NSTimeInterval) { +public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: Double) { if let view = view { CALayer.beginRecordingChanges() - UIView.animateWithDuration(duration, animations: { () -> Void in + UIView.animate(withDuration: duration, animations: { () -> Void in view.frame = toFrame }) view.layout() @@ -46,28 +46,28 @@ public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NST } for state in states { if let layer = state.layer { - if !CGRectEqualToRect(state.startBounds, state.endBounds) { + if !state.startBounds.equalTo(state.endBounds) { let boundsAnimation = CABasicAnimation(keyPath: "bounds") - boundsAnimation.fromValue = NSValue(CGRect: state.startBounds) - boundsAnimation.toValue = NSValue(CGRect: state.endBounds) + boundsAnimation.fromValue = NSValue(cgRect: state.startBounds) + boundsAnimation.toValue = NSValue(cgRect: state.endBounds) boundsAnimation.duration = duration boundsAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - boundsAnimation.removedOnCompletion = true + boundsAnimation.isRemovedOnCompletion = true boundsAnimation.fillMode = kCAFillModeForwards boundsAnimation.speed = speed - layer.addAnimation(boundsAnimation, forKey: "_rotationBounds") + layer.add(boundsAnimation, forKey: "_rotationBounds") } - if !CGPointEqualToPoint(state.startPosition, state.endPosition) { + if !state.startPosition.equalTo(state.endPosition) { let positionAnimation = CABasicAnimation(keyPath: "position") - positionAnimation.fromValue = NSValue(CGPoint: state.startPosition) - positionAnimation.toValue = NSValue(CGPoint: state.endPosition) + positionAnimation.fromValue = NSValue(cgPoint: state.startPosition) + positionAnimation.toValue = NSValue(cgPoint: state.endPosition) positionAnimation.duration = duration positionAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - positionAnimation.removedOnCompletion = true + positionAnimation.isRemovedOnCompletion = true positionAnimation.fillMode = kCAFillModeForwards positionAnimation.speed = speed - layer.addAnimation(positionAnimation, forKey: "_rotationPosition") + layer.add(positionAnimation, forKey: "_rotationPosition") } } } @@ -77,15 +77,15 @@ public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: NST public class Window: UIWindow { private let statusBarManager: StatusBarManager - private var updateViewSizeOnLayout: (Bool, NSTimeInterval) = (false, 0.0) + private var updateViewSizeOnLayout: (Bool, Double) = (false, 0.0) public var isUpdatingOrientationLayout = false - private let orientationChangeDuration: NSTimeInterval = { - UIDevice.currentDevice().userInterfaceIdiom == .Pad ? 0.4 : 0.3 + private let orientationChangeDuration: Double = { + UIDevice.current().userInterfaceIdiom == .pad ? 0.4 : 0.3 }() public convenience init() { - self.init(frame: UIScreen.mainScreen().bounds) + self.init(frame: UIScreen.main().bounds) } public override init(frame: CGRect) { @@ -100,8 +100,8 @@ public class Window: UIWindow { fatalError("init(coder:) has not been implemented") } - public override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? { - return self.viewController?.view.hitTest(point, withEvent: event) + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.viewController?.view.hitTest(point, with: event) } public override var frame: CGRect { @@ -163,11 +163,11 @@ public class Window: UIWindow { } } - var postUpdateToInterfaceOrientationBlocks: [Void -> Void] = [] + var postUpdateToInterfaceOrientationBlocks: [(Void) -> Void] = [] - override public func _updateToInterfaceOrientation(arg1: Int32, duration arg2: Double, force arg3: Bool) { + override public func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { self.isUpdatingOrientationLayout = true - super._updateToInterfaceOrientation(arg1, duration: arg2, force: arg3) + super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) self.isUpdatingOrientationLayout = false let blocks = self.postUpdateToInterfaceOrientationBlocks @@ -177,7 +177,7 @@ public class Window: UIWindow { } } - public func addPostUpdateToInterfaceOrientationBlock(f: Void -> Void) { + public func addPostUpdateToInterfaceOrientationBlock(f: (Void) -> Void) { postUpdateToInterfaceOrientationBlocks.append(f) } diff --git a/DisplayTests/DisplayTests.swift b/DisplayTests/DisplayTests.swift index c2d1a8257a..3f8d2f268a 100644 --- a/DisplayTests/DisplayTests.swift +++ b/DisplayTests/DisplayTests.swift @@ -28,7 +28,7 @@ class DisplayTests: XCTestCase { func testPerformanceExample() { // This is an example of a performance test case. - self.measureBlock { + self.measure { // Put the code you want to measure the time of here. } } From 326118adc2a990f20336264c5f1fa6bf4dba09e2 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 6 Jul 2016 15:40:10 +0300 Subject: [PATCH 016/245] no message --- Display.xcodeproj/project.pbxproj | 203 +++++-- Display/ActionSheet.swift | 7 - Display/ActionSheetButtonItem.swift | 22 + Display/ActionSheetButtonNode.swift | 63 +++ Display/ActionSheetController.swift | 44 ++ Display/ActionSheetControllerNode.swift | 158 ++++++ Display/ActionSheetItem.swift | 5 + Display/ActionSheetItemGroup.swift | 10 + Display/ActionSheetItemGroupNode.swift | 212 ++++++++ .../ActionSheetItemGroupsContainerNode.swift | 75 +++ Display/ActionSheetItemNode.swift | 30 +- Display/AsyncLayoutable.swift | 6 - Display/CAAnimationUtils.swift | 19 +- Display/CALayer+ImplicitAnimations.h | 22 - Display/CALayer+ImplicitAnimations.m | 163 ------ Display/CATracingLayer.h | 24 + Display/CATracingLayer.m | 275 ++++++++++ Display/ContainableController.swift | 42 ++ Display/ContainerViewLayout.swift | 83 +++ Display/DefaultDisplayTheme.swift | 5 - Display/Display.h | 2 +- Display/DisplayLinkDispatcher.swift | 3 + Display/Font.swift | 2 +- Display/GenerateImage.swift | 29 + Display/HighlightTrackingButton.swift | 23 + Display/ListView.swift | 103 +++- Display/MergedLayoutEvents.swift | 9 + Display/NavigationBackButtonNode.swift | 14 +- Display/NavigationBar.swift | 496 +++++++++++++++--- .../NavigationBarTransitionContainer.swift | 62 +++ Display/NavigationBarTransitionState.swift | 20 + Display/NavigationButtonNode.swift | 20 +- Display/NavigationController.swift | 407 ++++++-------- Display/NavigationItemTransitionState.swift | 4 - Display/NavigationItemWrapper.swift | 333 ------------ Display/NavigationTitleNode.swift | 8 +- Display/NavigationTransitionCoordinator.swift | 98 +++- Display/PresentableViewController.swift | 6 + Display/PresentationContext.swift | 142 +++++ Display/RuntimeUtils.h | 1 + Display/RuntimeUtils.m | 27 + Display/StatusBar.swift | 37 +- Display/StatusBarManager.swift | 114 ++-- Display/StatusBarSurfaceProvider.swift | 4 - Display/StatusBarUtils.m | 5 +- ...ainedControllerTransitionCoordinator.swift | 73 +++ Display/TabBarContollerNode.swift | 19 +- Display/TabBarController.swift | 26 +- Display/{DisplayTheme.swift => Theme.swift} | 4 +- Display/UIViewController+Navigation.h | 1 + Display/UIViewController+Navigation.m | 51 +- Display/UIWindow+OrientationChange.h | 6 - Display/UniversalMasterController.swift | 22 + Display/ViewController.swift | 192 ++----- Display/Window.swift | 318 ++++++++--- 55 files changed, 2834 insertions(+), 1315 deletions(-) delete mode 100644 Display/ActionSheet.swift create mode 100644 Display/ActionSheetButtonItem.swift create mode 100644 Display/ActionSheetButtonNode.swift create mode 100644 Display/ActionSheetController.swift create mode 100644 Display/ActionSheetControllerNode.swift create mode 100644 Display/ActionSheetItem.swift create mode 100644 Display/ActionSheetItemGroup.swift create mode 100644 Display/ActionSheetItemGroupNode.swift create mode 100644 Display/ActionSheetItemGroupsContainerNode.swift delete mode 100644 Display/AsyncLayoutable.swift delete mode 100644 Display/CALayer+ImplicitAnimations.h delete mode 100644 Display/CALayer+ImplicitAnimations.m create mode 100644 Display/CATracingLayer.h create mode 100644 Display/CATracingLayer.m create mode 100644 Display/ContainableController.swift create mode 100644 Display/ContainerViewLayout.swift delete mode 100644 Display/DefaultDisplayTheme.swift create mode 100644 Display/HighlightTrackingButton.swift create mode 100644 Display/MergedLayoutEvents.swift create mode 100644 Display/NavigationBarTransitionState.swift delete mode 100644 Display/NavigationItemTransitionState.swift delete mode 100644 Display/NavigationItemWrapper.swift create mode 100644 Display/PresentableViewController.swift create mode 100644 Display/PresentationContext.swift delete mode 100644 Display/StatusBarSurfaceProvider.swift create mode 100644 Display/SystemContainedControllerTransitionCoordinator.swift rename Display/{DisplayTheme.swift => Theme.swift} (65%) create mode 100644 Display/UniversalMasterController.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 264c2fb095..a301f45bbf 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -8,18 +8,24 @@ /* Begin PBXBuildFile section */ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; - D0078A6D1C92B3B900DF6D92 /* ActionSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */; }; - D0078A6F1C92B40300DF6D92 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */; }; + D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */; }; + D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; + D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; + D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; + D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; - D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */; }; - D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */; }; + D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* Theme.swift */; }; D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */; }; D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E7DF61C96C5F200C07816 /* NSWeakReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DF71C96C5F200C07816 /* NSWeakReference.m */; }; D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */; }; D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */; }; - D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */; }; + D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */; }; + D05BE4A91D1F1DDD002BD72C /* UniversalMasterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4A81D1F1DDD002BD72C /* UniversalMasterController.swift */; }; + D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */; }; + D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; @@ -27,8 +33,6 @@ D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; - D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */; }; - D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; }; D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */; }; @@ -46,8 +50,6 @@ D05CC3081B69575900E235A3 /* NSBag.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3061B69575900E235A3 /* NSBag.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */; }; D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30A1B695A9500E235A3 /* NavigationBar.swift */; }; - D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */; }; - D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */; }; D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */; }; D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */; }; D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */; }; @@ -63,9 +65,15 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; + D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; + D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; + D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; + D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; + D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; + D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; @@ -75,12 +83,15 @@ D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */; }; D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */; }; - D0C2DFCC1CC4431D0044FF83 /* AsyncLayoutable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC11CC4431D0044FF83 /* AsyncLayoutable.swift */; }; D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */; }; D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */; }; D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */; }; D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */; }; D0C2DFFC1CC528B70044FF83 /* SwiftSignalKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */; }; + D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; }; + D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; + D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; + D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; @@ -101,18 +112,24 @@ /* Begin PBXFileReference section */ D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; - D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheet.swift; sourceTree = ""; }; - D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; + D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentableViewController.swift; sourceTree = ""; }; + D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; + D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; + D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; + D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; - D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayTheme.swift; sourceTree = ""; }; - D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultDisplayTheme.swift; sourceTree = ""; }; + D03BCCEA1C72AE590097A291 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionContainer.swift; sourceTree = ""; }; D03E7DF61C96C5F200C07816 /* NSWeakReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSWeakReference.h; sourceTree = ""; }; D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = ""; }; D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = ""; }; D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; - D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarSurfaceProvider.swift; sourceTree = ""; }; + D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CATracingLayer.h; sourceTree = ""; }; + D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CATracingLayer.m; sourceTree = ""; }; + D05BE4A81D1F1DDD002BD72C /* UniversalMasterController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalMasterController.swift; sourceTree = ""; }; + D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = ""; }; + D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergedLayoutEvents.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -123,8 +140,6 @@ D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; - D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "CALayer+ImplicitAnimations.m"; sourceTree = ""; }; - D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CALayer+ImplicitAnimations.h"; sourceTree = ""; }; D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuntimeUtils.h; sourceTree = ""; }; D05CC2EE1B6955D000E235A3 /* UIKitUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIKitUtils.swift; sourceTree = ""; }; @@ -142,8 +157,6 @@ D05CC3061B69575900E235A3 /* NSBag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSBag.h; sourceTree = ""; }; D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTransitionCoordinator.swift; sourceTree = ""; }; D05CC30A1B695A9500E235A3 /* NavigationBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBar.swift; sourceTree = ""; }; - D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemWrapper.swift; sourceTree = ""; }; - D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationItemTransitionState.swift; sourceTree = ""; }; D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBackButtonNode.swift; sourceTree = ""; }; D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationButtonNode.swift; sourceTree = ""; }; D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTitleNode.swift; sourceTree = ""; }; @@ -159,9 +172,15 @@ D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; + D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; + D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; + D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; + D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; + D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarUtils.h; sourceTree = ""; }; D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarUtils.m; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; + D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; @@ -171,12 +190,15 @@ D0C2DFBE1CC4431D0044FF83 /* ListView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListView.swift; sourceTree = ""; }; D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItem.swift; sourceTree = ""; }; D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAnimation.swift; sourceTree = ""; }; - D0C2DFC11CC4431D0044FF83 /* AsyncLayoutable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsyncLayoutable.swift; sourceTree = ""; }; D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewTransactionQueue.swift; sourceTree = ""; }; D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAccessoryItem.swift; sourceTree = ""; }; D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewScroller.swift; sourceTree = ""; }; D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewAccessoryItemNode.swift; sourceTree = ""; }; D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = SwiftSignalKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = ""; }; + D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; + D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; + D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonNode.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; @@ -207,11 +229,36 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - D0078A6B1C92B3A600DF6D92 /* Action Sheet */ = { + D015F7501D1ADC6800E269B5 /* Window */ = { isa = PBXGroup; children = ( - D0078A6C1C92B3B900DF6D92 /* ActionSheet.swift */, - D0078A6E1C92B40300DF6D92 /* ActionSheetItemNode.swift */, + D05CC2A11B69326C00E235A3 /* Window.swift */, + ); + name = Window; + sourceTree = ""; + }; + D015F7551D1B142300E269B5 /* Tab Bar */ = { + isa = PBXGroup; + children = ( + D0DC48531BF93D8A00F672FD /* TabBarController.swift */, + D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */, + D0DC48551BF945DD00F672FD /* TabBarNode.swift */, + ); + name = "Tab Bar"; + sourceTree = ""; + }; + D015F7561D1B465600E269B5 /* Action Sheet */ = { + isa = PBXGroup; + children = ( + D015F7571D1B467200E269B5 /* ActionSheetController.swift */, + D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */, + D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */, + D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */, + D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */, + D08E90391D24159200533158 /* ActionSheetItem.swift */, + D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */, + D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */, + D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */, ); name = "Action Sheet"; sourceTree = ""; @@ -227,12 +274,31 @@ D03BCCE91C72AE4B0097A291 /* Theme */ = { isa = PBXGroup; children = ( - D03BCCEA1C72AE590097A291 /* DisplayTheme.swift */, - D03BCCEC1C72AEC30097A291 /* DefaultDisplayTheme.swift */, + D03BCCEA1C72AE590097A291 /* Theme.swift */, ); name = Theme; sourceTree = ""; }; + D05BE4A71D1F1DCC002BD72C /* Master */ = { + isa = PBXGroup; + children = ( + D05BE4A81D1F1DDD002BD72C /* UniversalMasterController.swift */, + ); + name = Master; + sourceTree = ""; + }; + D05BE4AC1D217F33002BD72C /* Utils */ = { + isa = PBXGroup; + children = ( + D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */, + D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */, + D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */, + D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */, + D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */, + ); + name = Utils; + sourceTree = ""; + }; D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( @@ -255,18 +321,16 @@ D05CC2651B69316F00E235A3 /* Display */ = { isa = PBXGroup; children = ( + D08122991D19A9E0005F7395 /* User Interface */, D0C2DFBA1CC443080044FF83 /* List View */, D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, - D0078A6B1C92B3A600DF6D92 /* Action Sheet */, D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, D0E49C861B83A1680099E553 /* Image Cache */, - D05CC2A11B69326C00E235A3 /* Window.swift */, - D05CC2E21B69552C00E235A3 /* ViewController.swift */, D05CC2E11B69534100E235A3 /* Supporting Files */, ); path = Display; @@ -318,8 +382,6 @@ D05CC2F51B6955D000E235A3 /* UIWindow+OrientationChange.m */, D05CC3021B69568600E235A3 /* NotificationCenterUtils.h */, D05CC3011B69568600E235A3 /* NotificationCenterUtils.m */, - D05CC2E61B69555800E235A3 /* CALayer+ImplicitAnimations.h */, - D05CC2E51B69555800E235A3 /* CALayer+ImplicitAnimations.m */, D05CC3061B69575900E235A3 /* NSBag.h */, D05CC3051B69575900E235A3 /* NSBag.m */, D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */, @@ -349,14 +411,12 @@ D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */, D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */, D05CC30A1B695A9500E235A3 /* NavigationBar.swift */, - D05CC30C1B695A9500E235A3 /* NavigationItemTransitionState.swift */, D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */, D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */, - D05CC30B1B695A9500E235A3 /* NavigationItemWrapper.swift */, D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */, - D05CC29F1B69326400E235A3 /* NavigationController.swift */, D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, + D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */, ); name = Navigation; sourceTree = ""; @@ -376,11 +436,44 @@ D0078A671C92B21400DF6D92 /* StatusBar.swift */, D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */, D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */, - D03E7E021C98160C00C07816 /* StatusBarSurfaceProvider.swift */, ); name = "Status Bar"; sourceTree = ""; }; + D08122991D19A9E0005F7395 /* User Interface */ = { + isa = PBXGroup; + children = ( + D05BE4AC1D217F33002BD72C /* Utils */, + D015F7501D1ADC6800E269B5 /* Window */, + D081229B1D19A9F0005F7395 /* Controllers */, + ); + name = "User Interface"; + sourceTree = ""; + }; + D081229A1D19A9EB005F7395 /* Navigation */ = { + isa = PBXGroup; + children = ( + D05CC29F1B69326400E235A3 /* NavigationController.swift */, + ); + name = Navigation; + sourceTree = ""; + }; + D081229B1D19A9F0005F7395 /* Controllers */ = { + isa = PBXGroup; + children = ( + D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */, + D015F7511D1AE08D00E269B5 /* ContainableController.swift */, + D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */, + D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */, + D05CC2E21B69552C00E235A3 /* ViewController.swift */, + D05BE4A71D1F1DCC002BD72C /* Master */, + D081229A1D19A9EB005F7395 /* Navigation */, + D015F7551D1B142300E269B5 /* Tab Bar */, + D015F7561D1B465600E269B5 /* Action Sheet */, + ); + name = Controllers; + sourceTree = ""; + }; D0C2DFBA1CC443080044FF83 /* List View */ = { isa = PBXGroup; children = ( @@ -390,7 +483,6 @@ D0C2DFBE1CC4431D0044FF83 /* ListView.swift */, D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */, D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */, - D0C2DFC11CC4431D0044FF83 /* AsyncLayoutable.swift */, D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */, D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */, D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, @@ -402,9 +494,6 @@ D0DC48521BF93D7C00F672FD /* Tabs */ = { isa = PBXGroup; children = ( - D0DC48531BF93D8A00F672FD /* TabBarController.swift */, - D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */, - D0DC48551BF945DD00F672FD /* TabBarNode.swift */, ); name = Tabs; sourceTree = ""; @@ -428,12 +517,12 @@ D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */, D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */, D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */, - D05CC2E91B69555800E235A3 /* CALayer+ImplicitAnimations.h in Headers */, D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */, D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */, D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */, D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */, D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */, + D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */, D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, D05CC3081B69575900E235A3 /* NSBag.h in Headers */, D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */, @@ -493,6 +582,7 @@ TargetAttributes = { D05CC2621B69316F00E235A3 = { CreatedOnToolsVersion = 7.0; + ProvisioningStyle = Manual; }; D05CC26C1B69316F00E235A3 = { CreatedOnToolsVersion = 7.0; @@ -541,10 +631,8 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */, D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, - D0078A6F1C92B40300DF6D92 /* ActionSheetItemNode.swift in Sources */, - D0078A6D1C92B3B900DF6D92 /* ActionSheet.swift in Sources */, - D05CC3181B695A9600E235A3 /* NavigationItemTransitionState.swift in Sources */, D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, @@ -552,6 +640,7 @@ D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, + D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */, D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, @@ -559,20 +648,25 @@ D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, + D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, + D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */, D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, - D05CC3171B695A9600E235A3 /* NavigationItemWrapper.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, - D03BCCEB1C72AE590097A291 /* DisplayTheme.swift in Sources */, + D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */, D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, + D05BE4A91D1F1DDD002BD72C /* UniversalMasterController.swift in Sources */, + D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */, + D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */, D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, @@ -581,12 +675,13 @@ D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, - D05CC2E81B69555800E235A3 /* CALayer+ImplicitAnimations.m in Sources */, - D03BCCED1C72AEC30097A291 /* DefaultDisplayTheme.swift in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, - D03E7E031C98160C00C07816 /* StatusBarSurfaceProvider.swift in Sources */, + D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */, + D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, + D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, + D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */, D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, - D0C2DFCC1CC4431D0044FF83 /* AsyncLayoutable.swift in Sources */, + D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, @@ -594,12 +689,18 @@ D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, + D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, + D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, + D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, + D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, + D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, + D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -715,6 +816,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -727,6 +829,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; @@ -736,6 +839,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -748,6 +852,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; }; @@ -819,6 +924,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -831,6 +937,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; }; name = Hockeyapp; diff --git a/Display/ActionSheet.swift b/Display/ActionSheet.swift deleted file mode 100644 index 5feab284f9..0000000000 --- a/Display/ActionSheet.swift +++ /dev/null @@ -1,7 +0,0 @@ -import Foundation - -public class ActionSheet { - public init() { - - } -} diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift new file mode 100644 index 0000000000..ad1de9c38e --- /dev/null +++ b/Display/ActionSheetButtonItem.swift @@ -0,0 +1,22 @@ +import Foundation + +public enum ActionSheetButtonColor { + case accent + case destructive +} + +public class ActionSheetButtonItem: ActionSheetItem { + public let title: String + public let color: ActionSheetButtonColor + public let action: () -> Void + + public init(title: String, color: ActionSheetButtonColor = .accent, action: () -> Void) { + self.title = title + self.color = color + self.action = action + } + + public func node() -> ActionSheetItemNode { + return ActionSheetButtonNode(title: AttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: self.color == .accent ? UIColor(0x1195f2) : UIColor.red()), action: self.action) + } +} diff --git a/Display/ActionSheetButtonNode.swift b/Display/ActionSheetButtonNode.swift new file mode 100644 index 0000000000..40b1aaea14 --- /dev/null +++ b/Display/ActionSheetButtonNode.swift @@ -0,0 +1,63 @@ +import UIKit +import AsyncDisplayKit + +public class ActionSheetButtonNode: ActionSheetItemNode { + public static let defaultFont: UIFont = Font.regular(20.0) + + private let action: () -> Void + + private let button: HighlightTrackingButton + private let label: UILabel + private var calculatedLabelSize: CGSize? + + public init(title: AttributedString, action: () -> Void) { + self.action = action + + self.button = HighlightTrackingButton() + self.label = UILabel() + + super.init() + + self.view.addSubview(self.button) + + self.label.attributedText = title + self.label.numberOfLines = 1 + self.label.isUserInteractionEnabled = false + self.view.addSubview(self.label) + + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + } else { + UIView.animate(withDuration: 0.3, animations: { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + }) + } + } + } + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + self.label.sizeToFit() + self.calculatedLabelSize = self.label.frame.size + + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + self.button.frame = CGRect(origin: CGPoint(), size: self.calculatedSize) + + if let calculatedLabelSize = self.calculatedLabelSize { + self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.calculatedSize.width - calculatedLabelSize.width) / 2.0), y: floorToScreenPixels((self.calculatedSize.height - calculatedLabelSize.height) / 2.0)), size: calculatedLabelSize) + } + } + + @objc func buttonPressed() { + self.action() + } +} diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift new file mode 100644 index 0000000000..149d9957fe --- /dev/null +++ b/Display/ActionSheetController.swift @@ -0,0 +1,44 @@ +import Foundation + +public class ActionSheetController: ViewController { + private var actionSheetNode: ActionSheetControllerNode { + return self.displayNode as! ActionSheetControllerNode + } + + private var groups: [ActionSheetItemGroup] = [] + + public func dismissAnimated() { + self.actionSheetNode.animateOut() + } + + public override func loadDisplayNode() { + self.displayNode = ActionSheetControllerNode() + self.displayNodeDidLoad() + self.navigationBar.isHidden = true + + self.actionSheetNode.dismiss = { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + + self.actionSheetNode.setGroups(self.groups) + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.actionSheetNode.containerLayoutUpdated(layout, transition: transition) + } + + public override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.actionSheetNode.animateIn() + } + + public func setItemGroups(_ groups: [ActionSheetItemGroup]) { + self.groups = groups + if self.isViewLoaded() { + self.actionSheetNode.setGroups(groups) + } + } +} diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift new file mode 100644 index 0000000000..5db30c1173 --- /dev/null +++ b/Display/ActionSheetControllerNode.swift @@ -0,0 +1,158 @@ +import UIKit +import AsyncDisplayKit + +private let containerInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 8.0, right: 16.0) + +private class ActionSheetControllerNodeScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } +} + +final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { + static let dimColor: UIColor = UIColor(white: 0.0, alpha: 0.4) + + private let dismissTapView: UIView + + private let leftDimView: UIView + private let rightDimView: UIView + private let topDimView: UIView + private let bottomDimView: UIView + + private let itemGroupsContainerNode: ActionSheetItemGroupsContainerNode + + private let scrollView: UIScrollView + + var dismiss: () -> Void = { } + + override init() { + self.scrollView = ActionSheetControllerNodeScrollView() + self.scrollView.alwaysBounceVertical = true + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + + self.dismissTapView = UIView() + + self.leftDimView = UIView() + self.leftDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.leftDimView.isUserInteractionEnabled = false + + self.rightDimView = UIView() + self.rightDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.rightDimView.isUserInteractionEnabled = false + + self.topDimView = UIView() + self.topDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.topDimView.isUserInteractionEnabled = false + + self.bottomDimView = UIView() + self.bottomDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.bottomDimView.isUserInteractionEnabled = false + + self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode() + + super.init() + + self.scrollView.delegate = self + + self.view.addSubview(self.scrollView) + + self.scrollView.addSubview(self.dismissTapView) + + self.scrollView.addSubview(self.leftDimView) + self.scrollView.addSubview(self.rightDimView) + self.scrollView.addSubview(self.topDimView) + self.scrollView.addSubview(self.bottomDimView) + + self.dismissTapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:)))) + + self.scrollView.addSubnode(self.itemGroupsContainerNode) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var insets = layout.insets(options: [.statusBar]) + + self.scrollView.frame = CGRect(origin: CGPoint(), size: layout.size) + self.dismissTapView.frame = CGRect(origin: CGPoint(), size: layout.size) + + self.itemGroupsContainerNode.measure(CGSize(width: layout.size.width - containerInsets.left - containerInsets.right - insets.left - insets.right, height: layout.size.height - containerInsets.top - containerInsets.bottom - insets.top - insets.bottom)) + self.itemGroupsContainerNode.frame = CGRect(origin: CGPoint(x: insets.left + containerInsets.left, y: layout.size.height - insets.bottom - containerInsets.bottom - self.itemGroupsContainerNode.calculatedSize.height), size: self.itemGroupsContainerNode.calculatedSize) + self.itemGroupsContainerNode.layout() + + self.updateScrollDimViews(size: layout.size) + } + + func animateIn() { + let tempDimView = UIView() + tempDimView.backgroundColor = ActionSheetControllerNode.dimColor + tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height) + self.view.addSubview(tempDimView) + + for node in [tempDimView, self.topDimView, self.leftDimView, self.rightDimView, self.bottomDimView] { + node.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.4) + } + + self.itemGroupsContainerNode.animateDimViewsAlpha(from: 0.0, to: 1.0, duration: 0.4) + + self.layer.animateBounds(from: self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height), to: self.bounds, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak tempDimView] _ in + tempDimView?.removeFromSuperview() + }) + } + + func animateOut() { + let tempDimView = UIView() + tempDimView.backgroundColor = ActionSheetControllerNode.dimColor + tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height) + self.view.addSubview(tempDimView) + + for node in [tempDimView, self.topDimView, self.leftDimView, self.rightDimView, self.bottomDimView] { + node.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + } + self.itemGroupsContainerNode.animateDimViewsAlpha(from: 1.0, to: 0.0, duration: 0.3) + + self.layer.animateBounds(from: self.bounds, to: self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height), duration: 0.35, timingFunction: kCAMediaTimingFunctionEaseOut, removeOnCompletion: false, completion: { [weak self, weak tempDimView] _ in + tempDimView?.removeFromSuperview() + + self?.dismiss() + }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + let result = super.hitTest(point, with: event) + return result + } + + @objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.animateOut() + } + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateScrollDimViews(size: self.scrollView.frame.size) + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + let contentOffset = self.scrollView.contentOffset + var additionalTopHeight = max(0.0, -contentOffset.y) + + if additionalTopHeight >= 30.0 { + self.animateOut() + } + } + + func updateScrollDimViews(size: CGSize) { + var additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) + var additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) + + self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) + self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) + + self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) + self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) + } + + func setGroups(_ groups: [ActionSheetItemGroup]) { + self.itemGroupsContainerNode.setGroups(groups) + } +} diff --git a/Display/ActionSheetItem.swift b/Display/ActionSheetItem.swift new file mode 100644 index 0000000000..469f36888c --- /dev/null +++ b/Display/ActionSheetItem.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol ActionSheetItem { + func node() -> ActionSheetItemNode +} diff --git a/Display/ActionSheetItemGroup.swift b/Display/ActionSheetItemGroup.swift new file mode 100644 index 0000000000..69acc5c379 --- /dev/null +++ b/Display/ActionSheetItemGroup.swift @@ -0,0 +1,10 @@ + +public final class ActionSheetItemGroup { + let items: [ActionSheetItem] + let leadingVisibleNodeCount: CGFloat? + + public init(items: [ActionSheetItem], leadingVisibleNodeCount: CGFloat? = nil) { + self.items = items + self.leadingVisibleNodeCount = leadingVisibleNodeCount + } +} diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift new file mode 100644 index 0000000000..a51be83e76 --- /dev/null +++ b/Display/ActionSheetItemGroupNode.swift @@ -0,0 +1,212 @@ +import UIKit +import AsyncDisplayKit + +private class ActionSheetItemGroupNodeScrollView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } +} + +final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { + private let centerDimView: UIImageView + private let topDimView: UIView + private let bottomDimView: UIView + let trailingDimView: UIView + + private let clippingNode: ASDisplayNode + private let backgroundEffectView: UIVisualEffectView + private let scrollView: UIScrollView + + private var itemNodes: [ActionSheetItemNode] = [] + private var leadingVisibleNodeCount: CGFloat = 100.0 + + var respectInputHeight = true + + override init() { + self.centerDimView = UIImageView() + self.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: ActionSheetControllerNode.dimColor) + + self.topDimView = UIView() + self.topDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.topDimView.isUserInteractionEnabled = false + + self.bottomDimView = UIView() + self.bottomDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.bottomDimView.isUserInteractionEnabled = false + + self.trailingDimView = UIView() + self.trailingDimView.backgroundColor = ActionSheetControllerNode.dimColor + + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + self.clippingNode.cornerRadius = 16.0 + + self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + + self.scrollView = ActionSheetItemGroupNodeScrollView() + self.scrollView.delaysContentTouches = false + self.scrollView.canCancelContentTouches = true + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + + super.init() + + self.view.addSubview(self.centerDimView) + self.view.addSubview(self.topDimView) + self.view.addSubview(self.bottomDimView) + self.view.addSubview(self.trailingDimView) + + self.scrollView.delegate = self + + self.clippingNode.view.addSubview(self.backgroundEffectView) + self.clippingNode.view.addSubview(self.scrollView) + + self.addSubnode(self.clippingNode) + } + + func updateItemNodes(_ nodes: [ActionSheetItemNode], leadingVisibleNodeCount: CGFloat = 1000.0) { + for node in self.itemNodes { + if !nodes.contains({ $0 === node }) { + node.removeFromSupernode() + } + } + + for node in nodes { + if !self.itemNodes.contains({ $0 === node }) { + self.scrollView.addSubnode(node) + } + } + + self.itemNodes = nodes + self.leadingVisibleNodeCount = leadingVisibleNodeCount + self.invalidateCalculatedLayout() + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + var itemNodesHeight: CGFloat = 0.0 + var leadingVisibleNodeSize: CGFloat = 0.0 + + var i = 0 + for node in self.itemNodes { + if CGFloat(0.0).isLess(than: itemNodesHeight) { + itemNodesHeight += UIScreenPixel + } + let size = node.measure(constrainedSize) + itemNodesHeight += size.height + + if ceil(CGFloat(i)).isLessThanOrEqualTo(leadingVisibleNodeCount) { + if CGFloat(0.0).isLess(than: leadingVisibleNodeSize) { + leadingVisibleNodeSize += UIScreenPixel + } + let factor: CGFloat = min(1.0, leadingVisibleNodeCount - CGFloat(i)) + leadingVisibleNodeSize += size.height * factor + } + i += 1 + } + + return CGSize(width: constrainedSize.width, height: min(itemNodesHeight, constrainedSize.height)) + } + + override func layout() { + let scrollViewFrame = CGRect(origin: CGPoint(), size: self.calculatedSize) + var updateOffset = false + if !self.scrollView.frame.equalTo(scrollViewFrame) { + self.scrollView.frame = scrollViewFrame + updateOffset = true + } + + let backgroundEffectViewFrame = CGRect(origin: CGPoint(), size: self.calculatedSize) + if !self.backgroundEffectView.frame.equalTo(backgroundEffectViewFrame) { + self.backgroundEffectView.frame = backgroundEffectViewFrame + } + + var itemNodesHeight: CGFloat = 0.0 + var leadingVisibleNodeSize: CGFloat = 0.0 + + var i = 0 + for node in self.itemNodes { + if CGFloat(0.0).isLess(than: itemNodesHeight) { + itemNodesHeight += UIScreenPixel + } + node.frame = CGRect(origin: CGPoint(x: 0.0, y: itemNodesHeight), size: node.calculatedSize) + itemNodesHeight += node.calculatedSize.height + + if CGFloat(i).isLessThanOrEqualTo(leadingVisibleNodeCount) { + if CGFloat(0.0).isLess(than: leadingVisibleNodeSize) { + leadingVisibleNodeSize += UIScreenPixel + } + let factor: CGFloat = min(1.0, leadingVisibleNodeCount - CGFloat(i)) + leadingVisibleNodeSize += node.calculatedSize.height * factor + } + i += 1 + } + + let scrollViewContentSize = CGSize(width: self.calculatedSize.width, height: itemNodesHeight) + if !self.scrollView.contentSize.equalTo(scrollViewContentSize) { + self.scrollView.contentSize = scrollViewContentSize + } + var scrollViewContentInsets = UIEdgeInsets(top: max(0.0, self.calculatedSize.height - leadingVisibleNodeSize), left: 0.0, bottom: 0.0, right: 0.0) + + if !UIEdgeInsetsEqualToEdgeInsets(self.scrollView.contentInset, scrollViewContentInsets) { + self.scrollView.contentInset = scrollViewContentInsets + } + + if updateOffset { + self.scrollView.contentOffset = CGPoint(x: 0.0, y: -scrollViewContentInsets.top) + } + + self.updateOverscroll() + } + + private func currentVerticalOverscroll() -> CGFloat { + var verticalOverscroll: CGFloat = 0.0 + if scrollView.contentOffset.y < 0.0 { + verticalOverscroll = scrollView.contentOffset.y + } else if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.size.height { + verticalOverscroll = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.bounds.size.height) + } + return verticalOverscroll + } + + private func currentRealVerticalOverscroll() -> CGFloat { + var verticalOverscroll: CGFloat = 0.0 + if scrollView.contentOffset.y < 0.0 { + verticalOverscroll = scrollView.contentOffset.y + } else if scrollView.contentOffset.y > scrollView.contentSize.height - scrollView.bounds.size.height { + verticalOverscroll = scrollView.contentOffset.y - (scrollView.contentSize.height - scrollView.bounds.size.height) + } + return verticalOverscroll + } + + private func updateOverscroll() { + let verticalOverscroll = self.currentVerticalOverscroll() + + self.clippingNode.layer.sublayerTransform = CATransform3DMakeTranslation(0.0, min(0.0, verticalOverscroll), 0.0) + let clippingNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, -verticalOverscroll)), size: CGSize(width: self.calculatedSize.width, height: self.calculatedSize.height - abs(verticalOverscroll))) + if !self.clippingNode.frame.equalTo(clippingNodeFrame) { + self.clippingNode.frame = clippingNodeFrame + + self.centerDimView.frame = clippingNodeFrame + self.topDimView.frame = CGRect(x: 0.0, y: 0.0, width: clippingNodeFrame.size.width, height: max(0.0, clippingNodeFrame.minY)) + self.bottomDimView.frame = CGRect(x: 0.0, y: clippingNodeFrame.maxY, width: clippingNodeFrame.size.width, height: max(0.0, self.bounds.size.height - clippingNodeFrame.maxY)) + } + } + + func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateOverscroll() + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.clippingNode.frame.contains(point) { + return super.hitTest(point, with: event) + } else { + return nil + } + } + + func animateDimViewsAlpha(from: CGFloat, to: CGFloat, duration: Double) { + for node in [self.centerDimView, self.topDimView, self.bottomDimView] { + node.layer.animateAlpha(from: from, to: to, duration: duration) + } + } +} diff --git a/Display/ActionSheetItemGroupsContainerNode.swift b/Display/ActionSheetItemGroupsContainerNode.swift new file mode 100644 index 0000000000..e385cc73ef --- /dev/null +++ b/Display/ActionSheetItemGroupsContainerNode.swift @@ -0,0 +1,75 @@ +import UIKit +import AsyncDisplayKit + +private let groupSpacing: CGFloat = 16.0 + +final class ActionSheetItemGroupsContainerNode: ASDisplayNode { + private var groupNodes: [ActionSheetItemGroupNode] = [] + + override init() { + super.init() + } + + func setGroups(_ groups: [ActionSheetItemGroup]) { + for groupNode in self.groupNodes { + groupNode.removeFromSupernode() + } + self.groupNodes.removeAll() + + for group in groups { + let groupNode = ActionSheetItemGroupNode() + groupNode.updateItemNodes(group.items.map({ $0.node() }), leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0) + self.groupNodes.append(groupNode) + self.addSubnode(groupNode) + } + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + var groupsHeight: CGFloat = 0.0 + + for groupNode in self.groupNodes.reversed() { + if CGFloat(0.0).isLess(than: groupsHeight) { + groupsHeight += groupSpacing + } + + let size = groupNode.measure(CGSize(width: constrainedSize.width, height: max(0.0, constrainedSize.height - groupsHeight))) + groupsHeight += size.height + } + + return CGSize(width: constrainedSize.width, height: min(groupsHeight, constrainedSize.height)) + } + + override func layout() { + var groupsHeight: CGFloat = 0.0 + for i in 0 ..< self.groupNodes.count { + let groupNode = self.groupNodes[i] + + let size = groupNode.calculatedSize + + if i != 0 { + groupsHeight += groupSpacing + self.groupNodes[i - 1].trailingDimView.frame = CGRect(x: 0.0, y: groupNodes[i - 1].bounds.size.height, width: size.width, height: groupSpacing) + } + + groupNode.frame = CGRect(origin: CGPoint(x: 0.0, y: groupsHeight), size: size) + groupNode.trailingDimView.frame = CGRect() + + groupsHeight += size.height + } + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for groupNode in self.groupNodes { + if groupNode.frame.contains(point) { + return groupNode.hitTest(self.convert(point, to: groupNode), with: event) + } + } + return nil + } + + func animateDimViewsAlpha(from: CGFloat, to: CGFloat, duration: Double) { + for node in self.groupNodes { + node.animateDimViewsAlpha(from: from, to: to, duration: duration) + } + } +} diff --git a/Display/ActionSheetItemNode.swift b/Display/ActionSheetItemNode.swift index bae40f9bf6..a41f3d71d7 100644 --- a/Display/ActionSheetItemNode.swift +++ b/Display/ActionSheetItemNode.swift @@ -1,8 +1,32 @@ -import Foundation +import UIKit import AsyncDisplayKit public class ActionSheetItemNode: ASDisplayNode { - override public init() { + public static let defaultBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.8) + public static let highlightedBackgroundColor: UIColor = UIColor(white: 0.9, alpha: 0.7) + + public let backgroundNode: ASDisplayNode + private let overflowSeparatorNode: ASDisplayNode + + public override init() { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + + self.overflowSeparatorNode = ASDisplayNode() + self.overflowSeparatorNode.backgroundColor = UIColor(white: 0.5, alpha: 0.3) + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.overflowSeparatorNode) } -} \ No newline at end of file + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + self.backgroundNode.frame = CGRect(origin: CGPoint(), size: self.calculatedSize) + self.overflowSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.calculatedSize.height), size: CGSize(width: self.calculatedSize.width, height: UIScreenPixel)) + } +} diff --git a/Display/AsyncLayoutable.swift b/Display/AsyncLayoutable.swift deleted file mode 100644 index 0accb93230..0000000000 --- a/Display/AsyncLayoutable.swift +++ /dev/null @@ -1,6 +0,0 @@ -import Foundation -import AsyncDisplayKit - -public protocol AsyncLayoutable: class { - static func asyncLayout(maybeNode: Self?) -> (constrainedSize: CGSize) -> (CGSize, () -> Self) -} diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 8547b44f3c..d9592f2dd1 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -17,7 +17,8 @@ import UIKit } private let completionKey = "CAAnimationUtils_completion" -private let springKey = "CAAnimationUtilsSpringCurve" + +public let kCAMediaTimingFunctionSpring = "CAAnimationUtilsSpringCurve" public extension CAAnimation { public var completion: ((Bool) -> Void)? { @@ -39,7 +40,7 @@ public extension CAAnimation { public extension CALayer { public func animate(from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - if timingFunction == springKey { + if timingFunction == kCAMediaTimingFunctionSpring { let animation = makeSpringAnimation(keyPath) animation.fromValue = from animation.toValue = to @@ -112,29 +113,29 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) } - internal func animatePosition(from: CGPoint, to: CGPoint, duration: Double, completion: ((Bool) -> Void)? = nil) { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { if from == to { return } - self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) + self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - internal func animateBounds(from: CGRect, to: CGRect, duration: Double, completion: ((Bool) -> Void)? = nil) { + func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { if from == to { return } - self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: springKey, duration: duration, removeOnCompletion: true, completion: completion) + self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double) { self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } - public func animateFrame(from: CGRect, to: CGRect, duration: Double, spring: Bool = false, completion: ((Bool) -> Void)? = nil) { + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, completion: ((Bool) -> Void)? = nil) { if from == to { return } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, completion: nil) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, completion: completion) + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, completion: nil) + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, completion: completion) } } diff --git a/Display/CALayer+ImplicitAnimations.h b/Display/CALayer+ImplicitAnimations.h deleted file mode 100644 index 0dc07cdb29..0000000000 --- a/Display/CALayer+ImplicitAnimations.h +++ /dev/null @@ -1,22 +0,0 @@ -#import - -@interface CALayer (ImplicitAnimations) - -+ (void)beginRecordingChanges; -+ (NSArray *)endRecordingChanges; - -+ (void)overrideAnimationSpeed:(CGFloat)speed block:(void (^)())block; - -@end - -@interface CALayerAnimation : NSObject - -@property (nonatomic, weak, readonly) CALayer *layer; - -@property (nonatomic, readonly) CGRect startBounds; -@property (nonatomic, readonly) CGRect endBounds; - -@property (nonatomic, readonly) CGPoint startPosition; -@property (nonatomic, readonly) CGPoint endPosition; - -@end diff --git a/Display/CALayer+ImplicitAnimations.m b/Display/CALayer+ImplicitAnimations.m deleted file mode 100644 index 42c6658de6..0000000000 --- a/Display/CALayer+ImplicitAnimations.m +++ /dev/null @@ -1,163 +0,0 @@ -#import "CALayer+ImplicitAnimations.h" - -#import - -#import "RuntimeUtils.h" -#import - -static bool recordingChanges = false; -static NSMutableArray *currentLayerAnimations() -{ - static NSMutableArray *array = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - array = [[NSMutableArray alloc] init]; - }); - - return array; -} - -@implementation CALayerAnimation - -- (instancetype)initWithLayer:(CALayer *)layer -{ - self = [super init]; - if (self != nil) - { - _layer = layer; - - _startBounds = layer.bounds; - _startPosition = layer.position; - - _endBounds = _startBounds; - _endPosition = _startPosition; - } - return self; -} - -- (void)setEndBounds:(CGRect)endBounds -{ - _endBounds = endBounds; -} - -- (void)setEndPosition:(CGPoint)endPosition -{ - _endPosition = endPosition; -} - -@end - -@interface CALayer (_ca836a62_) - -@end - -@implementation CALayer (_ca836a62_) - -- (void)_ca836a62_setBounds:(CGRect)bounds -{ - if (recordingChanges && [self.delegate isKindOfClass:[ASDisplayNode class]]) - { - CALayerAnimation *animation = nil; - for (CALayerAnimation *listAnimation in currentLayerAnimations()) - { - if (listAnimation.layer == self) - { - animation = listAnimation; - break; - } - } - if (animation == nil) - { - animation = [[CALayerAnimation alloc] initWithLayer:self]; - [currentLayerAnimations() addObject:animation]; - } - [animation setEndBounds:bounds]; - } - - [self _ca836a62_setBounds:bounds]; -} - -- (void)_ca836a62_setPosition:(CGPoint)position -{ - if (recordingChanges && [self.delegate isKindOfClass:[ASDisplayNode class]]) - { - CALayerAnimation *animation = nil; - for (CALayerAnimation *listAnimation in currentLayerAnimations()) - { - if (listAnimation.layer == self) - { - animation = listAnimation; - break; - } - } - if (animation == nil) - { - animation = [[CALayerAnimation alloc] initWithLayer:self]; - [currentLayerAnimations() addObject:animation]; - } - [animation setEndPosition:position]; - } - - [self _ca836a62_setPosition:position]; -} - -- (void)_ca836a62_addAnimation:(CAAnimation *)animation forKey:(NSString *)key { - if (speedOverride != 1.0f) { - animation.speed *= speedOverride; - } - [self _ca836a62_addAnimation:animation forKey:key]; -} - -static CGFloat speedOverride = 1.0f; - -+ (void)overrideAnimationSpeed:(CGFloat)speed block:(void (^)())block { - CGFloat previousOverride = speedOverride; - speedOverride = speed; - block(); - speedOverride = previousOverride; -} - -@end - -@interface LayerAnimationExtensions : NSObject - -@end - -@implementation LayerAnimationExtensions - -+ (void)load -{ - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^ - { - //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setBounds:) newSelector:@selector(_ca836a62_setBounds:)]; - //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(setPosition:) newSelector:@selector(_ca836a62_setPosition:)]; - //[RuntimeUtils swizzleInstanceMethodOfClass:[CALayer class] currentSelector:@selector(addAnimation:forKey:) newSelector:@selector(_ca836a62_addAnimation:forKey:)]; - }); -} - -@end - -@implementation CALayer (ImplicitAnimations) - -+ (void)beginRecordingChanges -{ - recordingChanges = true; - [currentLayerAnimations() removeAllObjects]; -} - -+ (NSArray *)endRecordingChanges -{ - recordingChanges = false; - NSArray *array = [[NSArray alloc] initWithArray:currentLayerAnimations()]; - [currentLayerAnimations() removeAllObjects]; - - return array; -} - -+ (void)overrideAnimationSpeed:(CGFloat)speed block:(void (^)())block { - -} - -@end diff --git a/Display/CATracingLayer.h b/Display/CATracingLayer.h new file mode 100644 index 0000000000..4f6b33fc2e --- /dev/null +++ b/Display/CATracingLayer.h @@ -0,0 +1,24 @@ +#import + +@interface CATracingLayer : CALayer + +@end + +@interface UITracingLayerView : UIView + +@end + +@interface CALayer (Tracing) + +- (id _Nullable)traceableInfo; +- (void)setTraceableInfo:(id _Nullable)info; + +- (bool)hasPositionOrOpacityAnimations; + +- (void)setInvalidateTracingSublayers:(void (^_Nullable)())block; +- (NSArray *> * _Nonnull)traceableLayerSurfaces; +- (void)adjustTraceableLayerTransforms:(CGSize)offset; + +- (void)invalidateUpTheTree; + +@end diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m new file mode 100644 index 0000000000..a6e905346f --- /dev/null +++ b/Display/CATracingLayer.m @@ -0,0 +1,275 @@ +#import "CATracingLayer.h" + +#import "RuntimeUtils.h" + +static void *CATracingLayerInvalidatedKey = &CATracingLayerInvalidatedKey; +static void *CATracingLayerIsInvalidatedBlock = &CATracingLayerIsInvalidatedBlock; +static void *CATracingLayerTraceablInfoKey = &CATracingLayerTraceablInfoKey; + +@implementation CALayer (Tracing) + +- (void)setInvalidateTracingSublayers:(void (^_Nullable)())block { + [self setAssociatedObject:[block copy] forKey:CATracingLayerIsInvalidatedBlock]; +} + +- (void (^_Nullable)())invalidateTracingSublayers { + return [self associatedObjectForKey:CATracingLayerIsInvalidatedBlock]; +} + +- (bool)isTraceable { + return [self associatedObjectForKey:CATracingLayerTraceablInfoKey] != nil || [self isKindOfClass:[CATracingLayer class]]; +} + +- (id _Nullable)traceableInfo { + return [self associatedObjectForKey:CATracingLayerTraceablInfoKey]; +} + +- (void)setTraceableInfo:(id _Nullable)info { + [self setAssociatedObject:info forKey:CATracingLayerTraceablInfoKey]; +} + +- (bool)hasPositionOrOpacityAnimations { + return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil || [self animationForKey:@"sublayerTransform"] != nil || [self animationForKey:@"opacity"] != nil; +} + +static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth) { + NSMutableArray *result = nil; + + bool hadTraceableSublayers = false; + for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { + if ([sublayer traceableInfo] != nil) { + NSMutableArray *array = layersByDepth[@(depth)]; + if (array == nil) { + array = [[NSMutableArray alloc] init]; + layersByDepth[@(depth)] = array; + } + [array addObject:sublayer]; + hadTraceableSublayers = true; + } + } + + if (!hadTraceableSublayers) { + for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { + if ([sublayer isKindOfClass:[CATracingLayer class]]) { + traceLayerSurfaces(depth + 1, sublayer, layersByDepth); + } + } + } +} + +- (NSArray *> *)traceableLayerSurfaces { + NSMutableDictionary *> *layersByDepth = [[NSMutableDictionary alloc] init]; + + traceLayerSurfaces(0, self, layersByDepth); + + NSMutableArray *> *result = [[NSMutableArray alloc] init]; + + for (id key in [[layersByDepth allKeys] sortedArrayUsingSelector:@selector(compare:)]) { + [result addObject:layersByDepth[key]]; + } + + return result; +} + +- (void)adjustTraceableLayerTransforms:(CGSize)offset { + CGRect frame = self.frame; + CGSize sublayerOffset = CGSizeMake(frame.origin.x + offset.width, frame.origin.y + offset.height); + for (CALayer *sublayer in self.sublayers) { + if ([sublayer traceableInfo] != nil) { + sublayer.sublayerTransform = CATransform3DMakeTranslation(-sublayerOffset.width, -sublayerOffset.height, 0.0f); + } else if ([sublayer isKindOfClass:[CATracingLayer class]]) { + [(CATracingLayer *)sublayer adjustTraceableLayerTransforms:sublayerOffset]; + } + } +} + +- (void)invalidateUpTheTree { + CALayer *superlayer = self; + while (true) { + if (superlayer == nil) { + break; + } + + void (^block)() = [superlayer invalidateTracingSublayers]; + if (block != nil) { + block(); + } + + superlayer = superlayer.superlayer; + } +} + +@end + +@interface CATracingLayerAnimationDelegate : NSObject { + id _delegate; + void (^_animationStopped)(); +} + +@end + +@implementation CATracingLayerAnimationDelegate + +- (instancetype)initWithDelegate:(id)delegate animationStopped:(void (^_Nonnull)())animationStopped { + _delegate = delegate; + _animationStopped = [animationStopped copy]; + return self; +} + +- (void)animationDidStart:(CAAnimation *)anim { + if ([_delegate respondsToSelector:@selector(animationDidStart:)]) { + [(id)_delegate animationDidStart:anim]; + } +} + +- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag { + if ([_delegate respondsToSelector:@selector(animationDidStop:finished:)]) { + [(id)_delegate animationDidStop:anim finished:flag]; + } + + if (_animationStopped) { + _animationStopped(); + } +} + +@end + +@interface CATracingLayer () + +@property (nonatomic) bool isInvalidated; + +@end + +@implementation CATracingLayer + +- (bool)isInvalidated { + return [[self associatedObjectForKey:CATracingLayerInvalidatedKey] intValue] != 0; +} + +- (void)setIsInvalidated:(bool)isInvalidated { + [self setAssociatedObject: isInvalidated ? @1 : @0 forKey:CATracingLayerInvalidatedKey]; +} + +- (void)setPosition:(CGPoint)position { + [super setPosition:position]; + + [self invalidateUpTheTree]; +} + +- (void)setOpacity:(float)opacity { + [super setOpacity:opacity]; + + [self invalidateUpTheTree]; +} + +- (void)addSublayer:(CALayer *)layer { + [super addSublayer:layer]; + + if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { + [self invalidateUpTheTree]; + } +} + +- (void)insertSublayer:(CALayer *)layer atIndex:(unsigned)idx { + [super insertSublayer:layer atIndex:idx]; + + if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { + [self invalidateUpTheTree]; + } +} + +- (void)insertSublayer:(CALayer *)layer below:(nullable CALayer *)sibling { + [super insertSublayer:layer below:sibling]; + + if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { + [self invalidateUpTheTree]; + } +} + +- (void)insertSublayer:(CALayer *)layer above:(nullable CALayer *)sibling { + [super insertSublayer:layer above:sibling]; + + if ([layer isTraceable] || [layer isKindOfClass:[CATracingLayer class]]) { + [self invalidateUpTheTree]; + } +} + +- (void)replaceSublayer:(CALayer *)layer with:(CALayer *)layer2 { + [super replaceSublayer:layer with:layer2]; + + if ([layer isTraceable] || [layer2 isTraceable]) { + [self invalidateUpTheTree]; + } +} + +- (void)removeFromSuperlayer { + if ([self isTraceable]) { + [self invalidateUpTheTree]; + } + + [super removeFromSuperlayer]; +} + +- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { + if ([anim isKindOfClass:[CABasicAnimation class]]) { + if ([key isEqualToString:@"position"]) { + CABasicAnimation *animCopy = [anim copy]; + CGPoint from = [animCopy.fromValue CGPointValue]; + CGPoint to = [animCopy.toValue CGPointValue]; + + animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(to.x - from.x, to.y - from.y, 0.0f)]; + animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + animCopy.keyPath = @"sublayerTransform"; + + __weak CATracingLayer *weakSelf = self; + anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + + [super addAnimation:anim forKey:key]; + + [self invalidateUpTheTree]; + + [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; + } else if ([key isEqualToString:@"opacity"]) { + __weak CATracingLayer *weakSelf = self; + anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + + [super addAnimation:anim forKey:key]; + + [self invalidateUpTheTree]; + } else { + [super addAnimation:anim forKey:key]; + } + } else { + [super addAnimation:anim forKey:key]; + } +} + +- (void)mirrorAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { + for (CALayer *sublayer in self.sublayers) { + if ([sublayer traceableInfo] != nil) { + [sublayer addAnimation:[animation copy] forKey:key]; + } else if ([sublayer isKindOfClass:[CATracingLayer class]]) { + [(CATracingLayer *)sublayer mirrorAnimationDownTheTree:animation key:key]; + } + } +} + +@end + +@implementation UITracingLayerView + ++ (Class)layerClass { + return [CATracingLayer class]; +} + +@end diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift new file mode 100644 index 0000000000..40165cdf5c --- /dev/null +++ b/Display/ContainableController.swift @@ -0,0 +1,42 @@ +import UIKit +import AsyncDisplayKit + +public enum ContainedViewLayoutTransitionCurve { + case easeInOut + case spring +} + +public extension ContainedViewLayoutTransitionCurve { + var timingFunction: String { + switch self { + case .easeInOut: + return kCAMediaTimingFunctionEaseInEaseOut + case .spring: + return kCAMediaTimingFunctionSpring + } + } +} + +public enum ContainedViewLayoutTransition { + case immediate + case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve) +} + +public extension ContainedViewLayoutTransition { + func updateFrame(node: ASDisplayNode, frame: CGRect) { + switch self { + case .immediate: + node.frame = frame + case let .animated(duration, curve): + let previousFrame = node.frame + node.frame = frame + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction) + } + } +} + +public protocol ContainableController: class { + var view: UIView! { get } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) +} diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift new file mode 100644 index 0000000000..adf8972412 --- /dev/null +++ b/Display/ContainerViewLayout.swift @@ -0,0 +1,83 @@ + +public struct ContainerViewLayoutInsetOptions: OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public init() { + self.rawValue = 0 + } + + public static let statusBar = ContainerViewLayoutInsetOptions(rawValue: 1 << 0) + public static let input = ContainerViewLayoutInsetOptions(rawValue: 1 << 1) +} + +public struct ContainerViewLayout: Equatable { + public let size: CGSize + public let intrinsicInsets: UIEdgeInsets + public let statusBarHeight: CGFloat? + public let inputHeight: CGFloat? + + public init() { + self.size = CGSize() + self.intrinsicInsets = UIEdgeInsets() + self.statusBarHeight = nil + self.inputHeight = nil + } + + public init(size: CGSize, intrinsicInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?) { + self.size = size + self.intrinsicInsets = intrinsicInsets + self.statusBarHeight = statusBarHeight + self.inputHeight = inputHeight + } + + public func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets { + var insets = self.intrinsicInsets + if let statusBarHeight = self.statusBarHeight where options.contains(.statusBar) { + insets.top += statusBarHeight + } + if let inputHeight = self.inputHeight where options.contains(.input) { + insets.bottom += inputHeight + } + return insets + } + + public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout { + return ContainerViewLayout(size: self.size, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) + } +} + +public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { + if !lhs.size.equalTo(rhs.size) { + return false + } + + if lhs.intrinsicInsets != rhs.intrinsicInsets { + return false + } + + if let lhsStatusBarHeight = lhs.statusBarHeight { + if let rhsStatusBarHeight = rhs.statusBarHeight { + return lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) + } else { + return false + } + } else if let _ = rhs.statusBarHeight { + return false + } + + if let lhsInputHeight = lhs.inputHeight { + if let rhsInputHeight = rhs.inputHeight { + return lhsInputHeight.isEqual(to: rhsInputHeight) + } else { + return false + } + } else if let _ = rhs.inputHeight { + return false + } + + return true +} diff --git a/Display/DefaultDisplayTheme.swift b/Display/DefaultDisplayTheme.swift deleted file mode 100644 index b77e9745b5..0000000000 --- a/Display/DefaultDisplayTheme.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -func defaultDisplayTheme() -> DisplayTheme { - return DisplayTheme(tintColor: UIColor.blue()) -} diff --git a/Display/Display.h b/Display/Display.h index 72849d0699..41406bb601 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -22,7 +22,6 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import -#import #import #import #import @@ -31,3 +30,4 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import +#import diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index 5efaa305bf..fca9f80b3a 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -11,6 +11,9 @@ public class DisplayLinkDispatcher: NSObject { super.init() self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) + if #available(iOS 10.0, *) { + self.displayLink.preferredFramesPerSecond = 60 + } self.displayLink.isPaused = true self.displayLink.add(to: RunLoop.main(), forMode: RunLoopMode.commonModes.rawValue) } diff --git a/Display/Font.swift b/Display/Font.swift index 00ae0dda14..801e389c73 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -13,6 +13,6 @@ public struct Font { public extension AttributedString { convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black()) { - self.init(string: string, attributes: [kCTFontAttributeName as String: font, kCTForegroundColorAttributeName as String: textColor]) + self.init(string: string, attributes: [NSFontAttributeName: font, NSForegroundColorAttributeName as String: textColor]) } } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 969ec517e5..5d58c6c413 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -62,6 +62,35 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) return UIImage(cgImage: image, scale: scale, orientation: .up) } +public func generateFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { + return generateImage(CGSize(width: radius * 2.0, height: radius * 2.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } + + if let color = color { + context.setFillColor(color.cgColor) + } else { + context.setFillColor(UIColor.clear().cgColor) + context.setBlendMode(.copy) + } + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + }) +} + +public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { + return generateFilledCircleImage(radius: radius, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)) +} + +private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { + return generateImage(size, contextGenerator: { size, context in + context.setFillColor(color.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + }) +} + public enum DrawingContextBltMode { case Alpha } diff --git a/Display/HighlightTrackingButton.swift b/Display/HighlightTrackingButton.swift new file mode 100644 index 0000000000..9f8d5bbfeb --- /dev/null +++ b/Display/HighlightTrackingButton.swift @@ -0,0 +1,23 @@ +import UIKit + +public class HighlightTrackingButton: UIButton { + public var highligthedChanged: (Bool) -> Void = { _ in } + + public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + self.highligthedChanged(true) + + return super.beginTracking(touch, with: event) + } + + public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + self.highligthedChanged(false) + + super.endTracking(touch, with: event) + } + + public override func cancelTracking(with event: UIEvent?) { + self.highligthedChanged(false) + + super.cancelTracking(with: event) + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index 78c409800e..0ca9c87a1f 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -97,6 +97,18 @@ public struct ListViewInsertItem { } } +public struct ListViewUpdateItem { + public let index: Int + public let item: ListViewItem + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.item = item + self.directionHint = directionHint + } +} + public struct ListViewDeleteAndInsertOptions: OptionSet { public let rawValue: Int @@ -953,6 +965,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) self.displayLink.add(to: RunLoop.main(), forMode: RunLoopMode.commonModes.rawValue) + if #available(iOS 10.0, *) { + self.displayLink.preferredFramesPerSecond = 60 + } self.displayLink.isPaused = true if useDynamicTuning { @@ -1324,7 +1339,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) } - public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: (ListViewDisplayedItemRange) -> Void = { _ in }) { + public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.count == 0 && insertIndicesAndItems.count == 0 && scrollToItem == nil && updateSizeAndInsets == nil { completion(self.immediateDisplayedItemRange()) return @@ -1333,7 +1348,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.transactionQueue.addTransaction({ [weak self] transactionCompletion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, completion: { [weak strongSelf] in + strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, completion: { [weak strongSelf] in completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)) transactionCompletion() @@ -1342,7 +1357,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: (Void) -> Void) { + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: (Void) -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && scrollToItem == nil { if let updateSizeAndInsets = updateSizeAndInsets where self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { self.visibleSize = updateSizeAndInsets.size @@ -1414,6 +1429,15 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } + for updatedItem in updateIndicesAndItems { + self.items[updatedItem.index] = updatedItem.item + for itemNode in self.itemNodes { + if itemNode.index == updatedItem.index { + previousNodes[updatedItem.index] = itemNode + } + } + } + let actions = { var previousFrames: [Int: CGRect] = [:] for i in 0 ..< state.nodes.count { @@ -1575,33 +1599,35 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in - var updatedState = state - var updatedOperations = operations - updatedState.removeInvisibleNodes(&updatedOperations) - - if self.debugInfo { - print("updateAdjacent completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") - } - - let stationaryItemIndex = updatedState.stationaryOffset?.0 - - let next = { - self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, completion: completion) - } - - if options.contains(.LowLatency) || options.contains(.Synchronous) { - Queue.mainQueue().async { - if self.debugInfo { - print("updateAdjacent LowLatency enqueue \((CACurrentMediaTime() - startTime) * 1000.0) ms") + self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in + self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in + var updatedState = state + var updatedOperations = operations + updatedState.removeInvisibleNodes(&updatedOperations) + + if self.debugInfo { + print("updateAdjacent completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } + + let stationaryItemIndex = updatedState.stationaryOffset?.0 + + let next = { + self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, completion: completion) + } + + if options.contains(.LowLatency) || options.contains(.Synchronous) { + Queue.mainQueue().async { + if self.debugInfo { + print("updateAdjacent LowLatency enqueue \((CACurrentMediaTime() - startTime) * 1000.0) ms") + } + next() + } + } else { + self.dispatchOnVSync { + next() } - next() } - } else { - self.dispatchOnVSync { - next() - } - } + }) }) }) } @@ -1717,6 +1743,27 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } + private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, inputPreviousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { + var state = inputState + var operations = inputOperations + var updateIndicesAndItems = updateIndicesAndItems + + if updateIndicesAndItems.isEmpty { + completion(state, operations) + } else { + var updateItem = updateIndicesAndItems[0] + for node in state.nodes { + if node.index == updateItem.index { + + + break + } + } + } + + assertionFailure() + } + private func referencePointForInsertionAtIndex(_ nodeIndex: Int) -> CGPoint { var index = 0 for itemNode in self.itemNodes { diff --git a/Display/MergedLayoutEvents.swift b/Display/MergedLayoutEvents.swift new file mode 100644 index 0000000000..575f4cea6a --- /dev/null +++ b/Display/MergedLayoutEvents.swift @@ -0,0 +1,9 @@ +import UIKit + +protocol MergeableLayoutEvent { + +} + +final class MergedLayoutEvents { + +} diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index e3a302e6e4..7aca21f201 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -9,12 +9,10 @@ public class NavigationBackButtonNode: ASControlNode { private func attributesForCurrentState() -> [String : AnyObject] { return [ NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.isEnabled ? UIColor.blue() : UIColor.gray() + NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray() ] } - var suspendLayout = false - let arrow: ASDisplayNode let label: ASTextNode @@ -32,6 +30,12 @@ public class NavigationBackButtonNode: ASControlNode { } } + var color: UIColor = UIColor(0x1195f2) { + didSet { + self.label.attributedString = AttributedString(string: self._text, attributes: self.attributesForCurrentState()) + } + } + private var touchCount = 0 var pressed: () -> () = {} @@ -72,10 +76,6 @@ public class NavigationBackButtonNode: ASControlNode { public override func layout() { super.layout() - if self.suspendLayout { - return - } - self.arrow.frame = CGRect(x: 0.0, y: floor((self.frame.size.height - arrow.frame.size.height) / 2.0), width: self.arrow.frame.size.width, height: self.arrow.frame.size.height) self.label.frame = self.labelFrame diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 320394d7aa..8b28fb7e2d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1,41 +1,53 @@ import UIKit import AsyncDisplayKit +private func generateBackArrowImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 13.0, height: 22.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + let _ = try? drawSvgPath(context, path: "M10.6569398,0.0 L0.0,11 L10.6569398,22 L13,19.1782395 L5.07681762,11 L13,2.82176047 Z ") + }) +} + +private var backArrowImageCache: [Int32: UIImage] = [:] + +private func backArrowImage(color: UIColor) -> UIImage? { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + color.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + let key = (Int32(alpha * 255.0) << 24) | (Int32(red * 255.0) << 16) | (Int32(green * 255.0) << 8) | Int32(blue * 255.0) + if let image = backArrowImageCache[key] { + return image + } else { + if let image = generateBackArrowImage(color: color) { + backArrowImageCache[key] = image + return image + } else { + return nil + } + } +} + public class NavigationBar: ASDisplayNode { - var item: UINavigationItem? { + public var foregroundColor: UIColor = UIColor.black() { didSet { - if let item = self.item { - self.itemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: self.previousItem) - self.itemWrapper?.backPressed = { [weak self] in - if let backPressed = self?.backPressed { - backPressed() - } - } - } else { - self.itemWrapper = nil + if let title = self.title { + self.titleNode.attributedText = AttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) } } } - var previousItem: UINavigationItem? { + public var accentColor: UIColor = UIColor(0x1195f2) { didSet { - if let item = self.item { - self.itemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: self.previousItem) - self.itemWrapper?.backPressed = { [weak self] in - if let backPressed = self?.backPressed { - backPressed() - } - } - } else { - self.itemWrapper = nil - } + self.backButtonNode.color = self.accentColor + self.leftButtonNode.color = self.accentColor + self.backButtonArrow.image = backArrowImage(color: self.accentColor) } } - private var itemWrapper: NavigationItemWrapper? - - private let stripeHeight: CGFloat = 1.0 / UIScreen.main().scale - var backPressed: () -> () = { } private var collapsed: Bool { @@ -44,75 +56,397 @@ public class NavigationBar: ASDisplayNode { } } - let stripeView: UIView - - public override init() { - stripeView = UIView() - stripeView.backgroundColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) - - //self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .Light)) - - super.init() - - self.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0) - //self.view.addSubview(self.effectView) - - self.view.addSubview(stripeView) + private let stripeNode: ASDisplayNode + public var stripeColor: UIColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) { + didSet { + self.stripeNode.backgroundColor = self.stripeColor + } } - /*private func updateTopItem(item: UINavigationItem, previousItem: UINavigationItem?, animation: ItemAnimation) { - if self.topItem !== item { - let previousTopItemWrapper = self.topItemWrapper - self.topItemWrapper = nil + private let clippingNode: ASDisplayNode + + private var itemTitleListenerKey: Int? + private var itemLeftButtonListenerKey: Int? + private var _item: UINavigationItem? + var item: UINavigationItem? { + get { + return self._item + } set(value) { + if let previousValue = self._item { + if let itemTitleListenerKey = self.itemTitleListenerKey { + previousValue.removeSetTitleListener(itemTitleListenerKey) + self.itemTitleListenerKey = nil + } + if let itemLeftButtonListenerKey = self.itemLeftButtonListenerKey { + previousValue.removeSetLeftBarButtonItemListener(itemLeftButtonListenerKey) + self.itemLeftButtonListenerKey = nil + } + } + self._item = value - self.topItem = item - self.topItemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: item, previousNavigationItem: previousItem) - self.topItemWrapper?.backPressed = { [weak self] in - if let backPressed = self?.backPressed { - backPressed() + self.leftButtonNode.removeFromSupernode() + + if let item = value { + self.title = item.title + self.itemTitleListenerKey = item.addSetTitleListener { [weak self] text in + if let strongSelf = self { + strongSelf.title = text + } + } + + self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] _, _ in + if let strongSelf = self { + strongSelf.updateLeftButton() + } + } + + self.updateLeftButton() + } else { + self.title = nil + self.updateLeftButton() + } + self.invalidateCalculatedLayout() + } + } + private var title: String? { + didSet { + if let title = self.title { + self.titleNode.attributedText = AttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + if self.titleNode.supernode == nil { + self.clippingNode.addSubnode(self.titleNode) + } + } else { + self.titleNode.removeFromSupernode() + } + + self.invalidateCalculatedLayout() + } + } + private let titleNode: ASTextNode + + var previousItemListenerKey: Int? + var _previousItem: UINavigationItem? + var previousItem: UINavigationItem? { + get { + return self._previousItem + } set(value) { + if let previousValue = self._previousItem, previousItemListenerKey = self.previousItemListenerKey { + previousValue.removeSetTitleListener(previousItemListenerKey) + self.previousItemListenerKey = nil + } + self._previousItem = value + + if let previousItem = value { + self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] text in + if let strongSelf = self { + strongSelf.backButtonNode.text = text ?? "Back" + strongSelf.invalidateCalculatedLayout() + } + } + } + self.updateLeftButton() + + self.invalidateCalculatedLayout() + } + } + + private func updateLeftButton() { + if let item = self.item { + if let leftBarButtonItem = item.leftBarButtonItem { + self.backButtonNode.removeFromSupernode() + self.backButtonArrow.removeFromSupernode() + + self.leftButtonNode.text = leftBarButtonItem.title ?? "" + if self.leftButtonNode.supernode == nil { + self.clippingNode.addSubnode(self.leftButtonNode) + } + } else { + self.leftButtonNode.removeFromSupernode() + + if let previousItem = self.previousItem { + self.backButtonNode.text = previousItem.title ?? "Back" + + if self.backButtonNode.supernode == nil { + self.clippingNode.addSubnode(self.backButtonNode) + self.clippingNode.addSubnode(self.backButtonArrow) + } + } else { + self.backButtonNode.removeFromSupernode() + + } + } + } else { + self.leftButtonNode.removeFromSupernode() + self.backButtonNode.removeFromSupernode() + self.backButtonArrow.removeFromSupernode() + } + } + + private let backButtonNode: NavigationButtonNode + private let backButtonArrow: ASImageNode + private let leftButtonNode: NavigationButtonNode + + private var _transitionState: NavigationBarTransitionState? + var transitionState: NavigationBarTransitionState? { + get { + return self._transitionState + } set(value) { + let updateNodes = self._transitionState?.navigationBar !== value?.navigationBar + + self._transitionState = value + + if updateNodes { + if let transitionTitleNode = self.transitionTitleNode { + transitionTitleNode.removeFromSupernode() + self.transitionTitleNode = nil + } + + if let transitionBackButtonNode = self.transitionBackButtonNode { + transitionBackButtonNode.removeFromSupernode() + self.transitionBackButtonNode = nil + } + + if let transitionBackArrowNode = self.transitionBackArrowNode { + transitionBackArrowNode.removeFromSupernode() + self.transitionBackArrowNode = nil + } + + if let value = value { + switch value.role { + case .top: + if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.foregroundColor) { + self.transitionTitleNode = transitionTitleNode + if self.leftButtonNode.supernode != nil { + self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) + } else if self.backButtonNode.supernode != nil { + self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.backButtonNode) + } else { + self.clippingNode.addSubnode(transitionTitleNode) + } + } + case .bottom: + if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.accentColor) { + self.transitionBackButtonNode = transitionBackButtonNode + self.clippingNode.addSubnode(transitionBackButtonNode) + } + if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.accentColor) { + self.transitionBackArrowNode = transitionBackArrowNode + self.clippingNode.addSubnode(transitionBackArrowNode) + } + } } } - self.topItemWrapper?.layoutItems() - - switch animation { - case .None: - break - case .Push: - self.topItemWrapper?.animatePush(previousTopItemWrapper, duration: 0.3) - break - case .Pop: - self.topItemWrapper?.animatePop(previousTopItemWrapper, duration: 0.3) - break + self.layout() + } + } + + private var transitionTitleNode: ASDisplayNode? + private var transitionBackButtonNode: NavigationButtonNode? + private var transitionBackArrowNode: ASDisplayNode? + + public override init() { + self.stripeNode = ASDisplayNode() + + self.titleNode = ASTextNode() + self.backButtonNode = NavigationButtonNode() + self.backButtonArrow = ASImageNode() + self.backButtonArrow.displayWithoutProcessing = true + self.backButtonArrow.displaysAsynchronously = false + self.backButtonArrow.image = backArrowImage(color: self.accentColor) + self.leftButtonNode = NavigationButtonNode() + + self.clippingNode = ASDisplayNode() + self.clippingNode.clipsToBounds = true + + super.init() + + self.addSubnode(self.clippingNode) + + self.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0) + + self.stripeNode.isLayerBacked = true + self.stripeNode.displaysAsynchronously = false + self.stripeNode.backgroundColor = self.stripeColor + self.addSubnode(self.stripeNode) + + self.titleNode.displaysAsynchronously = false + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.truncationMode = .byTruncatingTail + self.titleNode.isOpaque = false + + self.backButtonNode.highlightChanged = { [weak self] highlighted in + if let strongSelf = self { + strongSelf.backButtonArrow.alpha = (highlighted ? 0.4 : 1.0) + } + } + self.backButtonNode.pressed = { [weak self] in + if let backPressed = self?.backPressed { + backPressed() + } + } + + self.leftButtonNode.pressed = { [weak self] in + if let item = self?.item, leftBarButtonItem = item.leftBarButtonItem { + leftBarButtonItem.performActionOnTarget() } } } - public func beginInteractivePopProgress(previousItem: UINavigationItem, evenMorePreviousItem: UINavigationItem?) { - self.tempItem = previousItem - self.tempItemWrapper = NavigationItemWrapper(parentNode: self, navigationItem: previousItem, previousNavigationItem: evenMorePreviousItem) - - self.tempItemWrapper?.layoutItems() - - self.setInteractivePopProgress(0.0) - } - - public func endInteractivePopProgress() { - self.tempItem = nil - self.tempItemWrapper = nil - } - - public func setInteractivePopProgress(progress: CGFloat) { - if let topItemWrapper = self.topItemWrapper { - self.tempItemWrapper?.setInteractivePopProgress(progress, previousItemWrapper: topItemWrapper) - } - }*/ - public override func layout() { - self.stripeView.frame = CGRect(x: 0.0, y: self.frame.size.height, width: self.frame.size.width, height: stripeHeight) + var size = self.bounds.size - self.itemWrapper?.layoutItems() + let leftButtonInset: CGFloat = 8.0 + let backButtonInset: CGFloat = 27.0 + + self.clippingNode.frame = CGRect(origin: CGPoint(), size: size) + + self.stripeNode.frame = CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel) + + var nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 + var contentVerticalOrigin = size.height - nominalHeight + + var leftTitleInset: CGFloat = 8.0 + if self.backButtonNode.supernode != nil { + let backButtonSize = self.backButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + leftTitleInset += backButtonSize.width + backButtonInset + 8.0 + 8.0 + + let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 + self.backButtonNode.hitTestSlop = UIEdgeInsetsMake(-topHitTestSlop, -27.0, -topHitTestSlop, -8.0) + + if let transitionState = self.transitionState { + let progress = transitionState.progress + + switch transitionState.role { + case .top: + let initialX: CGFloat = backButtonInset + let finalX: CGFloat = floor((size.width - backButtonSize.width) / 2.0) - size.width + + self.backButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) + self.backButtonNode.alpha = 1.0 - progress + + if let transitionTitleNode = self.transitionTitleNode { + let transitionTitleSize = transitionTitleNode.measure(CGSize(width: size.width, height: nominalHeight)) + + let initialX: CGFloat = backButtonInset + floor((backButtonSize.width - transitionTitleSize.width) / 2.0) + let finalX: CGFloat = floor((size.width - transitionTitleSize.width) / 2.0) - size.width + + transitionTitleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - transitionTitleSize.height) / 2.0)), size: transitionTitleSize) + transitionTitleNode.alpha = progress + } + + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.alpha = max(0.0, 1.0 - progress * 1.3) + case .bottom: + self.backButtonNode.alpha = 1.0 + self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) + self.backButtonArrow.alpha = 1.0 + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + } + } else { + self.backButtonNode.alpha = 1.0 + self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) + self.backButtonArrow.alpha = 1.0 + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + } + } else if self.leftButtonNode.supernode != nil { + let leftButtonSize = self.leftButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + leftTitleInset += leftButtonSize.width + leftButtonInset + 8.0 + 8.0 + + self.leftButtonNode.alpha = 1.0 + self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize) + } + + if let transitionState = self.transitionState { + let progress = transitionState.progress + + switch transitionState.role { + case .top: + break + case .bottom: + if let transitionBackButtonNode = self.transitionBackButtonNode { + let transitionBackButtonSize = transitionBackButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + let initialX: CGFloat = backButtonInset + size.width * 0.3 + let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0) + + transitionBackButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - transitionBackButtonSize.height) / 2.0)), size: transitionBackButtonSize) + transitionBackButtonNode.alpha = 1.0 - progress + } + + if let transitionBackArrowNode = self.transitionBackArrowNode { + let initialX: CGFloat = 8.0 + size.width * 0.3 + let finalX: CGFloat = 8.0 + + transitionBackArrowNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + transitionBackArrowNode.alpha = max(0.0, 1.0 - progress * 1.3) + } + } + } + + if self.titleNode.supernode != nil { + let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight)) + + if let transitionState = self.transitionState, otherNavigationBar = transitionState.navigationBar { + let progress = transitionState.progress + + switch transitionState.role { + case .top: + let initialX = floor((size.width - titleSize.width) / 2.0) + let finalX: CGFloat = leftButtonInset + + self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + self.titleNode.alpha = 1.0 - progress + case .bottom: + var initialX: CGFloat = backButtonInset + if otherNavigationBar.backButtonNode.supernode != nil { + initialX += floor((otherNavigationBar.backButtonNode.frame.size.width - titleSize.width) / 2.0) + } + initialX += size.width * 0.3 + let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0) + + self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + self.titleNode.alpha = progress + } + } else { + self.titleNode.alpha = 1.0 + self.titleNode.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + } + } //self.effectView.frame = self.bounds } + + public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? { + if let title = self.title { + let node = ASTextNode() + node.attributedText = AttributedString(string: title, font: Font.medium(17.0), textColor: foregroundColor) + return node + } else { + return nil + } + } + + private func makeTransitionBackButtonNode(accentColor: UIColor) -> NavigationButtonNode? { + if self.backButtonNode.supernode != nil { + let node = NavigationButtonNode() + node.text = self.backButtonNode.text + node.color = accentColor + return node + } else { + return nil + } + } + + private func makeTransitionBackArrowNode(accentColor: UIColor) -> ASDisplayNode? { + if self.backButtonArrow.supernode != nil { + let node = ASImageNode() + node.image = backArrowImage(color: accentColor) + node.frame = self.backButtonArrow.frame + node.displayWithoutProcessing = true + node.displaysAsynchronously = false + return node + } else { + return nil + } + } } diff --git a/Display/NavigationBarTransitionContainer.swift b/Display/NavigationBarTransitionContainer.swift index fe65e3e534..1cbd27c65d 100644 --- a/Display/NavigationBarTransitionContainer.swift +++ b/Display/NavigationBarTransitionContainer.swift @@ -2,5 +2,67 @@ import Foundation import AsyncDisplayKit class NavigationBarTransitionContainer: ASDisplayNode { + var progress: CGFloat = 0.0 { + didSet { + self.layout() + } + } + let transition: NavigationTransition + let topNavigationBar: NavigationBar + let bottomNavigationBar: NavigationBar + + let topClippingNode: ASDisplayNode + let bottomClippingNode: ASDisplayNode + + let topNavigationBarSupernode: ASDisplayNode? + let bottomNavigationBarSupernode: ASDisplayNode? + + init(transition: NavigationTransition, topNavigationBar: NavigationBar, bottomNavigationBar: NavigationBar) { + self.transition = transition + + self.topNavigationBar = topNavigationBar + self.topNavigationBarSupernode = topNavigationBar.supernode + + self.bottomNavigationBar = bottomNavigationBar + self.bottomNavigationBarSupernode = bottomNavigationBar.supernode + + self.topClippingNode = ASDisplayNode() + self.topClippingNode.clipsToBounds = true + self.bottomClippingNode = ASDisplayNode() + self.bottomClippingNode.clipsToBounds = true + + super.init() + + self.topClippingNode.addSubnode(self.topNavigationBar) + self.bottomClippingNode.addSubnode(self.bottomNavigationBar) + + self.addSubnode(self.bottomClippingNode) + self.addSubnode(self.topClippingNode) + } + + func complete() { + self.topNavigationBarSupernode?.addSubnode(self.topNavigationBar) + self.bottomNavigationBarSupernode?.addSubnode(self.bottomNavigationBar) + } + + override func layout() { + super.layout() + + let size = self.bounds.size + + let position: CGFloat + switch self.transition { + case .Push: + position = 1.0 - progress + case .Pop: + position = progress + } + + let offset = floorToScreenPixels(size.width * position) + + self.topClippingNode.frame = CGRect(origin: CGPoint(x: offset, y: 0.0), size: size) + + self.bottomClippingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: offset, height: size.height)) + } } diff --git a/Display/NavigationBarTransitionState.swift b/Display/NavigationBarTransitionState.swift new file mode 100644 index 0000000000..4f8225051b --- /dev/null +++ b/Display/NavigationBarTransitionState.swift @@ -0,0 +1,20 @@ +import Foundation + +enum NavigationBarTransitionRole { + case top + case bottom +} + +final class NavigationBarTransitionState { + weak var navigationBar: NavigationBar? + let transition: NavigationTransition + let role: NavigationBarTransitionRole + let progress: CGFloat + + init(navigationBar: NavigationBar, transition: NavigationTransition, role: NavigationBarTransitionRole, progress: CGFloat) { + self.navigationBar = navigationBar + self.transition = transition + self.role = role + self.progress = progress + } +} diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index bcbc3afa05..5e45e9b88a 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -9,7 +9,7 @@ public class NavigationButtonNode: ASTextNode { private func attributesForCurrentState() -> [String : AnyObject] { return [ NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.isEnabled ? UIColor.blue() : UIColor.gray() + NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray() ] } @@ -25,6 +25,14 @@ public class NavigationButtonNode: ASTextNode { } } + public var color: UIColor = UIColor(0x1195f2) { + didSet { + if let text = self._text { + self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + private var _bold: Bool = false public var bold: Bool { get { @@ -40,7 +48,8 @@ public class NavigationButtonNode: ASTextNode { } private var touchCount = 0 - public var pressed: () -> () = {} + public var pressed: () -> () = { } + public var highlightChanged: (Bool) -> () = { _ in } public override init() { super.init() @@ -100,14 +109,15 @@ public class NavigationButtonNode: ASTextNode { let alpha: CGFloat = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) - if animated { + /*if animated { UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.beginFromCurrentState, animations: { () -> Void in self.alpha = alpha }, completion: nil) } - else { + else {*/ self.alpha = alpha - } + self.highlightChanged(highlighted) + //} } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index ed9b9a8cdd..98475c4c24 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -3,22 +3,26 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -private struct NavigationControllerLayout { - let layout: ViewControllerLayout - let statusBarHeight: CGFloat +private class NavigationControllerView: UIView { + override class func layerClass() -> AnyClass { + return CATracingLayer.self + } } -public class NavigationController: NavigationControllerProxy, WindowContentController, UIGestureRecognizerDelegate, StatusBarSurfaceProvider { +public class NavigationController: NavigationControllerProxy, ContainableController, UIGestureRecognizerDelegate { + public private(set) weak var overlayPresentingController: ViewController? + + private var containerLayout = ContainerViewLayout() - private let statusBarSurface: StatusBarSurface = StatusBarSurface() private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() + private var currentPresentDisposable = MetaDisposable() private var statusBarChangeObserver: AnyObject? - private var layout: NavigationControllerLayout? - private var pendingLayout: (NavigationControllerLayout, Double, Bool)? + //private var layout: NavigationControllerLayout? + //private var pendingLayout: (NavigationControllerLayout, Double, Bool)? private var _presentedViewController: UIViewController? public override var presentedViewController: UIViewController? { @@ -40,23 +44,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr public override init() { super.init() - - self.statusBarChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main(), using: { [weak self] notification in - if let strongSelf = self { - let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue().height ?? 20.0) - - let previousLayout: NavigationControllerLayout? - if let pendingLayout = strongSelf.pendingLayout { - previousLayout = pendingLayout.0 - } else { - previousLayout = strongSelf.layout - } - - strongSelf.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: previousLayout?.layout.size ?? CGSize(), insets: previousLayout?.layout.insets ?? UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: statusBarHeight), statusBarHeight: statusBarHeight), (strongSelf.pendingLayout?.2 ?? false) ? (strongSelf.pendingLayout?.1 ?? 0.3) : max(strongSelf.pendingLayout?.1 ?? 0.0, 0.35), true) - - strongSelf.view.setNeedsLayout() - } - }) } public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { @@ -67,9 +54,51 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr fatalError("init(coder:) has not been implemented") } + deinit { + self.currentPushDisposable.dispose() + self.currentPresentDisposable.dispose() + } + + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + if !self.isViewLoaded() { + self.loadView() + } + self.containerLayout = layout + self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) + + let containedLayout = ContainerViewLayout(size: layout.size, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) + + for controller in self.viewControllers { + if let controller = controller as? ContainableController { + controller.containerLayoutUpdated(containedLayout, transition: transition) + } else { + controller.viewWillTransition(to: layout.size, with: SystemContainedControllerTransitionCoordinator()) + } + } + + if let topViewController = self.topViewController { + if let topViewController = topViewController as? ContainableController { + topViewController.containerLayoutUpdated(containedLayout, transition: transition) + } else { + topViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + } + } + + if let presentedViewController = self.presentedViewController { + if let presentedViewController = presentedViewController as? ContainableController { + presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) + } else { + presentedViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + } + } + + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + navigationTransitionCoordinator.updateProgress() + } + } + public override func loadView() { - self.view = UIView() - //super.loadView() + self.view = NavigationControllerView() self.view.clipsToBounds = true self.navigationBar.removeFromSuperview() @@ -96,17 +125,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr bottomController.viewWillAppear(true) let bottomView = bottomController.view! - var bottomStatusBar: StatusBar? - if let bottomController = bottomController as? ViewController { - bottomStatusBar = bottomController.statusBar - } - - if let bottomStatusBar = bottomStatusBar { - self.statusBarSurface.insertStatusBar(bottomStatusBar, atIndex: 0) - } - - (self.view.window as? Window)?.updateStatusBars() - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator } @@ -137,12 +155,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidDisappear(true) bottomController.viewDidAppear(true) - - if let topStatusBar = (topController as? ViewController)?.statusBar { - self.statusBarSurface.removeStatusBar(topStatusBar) - } - - (self.view.window as? Window)?.updateStatusBars() } }) } @@ -166,11 +178,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidAppear(true) bottomController.viewDidDisappear(true) - - if let bottomStatusBar = (bottomController as? ViewController)?.statusBar { - self.statusBarSurface.removeStatusBar(bottomStatusBar) - } - (self.view.window as? Window)?.updateStatusBars() } }) } @@ -194,11 +201,6 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.viewDidAppear(true) bottomController.viewDidDisappear(true) - - if let bottomStatusBar = (bottomController as? ViewController)?.statusBar { - self.statusBarSurface.removeStatusBar(bottomStatusBar) - } - (self.view.window as? Window)?.updateStatusBars() } }) } @@ -208,13 +210,7 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } public func pushViewController(_ controller: ViewController) { - let layout: NavigationControllerLayout - if let currentLayout = self.layout { - layout = currentLayout - } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) - } - controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) + controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in if let strongSelf = self { strongSelf.pushViewController(controller, animated: true) @@ -242,82 +238,91 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr } public override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + for controller in viewControllers { + controller.navigation_setNavigationController(self) + } + if viewControllers.count > 0 { let topViewController = viewControllers[viewControllers.count - 1] as UIViewController - if let controller = topViewController as? WindowContentController { - let layout: NavigationControllerLayout - if let currentLayout = self.layout { - layout = currentLayout - } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) - } - - controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) + if let controller = topViewController as? ContainableController { + controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) } else { topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) } } if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! { - let topController = viewControllers.last! as UIViewController - let bottomController = self.viewControllers.last! as UIViewController - - if let topController = topController as? ViewController { - topController.navigationBar.previousItem = bottomController.navigationItem - } - - bottomController.viewWillDisappear(true) - let bottomView = bottomController.view! - topController.viewWillAppear(true) - let topView = topController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - if let topController = topController as? ViewController { - self.statusBarSurface.addStatusBar(topController.statusBar) - } - (self.view.window as? Window)?.updateStatusBars() - - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - strongSelf.navigationTransitionCoordinator = nil - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - strongSelf.setViewControllers(viewControllers, animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - bottomController.viewDidDisappear(true) - topController.viewDidAppear(true) - - if let bottomController = bottomController as? ViewController { - strongSelf.statusBarSurface.removeStatusBar(bottomController.statusBar) + if self.viewControllers.contains({ $0 === viewControllers.last }) { + let bottomController = viewControllers.last! as UIViewController + let topController = self.viewControllers.last! as UIViewController + + if let bottomController = bottomController as? ViewController { + if viewControllers.count >= 2 { + bottomController.navigationBar.previousItem = viewControllers[viewControllers.count - 2].navigationItem + } else { + bottomController.navigationBar.previousItem = nil } - (strongSelf.view.window as? Window)?.updateStatusBars() } - }) + + bottomController.viewWillDisappear(true) + let bottomView = bottomController.view! + topController.viewWillAppear(true) + let topView = topController.view! + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.navigationTransitionCoordinator = nil + + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + strongSelf.setViewControllers(viewControllers, animated: false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + topController.viewDidDisappear(true) + bottomController.viewDidAppear(true) + + topView.removeFromSuperview() + } + }) + } else { + let topController = viewControllers.last! as UIViewController + let bottomController = self.viewControllers.last! as UIViewController + + if let topController = topController as? ViewController { + topController.navigationBar.previousItem = bottomController.navigationItem + } + + bottomController.viewWillDisappear(true) + let bottomView = bottomController.view! + topController.viewWillAppear(true) + let topView = topController.view! + + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.navigationTransitionCoordinator = nil + + topController.setIgnoreAppearanceMethodInvocations(true) + bottomController.setIgnoreAppearanceMethodInvocations(true) + strongSelf.setViewControllers(viewControllers, animated: false) + topController.setIgnoreAppearanceMethodInvocations(false) + bottomController.setIgnoreAppearanceMethodInvocations(false) + + bottomController.viewDidDisappear(true) + topController.viewDidAppear(true) + + bottomView.removeFromSuperview() + } + }) + } } else { - var previousStatusBar: StatusBar? - if let previousController = self.viewControllers.last as? ViewController { - previousStatusBar = previousController.statusBar - } - var newStatusBar: StatusBar? - if let newController = viewControllers.last as? ViewController { - newStatusBar = newController.statusBar - } - - if previousStatusBar !== newStatusBar { - if let previousStatusBar = previousStatusBar { - self.statusBarSurface.removeStatusBar(previousStatusBar) - } - if let newStatusBar = newStatusBar { - self.statusBarSurface.addStatusBar(newStatusBar) - } - } - if let topController = self.viewControllers.last where topController.isViewLoaded() { topController.navigation_setNavigationController(nil) topController.view.removeFromSuperview() @@ -329,131 +334,49 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr topController.navigation_setNavigationController(self) self.view.addSubview(topController.view) } - - //super.setViewControllers(viewControllers, animated: animated) - } - } - - private func childControllerLayoutForLayout(_ layout: NavigationControllerLayout) -> ViewControllerLayout { - return ViewControllerLayout(size: layout.layout.size, insets: layout.layout.insets, inputViewHeight: 0.0, statusBarHeight: layout.statusBarHeight) - } - - public func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) { - let previousLayout: NavigationControllerLayout? - if let pendingLayout = self.pendingLayout { - previousLayout = pendingLayout.0 - } else { - previousLayout = self.layout - } - - self.pendingLayout = (NavigationControllerLayout(layout: ViewControllerLayout(size: layout.size, insets: layout.insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), statusBarHeight: previousLayout?.statusBarHeight ?? 20.0), duration, false) - - self.view.setNeedsLayout() - } - - public override func viewDidLayoutSubviews() { - super.viewDidLayoutSubviews() - - if let pendingLayout = self.pendingLayout { - self.layout = pendingLayout.0 - - if pendingLayout.1 > DBL_EPSILON { - animateRotation(self.view, toFrame: CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height), duration: pendingLayout.1) - } - else { - self.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) - } - - /*if pendingLayout.1 > DBL_EPSILON { - animateRotation(self._navigationBar, toFrame: self.navigationBarFrame(pendingLayout.0), duration: pendingLayout.1) - } - else { - self._navigationBar.frame = self.navigationBarFrame(pendingLayout.0) - }*/ - - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - //navigationTransitionView.frame = CGRectMake(0.0, 0.0, toSize.width, toSize.height) - - if self.viewControllers.count >= 2 { - let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController - - if let controller = bottomController as? WindowContentController { - let layout: NavigationControllerLayout - if let currentLayout = self.layout { - layout = currentLayout - } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) - } - - controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) - } else { - bottomController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) - } - } - - //self._navigationBar.setInteractivePopProgress(navigationTransitionCoordinator.progress) - } - - if let topViewController = self.topViewController { - if let controller = topViewController as? WindowContentController { - controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) - } else { - topViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) - } - } - - if let presentedViewController = self.presentedViewController { - if let controller = presentedViewController as? WindowContentController { - controller.setParentLayout(self.childControllerLayoutForLayout(pendingLayout.0), duration: pendingLayout.1, curve: 0) - } else { - presentedViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: pendingLayout.0.layout.size.width, height: pendingLayout.0.layout.size.height) - } - } - - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { - navigationTransitionCoordinator.updateProgress() - } - - self.pendingLayout = nil } } override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = viewControllerToPresent as? NavigationController { - controller.navigation_setPresenting(self) + controller.navigation_setDismiss { [weak self] in + if let strongSelf = self { + strongSelf.dismiss(animated: false, completion: nil) + } + } self._presentedViewController = controller self.view.endEditing(true) + controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) - let layout: NavigationControllerLayout - if let currentLayout = self.layout { - layout = currentLayout - } else { - layout = NavigationControllerLayout(layout: ViewControllerLayout(size: self.view.bounds.size, insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 0.0, right: 0.0), inputViewHeight: 0.0, statusBarHeight: 20.0), statusBarHeight: 20.0) + var ready: Signal = .single(true) + + if let controller = controller.topViewController as? ViewController { + ready = controller.ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue } - controller.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) - - if flag { - controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) - self.view.addSubview(controller.view) - (self.view.window as? Window)?.updateStatusBars() - UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { - controller.view.frame = self.view.bounds - (self.view.window as? Window)?.updateStatusBars() - }, completion: { _ in - if let completion = completion { - completion() + self.currentPresentDisposable.set(ready.start(next: { [weak self] _ in + if let strongSelf = self { + if flag { + controller.view.frame = strongSelf.view.bounds.offsetBy(dx: 0.0, dy: strongSelf.view.bounds.height) + strongSelf.view.addSubview(controller.view) + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { + controller.view.frame = strongSelf.view.bounds + }, completion: { _ in + if let completion = completion { + completion() + } + }) + } else { + controller.view.frame = strongSelf.view.bounds + strongSelf.view.addSubview(controller.view) + + if let completion = completion { + completion() + } } - }) - } else { - self.view.addSubview(controller.view) - (self.view.window as? Window)?.updateStatusBars() - - if let completion = completion { - completion() } - } + })) } else { preconditionFailure("NavigationController can't present \(viewControllerToPresent). Only subclasses of NavigationController are allowed.") } @@ -464,18 +387,16 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr if flag { UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { controller.view.frame = self.view.bounds.offsetBy(dx: 0.0, dy: self.view.bounds.height) - (self.view.window as? Window)?.updateStatusBars() }, completion: { _ in controller.view.removeFromSuperview() self._presentedViewController = nil - (self.view.window as? Window)?.updateStatusBars() if let completion = completion { completion() } }) } else { + controller.view.removeFromSuperview() self._presentedViewController = nil - (self.view.window as? Window)?.updateStatusBars() if let completion = completion { completion() } @@ -490,12 +411,4 @@ public class NavigationController: NavigationControllerProxy, WindowContentContr public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { return otherGestureRecognizer is UIPanGestureRecognizer } - - func statusBarSurfaces() -> [StatusBarSurface] { - var surfaces: [StatusBarSurface] = [self.statusBarSurface] - if let controller = self.presentedViewController as? StatusBarSurfaceProvider { - surfaces.append(contentsOf: controller.statusBarSurfaces()) - } - return surfaces - } } diff --git a/Display/NavigationItemTransitionState.swift b/Display/NavigationItemTransitionState.swift deleted file mode 100644 index 179e9a3821..0000000000 --- a/Display/NavigationItemTransitionState.swift +++ /dev/null @@ -1,4 +0,0 @@ -struct NavigationItemTransitionState { - let backButtonPosition: CGPoint? - let titlePosition: CGPoint -} diff --git a/Display/NavigationItemWrapper.swift b/Display/NavigationItemWrapper.swift deleted file mode 100644 index 725a569394..0000000000 --- a/Display/NavigationItemWrapper.swift +++ /dev/null @@ -1,333 +0,0 @@ -import UIKit -import AsyncDisplayKit - -internal class NavigationItemWrapper { - let parentNode: ASDisplayNode - - private var navigationItem: UINavigationItem - private var setTitleListenerKey: Int! - private var setLeftBarButtonItemListenerKey: Int! - private var setRightBarButtonItemListenerKey: Int! - - private var previousNavigationItem: UINavigationItem? - private var previousItemSetTitleListenerKey: Int? - - private let titleNode: NavigationTitleNode - private var backButtonNode: NavigationBackButtonNode - private var leftBarButtonItem: UIBarButtonItem? - private var leftBarButtonItemWrapper: BarButtonItemWrapper? - private var rightBarButtonItem: UIBarButtonItem? - private var rightBarButtonItemWrapper: BarButtonItemWrapper? - - var backPressed: () -> () = { } - - var suspendLayout = false - - init(parentNode: ASDisplayNode, navigationItem: UINavigationItem, previousNavigationItem: UINavigationItem?) { - self.parentNode = parentNode - self.navigationItem = navigationItem - self.previousNavigationItem = previousNavigationItem - - self.titleNode = NavigationTitleNode(text: "") - self.parentNode.addSubnode(titleNode) - - self.backButtonNode = NavigationBackButtonNode() - backButtonNode.pressed = { [weak self] in - if let backPressed = self?.backPressed { - backPressed() - } - } - self.parentNode.addSubnode(self.backButtonNode) - - self.previousItemSetTitleListenerKey = previousNavigationItem?.addSetTitleListener({ [weak self] title in - self?.setBackButtonTitle(title ?? "") - return - }) - - self.setTitleListenerKey = navigationItem.addSetTitleListener({ [weak self] title in - self?.setTitle(title ?? "") - return - }) - - self.setLeftBarButtonItemListenerKey = navigationItem.addSetLeftBarButtonItemListener({ [weak self] barButtonItem, animated in - self?.setLeftBarButtonItem(barButtonItem, animated: animated.boolValue) - return - }) - - self.setRightBarButtonItemListenerKey = navigationItem.addSetRightBarButtonItemListener({ [weak self] barButtonItem, animated in - self?.setRightBarButtonItem(barButtonItem, animated: animated.boolValue) - return - }) - - self.setTitle(navigationItem.title ?? "") - self.setBackButtonTitle(previousNavigationItem?.title ?? "Back") - self.setLeftBarButtonItem(navigationItem.leftBarButtonItem, animated: false) - self.setRightBarButtonItem(navigationItem.rightBarButtonItem, animated: false) - } - - deinit { - self.navigationItem.removeSetTitleListener(self.setTitleListenerKey) - self.navigationItem.removeSetLeftBarButtonItemListener(self.setLeftBarButtonItemListenerKey) - self.navigationItem.removeSetRightBarButtonItemListener(self.setRightBarButtonItemListenerKey) - - if let previousItemSetTitleListenerKey = self.previousItemSetTitleListenerKey { - self.previousNavigationItem?.removeSetTitleListener(previousItemSetTitleListenerKey) - } - - self.titleNode.removeFromSupernode() - self.backButtonNode.removeFromSupernode() - } - - func setBackButtonTitle(_ backButtonTitle: String) { - self.backButtonNode.text = backButtonTitle - self.layoutItems() - } - - func setTitle(_ title: String) { - self.titleNode.text = title - self.layoutItems() - } - - func setLeftBarButtonItem(_ leftBarButtonItem: UIBarButtonItem?, animated: Bool) { - if self.leftBarButtonItem !== leftBarButtonItem { - self.leftBarButtonItem = leftBarButtonItem - - self.leftBarButtonItemWrapper = nil - - if let leftBarButtonItem = leftBarButtonItem { - self.leftBarButtonItemWrapper = BarButtonItemWrapper(parentNode: self.parentNode, barButtonItem: leftBarButtonItem, layoutNeeded: { [weak self] in - self?.layoutItems() - return - }) - } - } - - self.backButtonNode.isHidden = self.previousNavigationItem == nil || self.leftBarButtonItemWrapper != nil - } - - func setRightBarButtonItem(_ rightBarButtonItem: UIBarButtonItem?, animated: Bool) { - if self.rightBarButtonItem !== rightBarButtonItem { - self.rightBarButtonItem = rightBarButtonItem - - self.rightBarButtonItemWrapper = nil - - if let rightBarButtonItem = rightBarButtonItem { - self.rightBarButtonItemWrapper = BarButtonItemWrapper(parentNode: self.parentNode, barButtonItem: rightBarButtonItem, layoutNeeded: { [weak self] in - self?.layoutItems() - return - }) - } - } - } - - private var collapsed: Bool { - get { - return self.parentNode.frame.size.height < (20.0 + 44.0) - } - } - - var titleFrame: CGRect { - get { - return CGRect(x: floor((self.parentNode.frame.size.width - self.titleNode.calculatedSize.width) / 2.0), y: self.collapsed ? 24.0 : 31.0, width: self.titleNode.calculatedSize.width, height: self.titleNode.calculatedSize.height) - } - } - - var titlePosition: CGPoint { - get { - let titleFrame = self.titleFrame - return CGPoint(x: titleFrame.midX, y: titleFrame.midY) - } - } - - var backButtonFrame: CGRect { - get { - return CGRect(x: self.collapsed ? 15.0 : 8.0, y: self.collapsed ? 24.0 : 31.0, width: backButtonNode.calculatedSize.width, height: backButtonNode.calculatedSize.height) - } - } - - var backButtonLabelFrame: CGRect { - get { - let backButtonFrame = self.backButtonFrame - let labelFrame = self.backButtonNode.labelFrame - return CGRect(origin: CGPoint(x: backButtonFrame.origin.x + labelFrame.origin.x, y: backButtonFrame.origin.y + labelFrame.origin.y), size: labelFrame.size) - } - } - - var backButtonLabelPosition: CGPoint { - get { - let backButtonLabelFrame = self.backButtonLabelFrame - return CGPoint(x: backButtonLabelFrame.midX, y: backButtonLabelFrame.midY) - } - } - - var leftButtonFrame: CGRect? { - get { - if let leftBarButtonItemWrapper = self.leftBarButtonItemWrapper { - return CGRect(x: self.collapsed ? 15.0 : 8.0, y: self.collapsed ? 24.0 : 31.0, width: leftBarButtonItemWrapper.buttonNode.calculatedSize.width, height: leftBarButtonItemWrapper.buttonNode.calculatedSize.height) - } - else { - return nil - } - } - } - - var rightButtonFrame: CGRect? { - get { - if let rightBarButtonItemWrapper = self.rightBarButtonItemWrapper { - return CGRect(x: self.parentNode.frame.size.width - rightBarButtonItemWrapper.buttonNode.calculatedSize.width - (self.collapsed ? 15.0 : 8.0), y: self.collapsed ? 24.0 : 31.0, width: rightBarButtonItemWrapper.buttonNode.calculatedSize.width, height: rightBarButtonItemWrapper.buttonNode.calculatedSize.height) - } - else { - return nil - } - } - } - - var transitionState: NavigationItemTransitionState { - get { - return NavigationItemTransitionState(backButtonPosition: self.backButtonNode.isHidden ? nil : self.backButtonLabelPosition, titlePosition: self.titlePosition) - } - } - - func layoutItems() { - if suspendLayout { - return - } - - self.backButtonNode.measure(self.parentNode.frame.size) - self.backButtonNode.frame = self.backButtonFrame - self.backButtonNode.layout() - - if let leftBarButtonItemWrapper = self.leftBarButtonItemWrapper { - leftBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) - leftBarButtonItemWrapper.buttonNode.frame = self.leftButtonFrame! - } - - if let rightBarButtonItemWrapper = self.rightBarButtonItemWrapper { - rightBarButtonItemWrapper.buttonNode.measure(self.parentNode.frame.size) - rightBarButtonItemWrapper.buttonNode.frame = self.rightButtonFrame! - } - - self.titleNode.measure(CGSize(width: max(0.0, self.parentNode.bounds.size.width - 140.0), height: CGFloat.greatestFiniteMagnitude)) - self.titleNode.frame = self.titleFrame - } - - func interpolatePosition(_ from: CGPoint, _ to: CGPoint, value: CGFloat) -> CGPoint { - return CGPoint(x: from.x * (CGFloat(1.0) - value) + to.x * value, y: from.y * (CGFloat(1.0) - value) + to.y * value) - } - - func interpolateValue(_ from: CGFloat, _ to: CGFloat, value: CGFloat) -> CGFloat { - return (from * (CGFloat(1.0) - value)) + (to * value) - } - - func applyPushAnimationProgress(previousItemState: NavigationItemTransitionState, value: CGFloat) { - let titleStartPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) - let titleStartAlpha: CGFloat = 0.0 - let titleEndPosition = self.titlePosition - let titleEndAlpha: CGFloat = 1.0 - self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) - self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) - - self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) - self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) - - self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: previousItemState.titlePosition.x - self.backButtonFrame.origin.x, y: previousItemState.titlePosition.y - self.backButtonFrame.origin.y), CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) - self.backButtonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) - } - - func applyPushAnimationProgress(nextItemState: NavigationItemTransitionState, value: CGFloat) { - let titleStartPosition = self.titlePosition - let titleStartAlpha: CGFloat = 1.0 - var titleEndPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) - if let nextItemBackButtonPosition = nextItemState.backButtonPosition { - titleEndPosition = nextItemBackButtonPosition - } - let titleEndAlpha: CGFloat = 0.0 - - self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) - self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) - - self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) - self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) - - self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: -self.backButtonLabelFrame.size.width - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) - self.backButtonNode.label.alpha = self.interpolateValue(1.0, 0.0, value: value) - self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, nextItemState.backButtonPosition == nil ? 0.0 : 1.0, value: value) - } - - func applyPopAnimationProgress(previousItemState: NavigationItemTransitionState, value: CGFloat) { - var titleStartPosition = CGPoint(x: -self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) - if let previousItemBackButtonPosition = previousItemState.backButtonPosition { - titleStartPosition = previousItemBackButtonPosition - } - let titleStartAlpha: CGFloat = 0.0 - let titleEndPosition = self.titlePosition - let titleEndAlpha: CGFloat = 1.0 - self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) - self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) - - self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) - self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(0.0, 1.0, value: value) - - self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: -self.backButtonLabelFrame.size.width - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), value: value) - self.backButtonNode.label.alpha = self.interpolateValue(0.0, 1.0, value: value) - self.backButtonNode.arrow.alpha = self.interpolateValue(previousItemState.backButtonPosition == nil ? 0.0 : 1.0, 1.0, value: value) - } - - func applyPopAnimationProgress(nextItemState: NavigationItemTransitionState, value: CGFloat) { - let titleStartPosition = self.titlePosition - let titleStartAlpha: CGFloat = 1.0 - let titleEndPosition = CGPoint(x: self.parentNode.frame.size.width + self.titleNode.frame.size.width / 2.0, y: self.titlePosition.y) - let titleEndAlpha: CGFloat = 0.0 - self.titleNode.position = self.interpolatePosition(titleStartPosition, titleEndPosition, value: value) - self.titleNode.alpha = self.interpolateValue(titleStartAlpha, titleEndAlpha, value: value) - - self.rightBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) - self.leftBarButtonItemWrapper?.buttonNode.alpha = self.interpolateValue(1.0, 0.0, value: value) - - self.backButtonNode.label.position = self.interpolatePosition(CGPoint(x: self.backButtonLabelPosition.x - self.backButtonFrame.origin.x, y: self.backButtonLabelPosition.y - self.backButtonFrame.origin.y), CGPoint(x: nextItemState.titlePosition.x - self.backButtonFrame.origin.x, y: nextItemState.titlePosition.y - self.backButtonFrame.origin.y), value: value) - self.backButtonNode.label.alpha = self.interpolateValue(1.0, 0.0, value: value) - self.backButtonNode.arrow.alpha = self.interpolateValue(1.0, 0.0, value: value) - } - - func animatePush(previousItemWrapper: NavigationItemWrapper?, duration: Double) { - if let previousItemWrapper = previousItemWrapper { - self.suspendLayout = true - self.backButtonNode.suspendLayout = true - - let transitionState = self.transitionState - let previousItemState = previousItemWrapper.transitionState - - self.applyPushAnimationProgress(previousItemState: previousItemState, value: 0.0) - previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 0.0) - - UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in - self.applyPushAnimationProgress(previousItemState: previousItemState, value: 1.0) - previousItemWrapper.applyPushAnimationProgress(nextItemState: transitionState, value: 1.0) - }, completion: { completed in - self.suspendLayout = false - self.backButtonNode.suspendLayout = false - - previousItemWrapper.applyPushAnimationProgress(nextItemState: self.transitionState, value: 1.0) - }) - } - } - - func animatePop(previousItemWrapper: NavigationItemWrapper?, duration: Double) { - if let previousItemWrapper = previousItemWrapper { - self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 0.0) - previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) - - UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { () -> Void in - self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: 1.0) - previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 1.0) - }, completion: { completed in - previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: 0.0) - }) - } - } - - func setInteractivePopProgress(progress: CGFloat, previousItemWrapper: NavigationItemWrapper) { - self.applyPopAnimationProgress(previousItemState: previousItemWrapper.transitionState, value: progress) - previousItemWrapper.applyPopAnimationProgress(nextItemState: self.transitionState, value: progress) - } -} diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 2e63cf1524..32449d08d0 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -15,6 +15,12 @@ public class NavigationTitleNode: ASDisplayNode { } } + public var color: UIColor = UIColor.black() { + didSet { + self.setText(self._text) + } + } + public init(text: NSString) { self.label = ASTextNode() self.label.maximumNumberOfLines = 1 @@ -35,7 +41,7 @@ public class NavigationTitleNode: ASDisplayNode { private func setText(_ text: NSString) { var titleAttributes = [String : AnyObject]() titleAttributes[NSFontAttributeName] = UIFont.boldSystemFont(ofSize: 17.0) - titleAttributes[NSForegroundColorAttributeName] = UIColor.black() + titleAttributes[NSForegroundColorAttributeName] = self.color let titleString = AttributedString(string: text as String, attributes: titleAttributes) self.label.attributedString = titleString self.invalidateCalculatedLayout() diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 83b1f7d321..7785f7f4d0 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -35,6 +35,8 @@ class NavigationTransitionCoordinator { private let dimView: UIView private let shadowView: UIImageView + private let inlineNavigationBarTransition: Bool + init(transition: NavigationTransition, container: UIView, topView: UIView, topNavigationBar: NavigationBar?, bottomView: UIView, bottomNavigationBar: NavigationBar?) { self.transition = transition self.container = container @@ -52,6 +54,16 @@ class NavigationTransitionCoordinator { self.dimView.backgroundColor = UIColor.black() self.shadowView = UIImageView(image: shadowImage) + if let topNavigationBar = topNavigationBar, bottomNavigationBar = bottomNavigationBar { + var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container) + var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container) + topFrame.origin.x = 0.0 + bottomFrame.origin.x = 0.0 + self.inlineNavigationBarTransition = topFrame.equalTo(bottomFrame) + } else { + self.inlineNavigationBarTransition = false + } + switch transition { case .Push: self.viewSuperview?.insertSubview(topView, belowSubview: topView) @@ -62,6 +74,7 @@ class NavigationTransitionCoordinator { self.viewSuperview?.insertSubview(self.dimView, belowSubview: topView) self.viewSuperview?.insertSubview(self.shadowView, belowSubview: dimView) + self.maybeCreateNavigationBarTransition() self.updateProgress() } @@ -77,14 +90,59 @@ class NavigationTransitionCoordinator { case .Pop: position = progress } - self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * self.container.bounds.size.width), y: 0.0), size: self.container.bounds.size) - self.dimView.frame = CGRect(origin: CGPoint(), size: CGSize(width: max(0.0, self.topView.frame.minX), height: self.container.bounds.size.height)) - self.shadowView.frame = CGRect(origin: CGPoint(x: self.dimView.frame.maxX - shadowWidth, y: 0.0), size: CGSize(width: shadowWidth, height: self.container.bounds.size.height)) + + var dimInset: CGFloat = 0.0 + if let topNavigationBar = self.topNavigationBar where self.inlineNavigationBarTransition { + dimInset = topNavigationBar.frame.size.height + } + + let containerSize = self.container.bounds.size + + self.topView.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(position * containerSize.width), y: 0.0), size: containerSize) + self.dimView.frame = CGRect(origin: CGPoint(x: 0.0, y: dimInset), size: CGSize(width: max(0.0, self.topView.frame.minX), height: self.container.bounds.size.height - dimInset)) + self.shadowView.frame = CGRect(origin: CGPoint(x: self.dimView.frame.maxX - shadowWidth, y: dimInset), size: CGSize(width: shadowWidth, height: containerSize.height - dimInset)) self.dimView.alpha = (1.0 - position) * 0.15 self.shadowView.alpha = (1.0 - position) * 0.9 - self.bottomView.frame = CGRect(origin: CGPoint(x: ((position - 1.0) * self.container.bounds.size.width * 0.3), y: 0.0), size: self.container.bounds.size) + self.bottomView.frame = CGRect(origin: CGPoint(x: ((position - 1.0) * containerSize.width * 0.3), y: 0.0), size: containerSize) - (self.container.window as? Window)?.updateStatusBars() + self.updateNavigationBarTransition() + } + + func updateNavigationBarTransition() { + if let topNavigationBar = self.topNavigationBar, bottomNavigationBar = self.bottomNavigationBar { + let position: CGFloat + switch self.transition { + case .Push: + position = 1.0 - progress + case .Pop: + position = progress + } + + topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position) + bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position) + } + } + + func maybeCreateNavigationBarTransition() { + if let topNavigationBar = self.topNavigationBar, bottomNavigationBar = self.bottomNavigationBar { + let position: CGFloat + switch self.transition { + case .Push: + position = 1.0 - progress + case .Pop: + position = progress + } + + topNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: bottomNavigationBar, transition: self.transition, role: .top, progress: position) + bottomNavigationBar.transitionState = NavigationBarTransitionState(navigationBar: topNavigationBar, transition: self.transition, role: .bottom, progress: position) + } + } + + func endNavigationBarTransition() { + if let topNavigationBar = self.topNavigationBar, bottomNavigationBar = self.bottomNavigationBar { + topNavigationBar.transitionState = nil + bottomNavigationBar.transitionState = nil + } } func animateCancel(_ completion: () -> ()) { @@ -111,6 +169,8 @@ class NavigationTransitionCoordinator { self.dimView.removeFromSuperview() self.shadowView.removeFromSuperview() + self.endNavigationBarTransition() + completion() } } @@ -119,25 +179,25 @@ class NavigationTransitionCoordinator { let distance = (1.0 - self.progress) * self.container.bounds.size.width let f = { switch self.transition { - case .Push: - if let viewSuperview = self.viewSuperview { - viewSuperview.addSubview(self.bottomView) - } else { - self.bottomView.removeFromSuperview() - } - //self.topView.removeFromSuperview() - case .Pop: - if let viewSuperview = self.viewSuperview { - viewSuperview.addSubview(self.topView) - } else { - self.topView.removeFromSuperview() - } - //self.bottomView.removeFromSuperview() + case .Push: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.bottomView) + } else { + self.bottomView.removeFromSuperview() + } + case .Pop: + if let viewSuperview = self.viewSuperview { + viewSuperview.addSubview(self.topView) + } else { + self.topView.removeFromSuperview() + } } self.dimView.removeFromSuperview() self.shadowView.removeFromSuperview() + self.endNavigationBarTransition() + completion() } diff --git a/Display/PresentableViewController.swift b/Display/PresentableViewController.swift new file mode 100644 index 0000000000..2752c36d8f --- /dev/null +++ b/Display/PresentableViewController.swift @@ -0,0 +1,6 @@ +import UIKit +import AsyncDisplayKit + +public protocol PresentableViewController: class { + +} diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift new file mode 100644 index 0000000000..9c9b6dadb9 --- /dev/null +++ b/Display/PresentationContext.swift @@ -0,0 +1,142 @@ +import SwiftSignalKit + +public enum PresentationContextType { + case current + case window +} + +final class PresentationContext { + private var _view: UIView? + var view: UIView? { + get { + return self._view + } set(value) { + let wasReady = self.ready + self._view = value + if wasReady != self.ready { + if !wasReady { + self.addViews() + } else { + self.removeViews() + } + } + } + } + + private var layout: ContainerViewLayout? + + private var ready: Bool { + return self.view != nil && self.layout != nil + } + + private var controllers: [ViewController] = [] + + private var presentationDisposables = DisposableSet() + + public func present(_ controller: ViewController) { + let controllerReady = controller.ready.get() + |> filter({ $0 }) + |> take(1) + |> deliverOnMainQueue + |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) + + if let view = self.view, initialLayout = self.layout { + controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) + controller.containerLayoutUpdated(initialLayout, transition: .immediate) + + self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in + if let strongSelf = self { + if strongSelf.controllers.contains({ $0 === controller }) { + return + } + + strongSelf.controllers.append(controller) + if let view = strongSelf.view, layout = strongSelf.layout { + controller.navigation_setDismiss { [weak strongSelf, weak controller] in + if let strongSelf = strongSelf, controller = controller { + strongSelf.dismiss(controller) + } + } + if layout != initialLayout { + controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) + view.addSubview(controller.view) + controller.containerLayoutUpdated(layout, transition: .immediate) + } else { + view.addSubview(controller.view) + } + view.layer.invalidateUpTheTree() + controller.viewWillAppear(false) + controller.viewDidAppear(false) + } + } + })) + } else { + self.controllers.append(controller) + } + } + + deinit { + self.presentationDisposables.dispose() + } + + private func dismiss(_ controller: ViewController) { + if let index = self.controllers.index(where: { $0 === controller }) { + self.controllers.remove(at: index) + controller.viewWillDisappear(false) + controller.view.removeFromSuperview() + controller.viewDidDisappear(false) + } + } + + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let wasReady = self.ready + self.layout = layout + + if wasReady != self.ready { + self.readyChanged(wasReady: wasReady) + } else if self.ready { + for controller in self.controllers { + controller.containerLayoutUpdated(layout, transition: transition) + } + } + } + + private func readyChanged(wasReady: Bool) { + if !wasReady { + self.addViews() + } else { + self.removeViews() + } + } + + private func addViews() { + if let view = self.view, layout = self.layout { + for controller in self.controllers { + controller.viewWillAppear(false) + view.addSubview(controller.view) + controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) + controller.containerLayoutUpdated(layout, transition: .immediate) + controller.viewDidAppear(false) + } + } + } + + private func removeViews() { + for controller in self.controllers { + controller.viewWillDisappear(false) + controller.view.removeFromSuperview() + controller.viewDidDisappear(false) + } + } + + func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for controller in self.controllers { + if controller.isViewLoaded() { + if let result = controller.view.hitTest(point, with: event) { + return result + } + } + } + return nil + } +} diff --git a/Display/RuntimeUtils.h b/Display/RuntimeUtils.h index e7c4cb7a6b..654d3504d4 100644 --- a/Display/RuntimeUtils.h +++ b/Display/RuntimeUtils.h @@ -19,5 +19,6 @@ typedef enum { - (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy; - (id)associatedObjectForKey:(void const *)key; - (bool)checkObjectIsKindOfClass:(Class)targetClass; +- (void)setClass:(Class)newClass; @end diff --git a/Display/RuntimeUtils.m b/Display/RuntimeUtils.m index 444adb0329..639e5848eb 100644 --- a/Display/RuntimeUtils.m +++ b/Display/RuntimeUtils.m @@ -81,4 +81,31 @@ return [self isKindOfClass:targetClass]; } +- (void)setClass:(Class)newClass { + object_setClass(self, newClass); +} + +static Class freedomMakeClass(Class superclass, Class subclass, SEL *copySelectors, int copySelectorsCount) +{ + if (superclass == Nil || subclass == Nil) + return nil; + + Class decoratedClass = objc_allocateClassPair(superclass, [[NSString alloc] initWithFormat:@"%@_%@", NSStringFromClass(superclass), NSStringFromClass(subclass)].UTF8String, 0); + + unsigned int count = 0; + Method *methodList = class_copyMethodList(subclass, &count); + if (methodList != NULL) { + for (unsigned int i = 0; i < count; i++) { + SEL methodName = method_getName(methodList[i]); + class_addMethod(decoratedClass, methodName, method_getImplementation(methodList[i]), method_getTypeEncoding(methodList[i])); + } + + free(methodList); + } + + objc_registerClassPair(decoratedClass); + + return decoratedClass; +} + @end diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index a09fb71b0e..336edf0886 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -25,24 +25,44 @@ public class StatusBarSurface { } public class StatusBar: ASDisplayNode { - public var style: StatusBarStyle = .Black - var proxyNode: StatusBarProxyNode? + public var style: StatusBarStyle = .Black { + didSet { + if self.style != oldValue { + self.layer.invalidateUpTheTree() + } + } + } + private var proxyNode: StatusBarProxyNode? + private var removeProxyNodeScheduled = false override init() { - super.init() + super.init(viewBlock: { + return UITracingLayerView() + }, didLoad: nil) + + self.layer.setTraceableInfo(NSWeakReference(value: self)) self.clipsToBounds = true self.isUserInteractionEnabled = false } func removeProxyNode() { - self.proxyNode?.isHidden = true - self.proxyNode?.removeFromSupernode() - self.proxyNode = nil + self.removeProxyNodeScheduled = true + + DispatchQueue.main.async(execute: { [weak self] in + if let strongSelf = self { + if strongSelf.removeProxyNodeScheduled { + strongSelf.removeProxyNodeScheduled = false + strongSelf.proxyNode?.isHidden = true + strongSelf.proxyNode?.removeFromSupernode() + strongSelf.proxyNode = nil + } + } + }) } func updateProxyNode() { - let origin = self.view.convert(CGPoint(), to: nil) + self.removeProxyNodeScheduled = false if let proxyNode = proxyNode { proxyNode.style = self.style } else { @@ -50,8 +70,5 @@ public class StatusBar: ASDisplayNode { self.proxyNode!.isHidden = false self.addSubnode(self.proxyNode!) } - - let frame = CGRect(origin: CGPoint(x: -origin.x, y: -origin.y), size: self.proxyNode!.frame.size) - self.proxyNode?.frame = frame } } diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 1bf0ef2b55..2e3df48206 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -27,6 +27,9 @@ private func optimizeMappedSurface(_ surface: MappedStatusBarSurface) -> MappedS if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { return surface } + if let lhsStatusBar = surface.statusBars[i - 1].statusBar, rhsStatusBar = surface.statusBars[i].statusBar where !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) { + return surface + } } let size = UIApplication.shared().statusBarFrame.size return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) @@ -40,7 +43,8 @@ private func displayHiddenAnimation() -> CAAnimation { animation.fromValue = NSNumber(value: Float(-40.0)) animation.toValue = NSNumber(value: Float(-40.0)) animation.fillMode = kCAFillModeBoth - animation.duration = 100000000.0 + animation.duration = 1.0 + animation.speed = 0.0 animation.isAdditive = true animation.isRemovedOnCompletion = false @@ -55,19 +59,65 @@ class StatusBarManager { } private func updateSurfaces(_ previousSurfaces: [StatusBarSurface]) { - let mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(mappedSurface($0)) }) + var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(mappedSurface($0)) }) + + var reduceSurfaces = true + var reduceSurfacesStatusBarStyle: StatusBarStyle? + outer: for surface in mappedSurfaces { + for mappedStatusBar in surface.statusBars { + if mappedStatusBar.frame.origin.equalTo(CGPoint()) { + if let reduceSurfacesStatusBarStyle = reduceSurfacesStatusBarStyle { + if mappedStatusBar.style != reduceSurfacesStatusBarStyle { + reduceSurfaces = false + break outer + } + } else { + reduceSurfacesStatusBarStyle = mappedStatusBar.style + } + } + } + } + + if reduceSurfaces { + outer: for surface in mappedSurfaces { + for mappedStatusBar in surface.statusBars { + if mappedStatusBar.frame.origin.equalTo(CGPoint()) { + if let statusBar = mappedStatusBar.statusBar where !statusBar.layer.hasPositionOrOpacityAnimations() { + mappedSurfaces = [MappedStatusBarSurface(statusBars: [mappedStatusBar], surface: surface.surface)] + break outer + } + } + } + } + } var visibleStatusBars: [StatusBar] = [] var globalStatusBar: (StatusBarStyle, CGFloat)? + + var coveredIdentity = false for i in 0 ..< mappedSurfaces.count { - if i == mappedSurfaces.count - 1 && mappedSurfaces[i].statusBars.count == 1 { - globalStatusBar = (mappedSurfaces[i].statusBars[0].style, mappedSurfaces[i].statusBars[0].frame.origin.y) - } else { - for mappedStatusBar in mappedSurfaces[i].statusBars { - if let statusBar = mappedStatusBar.statusBar { + for mappedStatusBar in mappedSurfaces[i].statusBars { + if let statusBar = mappedStatusBar.statusBar { + if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() { + if !coveredIdentity { + coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) + if i == 0 && globalStatusBar == nil { + globalStatusBar = (mappedStatusBar.style, statusBar.alpha) + } else { + visibleStatusBars.append(statusBar) + } + } + } else { visibleStatusBars.append(statusBar) } + } else { + if !coveredIdentity { + coveredIdentity = true + if i == 0 && globalStatusBar == nil { + globalStatusBar = (mappedStatusBar.style, 1.0) + } + } } } } @@ -93,59 +143,13 @@ class StatusBarManager { } if let globalStatusBar = globalStatusBar { - StatusBarUtils.statusBarWindow()!.isHidden = false let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent if UIApplication.shared().statusBarStyle != statusBarStyle { UIApplication.shared().setStatusBarStyle(statusBarStyle, animated: false) } - StatusBarUtils.statusBarWindow()!.layer.removeAnimation(forKey: "displayHidden") - StatusBarUtils.statusBarWindow()!.transform = CGAffineTransform(translationX: 0.0, y: globalStatusBar.1) + StatusBarUtils.statusBarWindow()!.alpha = globalStatusBar.1 } else { - if StatusBarUtils.statusBarWindow()!.layer.animation(forKey: "displayHidden") == nil { - StatusBarUtils.statusBarWindow()!.layer.add(displayHiddenAnimation(), forKey: "displayHidden") - } + StatusBarUtils.statusBarWindow()!.alpha = 0.0 } - - /*if self.items.count == 1 { - self.shouldHide = true - dispatch_async(dispatch_get_main_queue(), { - if self.shouldHide { - self.items[0].1.hidden = true - self.shouldHide = false - } - }) - //self.items[0].1.hidden = true - StatusBarUtils.statusBarWindow()!.hidden = false - } else if !self.items.isEmpty { - self.shouldHide = false - for (statusBar, node) in self.items { - node.hidden = false - var frame = statusBar.frame - frame.size.width = self.bounds.size.width - frame.size.height = 20.0 - node.frame = frame - - //print("origin: \(frame.origin.x)") - //print("style: \(node.style)") - - let bounds = frame - node.bounds = bounds - } - - UIView.performWithoutAnimation { - StatusBarUtils.statusBarWindow()!.hidden = true - } - } - - var statusBarStyle: UIStatusBarStyle = .Default - if let lastItem = self.items.last { - statusBarStyle = lastItem.0.style == .Black ? .Default : .LightContent - } - - if UIApplication.sharedApplication().statusBarStyle != statusBarStyle { - UIApplication.sharedApplication().setStatusBarStyle(statusBarStyle, animated: false) - } - - //print("window \(StatusBarUtils.statusBarWindow()!)")*/ } } diff --git a/Display/StatusBarSurfaceProvider.swift b/Display/StatusBarSurfaceProvider.swift deleted file mode 100644 index 25e3c9554a..0000000000 --- a/Display/StatusBarSurfaceProvider.swift +++ /dev/null @@ -1,4 +0,0 @@ - -protocol StatusBarSurfaceProvider { - func statusBarSurfaces() -> [StatusBarSurface] -} diff --git a/Display/StatusBarUtils.m b/Display/StatusBarUtils.m index 9890f83561..0f3f0e66fa 100644 --- a/Display/StatusBarUtils.m +++ b/Display/StatusBarUtils.m @@ -4,8 +4,9 @@ + (UIView *)statusBarWindow { UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; - UIView *view = window.subviews.firstObject; - return view; + return window; + //UIView *view = window.subviews.firstObject; + //return view; } + (UIView *)statusBar { diff --git a/Display/SystemContainedControllerTransitionCoordinator.swift b/Display/SystemContainedControllerTransitionCoordinator.swift new file mode 100644 index 0000000000..ea1034f5d6 --- /dev/null +++ b/Display/SystemContainedControllerTransitionCoordinator.swift @@ -0,0 +1,73 @@ +import UIKit + +final class SystemContainedControllerTransitionCoordinator:NSObject, UIViewControllerTransitionCoordinator { + public func isAnimated() -> Bool { + return false + } + + public func presentationStyle() -> UIModalPresentationStyle { + return .fullScreen + } + + public func initiallyInteractive() -> Bool { + return false + } + + public let isInterruptible: Bool = false + + public func isInteractive() -> Bool { + return false + } + + public func isCancelled() -> Bool { + return false + } + + public func transitionDuration() -> TimeInterval { + return 0.6 + } + + public func percentComplete() -> CGFloat { + return 0.0 + } + + public func completionVelocity() -> CGFloat { + return 0.0 + } + + public func completionCurve() -> UIViewAnimationCurve { + return .easeInOut + } + + public func viewController(forKey key: String) -> UIViewController? { + return nil + } + + public func view(forKey key: String) -> UIView? { + return nil + } + + public func containerView() -> UIView { + return UIView() + } + + public func targetTransform() -> CGAffineTransform { + return CGAffineTransform.identity + } + + public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { + return false + } + + public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { + return false + } + + public func notifyWhenInteractionEnds(_ handler: (UIViewControllerTransitionCoordinatorContext) -> ()) { + + } + + public func notifyWhenInteractionChanges(_ handler: (UIViewControllerTransitionCoordinatorContext) -> ()) { + + } +} diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 69909ed983..7ef4acd2af 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -1,7 +1,7 @@ import Foundation import AsyncDisplayKit -class TabBarControllerNode: ASDisplayNode { +final class TabBarControllerNode: ASDisplayNode { let tabBarNode: TabBarNode var currentControllerView: UIView? { @@ -17,21 +17,24 @@ class TabBarControllerNode: ASDisplayNode { init(itemSelected: (Int) -> Void) { self.tabBarNode = TabBarNode(itemSelected: itemSelected) - super.init() + super.init(viewBlock: { + return UITracingLayerView() + }, didLoad: nil) self.addSubnode(self.tabBarNode) } - func updateLayout(_ layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { - self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets.bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0)) + self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets(options: []).bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0)) self.tabBarNode.layout() } - if duration > DBL_EPSILON { - UIView.animate(withDuration: duration, delay: 0.0, options: UIViewAnimationOptions(rawValue: curve << 7), animations: update, completion: nil) - } else { - update() + switch transition { + case .immediate: + update() + case let .animated(duration, curve): + update() } } } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 283b6829db..82e4e493c4 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -3,6 +3,8 @@ import UIKit import AsyncDisplayKit public class TabBarController: ViewController { + private var containerLayout = ContainerViewLayout() + private var tabBarControllerNode: TabBarControllerNode { get { return super.displayNode as! TabBarControllerNode @@ -76,12 +78,9 @@ public class TabBarController: ViewController { if let currentController = self.currentController { currentController.willMove(toParentViewController: self) - if let layout = self.layout { - currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) - - currentController.setParentLayout(self.childControllerLayoutForLayout(layout), duration: 0.0, curve: 0) - } + currentController.containerLayoutUpdated(self.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) self.tabBarControllerNode.currentControllerView = currentController.view + currentController.navigationBar.isHidden = true self.addChildViewController(currentController) currentController.didMove(toParentViewController: self) @@ -95,20 +94,17 @@ public class TabBarController: ViewController { } } - private func childControllerLayoutForLayout(_ layout: ViewControllerLayout) -> ViewControllerLayout { - var insets = layout.insets - insets.bottom += 49.0 - return ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: layout.inputViewHeight, statusBarHeight: layout.statusBarHeight) - } - - override public func updateLayout(_ layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { - super.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) - self.tabBarControllerNode.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: curve) + self.containerLayout = layout + + self.tabBarControllerNode.containerLayoutUpdated(layout, transition: transition) if let currentController = self.currentController { currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) - currentController.setParentLayout(self.childControllerLayoutForLayout(layout), duration: duration, curve: curve) + + currentController.containerLayoutUpdated(layout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: transition) } } } diff --git a/Display/DisplayTheme.swift b/Display/Theme.swift similarity index 65% rename from Display/DisplayTheme.swift rename to Display/Theme.swift index 6a96470066..cf8ea4883c 100644 --- a/Display/DisplayTheme.swift +++ b/Display/Theme.swift @@ -1,7 +1,7 @@ import Foundation -public class DisplayTheme { - var tintColor: UIColor +public class Theme { + public let tintColor: UIColor public init(tintColor: UIColor) { self.tintColor = tintColor diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 55de13b6c3..474b3a6858 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -5,6 +5,7 @@ - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; - (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss; @end diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index b0b98adac8..128296b26d 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -5,9 +5,33 @@ #import "NSWeakReference.h" +@interface UIViewControllerPresentingProxy : UIViewController + +@property (nonatomic, copy) void (^dismiss)(); + +@end + +@implementation UIViewControllerPresentingProxy + +- (instancetype)init { + return self; +} + +- (void)dismissViewControllerAnimated:(BOOL)__unused flag completion:(void (^)(void))completion { + if (_dismiss) { + _dismiss(); + } + if (completion) { + completion(); + } +} + +@end + static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIViewControllerIgnoreAppearanceMethodInvocationsKey; static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNavigationControllerKey; -static const void *UIViewControllerPresentingViewControllerKey = &UIViewControllerPresentingViewControllerKey; +static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey; +static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey; static bool notyfyingShiftState = false; @@ -112,17 +136,15 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:navigationControlller] forKey:UIViewControllerNavigationControllerKey]; } -- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController { - [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:presentingViewController] forKey:UIViewControllerPresentingViewControllerKey]; -} - - (UINavigationController *)_65087dc8_navigationController { UINavigationController *navigationController = self._65087dc8_navigationController; if (navigationController != nil) { return navigationController; } - navigationController = self.parentViewController.navigationController; + UIViewController *parentController = self.parentViewController; + + navigationController = parentController.navigationController; if (navigationController != nil) { return navigationController; } @@ -130,13 +152,28 @@ static bool notyfyingShiftState = false; return ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerNavigationControllerKey]).value; } +- (void)navigation_setPresentingViewController:(UIViewController *)presentingViewController { + [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:presentingViewController] forKey:UIViewControllerPresentingControllerKey]; +} + +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss { + UIViewControllerPresentingProxy *proxy = [[UIViewControllerPresentingProxy alloc] init]; + proxy.dismiss = dismiss; + [self setAssociatedObject:proxy forKey:UIViewControllerPresentingProxyControllerKey]; +} + - (UIViewController *)_65087dc8_presentingViewController { UINavigationController *navigationController = self.navigationController; if (navigationController.presentingViewController != nil) { return navigationController.presentingViewController; } - return ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerPresentingViewControllerKey]).value; + UIViewController *controller = ((NSWeakReference *)[self associatedObjectForKey:UIViewControllerPresentingControllerKey]).value; + if (controller != nil) { + return controller; + } + + return [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; } @end diff --git a/Display/UIWindow+OrientationChange.h b/Display/UIWindow+OrientationChange.h index d985458ad0..b6f1320618 100644 --- a/Display/UIWindow+OrientationChange.h +++ b/Display/UIWindow+OrientationChange.h @@ -9,9 +9,3 @@ - (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3; @end - -@interface UINavigationBar (Condensed) - -- (void)setCondensed:(BOOL)condensed; - -@end diff --git a/Display/UniversalMasterController.swift b/Display/UniversalMasterController.swift new file mode 100644 index 0000000000..b751a92391 --- /dev/null +++ b/Display/UniversalMasterController.swift @@ -0,0 +1,22 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import SwiftSignalKit + +class UniversalMasterController: ViewController { + private var controllers: [ViewController] = [] + + public override init() { + super.init() + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + } + + +} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index f775677e1d..84d1952aaa 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -3,11 +3,12 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { - return lhs.size == rhs.size && lhs.insets == rhs.insets && lhs.inputViewHeight == rhs.inputViewHeight && lhs.statusBarHeight == rhs.statusBarHeight -} - -@objc public class ViewController: UIViewController, WindowContentController { +@objc public class ViewController: UIViewController, ContainableController { + private var containerLayout = ContainerViewLayout() + private let presentationContext: PresentationContext + + public private(set) var presentationArguments: Any? + private var _displayNode: ASDisplayNode? public final var displayNode: ASDisplayNode { get { @@ -39,11 +40,6 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { return self._ready } - private var updateLayoutOnLayout: (ViewControllerLayout, Double, UInt)? - public private(set) var layout: ViewControllerLayout? - - var keyboardFrameObserver: AnyObject? - private var scrollToTopView: ScrollToTopView? public var scrollToTop: (() -> Void)? { didSet { @@ -74,50 +70,15 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { public init() { self.statusBar = StatusBar() self.navigationBar = NavigationBar() + self.presentationContext = PresentationContext() super.init(nibName: nil, bundle: nil) + self.navigationBar.backPressed = { [weak self] in + self?.navigationController?.popViewController(animated: true) + } self.navigationBar.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false - - self.keyboardFrameObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in - if let strongSelf = self, _ = strongSelf._displayNode { - let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue() ?? CGRect() - let keyboardHeight = max(0.0, UIScreen.main().bounds.size.height - keyboardFrame.minY) - var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 - if duration > DBL_EPSILON { - duration = 0.5 - } - var curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? UInt(7 << 16) - - let previousLayout: ViewControllerLayout? - var previousDurationAndCurve: (Double, UInt)? - if let updateLayoutOnLayout = strongSelf.updateLayoutOnLayout { - previousLayout = updateLayoutOnLayout.0 - previousDurationAndCurve = (updateLayoutOnLayout.1, updateLayoutOnLayout.2) - } else{ - previousLayout = strongSelf.layout - } - let layout = ViewControllerLayout(size: previousLayout?.size ?? CGSize(), insets: previousLayout?.insets ?? UIEdgeInsets(), inputViewHeight: keyboardHeight, statusBarHeight: previousLayout?.statusBarHeight ?? 20.0) - let updated: Bool - if let previousLayout = previousLayout { - updated = previousLayout != layout - if duration < DBL_EPSILON && abs(min(previousLayout.inputViewHeight, layout.inputViewHeight) - 225.0) < CGFloat(FLT_EPSILON) && abs(max(previousLayout.inputViewHeight, layout.inputViewHeight) - 225.0 - 33.0) < CGFloat(FLT_EPSILON) { - duration = 0.1 - curve = 0 - } - } else { - updated = true - } - if updated { - //print("keyboard layout change: \(layout) rotating: \(strongSelf.view.window?.isRotating())") - - let durationAndCurve: (Double, UInt) = previousDurationAndCurve ?? (duration, curve) - strongSelf.updateLayoutOnLayout = (layout, durationAndCurve.0, durationAndCurve.1) - strongSelf.view.setNeedsLayout() - } - } - }) } required public init(coder aDecoder: NSCoder) { @@ -125,8 +86,33 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { } deinit { - if let keyboardFrameObserver = keyboardFrameObserver { - NotificationCenter.default().removeObserver(keyboardFrameObserver) + + } + + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.containerLayout = layout + + if !self.isViewLoaded() { + self.loadView() + } + self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) + if let _ = layout.statusBarHeight { + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + } + + let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 + var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0)) + if statusBarHeight.isLessThanOrEqualTo(0.0) { + navigationBarFrame.origin.y -= 20.0 + navigationBarFrame.size.height = 20.0 + 32.0 + } + + transition.updateFrame(node: self.navigationBar, frame: navigationBarFrame) + + self.presentationContext.containerLayoutUpdated(layout, transition: transition) + + if let scrollToTopView = self.scrollToTopView { + scrollToTopView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 10.0) } } @@ -134,6 +120,7 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { self.view = self.displayNode.view self.displayNode.addSubnode(self.navigationBar) self.view.addSubview(self.statusBar.view) + self.presentationContext.view = self.view } public func loadDisplayNode() { @@ -145,96 +132,31 @@ public func ==(lhs: ViewControllerLayout, rhs: ViewControllerLayout) -> Bool { self.updateScrollToTopView() } - public func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) { - if self._displayNode == nil { - self.loadDisplayNode() - } - - let previousLayout: ViewControllerLayout? - if let updateLayoutOnLayout = self.updateLayoutOnLayout { - previousLayout = updateLayoutOnLayout.0 - } else { - previousLayout = self.layout - } - - var insets = layout.insets - insets.top += 22.0 - - let layout = ViewControllerLayout(size: layout.size, insets: insets, inputViewHeight: previousLayout?.inputViewHeight ?? 0.0, statusBarHeight: layout.statusBarHeight) - let updated: Bool - if let previousLayout = previousLayout { - updated = previousLayout != layout - } else { - updated = true - } - if updated { - if previousLayout == nil { - self.layout = layout - self.updateLayout(layout, previousLayout: previousLayout, duration: duration, curve: 0) - } else { - self.updateLayoutOnLayout = (layout, duration, 0) - self.view.setNeedsLayout() - } - } - } - - public func updateLayout(_ layout: ViewControllerLayout, previousLayout: ViewControllerLayout?, duration: Double, curve: UInt) { - self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) - self.navigationBar.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: layout.size.width, height: 44.0 + 20.0)) - if let scrollToTopView = self.scrollToTopView { - scrollToTopView.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: 10.0) - } - } - - public func setNeedsLayoutWithDuration(_ duration: Double, curve: UInt) { - let previousLayout: ViewControllerLayout? - var previousDurationAndCurve: (Double, UInt)? - if let updateLayoutOnLayout = self.updateLayoutOnLayout { - previousLayout = updateLayoutOnLayout.0 - previousDurationAndCurve = (updateLayoutOnLayout.1, updateLayoutOnLayout.2) - } else{ - previousLayout = self.layout - } - if let previousLayout = previousLayout { - let durationAndCurve: (Double, UInt) = previousDurationAndCurve ?? (duration, curve) - self.updateLayoutOnLayout = (previousLayout, durationAndCurve.0, durationAndCurve.1) - self.view.setNeedsLayout() - } - } - - override public func viewDidLayoutSubviews() { - if let updateLayoutOnLayout = self.updateLayoutOnLayout { - if !Window.isDeviceRotating() { - if !((self.view.window as? Window)?.isUpdatingOrientationLayout ?? false) { - //print("\(self) apply inputHeight: \(updateLayoutOnLayout.0.inputViewHeight)") - let previousLayout = self.layout - self.layout = updateLayoutOnLayout.0 - self.updateLayout(updateLayoutOnLayout.0, previousLayout: previousLayout, duration: updateLayoutOnLayout.1, curve: updateLayoutOnLayout.2) - self.view.frame = CGRect(origin: self.view.frame.origin, size: updateLayoutOnLayout.0.size) - - self.updateLayoutOnLayout = nil - } else { - (self.view.window as? Window)?.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in - if let strongSelf = self { - strongSelf.view.setNeedsLayout() - } - }) - } - } else { - Window.addPostDeviceOrientationDidChange({ [weak self] in - if let strongSelf = self { - strongSelf.view.setNeedsLayout() - } - }) - } + public func requestLayout(transition: ContainedViewLayoutTransition) { + if self.isViewLoaded() { + self.containerLayoutUpdated(self.containerLayout, transition: transition) } } override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + preconditionFailure("use present(_:in)") + } + + override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { - navigationController.present(viewControllerToPresent, animated: flag, completion: completion) + navigationController.dismiss(animated: flag, completion: completion) } else { - super.present(viewControllerToPresent, animated: flag, completion: completion) + super.dismiss(animated: flag, completion: completion) + } + } + + public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil) { + controller.presentationArguments = arguments + switch context { + case .current: + self.presentationContext.present(controller) + case .window: + (self.view.window as? Window)?.present(controller) } } } diff --git a/Display/Window.swift b/Display/Window.swift index fdfa019949..01bc2e654f 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -1,88 +1,111 @@ import Foundation import AsyncDisplayKit -public class WindowRootViewController: UIViewController { - public override func preferredStatusBarStyle() -> UIStatusBarStyle { +private class WindowRootViewController: UIViewController { + override func preferredStatusBarStyle() -> UIStatusBarStyle { return .default } - public override func prefersStatusBarHidden() -> Bool { + override func prefersStatusBarHidden() -> Bool { return false } } -public struct ViewControllerLayout: Equatable { +private struct WindowLayout: Equatable { public let size: CGSize - public let insets: UIEdgeInsets - public let inputViewHeight: CGFloat - public let statusBarHeight: CGFloat + public let statusBarHeight: CGFloat? + public let inputHeight: CGFloat? } -public protocol WindowContentController { - func setParentLayout(_ layout: ViewControllerLayout, duration: Double, curve: UInt) - var view: UIView! { get } -} - -public func animateRotation(_ view: UIView?, toFrame: CGRect, duration: Double) { - if let view = view { - UIView.animate(withDuration: duration, animations: { () -> Void in - view.frame = toFrame - }) +private func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { + if !lhs.size.equalTo(rhs.size) { + return false } -} - -public func animateRotation(view: ASDisplayNode?, toFrame: CGRect, duration: Double) { - if let view = view { - CALayer.beginRecordingChanges() - UIView.animate(withDuration: duration, animations: { () -> Void in - view.frame = toFrame - }) - view.layout() - let states = CALayer.endRecordingChanges() as! [CALayerAnimation] - let k = Float(UIView.animationDurationFactor()) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - for state in states { - if let layer = state.layer { - if !state.startBounds.equalTo(state.endBounds) { - let boundsAnimation = CABasicAnimation(keyPath: "bounds") - boundsAnimation.fromValue = NSValue(cgRect: state.startBounds) - boundsAnimation.toValue = NSValue(cgRect: state.endBounds) - boundsAnimation.duration = duration - boundsAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - boundsAnimation.isRemovedOnCompletion = true - boundsAnimation.fillMode = kCAFillModeForwards - boundsAnimation.speed = speed - layer.add(boundsAnimation, forKey: "_rotationBounds") - } - - if !state.startPosition.equalTo(state.endPosition) { - let positionAnimation = CABasicAnimation(keyPath: "position") - positionAnimation.fromValue = NSValue(cgPoint: state.startPosition) - positionAnimation.toValue = NSValue(cgPoint: state.endPosition) - positionAnimation.duration = duration - positionAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - positionAnimation.isRemovedOnCompletion = true - positionAnimation.fillMode = kCAFillModeForwards - positionAnimation.speed = speed - layer.add(positionAnimation, forKey: "_rotationPosition") - } + + if let lhsStatusBarHeight = lhs.statusBarHeight { + if let rhsStatusBarHeight = rhs.statusBarHeight { + if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { + return false } + } else { + return false + } + } else if let _ = rhs.statusBarHeight { + return false + } + + if let lhsInputHeight = lhs.inputHeight { + if let rhsInputHeight = rhs.inputHeight { + if !lhsInputHeight.isEqual(to: rhsInputHeight) { + return false + } + } else { + return false + } + } else if let _ = rhs.inputHeight { + return false + } + + return true +} + +private struct UpdatingLayout { + var layout: WindowLayout + var transition: ContainedViewLayoutTransition + + mutating func update(transition: ContainedViewLayoutTransition, override: Bool) { + var update = false + if case .immediate = self.transition { + update = true + } else if override { + update = true + } + if update { + self.transition = transition } } + + mutating func update(size: CGSize, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: size, statusBarHeight: self.layout.statusBarHeight, inputHeight: self.layout.inputHeight) + } + + mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: self.layout.size, statusBarHeight: statusBarHeight, inputHeight: self.layout.inputHeight) + } + + mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: self.layout.size, statusBarHeight: self.layout.statusBarHeight, inputHeight: inputHeight) + } +} + +private let orientationChangeDuration: Double = UIDevice.current().userInterfaceIdiom == .pad ? 0.4 : 0.3 +private let statusBarHiddenInLandscape: Bool = UIDevice.current().userInterfaceIdiom == .phone + +private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout { + return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) } public class Window: UIWindow { private let statusBarManager: StatusBarManager + private var statusBarChangeObserver: AnyObject? + private var keyboardFrameChangeObserver: AnyObject? + + private var windowLayout: WindowLayout + private var updatingLayout: UpdatingLayout? - private var updateViewSizeOnLayout: (Bool, Double) = (false, 0.0) public var isUpdatingOrientationLayout = false - private let orientationChangeDuration: Double = { - UIDevice.current().userInterfaceIdiom == .pad ? 0.4 : 0.3 - }() + private let presentationContext: PresentationContext + + private var tracingStatusBarsInvalidated = false + + private var statusBarHidden = false public convenience init() { self.init(frame: UIScreen.main().bounds) @@ -90,17 +113,72 @@ public class Window: UIWindow { public override init(frame: CGRect) { self.statusBarManager = StatusBarManager() + self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: UIApplication.shared().statusBarFrame.size.height, inputHeight: 0.0) + self.presentationContext = PresentationContext() super.init(frame: frame) + self.layer.setInvalidateTracingSublayers { [weak self] in + self?.invalidateTracingStatusBars() + } + + self.presentationContext.view = self + self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + super.rootViewController = WindowRootViewController() + + self.statusBarChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main(), using: { [weak self] notification in + if let strongSelf = self { + let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue().height ?? 20.0) + + let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut) + strongSelf.updateLayout { $0.update(statusBarHeight: statusBarHeight, transition: transition, overrideTransition: false) } + } + }) + + self.keyboardFrameChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in + if let strongSelf = self { + let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue() ?? CGRect() + let keyboardHeight = max(0.0, UIScreen.main().bounds.size.height - keyboardFrame.minY) + var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 + if duration > DBL_EPSILON { + duration = 0.5 + } + var curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7 + + let transitionCurve: ContainedViewLayoutTransitionCurve + if curve == 7 { + transitionCurve = .spring + } else { + transitionCurve = .easeInOut + } + strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) } + } + }) } public required init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + if let statusBarChangeObserver = self.statusBarChangeObserver { + NotificationCenter.default().removeObserver(statusBarChangeObserver) + } + if let keyboardFrameChangeObserver = self.keyboardFrameChangeObserver { + NotificationCenter.default().removeObserver(keyboardFrameChangeObserver) + } + } + + private func invalidateTracingStatusBars() { + self.tracingStatusBarsInvalidated = true + self.setNeedsLayout() + } + public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let result = self.presentationContext.hitTest(point, with: event) { + return result + } return self.viewController?.view.hitTest(point, with: event) } @@ -113,8 +191,13 @@ public class Window: UIWindow { super.frame = value if sizeUpdated { - self.updateViewSizeOnLayout = (true, self.isRotating() ? self.orientationChangeDuration : 0.0) - self.setNeedsLayout() + let transition: ContainedViewLayoutTransition + if self.isRotating() { + transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) + } else { + transition = .immediate + } + self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) } } } } @@ -128,38 +211,78 @@ public class Window: UIWindow { super.bounds = value if sizeUpdated { - self.updateViewSizeOnLayout = (true, self.isRotating() ? self.orientationChangeDuration : 0.0) - self.setNeedsLayout() + let transition: ContainedViewLayoutTransition + if self.isRotating() { + transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) + } else { + transition = .immediate + } + self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) } } } } - private var _rootViewController: WindowContentController? - public var viewController: WindowContentController? { + private var rootController: ContainableController? + public var viewController: ContainableController? { get { - return _rootViewController + return rootController } set(value) { - self._rootViewController?.view.removeFromSuperview() - self._rootViewController = value - self._rootViewController?.view.frame = self.bounds - self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: 0.0), duration: 0.0, curve: 0) - - if let view = self._rootViewController?.view { - self.addSubview(view) + if let rootController = self.rootController { + rootController.view.removeFromSuperview() } + self.rootController = value - self.updateStatusBars() + if let rootController = self.rootController { + rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + + self.addSubview(rootController.view) + } } } override public func layoutSubviews() { super.layoutSubviews() - if self.updateViewSizeOnLayout.0 { - self.updateViewSizeOnLayout.0 = false + if self.tracingStatusBarsInvalidated { + self.tracingStatusBarsInvalidated = false - self._rootViewController?.setParentLayout(ViewControllerLayout(size: self.bounds.size, insets: UIEdgeInsets(), inputViewHeight: 0.0, statusBarHeight: 0.0), duration: updateViewSizeOnLayout.1, curve: 0) + if self.statusBarHidden { + self.statusBarManager.surfaces = [] + } else { + var statusBarSurfaces: [StatusBarSurface] = [] + for layers in self.layer.traceableLayerSurfaces() { + let surface = StatusBarSurface() + for layer in layers { + if let weakInfo = layer.traceableInfo() as? NSWeakReference { + if let statusBar = weakInfo.value as? StatusBar { + surface.addStatusBar(statusBar) + } + } + } + statusBarSurfaces.append(surface) + } + self.layer.adjustTraceableLayerTransforms(CGSize()) + self.statusBarManager.surfaces = statusBarSurfaces + } + } + + if !Window.isDeviceRotating() { + if !self.isUpdatingOrientationLayout { + self.commitUpdatingLayout() + } else { + self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in + if let strongSelf = self { + strongSelf.setNeedsLayout() + } + }) + } + } else { + Window.addPostDeviceOrientationDidChange({ [weak self] in + if let strongSelf = self { + strongSelf.setNeedsLayout() + } + }) } } @@ -181,7 +304,40 @@ public class Window: UIWindow { postUpdateToInterfaceOrientationBlocks.append(f) } - func updateStatusBars() { - self.statusBarManager.surfaces = (self._rootViewController as? StatusBarSurfaceProvider)?.statusBarSurfaces() ?? [] + private func updateLayout(_ update: @noescape(inout UpdatingLayout) -> ()) { + if self.updatingLayout == nil { + self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) + } + update(&self.updatingLayout!) + self.setNeedsLayout() + } + + private func commitUpdatingLayout() { + if let updatingLayout = self.updatingLayout { + self.updatingLayout = nil + if updatingLayout.layout != self.windowLayout { + var statusBarHeight = UIApplication.shared().statusBarFrame.size.height + var statusBarWasHidden = self.statusBarHidden + if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { + statusBarHeight = 0.0 + self.statusBarHidden = true + } else { + self.statusBarHidden = false + } + if self.statusBarHidden != statusBarWasHidden { + self.tracingStatusBarsInvalidated = true + self.setNeedsLayout() + } + self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight) + + self.rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) + + self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) + } + } + } + + func present(_ controller: ViewController) { + self.presentationContext.present(controller) } } From 1fbd4941dc3a1857c1b866d0116ed5921e998ea1 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 1 Aug 2016 19:10:42 +0300 Subject: [PATCH 017/245] no message --- Display.xcodeproj/project.pbxproj | 10 +++ Display/CAAnimationUtils.swift | 35 +++++--- Display/DisplayLinkDispatcher.swift | 2 +- Display/ListView.swift | 120 +++++++++++++++++++++---- Display/ListViewTransactionQueue.swift | 4 +- Display/NavigationBar.swift | 1 + Display/StatusBar.swift | 2 +- Display/StatusBarProxyNode.swift | 2 +- Display/UIKitUtils.swift | 40 ++++++++- Display/UniversalTapRecognizer.swift | 44 +++++++++ Display/Window.swift | 8 +- 11 files changed, 231 insertions(+), 37 deletions(-) create mode 100644 Display/UniversalTapRecognizer.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index a301f45bbf..004d453011 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; + D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; @@ -200,6 +201,7 @@ D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonNode.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; + D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; @@ -401,6 +403,7 @@ D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */, D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */, D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */, + D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */, ); name = Utils; sourceTree = ""; @@ -655,6 +658,7 @@ D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, + D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */, D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, @@ -832,6 +836,7 @@ PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -855,6 +860,7 @@ PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -865,6 +871,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -876,6 +883,7 @@ PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -939,6 +947,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; }; name = Hockeyapp; }; @@ -949,6 +958,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; }; name = Hockeyapp; }; diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index d9592f2dd1..4322e2bc7e 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,6 +1,6 @@ import UIKit -@objc private class CALayerAnimationDelegate: NSObject { +@objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { var completion: ((Bool) -> Void)? init(completion: ((Bool) -> Void)?) { @@ -9,7 +9,7 @@ import UIKit super.init() } - @objc override func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + @objc func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { if let completion = self.completion { completion(flag) } @@ -39,7 +39,7 @@ public extension CAAnimation { } public extension CALayer { - public func animate(from: NSValue, to: NSValue, keyPath: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if timingFunction == kCAMediaTimingFunctionSpring { let animation = makeSpringAnimation(keyPath) animation.fromValue = from @@ -57,6 +57,7 @@ public extension CALayer { } animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive self.add(animation, forKey: keyPath) } else { @@ -74,6 +75,7 @@ public extension CALayer { animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards animation.speed = speed + animation.isAdditive = additive if let completion = completion { animation.delegate = CALayerAnimationDelegate(completion: completion) } @@ -109,33 +111,42 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - public func animateScale(from: CGFloat, to: CGFloat, duration: Double) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true, completion: nil) + public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { + if let completion = completion { + completion(true) + } return } - self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { + if let completion = completion { + completion(true) + } return } - self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double) { self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) } - public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, completion: ((Bool) -> Void)? = nil) { + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { + if let completion = completion { + completion(true) + } return } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, completion: nil) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, completion: completion) + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: nil) + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } } diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index fca9f80b3a..16d054c4da 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -15,7 +15,7 @@ public class DisplayLinkDispatcher: NSObject { self.displayLink.preferredFramesPerSecond = 60 } self.displayLink.isPaused = true - self.displayLink.add(to: RunLoop.main(), forMode: RunLoopMode.commonModes.rawValue) + self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) } public func dispatch(f: (Void) -> Void) { diff --git a/Display/ListView.swift b/Display/ListView.swift index 0ca9c87a1f..2a9a64e5fa 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -737,6 +737,21 @@ private struct ListViewState { } } + var previousIndex: Int? + for node in self.nodes { + if let index = node.index { + if let currentPreviousIndex = previousIndex { + if index <= currentPreviousIndex { + print("index <= previousIndex + 1") + break + } + previousIndex = index + } else { + previousIndex = index + } + } + } + if let _ = self.scrollPosition { self.fixScrollPostition(itemCount) } @@ -789,6 +804,60 @@ private struct ListViewState { assertionFailure() } } + + mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: () -> Void, operations: inout [ListViewStateOperation]) { + var i = -1 + for node in self.nodes { + i += 1 + if node.index == itemIndex { + switch animation { + case .None: + let offsetDirection: ListViewInsertionOffsetDirection + if let direction = direction { + offsetDirection = ListViewInsertionOffsetDirection(direction) + } else { + if node.frame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetDirection = .Down + } else { + offsetDirection = .Up + } + } + + switch offsetDirection { + case .Up: + let offsetDelta = -(layout.size.height - node.frame.size.height) + var updatedFrame = node.frame + updatedFrame.origin.y += offsetDelta + updatedFrame.size.height = layout.size.height + self.nodes[i].frame = updatedFrame + + for j in 0 ..< i { + var frame = self.nodes[j].frame + frame.origin.y += offsetDelta + self.nodes[j].frame = frame + } + case .Down: + let offsetDelta = layout.size.height - node.frame.size.height + var updatedFrame = node.frame + updatedFrame.size.height = layout.size.height + self.nodes[i].frame = updatedFrame + + for j in i + 1 ..< self.nodes.count { + var frame = self.nodes[j].frame + frame.origin.y += offsetDelta + self.nodes[j].frame = frame + } + } + + operations.append(.UpdateLayout(index: itemIndex, layout: layout, apply: apply)) + case .System: + operations.append(.UpdateLayout(index: itemIndex, layout: layout, apply: apply)) + } + + break + } + } + } } private enum ListViewStateOperation { @@ -964,7 +1033,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) - self.displayLink.add(to: RunLoop.main(), forMode: RunLoopMode.commonModes.rawValue) + self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) if #available(iOS 10.0, *) { self.displayLink.preferredFramesPerSecond = 60 } @@ -1001,6 +1070,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { deinit { self.pauseAnimations() + self.displayLink.invalidate() } @objc func frictionSliderChanged(_ slider: UISlider) { @@ -1291,7 +1361,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.async(f) } }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in - if Thread.isMainThread() { + if Thread.isMainThread { if synchronous { completion(previousNode, layout, { previousNode.index = index @@ -1340,7 +1410,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: (ListViewDisplayedItemRange) -> Void = { _ in }) { - if deleteIndices.count == 0 && insertIndicesAndItems.count == 0 && scrollToItem == nil && updateSizeAndInsets == nil { + if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil { completion(self.immediateDisplayedItemRange()) return } @@ -1599,7 +1669,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in + self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in var updatedState = state var updatedOperations = operations @@ -1696,6 +1766,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let completion = inputCompletion let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None + if state.nodes.count > 1000 { + print("state.nodes.count > 1000") + } + while true { if self.items.count == 0 { completion(state, operations) @@ -1743,7 +1817,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, inputPreviousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { + private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { var state = inputState var operations = inputOperations var updateIndicesAndItems = updateIndicesAndItems @@ -1752,16 +1826,18 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { completion(state, operations) } else { var updateItem = updateIndicesAndItems[0] - for node in state.nodes { - if node.index == updateItem.index { + if let previousNode = previousNodes[updateItem.index] { + self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in + state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations) - - break - } + updateIndicesAndItems.remove(at: 0) + self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + }) + } else { + updateIndicesAndItems.remove(at: 0) + self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) } } - - assertionFailure() } private func referencePointForInsertionAtIndex(_ nodeIndex: Int) -> CGPoint { @@ -1996,7 +2072,13 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if abs(updatedApparentHeight - previousApparentHeight) > CGFloat(FLT_EPSILON) { node.apparentHeight = previousApparentHeight - node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in + if let node = node { + node.animateFrameTransition(progress) + } + }) + node.transitionOffset += previousApparentHeight - layout.size.height + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } } else { node.apparentHeight = updatedApparentHeight @@ -2393,7 +2475,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateVisibleItemsTransaction(completion: Void -> Void) { + private func updateVisibleItemsTransaction(completion: (Void) -> Void) { if self.items.count == 0 && self.itemNodes.count == 0 { completion() return @@ -2742,7 +2824,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) self.selectionTouchDelayTimer = timer - RunLoop.main().add(timer, forMode: RunLoopMode.commonModes) + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) super.touchesBegan(touches, with: event) @@ -2770,6 +2852,14 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return nil } + public func forEachItemNode(_ f: (ListViewItemNode) -> Void) { + for itemNode in self.itemNodes { + if itemNode.index != nil { + f(itemNode) + } + } + } + override public func touchesMoved(_ touches: Set, with event: UIEvent?) { self.touchesPosition = touches.first!.location(in: self.view) if let selectionTouchLocation = self.selectionTouchLocation { diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 8b55aabf7d..d63c5a7807 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -16,7 +16,7 @@ public final class ListViewTransactionQueue { if beginTransaction { transaction({ [weak self] in - if Thread.isMainThread() { + if Thread.isMainThread { if let strongSelf = self { strongSelf.endTransaction() } @@ -38,7 +38,7 @@ public final class ListViewTransactionQueue { if let nextTransaction = self.transactions.first { nextTransaction({ [weak self] in - if Thread.isMainThread() { + if Thread.isMainThread { if let strongSelf = self { strongSelf.endTransaction() } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 8b28fb7e2d..af0cda1dfd 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -120,6 +120,7 @@ public class NavigationBar: ASDisplayNode { } self.invalidateCalculatedLayout() + self.setNeedsLayout() } } private let titleNode: ASTextNode diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 336edf0886..11dab925ea 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -35,7 +35,7 @@ public class StatusBar: ASDisplayNode { private var proxyNode: StatusBarProxyNode? private var removeProxyNodeScheduled = false - override init() { + public override init() { super.init(viewBlock: { return UITracingLayerView() }, didLoad: nil) diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 75e178fcbe..9c8edc4894 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -257,7 +257,7 @@ class StatusBarProxyNode: ASDisplayNode { self.timer = Timer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in self?.updateItems() }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) - RunLoop.main().add(self.timer!, forMode: .commonModes) + RunLoop.main.add(self.timer!, forMode: .commonModes) } else { self.timer?.invalidate() self.timer = nil diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index b361beee46..006c010fb6 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -74,10 +74,20 @@ public extension CGSize { let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } + + public func multipliedByScreenScale() -> CGSize { + let scale = UIScreenScale + return CGSize(width: self.width * scale, height: self.height * scale) + } + + public func dividedByScreenScale() -> CGSize { + let scale = UIScreenScale + return CGSize(width: self.width / scale, height: self.height / scale) + } } public func assertNotOnMainThread(_ file: String = #file, line: Int = #line) { - assert(!Thread.isMainThread(), "\(file):\(line) running on main thread") + assert(!Thread.isMainThread, "\(file):\(line) running on main thread") } public extension UIImage { @@ -92,3 +102,31 @@ public extension UIImage { return result } } + +private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { + let view = UIView() + view.layer.contents = layer.contents + if let sublayers = layer.sublayers { + for sublayer in sublayers { + let subtree = makeSubtreeSnapshot(layer: sublayer) + if let subtree = subtree { + subtree.frame = sublayer.frame + view.addSubview(subtree) + } else { + return nil + } + } + } + return view +} + +public extension UIView { + public func snapshotContentTree() -> UIView? { + if let snapshot = makeSubtreeSnapshot(layer: self.layer) { + snapshot.frame = self.frame + return snapshot + } else { + return nil + } + } +} diff --git a/Display/UniversalTapRecognizer.swift b/Display/UniversalTapRecognizer.swift new file mode 100644 index 0000000000..47477fff9d --- /dev/null +++ b/Display/UniversalTapRecognizer.swift @@ -0,0 +1,44 @@ +import UIKit +import UIKit.UIGestureRecognizerSubclass + +private class TimerTargetWrapper: NSObject { + let f: () -> Void + + init(_ f: () -> Void) { + self.f = f + } + + @objc func timerEvent() { + self.f() + } +} + +class UniversalTapRecognizer: UITapGestureRecognizer { + private let tapMaxDelay: Double = 0.15 + + private var timer: Timer? + + deinit { + self.timer?.invalidate() + } + + override func reset() { + super.reset() + + self.timer?.invalidate() + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + let timer = Timer(timeInterval: self.tapMaxDelay, target: TimerTargetWrapper({ [weak self] in + if let strongSelf = self { + if strongSelf.state != .ended { + strongSelf.state = .failed + } + } + }), selector: #selector(TimerTargetWrapper.timerEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: .commonModes) + } +} diff --git a/Display/Window.swift b/Display/Window.swift index 01bc2e654f..04b2c4aa30 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -127,7 +127,7 @@ public class Window: UIWindow { super.rootViewController = WindowRootViewController() - self.statusBarChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main(), using: { [weak self] notification in + self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue().height ?? 20.0) @@ -136,7 +136,7 @@ public class Window: UIWindow { } }) - self.keyboardFrameChangeObserver = NotificationCenter.default().addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in + self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in if let strongSelf = self { let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue() ?? CGRect() let keyboardHeight = max(0.0, UIScreen.main().bounds.size.height - keyboardFrame.minY) @@ -163,10 +163,10 @@ public class Window: UIWindow { deinit { if let statusBarChangeObserver = self.statusBarChangeObserver { - NotificationCenter.default().removeObserver(statusBarChangeObserver) + NotificationCenter.default.removeObserver(statusBarChangeObserver) } if let keyboardFrameChangeObserver = self.keyboardFrameChangeObserver { - NotificationCenter.default().removeObserver(keyboardFrameChangeObserver) + NotificationCenter.default.removeObserver(keyboardFrameChangeObserver) } } From 2e501affef809e10ccaee3d53313ff594c53009c Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 9 Aug 2016 00:22:48 +0300 Subject: [PATCH 018/245] no message --- Display.xcodeproj/project.pbxproj | 4 -- .../xcschemes/xcschememanagement.plist | 2 +- Display/ASTransformLayerNode.swift | 2 +- Display/ActionSheetButtonItem.swift | 4 +- Display/ActionSheetButtonNode.swift | 2 +- Display/ActionSheetController.swift | 2 +- Display/ActionSheetItemGroupNode.swift | 4 +- Display/BarButtonItemWrapper.swift | 2 +- Display/Font.swift | 4 +- Display/GenerateImage.swift | 26 ++++++------- Display/ListView.swift | 12 +++--- Display/ListViewItemNode.swift | 2 +- Display/NavigationBackButtonNode.swift | 6 +-- Display/NavigationBar.swift | 37 +++++++++++++++++-- Display/NavigationButtonNode.swift | 10 ++--- Display/NavigationController.swift | 8 ++-- Display/NavigationTitleNode.swift | 4 +- Display/NavigationTransitionCoordinator.swift | 4 +- Display/PresentationContext.swift | 4 +- Display/RuntimeUtils.swift | 2 +- Display/StatusBarHostWindow.swift | 29 --------------- Display/StatusBarManager.swift | 10 ++--- Display/StatusBarProxyNode.swift | 8 ++-- ...ainedControllerTransitionCoordinator.swift | 22 +++++------ Display/TabBarNode.swift | 13 ++++--- Display/UIKitUtils.swift | 4 +- Display/UINavigationItem+Proxy.h | 3 ++ Display/UINavigationItem+Proxy.m | 36 +++++++++++++++--- Display/ViewController.swift | 6 +-- Display/Window.swift | 20 +++++----- 30 files changed, 159 insertions(+), 133 deletions(-) delete mode 100644 Display/StatusBarHostWindow.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 004d453011..769d03d1ab 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -64,7 +64,6 @@ D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; - D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; @@ -172,7 +171,6 @@ D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; - D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHostWindow.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; @@ -435,7 +433,6 @@ D07921AA1B6FC911005C23D9 /* Status Bar */ = { isa = PBXGroup; children = ( - D07921AB1B6FC92B005C23D9 /* StatusBarHostWindow.swift */, D0078A671C92B21400DF6D92 /* StatusBar.swift */, D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */, D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */, @@ -636,7 +633,6 @@ files = ( D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */, D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, - D07921AC1B6FC92B005C23D9 /* StatusBarHostWindow.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 687ccdfc6e..d32d4398d1 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayTests.xcscheme orderHint - 18 + 19 SuppressBuildableAutocreation diff --git a/Display/ASTransformLayerNode.swift b/Display/ASTransformLayerNode.swift index 14c16e15c8..72c5ef9729 100644 --- a/Display/ASTransformLayerNode.swift +++ b/Display/ASTransformLayerNode.swift @@ -26,7 +26,7 @@ class ASTransformLayer: CATransformLayer { } class ASTransformView: UIView { - override class func layerClass() -> AnyClass { + override class var layerClass: AnyClass { return ASTransformLayer.self } } diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index ad1de9c38e..b0435d21f6 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -17,6 +17,8 @@ public class ActionSheetButtonItem: ActionSheetItem { } public func node() -> ActionSheetItemNode { - return ActionSheetButtonNode(title: AttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: self.color == .accent ? UIColor(0x1195f2) : UIColor.red()), action: self.action) + let textColorIsAccent = self.color == ActionSheetButtonColor.accent + let textColor = textColorIsAccent ? UIColor(0x1195f2) : UIColor.red + return ActionSheetButtonNode(title: NSAttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: textColor), action: self.action) } } diff --git a/Display/ActionSheetButtonNode.swift b/Display/ActionSheetButtonNode.swift index 40b1aaea14..e740f94b43 100644 --- a/Display/ActionSheetButtonNode.swift +++ b/Display/ActionSheetButtonNode.swift @@ -10,7 +10,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { private let label: UILabel private var calculatedLabelSize: CGSize? - public init(title: AttributedString, action: () -> Void) { + public init(title: NSAttributedString, action: () -> Void) { self.action = action self.button = HighlightTrackingButton() diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 149d9957fe..afb7038bb6 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -37,7 +37,7 @@ public class ActionSheetController: ViewController { public func setItemGroups(_ groups: [ActionSheetItemGroup]) { self.groups = groups - if self.isViewLoaded() { + if self.isViewLoaded { self.actionSheetNode.setGroups(groups) } } diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift index a51be83e76..9ac3c0230c 100644 --- a/Display/ActionSheetItemGroupNode.swift +++ b/Display/ActionSheetItemGroupNode.swift @@ -66,13 +66,13 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { func updateItemNodes(_ nodes: [ActionSheetItemNode], leadingVisibleNodeCount: CGFloat = 1000.0) { for node in self.itemNodes { - if !nodes.contains({ $0 === node }) { + if !nodes.contains(where: { $0 === node }) { node.removeFromSupernode() } } for node in nodes { - if !self.itemNodes.contains({ $0 === node }) { + if !self.itemNodes.contains(where: { $0 === node }) { self.scrollView.addSubnode(node) } } diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift index de62a54dee..02b62f6aa3 100644 --- a/Display/BarButtonItemWrapper.swift +++ b/Display/BarButtonItemWrapper.swift @@ -24,7 +24,7 @@ internal class BarButtonItemWrapper { self.parentNode.addSubnode(self.buttonNode) self.setEnabledListenerKey = barButtonItem.addSetEnabledListener({ [weak self] enabled in - self?.buttonNode.isEnabled = enabled.boolValue + self?.buttonNode.isEnabled = enabled return }) diff --git a/Display/Font.swift b/Display/Font.swift index 801e389c73..862078c6ac 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -11,8 +11,8 @@ public struct Font { } } -public extension AttributedString { - convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black()) { +public extension NSAttributedString { + convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black) { self.init(string: string, attributes: [NSFontAttributeName: font, NSForegroundColorAttributeName as String: textColor]) } } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 5d58c6c413..031843fd81 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -2,7 +2,7 @@ import Foundation import UIKit let deviceColorSpace = CGColorSpaceCreateDeviceRGB() -let deviceScale = UIScreen.main().scale +let deviceScale = UIScreen.main.scale public func generateImage(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { let scale = deviceScale @@ -50,7 +50,7 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) return nil } - context.scale(x: scale, y: scale) + context.scaleBy(x: scale, y: scale) contextGenerator(size, context) @@ -73,7 +73,7 @@ public func generateFilledCircleImage(radius: CGFloat, color: UIColor?, backgrou if let color = color { context.setFillColor(color.cgColor) } else { - context.setFillColor(UIColor.clear().cgColor) + context.setFillColor(UIColor.clear.cgColor) context.setBlendMode(.copy) } context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) @@ -110,28 +110,28 @@ public class DrawingContext { public func withContext(_ f: @noescape(CGContext) -> ()) { if self._context == nil { if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { - c.scale(x: scale, y: scale) + c.scaleBy(x: scale, y: scale) self._context = c } } if let _context = self._context { - _context.translate(x: self.size.width / 2.0, y: self.size.height / 2.0) - _context.scale(x: 1.0, y: -1.0) - _context.translate(x: -self.size.width / 2.0, y: -self.size.height / 2.0) + _context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0) + _context.scaleBy(x: 1.0, y: -1.0) + _context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0) f(_context) - _context.translate(x: self.size.width / 2.0, y: self.size.height / 2.0) - _context.scale(x: 1.0, y: -1.0) - _context.translate(x: -self.size.width / 2.0, y: -self.size.height / 2.0) + _context.translateBy(x: self.size.width / 2.0, y: self.size.height / 2.0) + _context.scaleBy(x: 1.0, y: -1.0) + _context.translateBy(x: -self.size.width / 2.0, y: -self.size.height / 2.0) } } public func withFlippedContext(_ f: @noescape(CGContext) -> ()) { if self._context == nil { if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { - c.scale(x: scale, y: scale) + c.scaleBy(x: scale, y: scale) self._context = c } } @@ -177,7 +177,7 @@ public class DrawingContext { let colorValue = pixel.pointee return UIColor(UInt32(colorValue)) } else { - return UIColor.clear() + return UIColor.clear } } @@ -231,7 +231,7 @@ public class DrawingContext { } } -public enum ParsingError: ErrorProtocol { +public enum ParsingError: Error { case Generic } diff --git a/Display/ListView.swift b/Display/ListView.swift index 2a9a64e5fa..92b9b01f72 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -323,7 +323,7 @@ private struct ListViewState { var maxY: CGFloat = 0.0 for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame - frame.offsetInPlace(dx: 0.0, dy: offset) + frame = frame.offsetBy(dx: 0.0, dy: offset) self.nodes[i].frame = frame minY = min(minY, frame.minY) @@ -338,7 +338,7 @@ private struct ListViewState { if abs(additionalOffset) > CGFloat(FLT_EPSILON) { for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame - frame.offsetInPlace(dx: 0.0, dy: additionalOffset) + frame = frame.offsetBy(dx: 0.0, dy: additionalOffset) self.nodes[i].frame = frame } } @@ -356,7 +356,7 @@ private struct ListViewState { if abs(offset) > CGFloat(FLT_EPSILON) { for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame - frame.offsetInPlace(dx: 0.0, dy: offset) + frame = frame.offsetBy(dx: 0.0, dy: offset) self.nodes[i].frame = frame } } @@ -882,7 +882,7 @@ private final class ListViewBackingLayer: CALayer { private final class ListViewBackingView: UIView { weak var target: ASDisplayNode? - override class func layerClass() -> AnyClass { + override class var layerClass: AnyClass { return ListViewBackingLayer.self } @@ -1464,12 +1464,12 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } state.fixScrollPostition(self.items.count) - let sortedDeleteIndices = deleteIndices.sorted(isOrderedBefore: {$0.index < $1.index}) + let sortedDeleteIndices = deleteIndices.sorted(by: {$0.index < $1.index}) for deleteItem in sortedDeleteIndices.reversed() { self.items.remove(at: deleteItem.index) } - let sortedIndicesAndItems = insertIndicesAndItems.sorted(isOrderedBefore: { $0.index < $1.index }) + let sortedIndicesAndItems = insertIndicesAndItems.sorted(by: { $0.index < $1.index }) if self.items.count == 0 { if sortedIndicesAndItems[0].index != 0 { fatalError("deleteAndInsertItems: invalid insert into empty list") diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 7e2010d469..5eb8f8eabe 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -27,7 +27,7 @@ struct ListViewItemSpring { } private class ListViewItemView: UIView { - override class func layerClass() -> AnyClass { + override class var layerClass: AnyClass { return ASTransformLayer.self } } diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 7aca21f201..780a0b8d67 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -9,7 +9,7 @@ public class NavigationBackButtonNode: ASControlNode { private func attributesForCurrentState() -> [String : AnyObject] { return [ NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray() + NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray ] } @@ -25,14 +25,14 @@ public class NavigationBackButtonNode: ASControlNode { } set(value) { self._text = value - self.label.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) + self.label.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) self.invalidateCalculatedLayout() } } var color: UIColor = UIColor(0x1195f2) { didSet { - self.label.attributedString = AttributedString(string: self._text, attributes: self.attributesForCurrentState()) + self.label.attributedString = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) } } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index af0cda1dfd..ad5d920054 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -32,10 +32,10 @@ private func backArrowImage(color: UIColor) -> UIImage? { } public class NavigationBar: ASDisplayNode { - public var foregroundColor: UIColor = UIColor.black() { + public var foregroundColor: UIColor = UIColor.black { didSet { if let title = self.title { - self.titleNode.attributedText = AttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) } } } @@ -66,6 +66,7 @@ public class NavigationBar: ASDisplayNode { private let clippingNode: ASDisplayNode private var itemTitleListenerKey: Int? + private var itemTitleViewListenerKey: Int? private var itemLeftButtonListenerKey: Int? private var _item: UINavigationItem? var item: UINavigationItem? { @@ -94,6 +95,13 @@ public class NavigationBar: ASDisplayNode { } } + self.titleView = item.titleView + self.itemTitleViewListenerKey = item.addSetTitleViewListener { [weak self] titleView in + if let strongSelf = self { + strongSelf.titleView = titleView + } + } + self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] _, _ in if let strongSelf = self { strongSelf.updateLeftButton() @@ -111,7 +119,7 @@ public class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = AttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -123,6 +131,22 @@ public class NavigationBar: ASDisplayNode { self.setNeedsLayout() } } + + private var titleView: UIView? { + didSet { + if let oldValue = oldValue { + oldValue.removeFromSuperview() + } + + if let titleView = self.titleView { + self.clippingNode.view.addSubview(titleView) + } + + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + private let titleNode: ASTextNode var previousItemListenerKey: Int? @@ -414,13 +438,18 @@ public class NavigationBar: ASDisplayNode { } } + if let titleView = self.titleView { + let titleViewSize = CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight) + titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleViewSize) + } + //self.effectView.frame = self.bounds } public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? { if let title = self.title { let node = ASTextNode() - node.attributedText = AttributedString(string: title, font: Font.medium(17.0), textColor: foregroundColor) + node.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: foregroundColor) return node } else { return nil diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 5e45e9b88a..15f19a0a2d 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -9,7 +9,7 @@ public class NavigationButtonNode: ASTextNode { private func attributesForCurrentState() -> [String : AnyObject] { return [ NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray() + NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray ] } @@ -21,14 +21,14 @@ public class NavigationButtonNode: ASTextNode { set(value) { _text = value - self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } public var color: UIColor = UIColor(0x1195f2) { didSet { if let text = self._text { - self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } @@ -42,7 +42,7 @@ public class NavigationButtonNode: ASTextNode { if _bold != value { _bold = value - self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } @@ -129,7 +129,7 @@ public class NavigationButtonNode: ASTextNode { if self.isEnabled != value { super.isEnabled = value - self.attributedString = AttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 98475c4c24..f7f46264d3 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit import SwiftSignalKit private class NavigationControllerView: UIView { - override class func layerClass() -> AnyClass { + override class var layerClass: AnyClass { return CATracingLayer.self } } @@ -60,7 +60,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl } public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - if !self.isViewLoaded() { + if !self.isViewLoaded { self.loadView() } self.containerLayout = layout @@ -253,7 +253,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl } if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! { - if self.viewControllers.contains({ $0 === viewControllers.last }) { + if self.viewControllers.contains(where: { $0 === viewControllers.last }) { let bottomController = viewControllers.last! as UIViewController let topController = self.viewControllers.last! as UIViewController @@ -323,7 +323,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl }) } } else { - if let topController = self.viewControllers.last where topController.isViewLoaded() { + if let topController = self.viewControllers.last where topController.isViewLoaded { topController.navigation_setNavigationController(nil) topController.view.removeFromSuperview() } diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 32449d08d0..4ada590a0b 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -15,7 +15,7 @@ public class NavigationTitleNode: ASDisplayNode { } } - public var color: UIColor = UIColor.black() { + public var color: UIColor = UIColor.black { didSet { self.setText(self._text) } @@ -42,7 +42,7 @@ public class NavigationTitleNode: ASDisplayNode { var titleAttributes = [String : AnyObject]() titleAttributes[NSFontAttributeName] = UIFont.boldSystemFont(ofSize: 17.0) titleAttributes[NSForegroundColorAttributeName] = self.color - let titleString = AttributedString(string: text as String, attributes: titleAttributes) + let titleString = NSAttributedString(string: text as String, attributes: titleAttributes) self.label.attributedString = titleString self.invalidateCalculatedLayout() } diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 7785f7f4d0..2e650df56e 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -8,7 +8,7 @@ enum NavigationTransition { private let shadowWidth: CGFloat = 16.0 private func generateShadow() -> UIImage? { - return UIImage(named: "NavigationShadow", in: Bundle(for: NavigationBackButtonNode.self), compatibleWith: nil)?.precomposed().resizableImage(withCapInsets: UIEdgeInsetsZero, resizingMode: .tile) + return UIImage(named: "NavigationShadow", in: Bundle(for: NavigationBackButtonNode.self), compatibleWith: nil)?.precomposed().resizableImage(withCapInsets: UIEdgeInsets(), resizingMode: .tile) } private let shadowImage = generateShadow() @@ -51,7 +51,7 @@ class NavigationTransitionCoordinator { self.topNavigationBar = topNavigationBar self.bottomNavigationBar = bottomNavigationBar self.dimView = UIView() - self.dimView.backgroundColor = UIColor.black() + self.dimView.backgroundColor = UIColor.black self.shadowView = UIImageView(image: shadowImage) if let topNavigationBar = topNavigationBar, bottomNavigationBar = bottomNavigationBar { diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 9c9b6dadb9..589fba9030 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -46,7 +46,7 @@ final class PresentationContext { self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in if let strongSelf = self { - if strongSelf.controllers.contains({ $0 === controller }) { + if strongSelf.controllers.contains(where: { $0 === controller }) { return } @@ -131,7 +131,7 @@ final class PresentationContext { func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for controller in self.controllers { - if controller.isViewLoaded() { + if controller.isViewLoaded { if let result = controller.view.hitTest(point, with: event) { return result } diff --git a/Display/RuntimeUtils.swift b/Display/RuntimeUtils.swift index 54cd75f979..7ae936e860 100644 --- a/Display/RuntimeUtils.swift +++ b/Display/RuntimeUtils.swift @@ -2,7 +2,7 @@ import Foundation import UIKit private let systemVersion = { () -> (Int, Int) in - let string = UIDevice.current().systemVersion as NSString + let string = UIDevice.current.systemVersion as NSString var minor = 0 let range = string.range(of: ".") if range.location != NSNotFound { diff --git a/Display/StatusBarHostWindow.swift b/Display/StatusBarHostWindow.swift deleted file mode 100644 index 02dd884e7a..0000000000 --- a/Display/StatusBarHostWindow.swift +++ /dev/null @@ -1,29 +0,0 @@ -import Foundation -import UIKit - -private class StatusBarHostWindowController: UIViewController { - override func preferredStatusBarStyle() -> UIStatusBarStyle { - return UIStatusBarStyle.default - } - - override func prefersStatusBarHidden() -> Bool { - return false - } - - override func shouldAutorotate() -> Bool { - return true - } -} - -public class StatusBarHostWindow: UIWindow { - public init() { - super.init(frame: CGRect()) - - self.windowLevel = 10000.0 - self.rootViewController = StatusBarHostWindowController() - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 2e3df48206..043b27bbfb 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -31,7 +31,7 @@ private func optimizeMappedSurface(_ surface: MappedStatusBarSurface) -> MappedS return surface } } - let size = UIApplication.shared().statusBarFrame.size + let size = UIApplication.shared.statusBarFrame.size return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) } else { return surface @@ -124,7 +124,7 @@ class StatusBarManager { for surface in previousSurfaces { for statusBar in surface.statusBars { - if !visibleStatusBars.contains({$0 === statusBar}) { + if !visibleStatusBars.contains(where: {$0 === statusBar}) { statusBar.removeProxyNode() } } @@ -132,7 +132,7 @@ class StatusBarManager { for surface in self.surfaces { for statusBar in surface.statusBars { - if !visibleStatusBars.contains({$0 === statusBar}) { + if !visibleStatusBars.contains(where: {$0 === statusBar}) { statusBar.removeProxyNode() } } @@ -144,8 +144,8 @@ class StatusBarManager { if let globalStatusBar = globalStatusBar { let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent - if UIApplication.shared().statusBarStyle != statusBarStyle { - UIApplication.shared().setStatusBarStyle(statusBarStyle, animated: false) + if UIApplication.shared.statusBarStyle != statusBarStyle { + UIApplication.shared.setStatusBarStyle(statusBarStyle, animated: false) } StatusBarUtils.statusBarWindow()!.alpha = globalStatusBar.1 } else { diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 9c8edc4894..7907c2537f 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -43,16 +43,16 @@ private class StatusBarItemNode: ASDisplayNode { if let contents = sublayer.contents where CFGetTypeID(contents) == CGImage.typeID { let image = contents as! CGImage context.withFlippedContext { c in - c.translate(x: origin.x, y: origin.y) + c.translateBy(x: origin.x, y: origin.y) c.draw(in: CGRect(origin: CGPoint(), size: context.size), image: image) - c.translate(x: -origin.x, y: -origin.y) + c.translateBy(x: -origin.x, y: -origin.y) } } else { context.withContext { c in UIGraphicsPushContext(c) - c.translate(x: origin.x, y: origin.y) + c.translateBy(x: origin.x, y: origin.y) sublayer.render(in: c) - c.translate(x: -origin.x, y: -origin.y) + c.translateBy(x: -origin.x, y: -origin.y) UIGraphicsPopContext() } } diff --git a/Display/SystemContainedControllerTransitionCoordinator.swift b/Display/SystemContainedControllerTransitionCoordinator.swift index ea1034f5d6..69c6025858 100644 --- a/Display/SystemContainedControllerTransitionCoordinator.swift +++ b/Display/SystemContainedControllerTransitionCoordinator.swift @@ -1,41 +1,41 @@ import UIKit final class SystemContainedControllerTransitionCoordinator:NSObject, UIViewControllerTransitionCoordinator { - public func isAnimated() -> Bool { + public var isAnimated: Bool { return false } - public func presentationStyle() -> UIModalPresentationStyle { + public var presentationStyle: UIModalPresentationStyle { return .fullScreen } - public func initiallyInteractive() -> Bool { + public var initiallyInteractive: Bool { return false } public let isInterruptible: Bool = false - public func isInteractive() -> Bool { + public var isInteractive: Bool { return false } - public func isCancelled() -> Bool { + public var isCancelled: Bool { return false } - public func transitionDuration() -> TimeInterval { + public var transitionDuration: TimeInterval { return 0.6 } - public func percentComplete() -> CGFloat { + public var percentComplete: CGFloat { return 0.0 } - public func completionVelocity() -> CGFloat { + public var completionVelocity: CGFloat { return 0.0 } - public func completionCurve() -> UIViewAnimationCurve { + public var completionCurve: UIViewAnimationCurve { return .easeInOut } @@ -47,11 +47,11 @@ final class SystemContainedControllerTransitionCoordinator:NSObject, UIViewContr return nil } - public func containerView() -> UIView { + public var containerView: UIView { return UIView() } - public func targetTransform() -> CGAffineTransform { + public var targetTransform: CGAffineTransform { return CGAffineTransform.identity } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 9f314aff8b..387d91eaa5 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -2,7 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit -private let separatorHeight: CGFloat = 1.0 / UIScreen.main().scale +private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColor) -> UIImage { let font = Font.regular(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil).size @@ -24,10 +24,10 @@ private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColo if let image = image { let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) context.saveGState() - context.translate(x: imageRect.midX, y: imageRect.midY) - context.scale(x: 1.0, y: -1.0) - context.translate(x: -imageRect.midX, y: -imageRect.midY) - context.clipToMask(imageRect, mask: image.cgImage!) + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) context.setFillColor(tintColor.cgColor) context.fill(imageRect) context.restoreGState() @@ -140,7 +140,8 @@ class TabBarNode: ASDisplayNode { let node = self.tabBarNodes[i] node.measure(CGSize(width: internalWidth, height: size.height)) - node.frame = CGRect(origin: CGPoint(x: floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - node.calculatedSize.width / 2.0), y: 4.0), size: node.calculatedSize) + let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - node.calculatedSize.width / 2.0) + node.frame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: node.calculatedSize) } } } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 006c010fb6..0e9249d212 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -26,7 +26,7 @@ private func dumpLayers(_ layer: CALayer, indent: String = "") { } } -public let UIScreenScale = UIScreen.main().scale +public let UIScreenScale = UIScreen.main.scale public func floorToScreenPixels(_ value: CGFloat) -> CGFloat { return floor(value * UIScreenScale) / UIScreenScale } @@ -96,7 +96,7 @@ public extension UIImage { self.draw(at: CGPoint()) let result = UIGraphicsGetImageFromCurrentImageContext()! UIGraphicsEndImageContext() - if !UIEdgeInsetsEqualToEdgeInsets(self.capInsets, UIEdgeInsetsZero) { + if !UIEdgeInsetsEqualToEdgeInsets(self.capInsets, UIEdgeInsets()) { return result.resizableImage(withCapInsets: self.capInsets, resizingMode: self.resizingMode) } return result diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 0c2a5bd37a..4fd81f16e2 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -1,12 +1,15 @@ #import typedef void (^UINavigationItemSetTitleListener)(NSString *); +typedef void (^UINavigationItemSetTitleViewListener)(UIView *); typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL); @interface UINavigationItem (Proxy) - (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; - (void)removeSetTitleListener:(NSInteger)key; +- (NSInteger)addSetTitleViewListener:(UINavigationItemSetTitleViewListener)listener; +- (void)removeSetTitleViewListener:(NSInteger)key; - (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; - (void)removeSetLeftBarButtonItemListener:(NSInteger)key; - (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index d6944828dc..2a8b1a1b5c 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -4,6 +4,7 @@ #import "RuntimeUtils.h" static const void *setTitleListenerBagKey = &setTitleListenerBagKey; +static const void *setTitleViewListenerBagKey = &setTitleViewListenerBagKey; static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemListenerBagKey; static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; @@ -15,6 +16,7 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL dispatch_once(&onceToken, ^ { [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_ac91f40f_setTitle:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitleView:) newSelector:@selector(_ac91f40f_setTitleView:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; }); @@ -24,18 +26,25 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [self _ac91f40f_setTitle:title]; - [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) - { + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { listener(title); }]; } +- (void)_ac91f40f_setTitleView:(UIView *)titleView +{ + [self _ac91f40f_setTitleView:titleView]; + + [(NSBag *)[self associatedObjectForKey:setTitleViewListenerBagKey] enumerateItems:^(UINavigationItemSetTitleViewListener listener) { + listener(titleView); + }]; +} + - (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated { [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; - [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) - { + [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { listener(leftBarButtonItem, animated); }]; } @@ -44,8 +53,7 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; - [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) - { + [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { listener(rightBarButtonItem, animated); }]; } @@ -66,6 +74,22 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key]; } +- (NSInteger)addSetTitleViewListener:(UINavigationItemSetTitleViewListener)listener +{ + NSBag *bag = [self associatedObjectForKey:setTitleViewListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setTitleViewListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetTitleViewListener:(NSInteger)key +{ + [(NSBag *)[self associatedObjectForKey:setTitleViewListenerBagKey] removeItem:key]; +} + - (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener { NSBag *bag = [self associatedObjectForKey:setLeftBarButtonItemListenerBagKey]; diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 84d1952aaa..735441cdfc 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -43,7 +43,7 @@ import SwiftSignalKit private var scrollToTopView: ScrollToTopView? public var scrollToTop: (() -> Void)? { didSet { - if self.isViewLoaded() { + if self.isViewLoaded { self.updateScrollToTopView() } } @@ -92,7 +92,7 @@ import SwiftSignalKit public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.containerLayout = layout - if !self.isViewLoaded() { + if !self.isViewLoaded { self.loadView() } self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) @@ -133,7 +133,7 @@ import SwiftSignalKit } public func requestLayout(transition: ContainedViewLayoutTransition) { - if self.isViewLoaded() { + if self.isViewLoaded { self.containerLayoutUpdated(self.containerLayout, transition: transition) } } diff --git a/Display/Window.swift b/Display/Window.swift index 04b2c4aa30..536d0b0979 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -2,11 +2,11 @@ import Foundation import AsyncDisplayKit private class WindowRootViewController: UIViewController { - override func preferredStatusBarStyle() -> UIStatusBarStyle { + override var preferredStatusBarStyle: UIStatusBarStyle { return .default } - override func prefersStatusBarHidden() -> Bool { + override var prefersStatusBarHidden: Bool { return false } } @@ -84,8 +84,8 @@ private struct UpdatingLayout { } } -private let orientationChangeDuration: Double = UIDevice.current().userInterfaceIdiom == .pad ? 0.4 : 0.3 -private let statusBarHiddenInLandscape: Bool = UIDevice.current().userInterfaceIdiom == .phone +private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3 +private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout { return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) @@ -108,12 +108,12 @@ public class Window: UIWindow { private var statusBarHidden = false public convenience init() { - self.init(frame: UIScreen.main().bounds) + self.init(frame: UIScreen.main.bounds) } public override init(frame: CGRect) { self.statusBarManager = StatusBarManager() - self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: UIApplication.shared().statusBarFrame.size.height, inputHeight: 0.0) + self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: UIApplication.shared.statusBarFrame.size.height, inputHeight: 0.0) self.presentationContext = PresentationContext() super.init(frame: frame) @@ -129,7 +129,7 @@ public class Window: UIWindow { self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { - let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue().height ?? 20.0) + let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? 20.0) let transition: ContainedViewLayoutTransition = .animated(duration: 0.35, curve: .easeInOut) strongSelf.updateLayout { $0.update(statusBarHeight: statusBarHeight, transition: transition, overrideTransition: false) } @@ -138,8 +138,8 @@ public class Window: UIWindow { self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in if let strongSelf = self { - let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue() ?? CGRect() - let keyboardHeight = max(0.0, UIScreen.main().bounds.size.height - keyboardFrame.minY) + let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect() + let keyboardHeight = max(0.0, UIScreen.main.bounds.size.height - keyboardFrame.minY) var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 if duration > DBL_EPSILON { duration = 0.5 @@ -316,7 +316,7 @@ public class Window: UIWindow { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil if updatingLayout.layout != self.windowLayout { - var statusBarHeight = UIApplication.shared().statusBarFrame.size.height + var statusBarHeight = UIApplication.shared.statusBarFrame.size.height var statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { statusBarHeight = 0.0 From d403d674e1a503027ea8d8d7f1e73dac6708de33 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 16 Aug 2016 14:05:28 +0300 Subject: [PATCH 019/245] no message --- .../xcschemes/xcschememanagement.plist | 2 +- Display/CAAnimationUtils.swift | 2 +- Display/ContainableController.swift | 11 +- Display/ContainerViewLayout.swift | 2 +- Display/ListView.swift | 194 ++++++++++++------ Display/ListViewItem.swift | 5 + Display/ListViewItemNode.swift | 26 ++- Display/TabBarController.swift | 8 +- Display/UIKitUtils.swift | 2 +- Display/ViewController.swift | 20 ++ 10 files changed, 201 insertions(+), 71 deletions(-) diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index d32d4398d1..c29dd981e6 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayTests.xcscheme orderHint - 19 + 20 SuppressBuildableAutocreation diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 4322e2bc7e..92bf148c4e 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -107,7 +107,7 @@ public extension CALayer { self.add(animation, forKey: key) } - public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { + public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 40165cdf5c..fc6451e52e 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -23,14 +23,21 @@ public enum ContainedViewLayoutTransition { } public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect) { + func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) { switch self { case .immediate: node.frame = frame + if let completion = completion { + completion(true) + } case let .animated(duration, curve): let previousFrame = node.frame node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction) + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) } } } diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index adf8972412..a1f66c887c 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -40,7 +40,7 @@ public struct ContainerViewLayout: Equatable { insets.top += statusBarHeight } if let inputHeight = self.inputHeight where options.contains(.input) { - insets.bottom += inputHeight + insets.bottom = max(inputHeight, insets.bottom) } return insets } diff --git a/Display/ListView.swift b/Display/ListView.swift index 92b9b01f72..33bed43bcd 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1470,7 +1470,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } let sortedIndicesAndItems = insertIndicesAndItems.sorted(by: { $0.index < $1.index }) - if self.items.count == 0 { + if self.items.count == 0 && !sortedIndicesAndItems.isEmpty { if sortedIndicesAndItems[0].index != 0 { fatalError("deleteAndInsertItems: invalid insert into empty list") } @@ -2234,13 +2234,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.snapToBounds(scrollToItem != nil) + self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) + self.updateFloatingAccessoryNodes(animated: animated, currentTimestamp: timestamp) + if let scrollToItem = scrollToItem where scrollToItem.animated { - /*for itemNode in self.itemNodes { - print("item \(itemNode.index) frame \(itemNode.frame)") - }*/ - - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) - if self.itemNodes.count != 0 { var offset: CGFloat? @@ -2327,7 +2324,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { completion() } else { - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) if animated { self.setNeedsAnimations() } @@ -2375,8 +2371,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { node.accessoryItemNode?.removeFromSupernode() node.accessoryItemNode = nil - node.accessoryHeaderItemNode?.removeFromSupernode() - node.accessoryHeaderItemNode = nil + node.headerAccessoryItemNode?.removeFromSupernode() + node.headerAccessoryItemNode = nil } private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { @@ -2385,68 +2381,144 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { for itemNode in self.itemNodes { index += 1 - if let itemNodeIndex = itemNode.index { - if let accessoryItem = self.items[itemNodeIndex].accessoryItem { - let previousItem: ListViewItem? = itemNodeIndex == 0 ? nil : self.items[itemNodeIndex - 1] - let previousAccessoryItem = previousItem?.accessoryItem - - if (previousAccessoryItem == nil || !previousAccessoryItem!.isEqualToItem(accessoryItem)) { - if itemNode.accessoryItemNode == nil { - var didStealAccessoryNode = false - if index != count - 1 { - for i in index + 1 ..< count { - let nextItemNode = self.itemNodes[i] - if let nextItemNodeIndex = nextItemNode.index { - let nextItem = self.items[nextItemNodeIndex] - if let nextAccessoryItem = nextItem.accessoryItem where nextAccessoryItem.isEqualToItem(accessoryItem) { - if let nextAccessoryItemNode = nextItemNode.accessoryItemNode { - didStealAccessoryNode = true - - var previousAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin - let previousParentOrigin = nextItemNode.frame.origin - previousAccessoryItemNodeOrigin.x += previousParentOrigin.x - previousAccessoryItemNodeOrigin.y += previousParentOrigin.y - previousAccessoryItemNodeOrigin.y -= nextItemNode.bounds.origin.y - previousAccessoryItemNodeOrigin.y -= nextAccessoryItemNode.transitionOffset.y - nextAccessoryItemNode.transitionOffset = CGPoint() - - nextAccessoryItemNode.removeFromSupernode() - itemNode.addSubnode(nextAccessoryItemNode) - itemNode.accessoryItemNode = nextAccessoryItemNode - self.itemNodes[i].accessoryItemNode = nil - - var updatedAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin - let updatedParentOrigin = itemNode.frame.origin - updatedAccessoryItemNodeOrigin.x += updatedParentOrigin.x - updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y - updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y - - let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height - - nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) - } - } else { - break + guard let itemNodeIndex = itemNode.index else { + continue + } + + if let accessoryItem = self.items[itemNodeIndex].accessoryItem { + let previousItem: ListViewItem? = itemNodeIndex == 0 ? nil : self.items[itemNodeIndex - 1] + let previousAccessoryItem = previousItem?.accessoryItem + + if (previousAccessoryItem == nil || !previousAccessoryItem!.isEqualToItem(accessoryItem)) { + if itemNode.accessoryItemNode == nil { + var didStealAccessoryNode = false + if index != count - 1 { + for i in index + 1 ..< count { + let nextItemNode = self.itemNodes[i] + if let nextItemNodeIndex = nextItemNode.index { + let nextItem = self.items[nextItemNodeIndex] + if let nextAccessoryItem = nextItem.accessoryItem where nextAccessoryItem.isEqualToItem(accessoryItem) { + if let nextAccessoryItemNode = nextItemNode.accessoryItemNode { + didStealAccessoryNode = true + + var previousAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin + let previousParentOrigin = nextItemNode.frame.origin + previousAccessoryItemNodeOrigin.x += previousParentOrigin.x + previousAccessoryItemNodeOrigin.y += previousParentOrigin.y + previousAccessoryItemNodeOrigin.y -= nextItemNode.bounds.origin.y + previousAccessoryItemNodeOrigin.y -= nextAccessoryItemNode.transitionOffset.y + nextAccessoryItemNode.transitionOffset = CGPoint() + + nextAccessoryItemNode.removeFromSupernode() + itemNode.addSubnode(nextAccessoryItemNode) + itemNode.accessoryItemNode = nextAccessoryItemNode + self.itemNodes[i].accessoryItemNode = nil + + var updatedAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin + let updatedParentOrigin = itemNode.frame.origin + updatedAccessoryItemNodeOrigin.x += updatedParentOrigin.x + updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y + updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y + + let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height + + nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) } + } else { + break } } } - - if !didStealAccessoryNode { - let accessoryNode = accessoryItem.node() - itemNode.addSubnode(accessoryNode) - itemNode.accessoryItemNode = accessoryNode + } + + if !didStealAccessoryNode { + let accessoryNode = accessoryItem.node() + itemNode.addSubnode(accessoryNode) + itemNode.accessoryItemNode = accessoryNode + } + } + } else { + itemNode.accessoryItemNode?.removeFromSupernode() + itemNode.accessoryItemNode = nil + } + } + + if let headerAccessoryItem = self.items[itemNodeIndex].headerAccessoryItem { + let previousItem: ListViewItem? = itemNodeIndex == 0 ? nil : self.items[itemNodeIndex - 1] + let previousHeaderAccessoryItem = previousItem?.headerAccessoryItem + + if (previousHeaderAccessoryItem == nil || !previousHeaderAccessoryItem!.isEqualToItem(headerAccessoryItem)) { + if itemNode.headerAccessoryItemNode == nil { + var didStealHeaderAccessoryNode = false + if index != count - 1 { + for i in index + 1 ..< count { + let nextItemNode = self.itemNodes[i] + if let nextItemNodeIndex = nextItemNode.index { + let nextItem = self.items[nextItemNodeIndex] + if let nextHeaderAccessoryItem = nextItem.headerAccessoryItem where nextHeaderAccessoryItem.isEqualToItem(headerAccessoryItem) { + if let nextHeaderAccessoryItemNode = nextItemNode.headerAccessoryItemNode { + didStealHeaderAccessoryNode = true + + var previousHeaderAccessoryItemNodeOrigin = nextHeaderAccessoryItemNode.frame.origin + let previousParentOrigin = nextItemNode.frame.origin + previousHeaderAccessoryItemNodeOrigin.x += previousParentOrigin.x + previousHeaderAccessoryItemNodeOrigin.y += previousParentOrigin.y + previousHeaderAccessoryItemNodeOrigin.y -= nextItemNode.bounds.origin.y + previousHeaderAccessoryItemNodeOrigin.y -= nextHeaderAccessoryItemNode.transitionOffset.y + nextHeaderAccessoryItemNode.transitionOffset = CGPoint() + + nextHeaderAccessoryItemNode.removeFromSupernode() + itemNode.addSubnode(nextHeaderAccessoryItemNode) + itemNode.headerAccessoryItemNode = nextHeaderAccessoryItemNode + self.itemNodes[i].headerAccessoryItemNode = nil + + var updatedHeaderAccessoryItemNodeOrigin = nextHeaderAccessoryItemNode.frame.origin + let updatedParentOrigin = itemNode.frame.origin + updatedHeaderAccessoryItemNodeOrigin.x += updatedParentOrigin.x + updatedHeaderAccessoryItemNodeOrigin.y += updatedParentOrigin.y + updatedHeaderAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y + + let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height + + nextHeaderAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedHeaderAccessoryItemNodeOrigin.y - previousHeaderAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) + } + } else { + break + } + } } } - } else { - itemNode.accessoryItemNode?.removeFromSupernode() - itemNode.accessoryItemNode = nil + + if !didStealHeaderAccessoryNode { + let headerAccessoryNode = headerAccessoryItem.node() + itemNode.addSubnode(headerAccessoryNode) + itemNode.headerAccessoryItemNode = headerAccessoryNode + } } + } else { + itemNode.headerAccessoryItemNode?.removeFromSupernode() + itemNode.headerAccessoryItemNode = nil } } } } + private func updateFloatingAccessoryNodes(animated: Bool, currentTimestamp: Double) { + var previousFloatingAccessoryItem: ListViewAccessoryItem? + for itemNode in self.itemNodes { + if let index = itemNode.index, let floatingAccessoryItem = self.items[index].floatingAccessoryItem { + if itemNode.floatingAccessoryItemNode == nil { + let floatingAccessoryItemNode = floatingAccessoryItem.node() + itemNode.floatingAccessoryItemNode = floatingAccessoryItemNode + itemNode.addSubnode(floatingAccessoryItemNode) + } + } else { + itemNode.floatingAccessoryItemNode?.removeFromSupernode() + itemNode.floatingAccessoryItemNode = nil + } + } + } + private func enqueueUpdateVisibleItems() { if !self.enqueuedUpdateVisibleItems { self.enqueuedUpdateVisibleItems = true @@ -2852,7 +2924,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return nil } - public func forEachItemNode(_ f: (ListViewItemNode) -> Void) { + public func forEachItemNode(_ f: @noescape(ListViewItemNode) -> Void) { for itemNode in self.itemNodes { if itemNode.index != nil { f(itemNode) diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 5fff85d05b..e37b3e66ea 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -12,6 +12,7 @@ public protocol ListViewItem { var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } + var floatingAccessoryItem: ListViewAccessoryItem? { get } var selectable: Bool { get } func selected() @@ -26,6 +27,10 @@ public extension ListViewItem { return nil } + var floatingAccessoryItem: ListViewAccessoryItem? { + return nil + } + var selectable: Bool { return false } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 5eb8f8eabe..f9673188b3 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -62,7 +62,15 @@ public class ListViewItemNode: ASDisplayNode { } } - final var accessoryHeaderItemNode: ListViewAccessoryItemNode? + final var headerAccessoryItemNode: ListViewAccessoryItemNode? { + didSet { + if let headerAccessoryItemNode = self.headerAccessoryItemNode { + self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) + } + } + } + + final var floatingAccessoryItemNode: ListViewAccessoryItemNode? private final var spring: ListViewItemSpring? private final var animations: [(String, ListViewAnimation)] = [] @@ -138,7 +146,7 @@ public class ListViewItemNode: ASDisplayNode { //self.layerBacked = layerBacked - if layerBacked { + /*if layerBacked { super.init(layerBlock: { return ASTransformLayer() }, didLoad: nil) @@ -146,7 +154,10 @@ public class ListViewItemNode: ASDisplayNode { super.init(viewBlock: { return ListViewItemView() }, didLoad: nil) - } + }*/ + + super.init() + self.isLayerBacked = layerBacked } var apparentHeight: CGFloat = 0.0 @@ -169,6 +180,9 @@ public class ListViewItemNode: ASDisplayNode { if let accessoryItemNode = self.accessoryItemNode { self.layoutAccessoryItemNode(accessoryItemNode) } + if let headerAccessoryItemNode = self.headerAccessoryItemNode { + self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) + } } } } @@ -188,6 +202,9 @@ public class ListViewItemNode: ASDisplayNode { if let accessoryItemNode = self.accessoryItemNode { self.layoutAccessoryItemNode(accessoryItemNode) } + if let headerAccessoryItemNode = self.headerAccessoryItemNode { + self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) + } } } } @@ -216,6 +233,9 @@ public class ListViewItemNode: ASDisplayNode { public func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { } + public func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + } + public func reuse() { } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 82e4e493c4..09d7fc06f8 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -35,7 +35,7 @@ public class TabBarController: ViewController { } } - private var currentController: ViewController? + var currentController: ViewController? override public init() { super.init() @@ -76,6 +76,7 @@ public class TabBarController: ViewController { self.currentController = self.controllers[self._selectedIndex] } + var displayNavigationBar = false if let currentController = self.currentController { currentController.willMove(toParentViewController: self) currentController.containerLayoutUpdated(self.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) @@ -87,10 +88,15 @@ public class TabBarController: ViewController { self.navigationItem.title = currentController.navigationItem.title self.navigationItem.leftBarButtonItem = currentController.navigationItem.leftBarButtonItem self.navigationItem.rightBarButtonItem = currentController.navigationItem.rightBarButtonItem + displayNavigationBar = currentController.displayNavigationBar } else { self.navigationItem.title = nil self.navigationItem.leftBarButtonItem = nil self.navigationItem.rightBarButtonItem = nil + displayNavigationBar = false + } + if self.displayNavigationBar != displayNavigationBar { + self.setDisplayNavigationBar(displayNavigationBar) } } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 0e9249d212..93893ee277 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -17,7 +17,7 @@ public func dumpLayers(_ layer: CALayer) { } private func dumpLayers(_ layer: CALayer, indent: String = "") { - print("\(indent)\(layer)(\(layer.frame))") + print("\(indent)\(layer)(frame: \(layer.frame), bounds: \(layer.bounds))") if layer.sublayers != nil { let nextIndent = indent + ".." for sublayer in layer.sublayers ?? [] { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 735441cdfc..d6c859a883 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -35,6 +35,8 @@ import SwiftSignalKit public let statusBar: StatusBar public let navigationBar: NavigationBar + public var displayNavigationBar = true + private let _ready = Promise(true) public var ready: Promise { return self._ready @@ -107,6 +109,10 @@ import SwiftSignalKit navigationBarFrame.size.height = 20.0 + 32.0 } + if !self.displayNavigationBar { + navigationBarFrame.origin.y = -navigationBarFrame.size.height + } + transition.updateFrame(node: self.navigationBar, frame: navigationBarFrame) self.presentationContext.containerLayoutUpdated(layout, transition: transition) @@ -138,6 +144,20 @@ import SwiftSignalKit } } + public func setDisplayNavigationBar(_ displayNavigtionBar: Bool, transition: ContainedViewLayoutTransition = .immediate) { + if displayNavigtionBar != self.displayNavigationBar { + self.displayNavigationBar = displayNavigtionBar + if let parent = self.parent as? TabBarController { + if parent.currentController === self { + parent.displayNavigationBar = displayNavigationBar + parent.requestLayout(transition: transition) + } + } else { + self.requestLayout(transition: transition) + } + } + } + override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { preconditionFailure("use present(_:in)") } From d52005a3f27bf58fa3bd2114d6f205c4108965f3 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 19 Aug 2016 16:20:44 +0300 Subject: [PATCH 020/245] no message --- Display.xcodeproj/project.pbxproj | 15 ++++---- .../xcschemes/xcschememanagement.plist | 2 +- Display/Display.h | 1 - ...teractiveTransitionGestureRecognizer.swift | 3 +- Display/StatusBar.swift | 4 +-- Display/StatusBarHost.swift | 8 +++++ Display/StatusBarManager.swift | 25 +++++++++----- Display/StatusBarProxyNode.swift | 13 +++---- Display/StatusBarUtils.h | 9 ----- Display/StatusBarUtils.m | 33 ------------------ Display/ViewController.swift | 15 +++++++- Display/Window.swift | 34 ++++++++++++------- 12 files changed, 80 insertions(+), 82 deletions(-) create mode 100644 Display/StatusBarHost.swift delete mode 100644 Display/StatusBarUtils.h delete mode 100644 Display/StatusBarUtils.m diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 769d03d1ab..2305a05007 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -14,6 +14,7 @@ D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */; }; D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* Theme.swift */; }; D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */; }; @@ -69,8 +70,6 @@ D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; - D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; @@ -118,6 +117,7 @@ D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHost.swift; sourceTree = ""; }; D03BCCEA1C72AE590097A291 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionContainer.swift; sourceTree = ""; }; @@ -176,8 +176,6 @@ D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; - D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = StatusBarUtils.h; sourceTree = ""; }; - D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = StatusBarUtils.m; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; @@ -390,8 +388,6 @@ D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */, D05CC3221B695B0700E235A3 /* NavigationBarProxy.h */, D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */, - D0AE2C951C94529600F2FD3C /* StatusBarUtils.h */, - D0AE2C961C94529600F2FD3C /* StatusBarUtils.m */, D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */, D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */, D06EE8441B7140FF00837186 /* Font.swift */, @@ -436,6 +432,7 @@ D0078A671C92B21400DF6D92 /* StatusBar.swift */, D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */, D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */, + D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */, ); name = "Status Bar"; sourceTree = ""; @@ -517,7 +514,6 @@ D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */, D05CC3201B695A9600E235A3 /* NavigationControllerProxy.h in Headers */, D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */, - D0AE2C971C94529600F2FD3C /* StatusBarUtils.h in Headers */, D05CC2FB1B6955D000E235A3 /* UINavigationItem+Proxy.h in Headers */, D05CC3241B695B0700E235A3 /* NavigationBarProxy.h in Headers */, D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */, @@ -696,10 +692,10 @@ D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, - D0AE2C981C94529600F2FD3C /* StatusBarUtils.m in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, + D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -815,6 +811,7 @@ D05CC2781B69316F00E235A3 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -839,6 +836,7 @@ D05CC2791B69316F00E235A3 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -927,6 +925,7 @@ D086A56F1CC0115D00F08284 /* Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index c29dd981e6..3dc3380d40 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayTests.xcscheme orderHint - 20 + 15 SuppressBuildableAutocreation diff --git a/Display/Display.h b/Display/Display.h index 41406bb601..801c52ac4e 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -26,7 +26,6 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import -#import #import #import #import diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index e15db8edc5..79c07faaae 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -20,7 +20,8 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { override func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) - self.firstLocation = touches.first!.location(in: self.view) + let touch = touches.first! + self.firstLocation = touch.location(in: self.view) } override func touchesMoved(_ touches: Set, with event: UIEvent) { diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 11dab925ea..7a22cb7959 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -61,12 +61,12 @@ public class StatusBar: ASDisplayNode { }) } - func updateProxyNode() { + func updateProxyNode(statusBar: UIView) { self.removeProxyNodeScheduled = false if let proxyNode = proxyNode { proxyNode.style = self.style } else { - self.proxyNode = StatusBarProxyNode(style: self.style) + self.proxyNode = StatusBarProxyNode(style: self.style, statusBar: statusBar) self.proxyNode!.isHidden = false self.addSubnode(self.proxyNode!) } diff --git a/Display/StatusBarHost.swift b/Display/StatusBarHost.swift new file mode 100644 index 0000000000..90707fe7da --- /dev/null +++ b/Display/StatusBarHost.swift @@ -0,0 +1,8 @@ +import UIKit + +public protocol StatusBarHost { + var statusBarFrame: CGRect { get } + var statusBarStyle: UIStatusBarStyle { get set } + var statusBarWindow: UIView? { get } + var statusBarView: UIView? { get } +} diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 043b27bbfb..d085008c12 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -21,7 +21,7 @@ private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurfac return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface) } -private func optimizeMappedSurface(_ surface: MappedStatusBarSurface) -> MappedStatusBarSurface { +private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface) -> MappedStatusBarSurface { if surface.statusBars.count > 1 { for i in 1 ..< surface.statusBars.count { if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { @@ -31,7 +31,7 @@ private func optimizeMappedSurface(_ surface: MappedStatusBarSurface) -> MappedS return surface } } - let size = UIApplication.shared.statusBarFrame.size + let size = statusBarSize return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) } else { return surface @@ -52,14 +52,23 @@ private func displayHiddenAnimation() -> CAAnimation { } class StatusBarManager { + private var host: StatusBarHost + var surfaces: [StatusBarSurface] = [] { didSet { self.updateSurfaces(oldValue) } } + init(host: StatusBarHost) { + self.host = host + } + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface]) { - var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(mappedSurface($0)) }) + let statusBarFrame = self.host.statusBarFrame + let statusBarView = self.host.statusBarView! + + var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) }) var reduceSurfaces = true var reduceSurfacesStatusBarStyle: StatusBarStyle? @@ -139,17 +148,17 @@ class StatusBarManager { } for statusBar in visibleStatusBars { - statusBar.updateProxyNode() + statusBar.updateProxyNode(statusBar: statusBarView) } if let globalStatusBar = globalStatusBar { let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent - if UIApplication.shared.statusBarStyle != statusBarStyle { - UIApplication.shared.setStatusBarStyle(statusBarStyle, animated: false) + if self.host.statusBarStyle != statusBarStyle { + self.host.statusBarStyle = statusBarStyle } - StatusBarUtils.statusBarWindow()!.alpha = globalStatusBar.1 + self.host.statusBarWindow?.alpha = globalStatusBar.1 } else { - StatusBarUtils.statusBarWindow()!.alpha = 0.0 + self.host.statusBarWindow?.alpha = 0.0 } } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 7907c2537f..2141f6c1df 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -11,8 +11,8 @@ private enum StatusBarItemType { case Battery } -func makeStatusBarProxy(_ style: StatusBarStyle) -> StatusBarProxyNode { - return StatusBarProxyNode(style: style) +func makeStatusBarProxy(_ style: StatusBarStyle, statusBar: UIView) -> StatusBarProxyNode { + return StatusBarProxyNode(style: style, statusBar: statusBar) } private class StatusBarItemNode: ASDisplayNode { @@ -232,6 +232,8 @@ private class StatusBarProxyNodeTimerTarget: NSObject { } class StatusBarProxyNode: ASDisplayNode { + private let statusBar: UIView + var timer: Timer? var style: StatusBarStyle { didSet { @@ -266,8 +268,9 @@ class StatusBarProxyNode: ASDisplayNode { } } - init(style: StatusBarStyle) { + init(style: StatusBarStyle, statusBar: UIView) { self.style = style + self.statusBar = statusBar super.init() @@ -276,8 +279,6 @@ class StatusBarProxyNode: ASDisplayNode { self.clipsToBounds = true //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) - let statusBar = StatusBarUtils.statusBar()! - for subview in statusBar.subviews { let itemNode = StatusBarItemNode(style: style, targetView: subview) self.itemNodes.append(itemNode) @@ -292,7 +293,7 @@ class StatusBarProxyNode: ASDisplayNode { } private func updateItems() { - let statusBar = StatusBarUtils.statusBar()! + let statusBar = self.statusBar var i = 0 while i < self.itemNodes.count { diff --git a/Display/StatusBarUtils.h b/Display/StatusBarUtils.h deleted file mode 100644 index 45d160cca9..0000000000 --- a/Display/StatusBarUtils.h +++ /dev/null @@ -1,9 +0,0 @@ -#import -#import - -@interface StatusBarUtils : NSObject - -+ (UIView * _Nullable)statusBarWindow; -+ (UIView * _Nullable)statusBar; - -@end diff --git a/Display/StatusBarUtils.m b/Display/StatusBarUtils.m deleted file mode 100644 index 0f3f0e66fa..0000000000 --- a/Display/StatusBarUtils.m +++ /dev/null @@ -1,33 +0,0 @@ -#import "StatusBarUtils.h" - -@implementation StatusBarUtils - -+ (UIView *)statusBarWindow { - UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; - return window; - //UIView *view = window.subviews.firstObject; - //return view; -} - -+ (UIView *)statusBar { - UIWindow *window = [[UIApplication sharedApplication] valueForKey:@"statusBarWindow"]; - UIView *view = window.subviews.firstObject; - - static Class foregroundClass = nil; - static Class batteryClass = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - foregroundClass = NSClassFromString(@"UIStatusBarForegroundView"); - batteryClass = NSClassFromString(@"UIStatusBarBatteryItemView"); - }); - - for (UIView *foreground in view.subviews) { - if ([foreground isKindOfClass:foregroundClass]) { - return foreground; - } - } - - return nil; -} - -@end diff --git a/Display/ViewController.swift b/Display/ViewController.swift index d6c859a883..4564136d5f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -170,13 +170,26 @@ import SwiftSignalKit } } + private var window: Window? { + if let window = self.view.window as? Window { + return window + } else if let superwindow = self.view.window { + for subview in superwindow.subviews { + if let subview = subview as? Window { + return subview + } + } + } + return nil + } + public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil) { controller.presentationArguments = arguments switch context { case .current: self.presentationContext.present(controller) case .window: - (self.view.window as? Window)?.present(controller) + self.window?.present(controller) } } } diff --git a/Display/Window.swift b/Display/Window.swift index 536d0b0979..2b724f13dd 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -92,7 +92,8 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container } public class Window: UIWindow { - private let statusBarManager: StatusBarManager + private let statusBarHost: StatusBarHost? + private let statusBarManager: StatusBarManager? private var statusBarChangeObserver: AnyObject? private var keyboardFrameChangeObserver: AnyObject? @@ -107,13 +108,17 @@ public class Window: UIWindow { private var statusBarHidden = false - public convenience init() { - self.init(frame: UIScreen.main.bounds) - } - - public override init(frame: CGRect) { - self.statusBarManager = StatusBarManager() - self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: UIApplication.shared.statusBarFrame.size.height, inputHeight: 0.0) + public init(frame: CGRect, statusBarHost: StatusBarHost?) { + self.statusBarHost = statusBarHost + let statusBarHeight: CGFloat + if let statusBarHost = statusBarHost { + self.statusBarManager = StatusBarManager(host: statusBarHost) + statusBarHeight = statusBarHost.statusBarFrame.size.height + } else { + self.statusBarManager = nil + statusBarHeight = 20.0 + } + self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: statusBarHeight, inputHeight: 0.0) self.presentationContext = PresentationContext() super.init(frame: frame) @@ -244,11 +249,11 @@ public class Window: UIWindow { override public func layoutSubviews() { super.layoutSubviews() - if self.tracingStatusBarsInvalidated { + if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager { self.tracingStatusBarsInvalidated = false if self.statusBarHidden { - self.statusBarManager.surfaces = [] + statusBarManager.surfaces = [] } else { var statusBarSurfaces: [StatusBarSurface] = [] for layers in self.layer.traceableLayerSurfaces() { @@ -263,7 +268,7 @@ public class Window: UIWindow { statusBarSurfaces.append(surface) } self.layer.adjustTraceableLayerTransforms(CGSize()) - self.statusBarManager.surfaces = statusBarSurfaces + statusBarManager.surfaces = statusBarSurfaces } } @@ -316,7 +321,12 @@ public class Window: UIWindow { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil if updatingLayout.layout != self.windowLayout { - var statusBarHeight = UIApplication.shared.statusBarFrame.size.height + var statusBarHeight: CGFloat + if let statusBarHost = self.statusBarHost { + statusBarHeight = statusBarHost.statusBarFrame.size.height + } else { + statusBarHeight = 20.0 + } var statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { statusBarHeight = 0.0 From e0e8cc7d3413b23e73772239b59c95f3e631f694 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 23 Aug 2016 16:19:01 +0300 Subject: [PATCH 021/245] no message --- Display.xcodeproj/project.pbxproj | 12 --- .../xcschemes/xcschememanagement.plist | 2 +- Display/ASTransformLayerNode.swift | 8 +- Display/ActionSheetButtonItem.swift | 2 +- Display/ActionSheetButtonNode.swift | 2 +- Display/ActionSheetController.swift | 8 +- Display/ActionSheetItemNode.swift | 6 +- Display/BarButtonItemWrapper.swift | 2 +- Display/ContainerViewLayout.swift | 4 +- Display/DisplayLinkDispatcher.swift | 2 +- Display/GenerateImage.swift | 20 ++--- ...teractiveTransitionGestureRecognizer.swift | 2 +- Display/KeyboardHostWindow.swift | 28 ------- Display/ListView.swift | 80 +++++++++---------- Display/ListViewAccessoryItemNode.swift | 4 +- Display/ListViewAnimation.swift | 2 +- Display/ListViewItem.swift | 6 +- Display/ListViewItemNode.swift | 26 +++--- Display/ListViewTransactionQueue.swift | 2 +- Display/NavigationBar.swift | 6 +- Display/NavigationController.swift | 22 ++--- Display/NavigationTransitionCoordinator.swift | 14 ++-- Display/PresentationContext.swift | 8 +- Display/StatusBarManager.swift | 4 +- Display/StatusBarProxyNode.swift | 20 ++--- ...ainedControllerTransitionCoordinator.swift | 14 ++-- Display/TabBarContollerNode.swift | 2 +- Display/TabBarNode.swift | 6 +- Display/UniversalTapRecognizer.swift | 2 +- Display/ViewController.swift | 52 +++++++++--- Display/Window.swift | 2 +- 31 files changed, 182 insertions(+), 188 deletions(-) delete mode 100644 Display/KeyboardHostWindow.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 2305a05007..780cab3d93 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -64,7 +64,6 @@ D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; - D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; @@ -170,7 +169,6 @@ D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; - D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardHostWindow.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; @@ -324,7 +322,6 @@ D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, - D07921A71B6FC0AE005C23D9 /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, @@ -418,14 +415,6 @@ name = Navigation; sourceTree = ""; }; - D07921A71B6FC0AE005C23D9 /* Keyboard */ = { - isa = PBXGroup; - children = ( - D07921A81B6FC0C0005C23D9 /* KeyboardHostWindow.swift */, - ); - name = Keyboard; - sourceTree = ""; - }; D07921AA1B6FC911005C23D9 /* Status Bar */ = { isa = PBXGroup; children = ( @@ -639,7 +628,6 @@ D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, - D07921A91B6FC0C0005C23D9 /* KeyboardHostWindow.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 3dc3380d40..aa5ae0473f 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayTests.xcscheme orderHint - 15 + 14 SuppressBuildableAutocreation diff --git a/Display/ASTransformLayerNode.swift b/Display/ASTransformLayerNode.swift index 72c5ef9729..0e204e8074 100644 --- a/Display/ASTransformLayerNode.swift +++ b/Display/ASTransformLayerNode.swift @@ -2,7 +2,7 @@ import Foundation import AsyncDisplayKit class ASTransformLayer: CATransformLayer { - override var contents: AnyObject? { + override var contents: Any? { get { return nil } set(value) { @@ -31,7 +31,7 @@ class ASTransformView: UIView { } } -public class ASTransformLayerNode: ASDisplayNode { +open class ASTransformLayerNode: ASDisplayNode { public override init() { super.init(layerBlock: { return ASTransformLayer() @@ -39,7 +39,7 @@ public class ASTransformLayerNode: ASDisplayNode { } } -public class ASTransformViewNode: ASDisplayNode { +open class ASTransformViewNode: ASDisplayNode { public override init() { super.init(viewBlock: { return ASTransformView() @@ -47,7 +47,7 @@ public class ASTransformViewNode: ASDisplayNode { } } -public class ASTransformNode: ASDisplayNode { +open class ASTransformNode: ASDisplayNode { public init(layerBacked: Bool = true) { if layerBacked { super.init(layerBlock: { diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index b0435d21f6..ccd5ebfd43 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -10,7 +10,7 @@ public class ActionSheetButtonItem: ActionSheetItem { public let color: ActionSheetButtonColor public let action: () -> Void - public init(title: String, color: ActionSheetButtonColor = .accent, action: () -> Void) { + public init(title: String, color: ActionSheetButtonColor = .accent, action: @escaping () -> Void) { self.title = title self.color = color self.action = action diff --git a/Display/ActionSheetButtonNode.swift b/Display/ActionSheetButtonNode.swift index e740f94b43..396d6623bf 100644 --- a/Display/ActionSheetButtonNode.swift +++ b/Display/ActionSheetButtonNode.swift @@ -10,7 +10,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { private let label: UILabel private var calculatedLabelSize: CGSize? - public init(title: NSAttributedString, action: () -> Void) { + public init(title: NSAttributedString, action: @escaping () -> Void) { self.action = action self.button = HighlightTrackingButton() diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index afb7038bb6..0b6191f424 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -1,6 +1,6 @@ import Foundation -public class ActionSheetController: ViewController { +open class ActionSheetController: ViewController { private var actionSheetNode: ActionSheetControllerNode { return self.displayNode as! ActionSheetControllerNode } @@ -11,7 +11,7 @@ public class ActionSheetController: ViewController { self.actionSheetNode.animateOut() } - public override func loadDisplayNode() { + open override func loadDisplayNode() { self.displayNode = ActionSheetControllerNode() self.displayNodeDidLoad() self.navigationBar.isHidden = true @@ -23,13 +23,13 @@ public class ActionSheetController: ViewController { self.actionSheetNode.setGroups(self.groups) } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.actionSheetNode.containerLayoutUpdated(layout, transition: transition) } - public override func viewWillAppear(_ animated: Bool) { + open override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.actionSheetNode.animateIn() diff --git a/Display/ActionSheetItemNode.swift b/Display/ActionSheetItemNode.swift index a41f3d71d7..22214130a3 100644 --- a/Display/ActionSheetItemNode.swift +++ b/Display/ActionSheetItemNode.swift @@ -1,7 +1,7 @@ import UIKit import AsyncDisplayKit -public class ActionSheetItemNode: ASDisplayNode { +open class ActionSheetItemNode: ASDisplayNode { public static let defaultBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.8) public static let highlightedBackgroundColor: UIColor = UIColor(white: 0.9, alpha: 0.7) @@ -21,11 +21,11 @@ public class ActionSheetItemNode: ASDisplayNode { self.addSubnode(self.overflowSeparatorNode) } - public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + open override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { return CGSize(width: constrainedSize.width, height: 57.0) } - public override func layout() { + open override func layout() { self.backgroundNode.frame = CGRect(origin: CGPoint(), size: self.calculatedSize) self.overflowSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.calculatedSize.height), size: CGSize(width: self.calculatedSize.width, height: UIScreenPixel)) } diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift index 02b62f6aa3..3e6f1b5695 100644 --- a/Display/BarButtonItemWrapper.swift +++ b/Display/BarButtonItemWrapper.swift @@ -11,7 +11,7 @@ internal class BarButtonItemWrapper { private var setEnabledListenerKey: Int! private var setTitleListenerKey: Int! - init(parentNode: ASDisplayNode, barButtonItem: UIBarButtonItem, layoutNeeded: () -> ()) { + init(parentNode: ASDisplayNode, barButtonItem: UIBarButtonItem, layoutNeeded: @escaping () -> ()) { self.parentNode = parentNode self.barButtonItem = barButtonItem self.layoutNeeded = layoutNeeded diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index a1f66c887c..f2df5b9655 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -36,10 +36,10 @@ public struct ContainerViewLayout: Equatable { public func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets { var insets = self.intrinsicInsets - if let statusBarHeight = self.statusBarHeight where options.contains(.statusBar) { + if let statusBarHeight = self.statusBarHeight , options.contains(.statusBar) { insets.top += statusBarHeight } - if let inputHeight = self.inputHeight where options.contains(.input) { + if let inputHeight = self.inputHeight , options.contains(.input) { insets.bottom = max(inputHeight, insets.bottom) } return insets diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index 16d054c4da..7e14daed58 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -18,7 +18,7 @@ public class DisplayLinkDispatcher: NSObject { self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) } - public func dispatch(f: (Void) -> Void) { + public func dispatch(f: @escaping (Void) -> Void) { self.blocksToDispatch.append(f) self.displayLink.isPaused = false } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 031843fd81..ecd6729ba5 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -9,7 +9,7 @@ public func generateImage(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutable let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) let length = bytesPerRow * Int(scaledSize.height) - let bytes = UnsafeMutablePointer(malloc(length)!) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) }) @@ -34,7 +34,7 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) let length = bytesPerRow * Int(scaledSize.height) - let bytes = UnsafeMutablePointer(malloc(length)!) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) @@ -102,7 +102,7 @@ public class DrawingContext { public let bytesPerRow: Int private let bitmapInfo: CGBitmapInfo public let length: Int - public let bytes: UnsafeMutablePointer + public let bytes: UnsafeMutableRawPointer let provider: CGDataProvider? private var _context: CGContext? @@ -151,7 +151,7 @@ public class DrawingContext { self.bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue) - self.bytes = UnsafeMutablePointer(malloc(length)) + self.bytes = malloc(length)! if clear { memset(self.bytes, 0, self.length) } @@ -172,7 +172,7 @@ public class DrawingContext { let x = Int(point.x * self.scale) let y = Int(point.y * self.scale) if x >= 0 && x < Int(self.scaledSize.width) && y >= 0 && y < Int(self.scaledSize.height) { - let srcLine = UnsafeMutablePointer(self.bytes + y * self.bytesPerRow) + let srcLine = self.bytes.advanced(by: y * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) let pixel = srcLine + x let colorValue = pixel.pointee return UIColor(UInt32(colorValue)) @@ -197,8 +197,8 @@ public class DrawingContext { switch mode { case .Alpha: while dstY < maxDstY { - let srcLine = UnsafeMutablePointer(other.bytes + srcY * other.bytesPerRow) - let dstLine = UnsafeMutablePointer(self.bytes + dstY * self.bytesPerRow) + let srcLine = other.bytes.advanced(by: srcY * other.bytesPerRow).assumingMemoryBound(to: UInt32.self) + let dstLine = self.bytes.advanced(by: dstY * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) var dx = dstX var sx = srcX @@ -278,13 +278,13 @@ public func drawSvgPath(_ context: CGContext, path: StaticString) throws { let y = try readCGFloat(&index, end: end, separator: 32) //print("Move to \(x), \(y)") - context.moveTo(x: x, y: y) + context.move(to: CGPoint(x: x, y: y)) } else if c == 76 { // L let x = try readCGFloat(&index, end: end, separator: 44) let y = try readCGFloat(&index, end: end, separator: 32) //print("Line to \(x), \(y)") - context.addLineTo(x: x, y: y) + context.addLine(to: CGPoint(x: x, y: y)) } else if c == 67 { // C let x1 = try readCGFloat(&index, end: end, separator: 44) let y1 = try readCGFloat(&index, end: end, separator: 32) @@ -292,7 +292,7 @@ public func drawSvgPath(_ context: CGContext, path: StaticString) throws { let y2 = try readCGFloat(&index, end: end, separator: 32) let x = try readCGFloat(&index, end: end, separator: 44) let y = try readCGFloat(&index, end: end, separator: 32) - context.addCurveTo(cp1x: x1, cp1y: y1, cp2x: x2, cp2y: y2, endingAtX: x, y: y) + context.addCurve(to: CGPoint(x: x1, y: y1), control1: CGPoint(x: x2, y: y2), control2: CGPoint(x: x, y: y)) //print("Line to \(x), \(y)") diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index 79c07faaae..e6fde67334 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -5,7 +5,7 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { var validatedGesture = false var firstLocation: CGPoint = CGPoint() - override init(target: AnyObject?, action: Selector?) { + override init(target: Any?, action: Selector?) { super.init(target: target, action: action) self.maximumNumberOfTouches = 1 diff --git a/Display/KeyboardHostWindow.swift b/Display/KeyboardHostWindow.swift deleted file mode 100644 index 114a07f70c..0000000000 --- a/Display/KeyboardHostWindow.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import UIKit - -public class KeyboardHostWindow: UIWindow { - public let textField: UITextField - - convenience public init() { - self.init(frame: CGRect()) - } - - override init(frame: CGRect) { - self.textField = UITextField(frame: CGRect(x: -110.0, y: 0.0, width: 100.0, height: 50.0)) - - super.init(frame: frame) - - self.windowLevel = -1.0 - self.rootViewController = UIViewController() - self.addSubview(self.textField) - } - - required public init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - public func acquireFirstResponder() { - textField.becomeFirstResponder() - } -} diff --git a/Display/ListView.swift b/Display/ListView.swift index 33bed43bcd..970e34e9f8 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -298,7 +298,7 @@ private struct ListViewState { mutating func fixScrollPostition(_ itemCount: Int) { if let (fixedIndex, fixedPosition) = self.scrollPosition { for node in self.nodes { - if let index = node.index where index == fixedIndex { + if let index = node.index , index == fixedIndex { let offset: CGFloat switch fixedPosition { case .Bottom: @@ -372,7 +372,7 @@ private struct ListViewState { mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) { if index < boundary { for node in self.nodes { - if let nodeIndex = node.index where nodeIndex >= index { + if let nodeIndex = node.index , nodeIndex >= index { if let frame = frames[nodeIndex] { self.stationaryOffset = (nodeIndex, frame.minY) break @@ -381,7 +381,7 @@ private struct ListViewState { } } else { for node in self.nodes.reversed() { - if let nodeIndex = node.index where nodeIndex <= index { + if let nodeIndex = node.index , nodeIndex <= index { if let frame = frames[nodeIndex] { self.stationaryOffset = (nodeIndex, frame.minY) break @@ -459,7 +459,7 @@ private struct ListViewState { if let (fixedIndex, _) = self.scrollPosition { for i in 0 ..< self.nodes.count { let node = self.nodes[i] - if let index = node.index where index == fixedIndex { + if let index = node.index , index == fixedIndex { fixedNode = (i, index, node.frame) break } @@ -474,7 +474,7 @@ private struct ListViewState { if let (fixedIndex, _) = self.stationaryOffset { for i in 0 ..< self.nodes.count { let node = self.nodes[i] - if let index = node.index where index == fixedIndex { + if let index = node.index , index == fixedIndex { fixedNode = (i, index, node.frame) break } @@ -485,7 +485,7 @@ private struct ListViewState { if fixedNode == nil { for i in 0 ..< self.nodes.count { let node = self.nodes[i] - if let index = node.index where node.frame.maxY >= self.insets.top { + if let index = node.index , node.frame.maxY >= self.insets.top { fixedNode = (i, index, node.frame) break } @@ -510,7 +510,7 @@ private struct ListViewState { if index != currentUpperNode.index - 1 { if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] where currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { directionHint = ListViewInsertionOffsetDirection(hint) } return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) @@ -524,7 +524,7 @@ private struct ListViewState { if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] where currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { directionHint = ListViewInsertionOffsetDirection(hint) } @@ -539,7 +539,7 @@ private struct ListViewState { if index != currentLowerNode.index + 1 { if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] where currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { directionHint = ListViewInsertionOffsetDirection(hint) } return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) @@ -554,7 +554,7 @@ private struct ListViewState { if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] where currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { directionHint = ListViewInsertionOffsetDirection(hint) } return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) @@ -592,7 +592,7 @@ private struct ListViewState { let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) for i in 0 ..< self.nodes.count { let node = self.nodes[i] - if let index = node.index where node.frame.maxY > upperBound { + if let index = node.index , node.frame.maxY > upperBound { if i != 0 { var previousIndex = index for j in (0 ..< i).reversed() { @@ -616,7 +616,7 @@ private struct ListViewState { let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) for i in (0 ..< self.nodes.count).reversed() { let node = self.nodes[i] - if let index = node.index where node.frame.minY < lowerBound { + if let index = node.index , node.frame.minY < lowerBound { if i != self.nodes.count - 1 { var previousIndex = index var removeIndices: [Int] = [] @@ -700,7 +700,7 @@ private struct ListViewState { return height } - mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { + mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) let nodeOrigin: CGPoint @@ -774,12 +774,12 @@ private struct ListViewState { } operations.append(.Remove(index: index, offsetDirection: offsetDirection)) - if let referenceNode = referenceNode where animated { + if let referenceNode = referenceNode , animated { self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) operations.append(.InsertPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) } else { if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { - if let direction = direction where direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { for i in (0 ..< index).reversed() { var frame = self.nodes[i].frame frame.origin.y += nodeFrame.size.height @@ -805,7 +805,7 @@ private struct ListViewState { } } - mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: () -> Void, operations: inout [ListViewStateOperation]) { + mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> Void, operations: inout [ListViewStateOperation]) { var i = -1 for node in self.nodes { i += 1 @@ -912,7 +912,7 @@ private final class ListViewBackingView: UIView { private final class ListViewTimerProxy: NSObject { private let action: () -> () - init(_ action: () -> ()) { + init(_ action: @escaping () -> ()) { self.action = action super.init() } @@ -1111,7 +1111,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func dispatchOnVSync(forceNext: Bool = false, action: () -> ()) { + private func dispatchOnVSync(forceNext: Bool = false, action: @escaping () -> ()) { Queue.mainQueue().async { if !forceNext && self.inVSync { action() @@ -1280,7 +1280,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { private func updateVisibleContentOffset() { var offset: CGFloat? - if let itemNode = self.itemNodes.first, index = itemNode.index where index == 0 { + if let itemNode = self.itemNodes.first, let index = itemNode.index , index == 0 { offset = -(itemNode.apparentFrame.minY - self.insets.top) } @@ -1348,11 +1348,11 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.ignoreScrollingEvents = false } - private func async(_ f: () -> Void) { + private func async(_ f: @escaping () -> Void) { DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: (ListViewItemNode, ListViewItemNodeLayout, () -> Void) -> Void) { + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> Void) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -1409,7 +1409,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) } - public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: (ListViewDisplayedItemRange) -> Void = { _ in }) { + public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil { completion(self.immediateDisplayedItemRange()) return @@ -1427,9 +1427,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: (Void) -> Void) { + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: @escaping (Void) -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && scrollToItem == nil { - if let updateSizeAndInsets = updateSizeAndInsets where self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { + if let updateSizeAndInsets = updateSizeAndInsets , self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets @@ -1709,7 +1709,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateAdjacent(synchronous: Bool, animated: Bool, state: ListViewState, updateAdjacentItemsIndices: Set, operations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { + private func updateAdjacent(synchronous: Bool, animated: Bool, state: ListViewState, updateAdjacentItemsIndices: Set, operations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { if updateAdjacentItemsIndices.isEmpty { completion(state, operations) } else { @@ -1722,7 +1722,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var i = 0 for node in state.nodes { - if case let .Node(index, _, referenceNode) = node where index == nodeIndex { + if case let .Node(index, _, referenceNode) = node , index == nodeIndex { if let referenceNode = referenceNode { continueWithoutNode = false self.items[index].updateNode(async: { f in @@ -1758,7 +1758,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], inputCompletion: (ListViewState, [ListViewStateOperation]) -> Void) { + private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { let animatedInsertIndices = inputAnimatedInsertIndices var state = inputState var previousNodes = inputPreviousNodes @@ -1817,7 +1817,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], completion: (ListViewState, [ListViewStateOperation]) -> Void) { + private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { var state = inputState var operations = inputOperations var updateIndicesAndItems = updateIndicesAndItems @@ -1895,7 +1895,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var offsetHeight = node.apparentHeight var takenAnimation = false - if let _ = previousFrame where animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { + if let _ = previousFrame , animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { let nextNode = self.itemNodes[nodeIndex + 1] if nextNode.index == nil { let nextHeight = nextNode.apparentHeight @@ -2121,7 +2121,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.stopScrolling() for itemNode in self.itemNodes { - if let index = itemNode.index where index == scrollToItem.index { + if let index = itemNode.index , index == scrollToItem.index { let offset: CGFloat switch scrollToItem.position { case .Bottom: @@ -2157,7 +2157,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { }*/ } else if let stationaryItemIndex = stationaryItemIndex { for itemNode in self.itemNodes { - if let index = itemNode.index where index == stationaryItemIndex { + if let index = itemNode.index , index == stationaryItemIndex { for (previousNode, previousFrame) in previousApparentFrames { if previousNode === itemNode { let offset = previousFrame.minY - itemNode.frame.minY @@ -2237,7 +2237,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) self.updateFloatingAccessoryNodes(animated: animated, currentTimestamp: timestamp) - if let scrollToItem = scrollToItem where scrollToItem.animated { + if let scrollToItem = scrollToItem , scrollToItem.animated { if self.itemNodes.count != 0 { var offset: CGFloat? @@ -2275,7 +2275,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { offset = offsetValue - sizeAndInsetsOffset } - if let offset = offset where abs(offset) > CGFloat(FLT_EPSILON) { + if let offset = offset , abs(offset) > CGFloat(FLT_EPSILON) { for itemNode in temporaryPreviousNodes { itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) temporaryPreviousNodes.append(itemNode) @@ -2355,7 +2355,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if self.debugInfo { var previousMaxY: CGFloat? for node in self.itemNodes { - if let previousMaxY = previousMaxY where abs(previousMaxY - node.apparentFrame.minY) > CGFloat(FLT_EPSILON) { + if let previousMaxY = previousMaxY , abs(previousMaxY - node.apparentFrame.minY) > CGFloat(FLT_EPSILON) { print("monotonity violated") break } @@ -2397,7 +2397,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let nextItemNode = self.itemNodes[i] if let nextItemNodeIndex = nextItemNode.index { let nextItem = self.items[nextItemNodeIndex] - if let nextAccessoryItem = nextItem.accessoryItem where nextAccessoryItem.isEqualToItem(accessoryItem) { + if let nextAccessoryItem = nextItem.accessoryItem , nextAccessoryItem.isEqualToItem(accessoryItem) { if let nextAccessoryItemNode = nextItemNode.accessoryItemNode { didStealAccessoryNode = true @@ -2455,7 +2455,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let nextItemNode = self.itemNodes[i] if let nextItemNodeIndex = nextItemNode.index { let nextItem = self.items[nextItemNodeIndex] - if let nextHeaderAccessoryItem = nextItem.headerAccessoryItem where nextHeaderAccessoryItem.isEqualToItem(headerAccessoryItem) { + if let nextHeaderAccessoryItem = nextItem.headerAccessoryItem , nextHeaderAccessoryItem.isEqualToItem(headerAccessoryItem) { if let nextHeaderAccessoryItemNode = nextItemNode.headerAccessoryItemNode { didStealHeaderAccessoryNode = true @@ -2547,7 +2547,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateVisibleItemsTransaction(completion: (Void) -> Void) { + private func updateVisibleItemsTransaction(completion: @escaping (Void) -> Void) { if self.items.count == 0 && self.itemNodes.count == 0 { completion() return @@ -2605,7 +2605,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } i -= 1 } - if let firstIndex = firstIndex, lastIndex = lastIndex { + if let firstIndex = firstIndex, let lastIndex = lastIndex { var firstVisibleIndex: Int? for i in firstIndex.nodeIndex ... lastIndex.nodeIndex { if let index = self.itemNodes[i].index { @@ -2662,7 +2662,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - private func updateSizeAndInsetsTransaction(size: CGSize, insets: UIEdgeInsets, duration: Double, options: UIViewAnimationOptions, completion: (Void) -> Void) { + private func updateSizeAndInsetsTransaction(size: CGSize, insets: UIEdgeInsets, duration: Double, options: UIViewAnimationOptions, completion: @escaping (Void) -> Void) { if size.equalTo(self.visibleSize) && UIEdgeInsetsEqualToEdgeInsets(self.insets, insets) { completion() } else { @@ -2875,7 +2875,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.selectionTouchDelayTimer?.invalidate() let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in - if let strongSelf = self where strongSelf.selectionTouchLocation != nil { + if let strongSelf = self , strongSelf.selectionTouchLocation != nil { strongSelf.clearHighlightAnimated(false) let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift index 284bfcc13e..baeccbac18 100644 --- a/Display/ListViewAccessoryItemNode.swift +++ b/Display/ListViewAccessoryItemNode.swift @@ -1,7 +1,7 @@ import Foundation import AsyncDisplayKit -public class ListViewAccessoryItemNode: ASDisplayNode { +open class ListViewAccessoryItemNode: ASDisplayNode { var transitionOffset: CGPoint = CGPoint() { didSet { self.bounds = CGRect(origin: self.transitionOffset, size: self.bounds.size) @@ -10,7 +10,7 @@ public class ListViewAccessoryItemNode: ASDisplayNode { private var transitionOffsetAnimation: ListViewAnimation? - final func animateTransitionOffset(_ from: CGPoint, beginAt: Double, duration: Double, curve: (CGFloat) -> CGFloat) { + final func animateTransitionOffset(_ from: CGPoint, beginAt: Double, duration: Double, curve: @escaping (CGFloat) -> CGFloat) { self.transitionOffset = from self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] _, currentValue in if let strongSelf = self { diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index a0e032ed5f..91b91d9c0e 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -94,7 +94,7 @@ public final class ListViewAnimation { private let update: (CGFloat, Interpolatable) -> Void private let completed: (Bool) -> Void - public init(from: T, to: T, duration: Double, curve: (CGFloat) -> CGFloat, beginAt: Double, update: (CGFloat, T) -> Void, completed: (Bool) -> Void = { _ in }) { + public init(from: T, to: T, duration: Double, curve: @escaping (CGFloat) -> CGFloat, beginAt: Double, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) { self.from = from self.to = to self.duration = duration diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index e37b3e66ea..6c22519549 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -7,8 +7,8 @@ public enum ListViewItemUpdateAnimation { } public protocol ListViewItem { - func nodeConfiguredForWidth(async: (() -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: (ListViewItemNode, () -> Void) -> Void) - func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: (ListViewItemNodeLayout, () -> Void) -> Void) + func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> Void) -> Void) + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } @@ -38,7 +38,7 @@ public extension ListViewItem { func selected() { } - func updateNode(async: (() -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: (ListViewItemNodeLayout, () -> Void) -> Void) { + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) { completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {}) } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index f9673188b3..c2dfd53fbc 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -51,7 +51,7 @@ public struct ListViewItemNodeLayout { } } -public class ListViewItemNode: ASDisplayNode { +open class ListViewItemNode: ASDisplayNode { final var index: Int? final var accessoryItemNode: ListViewAccessoryItemNode? { @@ -164,7 +164,7 @@ public class ListViewItemNode: ASDisplayNode { private var _bounds: CGRect = CGRect() private var _position: CGPoint = CGPoint() - public override var frame: CGRect { + open override var frame: CGRect { get { return CGRect(origin: CGPoint(x: self._position.x - self._bounds.width / 2.0, y: self._position.y - self._bounds.height / 2.0), size: self._bounds.size) } set(value) { @@ -187,7 +187,7 @@ public class ListViewItemNode: ASDisplayNode { } } - public override var bounds: CGRect { + open override var bounds: CGRect { get { return self._bounds } set(value) { @@ -209,7 +209,7 @@ public class ListViewItemNode: ASDisplayNode { } } - public override var position: CGPoint { + open override var position: CGPoint { get { return self._position } set(value) { @@ -230,13 +230,13 @@ public class ListViewItemNode: ASDisplayNode { return bounds } - public func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + open func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { } - public func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + open func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { } - public func reuse() { + open func reuse() { } final func addScrollingOffset(_ scrollingOffset: CGFloat) { @@ -316,7 +316,7 @@ public class ListViewItemNode: ASDisplayNode { return continueAnimations } - public func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + open func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { } public func animationForKey(_ key: String) -> ListViewAnimation? { @@ -404,19 +404,19 @@ public class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("transitionOffset", animation: animation) } - public func animateInsertion(_ currentTimestamp: Double, duration: Double) { + open func animateInsertion(_ currentTimestamp: Double, duration: Double) { } - public func animateAdded(_ currentTimestamp: Double, duration: Double) { + open func animateAdded(_ currentTimestamp: Double, duration: Double) { } - public func setHighlighted(_ highlighted: Bool, animated: Bool) { + open func setHighlighted(_ highlighted: Bool, animated: Bool) { } - public func setupGestures() { + open func setupGestures() { } - public func animateFrameTransition(_ progress: CGFloat) { + open func animateFrameTransition(_ progress: CGFloat) { } } diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index d63c5a7807..0316299fa7 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -1,7 +1,7 @@ import Foundation import SwiftSignalKit -public typealias ListViewTransaction = ((Void) -> Void) -> Void +public typealias ListViewTransaction = @escaping (@escaping (Void) -> Void) -> Void public final class ListViewTransactionQueue { private var transactions: [ListViewTransaction] = [] diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index ad5d920054..fa4dd9cc10 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -155,7 +155,7 @@ public class NavigationBar: ASDisplayNode { get { return self._previousItem } set(value) { - if let previousValue = self._previousItem, previousItemListenerKey = self.previousItemListenerKey { + if let previousValue = self._previousItem, let previousItemListenerKey = self.previousItemListenerKey { previousValue.removeSetTitleListener(previousItemListenerKey) self.previousItemListenerKey = nil } @@ -312,7 +312,7 @@ public class NavigationBar: ASDisplayNode { } self.leftButtonNode.pressed = { [weak self] in - if let item = self?.item, leftBarButtonItem = item.leftBarButtonItem { + if let item = self?.item, let leftBarButtonItem = item.leftBarButtonItem { leftBarButtonItem.performActionOnTarget() } } @@ -411,7 +411,7 @@ public class NavigationBar: ASDisplayNode { if self.titleNode.supernode != nil { let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight)) - if let transitionState = self.transitionState, otherNavigationBar = transitionState.navigationBar { + if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { let progress = transitionState.progress switch transitionState.role { diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index f7f46264d3..f1f3e777cb 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -9,7 +9,7 @@ private class NavigationControllerView: UIView { } } -public class NavigationController: NavigationControllerProxy, ContainableController, UIGestureRecognizerDelegate { +open class NavigationController: NavigationControllerProxy, ContainableController, UIGestureRecognizerDelegate { public private(set) weak var overlayPresentingController: ViewController? private var containerLayout = ContainerViewLayout() @@ -25,12 +25,12 @@ public class NavigationController: NavigationControllerProxy, ContainableControl //private var pendingLayout: (NavigationControllerLayout, Double, Bool)? private var _presentedViewController: UIViewController? - public override var presentedViewController: UIViewController? { + open override var presentedViewController: UIViewController? { return self._presentedViewController } private var _viewControllers: [UIViewController] = [] - public override var viewControllers: [UIViewController] { + open override var viewControllers: [UIViewController] { get { return self._viewControllers } set(value) { @@ -38,7 +38,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl } } - public override var topViewController: UIViewController? { + open override var topViewController: UIViewController? { return self._viewControllers.last } @@ -97,7 +97,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl } } - public override func loadView() { + open override func loadView() { self.view = NavigationControllerView() self.view.clipsToBounds = true @@ -218,7 +218,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl })) } - public override func pushViewController(_ viewController: UIViewController, animated: Bool) { + open override func pushViewController(_ viewController: UIViewController, animated: Bool) { self.currentPushDisposable.set(nil) var controllers = self.viewControllers @@ -226,7 +226,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl self.setViewControllers(controllers, animated: animated) } - public override func popViewController(animated: Bool) -> UIViewController? { + open override func popViewController(animated: Bool) -> UIViewController? { var controller: UIViewController? var controllers = self.viewControllers if controllers.count != 0 { @@ -237,7 +237,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl return controller } - public override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + open override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { for controller in viewControllers { controller.navigation_setNavigationController(self) } @@ -323,7 +323,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl }) } } else { - if let topController = self.viewControllers.last where topController.isViewLoaded { + if let topController = self.viewControllers.last , topController.isViewLoaded { topController.navigation_setNavigationController(nil) topController.view.removeFromSuperview() } @@ -337,7 +337,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl } } - override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = viewControllerToPresent as? NavigationController { controller.navigation_setDismiss { [weak self] in if let strongSelf = self { @@ -382,7 +382,7 @@ public class NavigationController: NavigationControllerProxy, ContainableControl } } - override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = self.presentedViewController { if flag { UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 2e650df56e..9e38577d36 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -54,7 +54,7 @@ class NavigationTransitionCoordinator { self.dimView.backgroundColor = UIColor.black self.shadowView = UIImageView(image: shadowImage) - if let topNavigationBar = topNavigationBar, bottomNavigationBar = bottomNavigationBar { + if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar { var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container) var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container) topFrame.origin.x = 0.0 @@ -92,7 +92,7 @@ class NavigationTransitionCoordinator { } var dimInset: CGFloat = 0.0 - if let topNavigationBar = self.topNavigationBar where self.inlineNavigationBarTransition { + if let topNavigationBar = self.topNavigationBar , self.inlineNavigationBarTransition { dimInset = topNavigationBar.frame.size.height } @@ -109,7 +109,7 @@ class NavigationTransitionCoordinator { } func updateNavigationBarTransition() { - if let topNavigationBar = self.topNavigationBar, bottomNavigationBar = self.bottomNavigationBar { + if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { let position: CGFloat switch self.transition { case .Push: @@ -124,7 +124,7 @@ class NavigationTransitionCoordinator { } func maybeCreateNavigationBarTransition() { - if let topNavigationBar = self.topNavigationBar, bottomNavigationBar = self.bottomNavigationBar { + if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { let position: CGFloat switch self.transition { case .Push: @@ -139,13 +139,13 @@ class NavigationTransitionCoordinator { } func endNavigationBarTransition() { - if let topNavigationBar = self.topNavigationBar, bottomNavigationBar = self.bottomNavigationBar { + if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { topNavigationBar.transitionState = nil bottomNavigationBar.transitionState = nil } } - func animateCancel(_ completion: () -> ()) { + func animateCancel(_ completion: @escaping () -> ()) { UIView.animate(withDuration: 0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in self.progress = 0.0 }) { (completed) -> Void in @@ -175,7 +175,7 @@ class NavigationTransitionCoordinator { } } - func animateCompletion(_ velocity: CGFloat, completion: () -> ()) { + func animateCompletion(_ velocity: CGFloat, completion: @escaping () -> ()) { let distance = (1.0 - self.progress) * self.container.bounds.size.width let f = { switch self.transition { diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 589fba9030..aad06a904a 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -40,7 +40,7 @@ final class PresentationContext { |> deliverOnMainQueue |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) - if let view = self.view, initialLayout = self.layout { + if let view = self.view, let initialLayout = self.layout { controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) @@ -51,9 +51,9 @@ final class PresentationContext { } strongSelf.controllers.append(controller) - if let view = strongSelf.view, layout = strongSelf.layout { + if let view = strongSelf.view, let layout = strongSelf.layout { controller.navigation_setDismiss { [weak strongSelf, weak controller] in - if let strongSelf = strongSelf, controller = controller { + if let strongSelf = strongSelf, let controller = controller { strongSelf.dismiss(controller) } } @@ -110,7 +110,7 @@ final class PresentationContext { } private func addViews() { - if let view = self.view, layout = self.layout { + if let view = self.view, let layout = self.layout { for controller in self.controllers { controller.viewWillAppear(false) view.addSubview(controller.view) diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index d085008c12..f8efdf92e1 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -27,7 +27,7 @@ private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusB if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { return surface } - if let lhsStatusBar = surface.statusBars[i - 1].statusBar, rhsStatusBar = surface.statusBars[i].statusBar where !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) { + if let lhsStatusBar = surface.statusBars[i - 1].statusBar, let rhsStatusBar = surface.statusBars[i].statusBar , !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) { return surface } } @@ -91,7 +91,7 @@ class StatusBarManager { outer: for surface in mappedSurfaces { for mappedStatusBar in surface.statusBars { if mappedStatusBar.frame.origin.equalTo(CGPoint()) { - if let statusBar = mappedStatusBar.statusBar where !statusBar.layer.hasPositionOrOpacityAnimations() { + if let statusBar = mappedStatusBar.statusBar , !statusBar.layer.hasPositionOrOpacityAnimations() { mappedSurfaces = [MappedStatusBarSurface(statusBars: [mappedStatusBar], surface: surface.surface)] break outer } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 2141f6c1df..b9c2ffc258 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -29,22 +29,22 @@ private class StatusBarItemNode: ASDisplayNode { func update() { let context = DrawingContext(size: self.targetView.frame.size, clear: true) - if let contents = self.targetView.layer.contents where (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents) == CGImage.typeID && false { + if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as! CFTypeRef) == CGImage.typeID && false { let image = contents as! CGImage context.withFlippedContext { c in c.setAlpha(CGFloat(self.targetView.layer.opacity)) - c.draw(in: CGRect(origin: CGPoint(), size: context.size), image: image) + c.draw(image, in: CGRect(origin: CGPoint(), size: context.size)) c.setAlpha(1.0) } if let sublayers = self.targetView.layer.sublayers { for sublayer in sublayers { let origin = sublayer.frame.origin - if let contents = sublayer.contents where CFGetTypeID(contents) == CGImage.typeID { + if let contents = sublayer.contents , CFGetTypeID(contents as! CFTypeRef) == CGImage.typeID { let image = contents as! CGImage context.withFlippedContext { c in c.translateBy(x: origin.x, y: origin.y) - c.draw(in: CGRect(origin: CGPoint(), size: context.size), image: image) + c.draw(image, in: CGRect(origin: CGPoint(), size: context.size)) c.translateBy(x: -origin.x, y: -origin.y) } } else { @@ -82,7 +82,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let maxY = Int(context.size.height * context.scale) let maxX = Int(context.size.width * context.scale) if minY < maxY && minX < maxX { - let basePixel = UnsafeMutablePointer(context.bytes) + let basePixel = context.bytes.assumingMemoryBound(to: UInt32.self) let pixelsPerRow = context.bytesPerRow / 4 let midX = (maxX + minX) / 2 @@ -152,8 +152,8 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseG = (baseColor >> 8) & 0xff let baseB = baseColor & 0xff - var pixel = UnsafeMutablePointer(context.bytes) - let end = UnsafeMutablePointer(context.bytes + context.length) + var pixel = context.bytes.assumingMemoryBound(to: UInt32.self) + let end = context.bytes.advanced(by: context.length).assumingMemoryBound(to: UInt32.self) while pixel != end { let alpha = (pixel.pointee & 0xff000000) >> 24 @@ -188,8 +188,8 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp } } case .Generic: - var pixel = UnsafeMutablePointer(context.bytes) - let end = UnsafeMutablePointer(context.bytes + context.length) + var pixel = context.bytes.assumingMemoryBound(to: UInt32.self) + let end = context.bytes.advanced(by: context.length).assumingMemoryBound(to: UInt32.self) let baseColor: UInt32 switch style { @@ -222,7 +222,7 @@ private let batteryItemClass: AnyClass? = NSClassFromString("UIStatusBarBatteryI private class StatusBarProxyNodeTimerTarget: NSObject { let action: () -> Void - init(action: () -> Void) { + init(action: @escaping () -> Void) { self.action = action } diff --git a/Display/SystemContainedControllerTransitionCoordinator.swift b/Display/SystemContainedControllerTransitionCoordinator.swift index 69c6025858..723d5e36b5 100644 --- a/Display/SystemContainedControllerTransitionCoordinator.swift +++ b/Display/SystemContainedControllerTransitionCoordinator.swift @@ -1,6 +1,6 @@ import UIKit -final class SystemContainedControllerTransitionCoordinator:NSObject, UIViewControllerTransitionCoordinator { +final class SystemContainedControllerTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator { public var isAnimated: Bool { return false } @@ -39,11 +39,11 @@ final class SystemContainedControllerTransitionCoordinator:NSObject, UIViewContr return .easeInOut } - public func viewController(forKey key: String) -> UIViewController? { + public func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController? { return nil } - public func view(forKey key: String) -> UIView? { + public func view(forKey key: UITransitionContextViewKey) -> UIView? { return nil } @@ -55,19 +55,19 @@ final class SystemContainedControllerTransitionCoordinator:NSObject, UIViewContr return CGAffineTransform.identity } - public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { + public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: (@escaping (UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { return false } - public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { + public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: (@escaping (UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { return false } - public func notifyWhenInteractionEnds(_ handler: (UIViewControllerTransitionCoordinatorContext) -> ()) { + public func notifyWhenInteractionEnds(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> ()) { } - public func notifyWhenInteractionChanges(_ handler: (UIViewControllerTransitionCoordinatorContext) -> ()) { + public func notifyWhenInteractionChanges(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> ()) { } } diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 7ef4acd2af..7c8ff8792a 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -14,7 +14,7 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(itemSelected: (Int) -> Void) { + init(itemSelected: @escaping (Int) -> Void) { self.tabBarNode = TabBarNode(itemSelected: itemSelected) super.init(viewBlock: { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 387d91eaa5..a9d51ee2f9 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -68,7 +68,7 @@ class TabBarNode: ASDisplayNode { let separatorNode: ASDisplayNode private var tabBarNodes: [ASImageNode] = [] - init(itemSelected: (Int) -> Void) { + init(itemSelected: @escaping (Int) -> Void) { self.itemSelected = itemSelected self.separatorNode = ASDisplayNode() @@ -96,7 +96,7 @@ class TabBarNode: ASDisplayNode { node.displaysAsynchronously = false node.displayWithoutProcessing = true node.isLayerBacked = true - if let selectedIndex = self.selectedIndex where selectedIndex == i { + if let selectedIndex = self.selectedIndex , selectedIndex == i { node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) @@ -115,7 +115,7 @@ class TabBarNode: ASDisplayNode { let node = self.tabBarNodes[index] let item = self.tabBarItems[index] - if let selectedIndex = self.selectedIndex where selectedIndex == index { + if let selectedIndex = self.selectedIndex , selectedIndex == index { node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) diff --git a/Display/UniversalTapRecognizer.swift b/Display/UniversalTapRecognizer.swift index 47477fff9d..a98268147b 100644 --- a/Display/UniversalTapRecognizer.swift +++ b/Display/UniversalTapRecognizer.swift @@ -4,7 +4,7 @@ import UIKit.UIGestureRecognizerSubclass private class TimerTargetWrapper: NSObject { let f: () -> Void - init(_ f: () -> Void) { + init(_ f: @escaping () -> Void) { self.f = f } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 4564136d5f..aaa22f1f77 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -3,7 +3,20 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -@objc public class ViewController: UIViewController, ContainableController { +private func findCurrentResponder(_ view: UIView) -> UIResponder? { + if view.isFirstResponder { + return view + } else { + for subview in view.subviews { + if let result = findCurrentResponder(subview) { + return result + } + } + return nil + } +} + +@objc open class ViewController: UIViewController, ContainableController { private var containerLayout = ContainerViewLayout() private let presentationContext: PresentationContext @@ -37,8 +50,11 @@ import SwiftSignalKit public var displayNavigationBar = true + private weak var activeInputViewCandidate: UIResponder? + private weak var activeInputView: UIResponder? + private let _ready = Promise(true) - public var ready: Promise { + open var ready: Promise { return self._ready } @@ -53,7 +69,7 @@ import SwiftSignalKit private func updateScrollToTopView() { if self.scrollToTop != nil { - if let displayNode = self._displayNode where self.scrollToTopView == nil { + if let displayNode = self._displayNode , self.scrollToTopView == nil { let scrollToTopView = ScrollToTopView(frame: CGRect(x: 0.0, y: -1.0, width: displayNode.frame.size.width, height: 1.0)) scrollToTopView.action = { [weak self] in if let scrollToTop = self?.scrollToTop { @@ -91,7 +107,7 @@ import SwiftSignalKit } - public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.containerLayout = layout if !self.isViewLoaded { @@ -122,19 +138,19 @@ import SwiftSignalKit } } - public override func loadView() { + open override func loadView() { self.view = self.displayNode.view self.displayNode.addSubnode(self.navigationBar) self.view.addSubview(self.statusBar.view) self.presentationContext.view = self.view } - public func loadDisplayNode() { + open func loadDisplayNode() { self.displayNode = ASDisplayNode() self.displayNodeDidLoad() } - public func displayNodeDidLoad() { + open func displayNodeDidLoad() { self.updateScrollToTopView() } @@ -158,11 +174,11 @@ import SwiftSignalKit } } - override public func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { preconditionFailure("use present(_:in)") } - override public func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { + override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { navigationController.dismiss(animated: flag, completion: completion) } else { @@ -192,4 +208,22 @@ import SwiftSignalKit self.window?.present(controller) } } + + open override func viewWillDisappear(_ animated: Bool) { + self.activeInputViewCandidate = findCurrentResponder(self.view) + + super.viewWillDisappear(animated) + } + + open override func viewDidDisappear(_ animated: Bool) { + self.activeInputView = self.activeInputViewCandidate + + super.viewDidDisappear(animated) + } + + open override func viewDidAppear(_ animated: Bool) { + self.activeInputView = nil + + super.viewDidAppear(animated) + } } diff --git a/Display/Window.swift b/Display/Window.swift index 2b724f13dd..bfe5fc62ea 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -305,7 +305,7 @@ public class Window: UIWindow { } } - public func addPostUpdateToInterfaceOrientationBlock(f: (Void) -> Void) { + public func addPostUpdateToInterfaceOrientationBlock(f: @escaping (Void) -> Void) { postUpdateToInterfaceOrientationBlocks.append(f) } From 684ab193c8342c82b791c4c9fc3d45423dd4d635 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 5 Sep 2016 23:19:33 +0300 Subject: [PATCH 022/245] no message --- Display.xcodeproj/project.pbxproj | 28 ++++++ .../xcschemes/Display.xcscheme | 80 +++++++++++++++ .../xcschemes/xcschememanagement.plist | 2 +- Display/CAAnimationUtils.swift | 2 +- Display/ContainableController.swift | 36 +++++++ Display/ContainerViewLayout.swift | 8 +- Display/ContextMenuAction.swift | 14 +++ Display/ContextMenuActionNode.swift | 58 +++++++++++ Display/ContextMenuContainerNode.swift | 84 ++++++++++++++++ Display/ContextMenuController.swift | 72 ++++++++++++++ Display/ContextMenuNode.swift | 99 +++++++++++++++++++ Display/Font.swift | 6 +- Display/GenerateImage.swift | 33 ++++++- Display/ListView.swift | 56 ++++++++--- Display/ListViewItemNode.swift | 3 - Display/ListViewScroller.swift | 4 + Display/NavigationBar.swift | 49 ++++++++- Display/NavigationButtonNode.swift | 30 ++++++ Display/NavigationController.swift | 5 +- Display/StatusBarHost.swift | 2 + Display/UIBarButtonItem+Proxy.h | 5 + Display/UIBarButtonItem+Proxy.m | 17 +++- Display/UIKitUtils.m | 3 +- Display/UINavigationItem+Proxy.m | 14 ++- Display/Window.swift | 1 + 25 files changed, 680 insertions(+), 31 deletions(-) create mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme create mode 100644 Display/ContextMenuAction.swift create mode 100644 Display/ContextMenuActionNode.swift create mode 100644 Display/ContextMenuContainerNode.swift create mode 100644 Display/ContextMenuController.swift create mode 100644 Display/ContextMenuNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 780cab3d93..b81f97ac7b 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -13,7 +13,11 @@ D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; + D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; + D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; + D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */; }; D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */; }; D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* Theme.swift */; }; D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; @@ -96,6 +100,7 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; + D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,7 +120,11 @@ D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; + D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; + D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; + D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuController.swift; sourceTree = ""; }; D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHost.swift; sourceTree = ""; }; D03BCCEA1C72AE590097A291 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; @@ -202,6 +211,7 @@ D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; + D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -267,6 +277,18 @@ name = Nodes; sourceTree = ""; }; + D03725BF1D6DF57B007FC290 /* Context Menu */ = { + isa = PBXGroup; + children = ( + D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */, + D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */, + D03725C01D6DF594007FC290 /* ContextMenuNode.swift */, + D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */, + D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */, + ); + name = "Context Menu"; + sourceTree = ""; + }; D03BCCE91C72AE4B0097A291 /* Theme */ = { isa = PBXGroup; children = ( @@ -456,6 +478,7 @@ D081229A1D19A9EB005F7395 /* Navigation */, D015F7551D1B142300E269B5 /* Tab Bar */, D015F7561D1B465600E269B5 /* Action Sheet */, + D03725BF1D6DF57B007FC290 /* Context Menu */, ); name = Controllers; sourceTree = ""; @@ -620,9 +643,11 @@ D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, + D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, + D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */, D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */, D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, @@ -660,8 +685,11 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, + D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, + D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, + D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */, D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme new file mode 100644 index 0000000000..e81aa39f54 --- /dev/null +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index aa5ae0473f..b987a65bba 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 0 + 20 DisplayTests.xcscheme diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 92bf148c4e..74c9a54705 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -108,7 +108,7 @@ public extension CALayer { } public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index fc6451e52e..04f1b2fb64 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -40,6 +40,42 @@ public extension ContainedViewLayoutTransition { }) } } + + func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + layer.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = layer.frame + layer.frame = frame + layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + node.alpha = alpha + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = node.alpha + node.alpha = alpha + node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } } public protocol ContainableController: class { diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index f2df5b9655..74a06eda9b 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -61,7 +61,9 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { if let lhsStatusBarHeight = lhs.statusBarHeight { if let rhsStatusBarHeight = rhs.statusBarHeight { - return lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) + if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { + return false + } } else { return false } @@ -71,7 +73,9 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { if let lhsInputHeight = lhs.inputHeight { if let rhsInputHeight = rhs.inputHeight { - return lhsInputHeight.isEqual(to: rhsInputHeight) + if !lhsInputHeight.isEqual(to: rhsInputHeight) { + return false + } } else { return false } diff --git a/Display/ContextMenuAction.swift b/Display/ContextMenuAction.swift new file mode 100644 index 0000000000..898a8db7c1 --- /dev/null +++ b/Display/ContextMenuAction.swift @@ -0,0 +1,14 @@ + +public enum ContextMenuActionContent { + case text(String) +} + +public struct ContextMenuAction { + public let content: ContextMenuActionContent + public let action: () -> Void + + public init(content: ContextMenuActionContent, action: @escaping () -> Void) { + self.content = content + self.action = action + } +} diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift new file mode 100644 index 0000000000..8e7b91ce68 --- /dev/null +++ b/Display/ContextMenuActionNode.swift @@ -0,0 +1,58 @@ +import Foundation +import AsyncDisplayKit + +final class ContextMenuActionNode: ASDisplayNode { + private let textNode: ASTextNode + private let action: () -> Void + private let button: HighlightTrackingButton + + var dismiss: (() -> Void)? + + init(action: ContextMenuAction) { + self.textNode = ASTextNode() + switch action.content { + case let .text(title): + self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white) + } + self.action = action.action + + self.button = HighlightTrackingButton() + + super.init() + + self.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + self.addSubnode(self.textNode) + + self.button.highligthedChanged = { [weak self] highlighted in + self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8) + } + self.view.addSubview(self.button) + } + + override func didLoad() { + super.didLoad() + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside]) + } + + @objc private func buttonPressed() { + self.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + + self.action() + if let dismiss = self.dismiss { + dismiss() + } + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let textSize = self.textNode.measure(constrainedSize) + return CGSize(width: textSize.width + 36.0, height: 54.0) + } + + override func layout() { + super.layout() + + self.button.frame = self.bounds + self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - self.textNode.calculatedSize.width) / 2.0), y: floor((self.bounds.size.height - self.textNode.calculatedSize.height) / 2.0)), size: self.textNode.calculatedSize) + } +} diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift new file mode 100644 index 0000000000..c7d22be218 --- /dev/null +++ b/Display/ContextMenuContainerNode.swift @@ -0,0 +1,84 @@ +import Foundation +import AsyncDisplayKit + +private struct CachedMaskParams: Equatable { + let size: CGSize + let relativeArrowPosition: CGFloat + let arrowOnBottom: Bool +} + +private func ==(lhs: CachedMaskParams, rhs: CachedMaskParams) -> Bool { + return lhs.size.equalTo(rhs.size) && lhs.relativeArrowPosition.isEqual(to: rhs.relativeArrowPosition) && lhs.arrowOnBottom == rhs.arrowOnBottom +} + +private final class ContextMenuContainerMaskView: UIView { + override class var layerClass: AnyClass { + return CAShapeLayer.self + } +} + +final class ContextMenuContainerNode: ASDisplayNode { + private var cachedMaskParams: CachedMaskParams? + private let maskView = ContextMenuContainerMaskView() + + var relativeArrowPosition: (CGFloat, Bool)? + + //private let effectView: UIVisualEffectView + + override init() { + //self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + + super.init() + + self.backgroundColor = UIColor(0xeaecec) + //self.view.addSubview(self.effectView) + //self.effectView.mask = self.maskView + self.view.mask = self.maskView + } + + override func didLoad() { + super.didLoad() + + self.layer.allowsGroupOpacity = true + } + + override func layout() { + super.layout() + + //self.effectView.frame = self.bounds + + let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true) + if self.cachedMaskParams != maskParams { + let path = UIBezierPath() + let cornerRadius: CGFloat = 6.0 + let verticalInset: CGFloat = 9.0 + let arrowWidth: CGFloat = 18.0 + let requestedArrowPosition = maskParams.relativeArrowPosition + let arrowPosition = max(cornerRadius + arrowWidth / 2.0, min(maskParams.size.width - cornerRadius - arrowWidth / 2.0, requestedArrowPosition)) + let arrowOnBottom = maskParams.arrowOnBottom + + path.move(to: CGPoint(x: 0.0, y: verticalInset + cornerRadius)) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(M_PI), endAngle: CGFloat(3 * M_PI / 2), clockwise: true) + if !arrowOnBottom { + path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: verticalInset)) + path.addLine(to: CGPoint(x: arrowPosition, y: 0.0)) + path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: verticalInset)) + } + path.addLine(to: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset)) + path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(3 * M_PI / 2), endAngle: 0.0, clockwise: true) + path.addLine(to: CGPoint(x: maskParams.size.width, y: maskParams.size.height - cornerRadius - verticalInset)) + path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: 0.0, endAngle: CGFloat(M_PI / 2.0), clockwise: true) + if arrowOnBottom { + path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: maskParams.size.height - verticalInset)) + path.addLine(to: CGPoint(x: arrowPosition, y: maskParams.size.height)) + path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: maskParams.size.height - verticalInset)) + } + path.addLine(to: CGPoint(x: cornerRadius, y: maskParams.size.height - verticalInset)) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: CGFloat(M_PI / 2.0), endAngle: CGFloat(M_PI), clockwise: true) + path.close() + + self.cachedMaskParams = maskParams + (self.maskView.layer as? CAShapeLayer)?.path = path.cgPath + } + } +} diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift new file mode 100644 index 0000000000..3572374577 --- /dev/null +++ b/Display/ContextMenuController.swift @@ -0,0 +1,72 @@ +import Foundation +import AsyncDisplayKit + +public final class ContextMenuControllerPresentationArguments { + fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect)? + + public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect)?) { + self.sourceNodeAndRect = sourceNodeAndRect + } +} + +public final class ContextMenuController: ViewController { + private var contextMenuNode: ContextMenuNode { + return self.displayNode as! ContextMenuNode + } + + private let actions: [ContextMenuAction] + + private var layout: ContainerViewLayout? + + public init(actions: [ContextMenuAction]) { + self.actions = actions + + super.init() + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func loadDisplayNode() { + self.displayNode = ContextMenuNode(actions: self.actions, dismiss: { [weak self] in + self?.contextMenuNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + }) + self.displayNodeDidLoad() + self.navigationBar.isHidden = true + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.contextMenuNode.animateIn() + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if self.layout != nil && self.layout! != layout { + self.contextMenuNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + } else { + self.layout = layout + + if let presentationArguments = self.presentationArguments as? ContextMenuControllerPresentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { + self.contextMenuNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) + } else { + self.contextMenuNode.sourceRect = nil + } + + self.contextMenuNode.containerLayoutUpdated(layout, transition: transition) + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.contextMenuNode.animateIn() + } +} diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift new file mode 100644 index 0000000000..40b82270ff --- /dev/null +++ b/Display/ContextMenuNode.swift @@ -0,0 +1,99 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +final class ContextMenuNode: ASDisplayNode { + private let actions: [ContextMenuAction] + private let dismiss: () -> Void + + private let containerNode: ContextMenuContainerNode + private let actionNodes: [ContextMenuActionNode] + + var sourceRect: CGRect? + + private var dismissedByTouchOutside = false + + init(actions: [ContextMenuAction], dismiss: @escaping () -> Void) { + self.actions = actions + self.dismiss = dismiss + + self.containerNode = ContextMenuContainerNode() + + self.actionNodes = actions.map { action in + return ContextMenuActionNode(action: action) + } + + super.init() + + self.addSubnode(self.containerNode) + let dismissNode = { [weak self] in + dismiss() + } + for actionNode in self.actionNodes { + actionNode.dismiss = dismissNode + self.containerNode.addSubnode(actionNode) + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var actionsWidth: CGFloat = 0.0 + let actionSeparatorWidth: CGFloat = UIScreenPixel + for actionNode in self.actionNodes { + if !actionsWidth.isZero { + actionsWidth += actionSeparatorWidth + } + let actionSize = actionNode.measure(CGSize(width: layout.size.width, height: 54.0)) + actionNode.frame = CGRect(origin: CGPoint(x: actionsWidth, y: 0.0), size: actionSize) + actionsWidth += actionSize.width + } + + let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize()) + + let insets = layout.insets(options: [.statusBar, .input]) + + let verticalOrigin: CGFloat + var arrowOnBottom = true + if sourceRect.minY - 54.0 > insets.top { + verticalOrigin = sourceRect.minY - 54.0 + } else { + verticalOrigin = min(layout.size.height - insets.bottom - 54.0, sourceRect.maxY) + arrowOnBottom = false + } + + let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) + + self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0)) + self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) + + self.containerNode.layout() + } + + func animateIn() { + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + func animateOut(completion: @escaping () -> Void) { + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + completion() + }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let event = event { + var eventIsPresses = false + if #available(iOSApplicationExtension 9.0, *) { + eventIsPresses = event.type == .presses + } + if event.type == .touches || eventIsPresses { + if !self.containerNode.frame.contains(point) { + if !self.dismissedByTouchOutside { + self.dismissedByTouchOutside = true + self.dismiss() + } + return nil + } + } + } + return super.hitTest(point, with: event) + } +} diff --git a/Display/Font.swift b/Display/Font.swift index 862078c6ac..295dd1e4f2 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -7,7 +7,11 @@ public struct Font { } public static func medium(_ size: CGFloat) -> UIFont { - return UIFont.boldSystemFont(ofSize: size) + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: UIFontWeightMedium) + } else { + return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) + } } } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index ecd6729ba5..3511197194 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -4,7 +4,7 @@ import UIKit let deviceColorSpace = CGColorSpaceCreateDeviceRGB() let deviceScale = UIScreen.main.scale -public func generateImage(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { +public func generateImagePixel(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { let scale = deviceScale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) @@ -84,6 +84,37 @@ public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor return generateFilledCircleImage(radius: radius, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)) } +public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor: UIColor? = nil) -> UIImage? { + guard let image = image else { + return nil + } + + let imageSize = image.size + + UIGraphicsBeginImageContextWithOptions(imageSize, backgroundColor != nil, image.scale) + if let context = UIGraphicsGetCurrentContext() { + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: imageSize)) + } + + let imageRect = CGRect(origin: CGPoint(), size: imageSize) + context.saveGState() + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(color.cgColor) + context.fill(imageRect) + context.restoreGState() + } + + let tintedImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + + return tintedImage +} + private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { return generateImage(size, contextGenerator: { size, context in context.setFillColor(color.cgColor) diff --git a/Display/ListView.swift b/Display/ListView.swift index 970e34e9f8..ded97be467 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -48,7 +48,7 @@ public enum ListViewScrollToItemDirectionHint { } public enum ListViewAnimationCurve { - case Spring(speed: CGFloat) + case Spring(duration: Double) case Default } @@ -922,6 +922,12 @@ private final class ListViewTimerProxy: NSObject { } } +public enum ListViewVisibleContentOffset { + case known(CGFloat) + case unknown + case none +} + public final class ListView: ASDisplayNode, UIScrollViewDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() @@ -965,7 +971,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { public final var displayedItemRangeChanged: (ListViewDisplayedItemRange) -> Void = { _ in } public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) - public final var visibleContentOffsetChanged: (CGFloat?) -> Void = { _ in } + public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } private final var animations: [ListViewAnimation] = [] private final var actionsForVSync: [() -> ()] = [] @@ -1029,7 +1035,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.scroller.isHidden = true self.scroller.delegate = self self.view.addSubview(self.scroller) - self.scroller.panGestureRecognizer.cancelsTouchesInView = false + self.scroller.panGestureRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) @@ -1279,9 +1285,18 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } private func updateVisibleContentOffset() { - var offset: CGFloat? - if let itemNode = self.itemNodes.first, let index = itemNode.index , index == 0 { - offset = -(itemNode.apparentFrame.minY - self.insets.top) + var offset: ListViewVisibleContentOffset = .unknown + var topItemIndexAndFrame: (Int, CGRect) = (-1, CGRect()) + for itemNode in self.itemNodes { + if let index = itemNode.index { + topItemIndexAndFrame = (index, itemNode.apparentFrame) + break + } + } + if topItemIndexAndFrame.0 == 0 { + offset = .known(-(topItemIndexAndFrame.1.minY - self.insets.top)) + } else if topItemIndexAndFrame.0 == -1 { + offset = .none } self.visibleContentOffsetChanged(offset) @@ -1888,10 +1903,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { //self.addSubnode(node) } - if previousFrame == nil { - node.setupGestures() - } - var offsetHeight = node.apparentHeight var takenAnimation = false @@ -2202,12 +2213,19 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if updateSizeAndInsets.duration > DBL_EPSILON { let animation: CABasicAnimation switch updateSizeAndInsets.curve { - case let .Spring(speed): + case let .Spring(duration): let springAnimation = makeSpringAnimation("sublayerTransform") - springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) springAnimation.isRemovedOnCompletion = true + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + springAnimation.isAdditive = true animation = springAnimation case .Default: @@ -2284,14 +2302,21 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let animation: CABasicAnimation switch scrollToItem.curve { - case let .Spring(speed): + case let .Spring(duration): let springAnimation = makeSpringAnimation("sublayerTransform") springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) springAnimation.isRemovedOnCompletion = true springAnimation.isAdditive = true springAnimation.fillMode = kCAFillModeForwards - springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + animation = springAnimation case .Default: let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") @@ -2877,9 +2902,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in if let strongSelf = self , strongSelf.selectionTouchLocation != nil { strongSelf.clearHighlightAnimated(false) - let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) - if let index = index { + if let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) { if strongSelf.items[index].selectable { strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index c2dfd53fbc..a4e3480e2d 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -413,9 +413,6 @@ open class ListViewItemNode: ASDisplayNode { open func setHighlighted(_ highlighted: Bool, animated: Bool) { } - open func setupGestures() { - } - open func animateFrameTransition(_ progress: CGFloat) { } diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 73d8ff4b1a..935a6fa064 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -14,4 +14,8 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } + + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index fa4dd9cc10..47458f96d7 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -44,6 +44,7 @@ public class NavigationBar: ASDisplayNode { didSet { self.backButtonNode.color = self.accentColor self.leftButtonNode.color = self.accentColor + self.rightButtonNode.color = self.accentColor self.backButtonArrow.image = backArrowImage(color: self.accentColor) } } @@ -68,6 +69,7 @@ public class NavigationBar: ASDisplayNode { private var itemTitleListenerKey: Int? private var itemTitleViewListenerKey: Int? private var itemLeftButtonListenerKey: Int? + private var itemRightButtonListenerKey: Int? private var _item: UINavigationItem? var item: UINavigationItem? { get { @@ -86,6 +88,7 @@ public class NavigationBar: ASDisplayNode { self._item = value self.leftButtonNode.removeFromSupernode() + self.rightButtonNode.removeFromSupernode() if let item = value { self.title = item.title @@ -105,13 +108,25 @@ public class NavigationBar: ASDisplayNode { self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] _, _ in if let strongSelf = self { strongSelf.updateLeftButton() + strongSelf.invalidateCalculatedLayout() + strongSelf.setNeedsLayout() + } + } + + self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] _, _ in + if let strongSelf = self { + strongSelf.updateRightButton() + strongSelf.invalidateCalculatedLayout() + strongSelf.setNeedsLayout() } } self.updateLeftButton() + self.updateRightButton() } else { self.title = nil self.updateLeftButton() + self.updateRightButton() } self.invalidateCalculatedLayout() } @@ -207,9 +222,26 @@ public class NavigationBar: ASDisplayNode { } } + private func updateRightButton() { + if let item = self.item { + if let rightBarButtonItem = item.rightBarButtonItem { + self.rightButtonNode.text = rightBarButtonItem.title ?? "" + self.rightButtonNode.node = rightBarButtonItem.customDisplayNode + if self.rightButtonNode.supernode == nil { + self.clippingNode.addSubnode(self.rightButtonNode) + } + } else { + self.rightButtonNode.removeFromSupernode() + } + } else { + self.rightButtonNode.removeFromSupernode() + } + } + private let backButtonNode: NavigationButtonNode private let backButtonArrow: ASImageNode private let leftButtonNode: NavigationButtonNode + private let rightButtonNode: NavigationButtonNode private var _transitionState: NavigationBarTransitionState? var transitionState: NavigationBarTransitionState? { @@ -280,6 +312,7 @@ public class NavigationBar: ASDisplayNode { self.backButtonArrow.displaysAsynchronously = false self.backButtonArrow.image = backArrowImage(color: self.accentColor) self.leftButtonNode = NavigationButtonNode() + self.rightButtonNode = NavigationButtonNode() self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true @@ -316,6 +349,12 @@ public class NavigationBar: ASDisplayNode { leftBarButtonItem.performActionOnTarget() } } + + self.rightButtonNode.pressed = { [weak self] in + if let item = self?.item, let rightBarButtonItem = item.rightBarButtonItem { + rightBarButtonItem.performActionOnTarget() + } + } } public override func layout() { @@ -332,6 +371,7 @@ public class NavigationBar: ASDisplayNode { var contentVerticalOrigin = size.height - nominalHeight var leftTitleInset: CGFloat = 8.0 + var rightTitleInset: CGFloat = 8.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) leftTitleInset += backButtonSize.width + backButtonInset + 8.0 + 8.0 @@ -382,6 +422,13 @@ public class NavigationBar: ASDisplayNode { self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize) } + if self.rightButtonNode.supernode != nil { + let rightButtonSize = self.rightButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + rightTitleInset += rightButtonSize.width + leftButtonInset + 8.0 + 8.0 + self.rightButtonNode.alpha = 1.0 + self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize) + } + if let transitionState = self.transitionState { let progress = transitionState.progress @@ -409,7 +456,7 @@ public class NavigationBar: ASDisplayNode { } if self.titleNode.supernode != nil { - let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight)) + let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { let progress = transitionState.progress diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 15f19a0a2d..fe0a874573 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -25,6 +25,19 @@ public class NavigationButtonNode: ASTextNode { } } + public var node: ASDisplayNode? { + didSet { + if self.node !== oldValue { + oldValue?.removeFromSupernode() + if let node = self.node { + self.addSubnode(node) + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + } + } + public var color: UIColor = UIColor(0x1195f2) { didSet { if let text = self._text { @@ -60,6 +73,23 @@ public class NavigationButtonNode: ASTextNode { self.displaysAsynchronously = false } + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let superSize = super.calculateSizeThatFits(constrainedSize) + if let node = self.node { + let nodeSize = node.measure(constrainedSize) + return CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + } + return superSize + } + + override public func layout() { + super.layout() + + if let node = self.node { + node.frame = CGRect(origin: CGPoint(), size: node.calculatedSize) + } + } + private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index f1f3e777cb..7114d4ca23 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -409,6 +409,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return otherGestureRecognizer is UIPanGestureRecognizer + if let panRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false } } diff --git a/Display/StatusBarHost.swift b/Display/StatusBarHost.swift index 90707fe7da..073aeaf6c9 100644 --- a/Display/StatusBarHost.swift +++ b/Display/StatusBarHost.swift @@ -5,4 +5,6 @@ public protocol StatusBarHost { var statusBarStyle: UIStatusBarStyle { get set } var statusBarWindow: UIView? { get } var statusBarView: UIView? { get } + + var keyboardView: UIView? { get } } diff --git a/Display/UIBarButtonItem+Proxy.h b/Display/UIBarButtonItem+Proxy.h index 031bf15fb3..13ada65244 100644 --- a/Display/UIBarButtonItem+Proxy.h +++ b/Display/UIBarButtonItem+Proxy.h @@ -1,10 +1,15 @@ #import +#import typedef void (^UIBarButtonItemSetTitleListener)(NSString *); typedef void (^UIBarButtonItemSetEnabledListener)(BOOL); @interface UIBarButtonItem (Proxy) +@property (nonatomic, strong, readonly) ASDisplayNode *customDisplayNode; + +- (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode; + - (void)performActionOnTarget; - (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener; diff --git a/Display/UIBarButtonItem+Proxy.m b/Display/UIBarButtonItem+Proxy.m index 01e6ad0e0c..e9d486bc7a 100644 --- a/Display/UIBarButtonItem+Proxy.m +++ b/Display/UIBarButtonItem+Proxy.m @@ -5,6 +5,7 @@ static const void *setEnabledListenerBagKey = &setEnabledListenerBagKey; static const void *setTitleListenerBagKey = &setTitleListenerBagKey; +static const void *customDisplayNodeKey = &customDisplayNodeKey; @implementation UIBarButtonItem (Proxy) @@ -18,6 +19,18 @@ static const void *setTitleListenerBagKey = &setTitleListenerBagKey; }); } +- (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode { + self = [super init]; + if (self != nil) { + [self setAssociatedObject:customDisplayNode forKey:customDisplayNodeKey]; + } + return self; +} + +- (ASDisplayNode *)customDisplayNode { + return [self associatedObjectForKey:customDisplayNodeKey]; +} + - (void)_c1e56039_setEnabled:(BOOL)enabled { [self _c1e56039_setEnabled:enabled]; @@ -40,7 +53,9 @@ static const void *setTitleListenerBagKey = &setTitleListenerBagKey; - (void)performActionOnTarget { - NSAssert(self.target != nil, @"self.target != nil"); + if (self.target == nil) { + return; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 256b17d629..76acce5862 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -37,7 +37,8 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { springAnimation.stiffness = 1000.0f; springAnimation.damping = 500.0f; springAnimation.initialVelocity = 0.0f; - springAnimation.duration = springAnimation.settlingDuration; + springAnimation.duration = 0.5;//springAnimation.settlingDuration; + springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; } diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index 2a8b1a1b5c..e8ad9c81a8 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -17,8 +17,10 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_ac91f40f_setTitle:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitleView:) newSelector:@selector(_ac91f40f_setTitleView:)]; - [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; - [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; }); } @@ -40,6 +42,10 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL }]; } +- (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem { + [self setLeftBarButtonItem:leftBarButtonItem animated:false]; +} + - (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated { [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; @@ -49,6 +55,10 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL }]; } +- (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem { + [self setRightBarButtonItem:rightBarButtonItem animated:false]; +} + - (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem animated:(BOOL)animated { [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; diff --git a/Display/Window.swift b/Display/Window.swift index bfe5fc62ea..d5ab98880d 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -157,6 +157,7 @@ public class Window: UIWindow { } else { transitionCurve = .easeInOut } + strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) } } }) From a3bbfd59b35d98fc49aeffd4cc5764d69dc2ced8 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 7 Oct 2016 19:13:32 +0300 Subject: [PATCH 023/245] no message --- Display.xcodeproj/project.pbxproj | 38 +- Display/CAAnimationUtils.swift | 50 ++- Display/ContextMenuNode.swift | 9 +- Display/GridItem.swift | 5 + Display/GridItemNode.swift | 6 + Display/GridNode.swift | 388 ++++++++++++++++++ Display/GridNodeScroller.swift | 29 ++ Display/ListView.swift | 271 ++++-------- Display/ListViewItem.swift | 4 +- Display/ListViewItemNode.swift | 8 +- Display/ListViewTransactionQueue.swift | 4 +- Display/NavigationBar.swift | 8 +- ...ainedControllerTransitionCoordinator.swift | 4 +- Display/UIKitUtils.h | 1 + Display/UIKitUtils.m | 11 + 15 files changed, 627 insertions(+), 209 deletions(-) create mode 100644 Display/GridItem.swift create mode 100644 Display/GridItemNode.swift create mode 100644 Display/GridNode.swift create mode 100644 Display/GridNodeScroller.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index b81f97ac7b..f73fb852a4 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -13,6 +13,10 @@ D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; + D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDD1D9049620066BF65 /* GridNode.swift */; }; + D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; + D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; + D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE31D904A000066BF65 /* GridItem.swift */; }; D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; @@ -120,6 +124,10 @@ D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; + D01E2BDD1D9049620066BF65 /* GridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNode.swift; sourceTree = ""; }; + D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; + D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; + D01E2BE31D904A000066BF65 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; @@ -269,6 +277,26 @@ name = "Action Sheet"; sourceTree = ""; }; + D01E2BDC1D90494A0066BF65 /* Grid Node */ = { + isa = PBXGroup; + children = ( + D01E2BDD1D9049620066BF65 /* GridNode.swift */, + D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */, + D01E2BE31D904A000066BF65 /* GridItem.swift */, + D01E2BE11D9049F60066BF65 /* GridItemNode.swift */, + ); + name = "Grid Node"; + sourceTree = ""; + }; + D01E2BE51D904A530066BF65 /* Collection Nodes */ = { + isa = PBXGroup; + children = ( + D0C2DFBA1CC443080044FF83 /* List Node */, + D01E2BDC1D90494A0066BF65 /* Grid Node */, + ); + name = "Collection Nodes"; + sourceTree = ""; + }; D02BDAEC1B6A7053008AFAD2 /* Nodes */ = { isa = PBXGroup; children = ( @@ -340,7 +368,7 @@ isa = PBXGroup; children = ( D08122991D19A9E0005F7395 /* User Interface */, - D0C2DFBA1CC443080044FF83 /* List View */, + D01E2BE51D904A530066BF65 /* Collection Nodes */, D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, @@ -483,7 +511,7 @@ name = Controllers; sourceTree = ""; }; - D0C2DFBA1CC443080044FF83 /* List View */ = { + D0C2DFBA1CC443080044FF83 /* List Node */ = { isa = PBXGroup; children = ( D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */, @@ -497,7 +525,7 @@ D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */, ); - name = "List View"; + name = "List Node"; sourceTree = ""; }; D0DC48521BF93D7C00F672FD /* Tabs */ = { @@ -661,6 +689,8 @@ D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */, D05CC3161B695A9600E235A3 /* NavigationBar.swift in Sources */, D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, + D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */, + D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */, D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, @@ -691,8 +721,10 @@ D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, + D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */, D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */, D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, + D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */, D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 74c9a54705..95a5cff473 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -83,6 +83,28 @@ public extension CALayer { self.add(animation, forKey: keyPath) } } + + public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation = makeSpringBounceAnimation(keyPath, initialVelocity) + animation.fromValue = from + animation.toValue = to + animation.isRemovedOnCompletion = removeOnCompletion + animation.fillMode = kCAFillModeForwards + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + animation.speed = speed * Float(animation.duration / duration) + animation.isAdditive = additive + + self.add(animation, forKey: keyPath) + } public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { let k = Float(UIView.animationDurationFactor()) @@ -115,7 +137,7 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { if let completion = completion { completion(true) @@ -146,7 +168,29 @@ public extension CALayer { } return } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: nil) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + var interrupted = false + var completedPosition = false + var completedBounds = false + var partialCompletion: () -> Void = { + if interrupted || (completedPosition && completedBounds) { + if let completion = completion { + completion(!interrupted) + } + } + } + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in + if !value { + interrupted = true + } + completedPosition = true + partialCompletion() + }) + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in + if !value { + interrupted = true + } + completedBounds = true + partialCompletion() + }) } } diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index 40b82270ff..8f8aabcbad 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -10,6 +10,7 @@ final class ContextMenuNode: ASDisplayNode { private let actionNodes: [ContextMenuActionNode] var sourceRect: CGRect? + var arrowOnBottom: Bool = true private var dismissedByTouchOutside = false @@ -59,6 +60,7 @@ final class ContextMenuNode: ASDisplayNode { verticalOrigin = min(layout.size.height - insets.bottom - 54.0, sourceRect.maxY) arrowOnBottom = false } + self.arrowOnBottom = arrowOnBottom let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) @@ -69,7 +71,12 @@ final class ContextMenuNode: ASDisplayNode { } func animateIn() { - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + self.containerNode.layer.animateSpring(from: NSNumber(value: Float(0.2)), to: NSNumber(value: Float(1.0)), keyPath: "transform.scale", duration: 0.4) + + let containerPosition = self.containerNode.layer.position + self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: containerPosition.x, y: containerPosition.y + (self.arrowOnBottom ? 1.0 : -1.0) * self.containerNode.bounds.size.height / 2.0)), to: NSValue(cgPoint: containerPosition), keyPath: "position", duration: 0.4) + + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) } func animateOut(completion: @escaping () -> Void) { diff --git a/Display/GridItem.swift b/Display/GridItem.swift new file mode 100644 index 0000000000..bcbe1e9533 --- /dev/null +++ b/Display/GridItem.swift @@ -0,0 +1,5 @@ +import Foundation + +public protocol GridItem { + func node(layout: GridNodeLayout) -> GridItemNode +} diff --git a/Display/GridItemNode.swift b/Display/GridItemNode.swift new file mode 100644 index 0000000000..8a4de56269 --- /dev/null +++ b/Display/GridItemNode.swift @@ -0,0 +1,6 @@ +import Foundation +import AsyncDisplayKit + +open class GridItemNode: ASDisplayNode { + +} diff --git a/Display/GridNode.swift b/Display/GridNode.swift new file mode 100644 index 0000000000..f89d9a24f1 --- /dev/null +++ b/Display/GridNode.swift @@ -0,0 +1,388 @@ +import Foundation +import AsyncDisplayKit + +public struct GridNodeInsertItem { + public let index: Int + public let item: GridItem + public let previousIndex: Int? + + public init(index: Int, item: GridItem, previousIndex: Int?) { + self.index = index + self.item = item + self.previousIndex = previousIndex + } +} + +public struct GridNodeUpdateItem { + public let index: Int + public let item: GridItem + + public init(index: Int, item: GridItem) { + self.index = index + self.item = item + } +} + +public enum GridNodeScrollToItemPosition { + case top + case bottom + case center +} + +public struct GridNodeScrollToItem { + public let index: Int + public let position: GridNodeScrollToItemPosition + + public init(index: Int, position: GridNodeScrollToItemPosition) { + self.index = index + self.position = position + } +} + +public struct GridNodeLayout: Equatable { + public let size: CGSize + public let insets: UIEdgeInsets + public let preloadSize: CGFloat + public let itemSize: CGSize + public let indexOffset: Int + + public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, itemSize: CGSize, indexOffset: Int) { + self.size = size + self.insets = insets + self.preloadSize = preloadSize + self.itemSize = itemSize + self.indexOffset = indexOffset + } + + public static func ==(lhs: GridNodeLayout, rhs: GridNodeLayout) -> Bool { + return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.itemSize.equalTo(rhs.itemSize) && lhs.indexOffset == rhs.indexOffset + } +} + +public struct GridNodeUpdateLayout { + public let layout: GridNodeLayout + public let transition: ContainedViewLayoutTransition + + public init(layout: GridNodeLayout, transition: ContainedViewLayoutTransition) { + self.layout = layout + self.transition = transition + } +} + +/*private func binarySearch(_ inputArr: [GridNodePresentationItem], searchItem: CGFloat) -> Int? { + if inputArr.isEmpty { + return nil + } + + var lowerPosition = inputArr[0].frame.origin.y + inputArr[0].frame.size.height + var upperPosition = inputArr[inputArr.count - 1].frame.origin.y + + if lowerPosition > upperPosition { + return nil + } + + while (true) { + let currentPosition = (lowerIndex + upperIndex) / 2 + if (inputArr[currentIndex] == searchItem) { + return currentIndex + } else if (lowerIndex > upperIndex) { + return nil + } else { + if (inputArr[currentIndex] > searchItem) { + upperIndex = currentIndex - 1 + } else { + lowerIndex = currentIndex + 1 + } + } + } +}*/ + +public struct GridNodeTransaction { + public let deleteItems: [Int] + public let insertItems: [GridNodeInsertItem] + public let updateItems: [GridNodeUpdateItem] + public let scrollToItem: GridNodeScrollToItem? + public let updateLayout: GridNodeUpdateLayout? + public let stationaryItemRange: (Int, Int)? + + public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, stationaryItemRange: (Int, Int)?) { + self.deleteItems = deleteItems + self.insertItems = insertItems + self.updateItems = updateItems + self.scrollToItem = scrollToItem + self.updateLayout = updateLayout + self.stationaryItemRange = stationaryItemRange + } +} + +private struct GridNodePresentationItem { + let index: Int + let frame: CGRect +} + +private struct GridNodePresentationLayout { + let layout: GridNodeLayout + let contentOffset: CGPoint + let contentSize: CGSize + let items: [GridNodePresentationItem] +} + +private final class GridNodeItemLayout { + let contentSize: CGSize + let items: [GridNodePresentationItem] + + init(contentSize: CGSize, items: [GridNodePresentationItem]) { + self.contentSize = contentSize + self.items = items + } +} + +public struct GridNodeDisplayedItemRange: Equatable { + public let loadedRange: Range? + public let visibleRange: Range? + + public static func ==(lhs: GridNodeDisplayedItemRange, rhs: GridNodeDisplayedItemRange) -> Bool { + return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange + } +} + +open class GridNode: GridNodeScroller, UIScrollViewDelegate { + private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize(), indexOffset: 0) + private var items: [GridItem] = [] + private var itemNodes: [Int: GridItemNode] = [:] + private var itemLayout = GridNodeItemLayout(contentSize: CGSize(), items: []) + + private var applyingContentOffset = false + + public override init() { + super.init() + + self.scrollView.showsVerticalScrollIndicator = false + self.scrollView.showsHorizontalScrollIndicator = false + self.scrollView.scrollsToTop = false + self.scrollView.delegate = self + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public func transaction(_ transaction: GridNodeTransaction, completion: (GridNodeDisplayedItemRange) -> Void) { + if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout) { + completion(self.displayedItemRange()) + return + } + + if let updateLayout = transaction.updateLayout { + self.gridLayout = updateLayout.layout + } + + for updatedItem in transaction.updateItems { + self.items[updatedItem.index] = updatedItem.item + if let itemNode = self.itemNodes[updatedItem.index] { + //update node + } + } + + if !transaction.deleteItems.isEmpty || !transaction.insertItems.isEmpty { + let deleteItems = transaction.deleteItems.sorted() + + for deleteItemIndex in deleteItems.reversed() { + self.items.remove(at: deleteItemIndex) + self.removeItemNodeWithIndex(deleteItemIndex) + } + + var remappedDeletionItemNodes: [Int: GridItemNode] = [:] + + for (index, itemNode) in self.itemNodes { + var indexOffset = 0 + for deleteIndex in deleteItems { + if deleteIndex < index { + indexOffset += 1 + } else { + break + } + } + + remappedDeletionItemNodes[index - indexOffset] = itemNode + } + + let insertItems = transaction.insertItems.sorted(by: { $0.index < $1.index }) + if self.items.count == 0 && !insertItems.isEmpty { + if insertItems[0].index != 0 { + fatalError("transaction: invalid insert into empty list") + } + } + + for insertedItem in insertItems { + self.items.insert(insertedItem.item, at: insertedItem.index) + } + + var remappedInsertionItemNodes: [Int: GridItemNode] = [:] + for (index, itemNode) in remappedDeletionItemNodes { + var indexOffset = 0 + for insertedItem in transaction.insertItems { + if insertedItem.index <= index + indexOffset { + indexOffset += 1 + } + } + + remappedInsertionItemNodes[index + indexOffset] = itemNode + } + + self.itemNodes = remappedInsertionItemNodes + } + + self.itemLayout = self.generateItemLayout() + + self.applyPresentaionLayout(self.generatePresentationLayout(scrollToItemIndex: 0)) + + completion(self.displayedItemRange()) + } + + public func scrollViewDidScroll(_ scrollView: UIScrollView) { + if !self.applyingContentOffset { + self.applyPresentaionLayout(self.generatePresentationLayout()) + } + } + + private func displayedItemRange() -> GridNodeDisplayedItemRange { + var minIndex: Int? + var maxIndex: Int? + for index in self.itemNodes.keys { + if minIndex == nil || minIndex! > index { + minIndex = index + } + if maxIndex == nil || maxIndex! < index { + maxIndex = index + } + } + + if let minIndex = minIndex, let maxIndex = maxIndex { + return GridNodeDisplayedItemRange(loadedRange: minIndex ..< maxIndex, visibleRange: minIndex ..< maxIndex) + } else { + return GridNodeDisplayedItemRange(loadedRange: nil, visibleRange: nil) + } + } + + private func generateItemLayout() -> GridNodeItemLayout { + if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.items.isEmpty { + var contentSize = CGSize(width: gridLayout.size.width, height: 0.0) + var items: [GridNodePresentationItem] = [] + + var incrementedCurrentRow = false + var nextItemOrigin = CGPoint(x: 0.0, y: 0.0) + var index = 0 + for item in self.items { + if !incrementedCurrentRow { + incrementedCurrentRow = true + contentSize.height += gridLayout.itemSize.height + } + + items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: gridLayout.itemSize))) + index += 1 + + nextItemOrigin.x += gridLayout.itemSize.width + if nextItemOrigin.x + gridLayout.itemSize.width > gridLayout.size.width { + nextItemOrigin.x = 0.0 + nextItemOrigin.y += gridLayout.itemSize.height + incrementedCurrentRow = false + } + } + + return GridNodeItemLayout(contentSize: contentSize, items: items) + } else { + return GridNodeItemLayout(contentSize: CGSize(), items: []) + } + } + + private func generatePresentationLayout(scrollToItemIndex: Int? = nil) -> GridNodePresentationLayout { + if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.itemLayout.items.isEmpty { + let contentOffset: CGPoint + if let scrollToItemIndex = scrollToItemIndex { + let itemFrame = self.itemLayout.items[scrollToItemIndex] + + let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) + var verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + + if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { + verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height + } + if verticalOffset < -self.gridLayout.insets.top { + verticalOffset = -self.gridLayout.insets.top + } + + contentOffset = CGPoint(x: 0.0, y: verticalOffset) + } else { + contentOffset = self.scrollView.contentOffset + } + + let lowerDisplayBound = contentOffset.y - self.gridLayout.preloadSize + let upperDisplayBound = contentOffset.y + self.gridLayout.size.height + self.gridLayout.preloadSize + + var presentationItems: [GridNodePresentationItem] = [] + for item in self.itemLayout.items { + if item.frame.origin.y < lowerDisplayBound { + continue + } + if item.frame.origin.y + item.frame.size.height > upperDisplayBound { + break + } + presentationItems.append(item) + } + + return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: contentOffset, contentSize: self.itemLayout.contentSize, items: presentationItems) + } else { + return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: CGPoint(), contentSize: self.itemLayout.contentSize, items: []) + } + } + + private func applyPresentaionLayout(_ presentationLayout: GridNodePresentationLayout) { + applyingContentOffset = true + self.scrollView.contentSize = presentationLayout.contentSize + self.scrollView.contentInset = presentationLayout.layout.insets + if !self.scrollView.contentOffset.equalTo(presentationLayout.contentOffset) { + self.scrollView.setContentOffset(presentationLayout.contentOffset, animated: false) + } + applyingContentOffset = false + + var existingItemIndices = Set() + for item in presentationLayout.items { + existingItemIndices.insert(item.index) + + if let itemNode = self.itemNodes[item.index] { + itemNode.frame = item.frame + } else { + let itemNode = self.items[item.index].node(layout: presentationLayout.layout) + itemNode.frame = item.frame + self.addItemNode(index: item.index, itemNode: itemNode) + } + } + + for index in self.itemNodes.keys { + if !existingItemIndices.contains(index) { + self.removeItemNodeWithIndex(index) + } + } + } + + private func addItemNode(index: Int, itemNode: GridItemNode) { + assert(self.itemNodes[index] == nil) + self.itemNodes[index] = itemNode + if itemNode.supernode == nil { + self.addSubnode(itemNode) + } + } + + private func removeItemNodeWithIndex(_ index: Int) { + if let itemNode = self.itemNodes.removeValue(forKey: index) { + itemNode.removeFromSupernode() + } + } + + public func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void) { + for (_, node) in self.itemNodes { + f(node) + } + } +} diff --git a/Display/GridNodeScroller.swift b/Display/GridNodeScroller.swift new file mode 100644 index 0000000000..6ad5e93348 --- /dev/null +++ b/Display/GridNodeScroller.swift @@ -0,0 +1,29 @@ +import UIKit + +private class GridNodeScrollerView: UIScrollView { + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } + + @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } +} + +open class GridNodeScroller: ASDisplayNode, UIGestureRecognizerDelegate { + var scrollView: UIScrollView { + return self.view as! UIScrollView + } + + override init() { + super.init(viewBlock: { + return GridNodeScrollerView() + }, didLoad: nil) + + self.scrollView.scrollsToTop = false + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index ded97be467..72aa8f8fa8 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -99,11 +99,13 @@ public struct ListViewInsertItem { public struct ListViewUpdateItem { public let index: Int + public let previousIndex: Int public let item: ListViewItem public let directionHint: ListViewItemOperationDirectionHint? - public init(index: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { + public init(index: Int, previousIndex: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { self.index = index + self.previousIndex = previousIndex self.item = item self.directionHint = directionHint } @@ -579,8 +581,9 @@ private struct ListViewState { while i >= 0 { let itemNode = self.nodes[i] let frame = itemNode.frame + //print("node \(i) frame \(frame)") if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { - //print("remove \(i)") + //print("remove invisible 1 \(i) frame \(frame)") operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) self.nodes.remove(at: i) } @@ -599,7 +602,7 @@ private struct ListViewState { if self.nodes[j].frame.maxY < upperBound { if let index = self.nodes[j].index { if index != previousIndex - 1 { - print("remove monotonity \(j) (\(index))") + //print("remove monotonity \(j) (\(index))") operations.append(.Remove(index: j, offsetDirection: .Down)) self.nodes.remove(at: j) } else { @@ -633,7 +636,7 @@ private struct ListViewState { } if !removeIndices.isEmpty { for i in removeIndices.reversed() { - print("remove monotonity \(i) (\(self.nodes[i].index!))") + //print("remove monotonity \(i) (\(self.nodes[i].index!))") operations.append(.Remove(index: i, offsetDirection: .Up)) self.nodes.remove(at: i) } @@ -776,7 +779,7 @@ private struct ListViewState { if let referenceNode = referenceNode , animated { self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) - operations.append(.InsertPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) + operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) } else { if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { @@ -849,9 +852,9 @@ private struct ListViewState { } } - operations.append(.UpdateLayout(index: itemIndex, layout: layout, apply: apply)) + operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) case .System: - operations.append(.UpdateLayout(index: itemIndex, layout: layout, apply: apply)) + operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) } break @@ -862,7 +865,7 @@ private struct ListViewState { private enum ListViewStateOperation { case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) - case InsertPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) + case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) case Remap([Int: Int]) case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) @@ -928,7 +931,7 @@ public enum ListViewVisibleContentOffset { case none } -public final class ListView: ASDisplayNode, UIScrollViewDelegate { +open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() private final var insets = UIEdgeInsets() @@ -1038,6 +1041,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.scroller.panGestureRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) + let trackingRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.trackingGesture(_:))) + trackingRecognizer.delegate = self + self.view.addGestureRecognizer(trackingRecognizer) + self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) if #available(iOS 10.0, *) { @@ -1181,17 +1188,18 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var useScrollDynamics = false + let anchor: CGFloat + if self.isTracking { + anchor = self.touchesPosition.y + } else if deltaY < 0.0 { + anchor = self.visibleSize.height + } else { + anchor = 0.0 + } + for itemNode in self.itemNodes { if itemNode.wantsScrollDynamics { useScrollDynamics = true - let anchor: CGFloat - if self.isTracking { - anchor = self.touchesPosition.y - } else if deltaY < 0.0 { - anchor = self.visibleSize.height - } else { - anchor = 0.0 - } var distance: CGFloat let itemFrame = itemNode.apparentFrame @@ -1443,11 +1451,19 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: @escaping (Void) -> Void) { - if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && scrollToItem == nil { + if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { if let updateSizeAndInsets = updateSizeAndInsets , self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets + if useDynamicTuning { + let size = updateSizeAndInsets.size + self.frictionSlider.frame = CGRect(x: 10.0, y: size.height - insets.bottom - 10.0 - self.frictionSlider.bounds.height, width: size.width - 20.0, height: self.frictionSlider.bounds.height) + self.springSlider.frame = CGRect(x: 10.0, y: self.frictionSlider.frame.minY - self.springSlider.bounds.height, width: size.width - 20.0, height: self.springSlider.bounds.height) + self.freeResistanceSlider.frame = CGRect(x: 10.0, y: self.springSlider.frame.minY - self.freeResistanceSlider.bounds.height, width: size.width - 20.0, height: self.freeResistanceSlider.bounds.height) + self.scrollingResistanceSlider.frame = CGRect(x: 10.0, y: self.freeResistanceSlider.frame.minY - self.scrollingResistanceSlider.bounds.height, width: size.width - 20.0, height: self.scrollingResistanceSlider.bounds.height) + } + self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size) self.scroller.contentSize = CGSize(width: updateSizeAndInsets.size.width, height: infiniteScrollSize * 2.0) @@ -1491,17 +1507,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - if self.debugInfo { - //print("deleteAndInsertItemsTransaction deleteIndices: \(deleteIndices.map({$0.index})) insertIndicesAndItems: \(insertIndicesAndItems.map({"\($0.index) <- \($0.previousIndex)"}))") - } - - /*if scrollToItem != nil { - print("Current indices:") - for itemNode in self.itemNodes { - print(" \(itemNode.index)") - } - }*/ - var previousNodes: [Int: ListViewItemNode] = [:] for insertedItem in sortedIndicesAndItems { self.items.insert(insertedItem.item, at: insertedItem.index) @@ -1517,8 +1522,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { for updatedItem in updateIndicesAndItems { self.items[updatedItem.index] = updatedItem.item for itemNode in self.itemNodes { - if itemNode.index == updatedItem.index { + if itemNode.index == updatedItem.previousIndex { previousNodes[updatedItem.index] = itemNode + break } } } @@ -1750,12 +1756,23 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { var updatedState = state var updatedOperations = operations + let heightDelta = layout.size.height - updatedState.nodes[i].frame.size.height + updatedOperations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) - if nodeIndex + 1 < updatedState.nodes.count { - for i in nodeIndex + 1 ..< updatedState.nodes.count { - let frame = updatedState.nodes[i].frame - updatedState.nodes[i].frame = frame.offsetBy(dx: 0.0, dy: frame.size.height) + if !animated { + let previousFrame = updatedState.nodes[i].frame + updatedState.nodes[i].frame = CGRect(origin: previousFrame.origin, size: layout.size) + if previousFrame.minY < updatedState.insets.top { + for j in 0 ... i { + updatedState.nodes[j].frame = updatedState.nodes[j].frame.offsetBy(dx: 0.0, dy: -heightDelta) + } + } else { + if i != updatedState.nodes.count { + for j in i + 1 ..< updatedState.nodes.count { + updatedState.nodes[j].frame = updatedState.nodes[j].frame.offsetBy(dx: 0.0, dy: heightDelta) + } + } } } @@ -1998,9 +2015,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { previousApparentFrames.append((itemNode, itemNode.apparentFrame)) } - if self.debugInfo { - //print("replay before \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))") - } + //var takenPreviousNodes = Set() for operation in operations { switch operation { @@ -2014,7 +2029,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) self.addSubnode(node) - case let .InsertPlaceholder(index, referenceNode, offsetDirection): + case let .InsertDisappearingPlaceholder(index, referenceNode, offsetDirection): var height: CGFloat? for (node, previousFrame) in previousApparentFrames { @@ -2666,143 +2681,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return ListViewDisplayedItemRange(loadedRange: loadedRange, visibleRange: visibleRange) } - public func updateSizeAndInsets(size: CGSize, insets: UIEdgeInsets, duration: Double = 0.0, options: UIViewAnimationOptions = UIViewAnimationOptions()) { - self.transactionQueue.addTransaction({ [weak self] completion in - if let strongSelf = self { - strongSelf.transactionOffset = 0.0 - strongSelf.updateSizeAndInsetsTransaction(size: size, insets: insets, duration: duration, options: options, completion: { [weak self] in - if let strongSelf = self { - strongSelf.transactionOffset = 0.0 - strongSelf.updateVisibleItemsTransaction(completion: completion) - } - }) - } - }) - - if useDynamicTuning { - self.frictionSlider.frame = CGRect(x: 10.0, y: size.height - insets.bottom - 10.0 - self.frictionSlider.bounds.height, width: size.width - 20.0, height: self.frictionSlider.bounds.height) - self.springSlider.frame = CGRect(x: 10.0, y: self.frictionSlider.frame.minY - self.springSlider.bounds.height, width: size.width - 20.0, height: self.springSlider.bounds.height) - self.freeResistanceSlider.frame = CGRect(x: 10.0, y: self.springSlider.frame.minY - self.freeResistanceSlider.bounds.height, width: size.width - 20.0, height: self.freeResistanceSlider.bounds.height) - self.scrollingResistanceSlider.frame = CGRect(x: 10.0, y: self.freeResistanceSlider.frame.minY - self.scrollingResistanceSlider.bounds.height, width: size.width - 20.0, height: self.scrollingResistanceSlider.bounds.height) - } - } - - private func updateSizeAndInsetsTransaction(size: CGSize, insets: UIEdgeInsets, duration: Double, options: UIViewAnimationOptions, completion: @escaping (Void) -> Void) { - if size.equalTo(self.visibleSize) && UIEdgeInsetsEqualToEdgeInsets(self.insets, insets) { - completion() - } else { - if abs(size.width - self.visibleSize.width) > CGFloat(FLT_EPSILON) { - let itemNodes = self.itemNodes - for itemNode in itemNodes { - itemNode.removeAllAnimations() - itemNode.transitionOffset = 0.0 - if let index = itemNode.index { - itemNode.layoutForWidth(size.width, item: self.items[index], previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1]) - } - itemNode.apparentHeight = itemNode.bounds.height - } - - if itemNodes.count != 0 { - for i in 0 ..< itemNodes.count - 1 { - var nextFrame = itemNodes[i + 1].frame - nextFrame.origin.y = itemNodes[i].apparentFrame.maxY - itemNodes[i + 1].frame = nextFrame - } - } - } - - var offsetFix = insets.top - self.insets.top - - self.visibleSize = size - self.insets = insets - - var completeOffset = offsetFix - - for itemNode in self.itemNodes { - let position = itemNode.position - itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) - } - - let completeDeltaHeight = offsetFix - offsetFix = 0.0 - - if Double(completeDeltaHeight) < DBL_EPSILON && self.itemNodes.count != 0 { - let firstItemNode = self.itemNodes[0] - let lastItemNode = self.itemNodes[self.itemNodes.count - 1] - - if lastItemNode.index == self.items.count - 1 { - if firstItemNode.index == 0 { - let topGap = firstItemNode.apparentFrame.origin.y - self.insets.top - let bottomGap = self.visibleSize.height - lastItemNode.apparentFrame.maxY - self.insets.bottom - if Double(bottomGap) > DBL_EPSILON { - offsetFix = -bottomGap - if topGap + bottomGap > 0.0 { - offsetFix = topGap - } - - let absOffsetFix = abs(offsetFix) - let absCompleteDeltaHeight = abs(completeDeltaHeight) - offsetFix = min(absOffsetFix, absCompleteDeltaHeight) * (offsetFix < 0 ? -1.0 : 1.0) - } - } else { - offsetFix = completeDeltaHeight - } - } - } - - if Double(abs(offsetFix)) > DBL_EPSILON { - completeOffset -= offsetFix - for itemNode in self.itemNodes { - let position = itemNode.position - itemNode.position = CGPoint(x: position.x, y: position.y - offsetFix) - } - } - - self.snapToBounds() - - self.ignoreScrollingEvents = true - self.scroller.frame = CGRect(origin: CGPoint(), size: size) - self.scroller.contentSize = CGSize(width: size.width, height: infiniteScrollSize * 2.0) - self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) - self.scroller.contentOffset = self.lastContentOffset - - self.updateScroller() - self.updateVisibleItemRange() - - let completion = { [weak self] (_: Bool) -> Void in - if let strongSelf = self { - strongSelf.updateVisibleItemsTransaction(completion: completion) - strongSelf.ignoreScrollingEvents = false - } - } - - if duration > DBL_EPSILON { - let animation: CABasicAnimation - if (options.rawValue & UInt(7 << 16)) != 0 { - let springAnimation = makeSpringAnimation("sublayerTransform") - springAnimation.duration = duration * UIView.animationDurationFactor() - springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - springAnimation.isRemovedOnCompletion = true - animation = springAnimation - } else { - let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - basicAnimation.duration = duration * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - basicAnimation.isRemovedOnCompletion = true - animation = basicAnimation - } - - animation.completion = completion - self.layer.add(animation, forKey: "sublayerTransform") - } else { - completion(true) - } - } - } - private func updateAnimations() { self.inVSync = true let actionsForVSync = self.actionsForVSync @@ -2893,10 +2771,9 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - override public func touchesBegan(_ touches: Set, with event: UIEvent?) { - self.isTracking = true - self.touchesPosition = (touches.first!).location(in: self.view) - self.selectionTouchLocation = self.touchesPosition + override open func touchesBegan(_ touches: Set, with event: UIEvent?) { + self.touchesPosition = touches.first!.location(in: self.view) + self.selectionTouchLocation = touches.first!.location(in: self.view) self.selectionTouchDelayTimer?.invalidate() let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in @@ -2948,7 +2825,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { return nil } - public func forEachItemNode(_ f: @noescape(ListViewItemNode) -> Void) { + public func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void) { for itemNode in self.itemNodes { if itemNode.index != nil { f(itemNode) @@ -2956,10 +2833,10 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } } - override public func touchesMoved(_ touches: Set, with event: UIEvent?) { - self.touchesPosition = touches.first!.location(in: self.view) + override open func touchesMoved(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { - let distance = CGPoint(x: selectionTouchLocation.x - self.touchesPosition.x, y: selectionTouchLocation.y - self.touchesPosition.y) + let location = touches.first!.location(in: self.view) + let distance = CGPoint(x: selectionTouchLocation.x - location.x, y: selectionTouchLocation.y - location.y) let maxMovementDistance: CGFloat = 4.0 if distance.x * distance.x + distance.y * distance.y > maxMovementDistance * maxMovementDistance { self.selectionTouchLocation = nil @@ -2972,9 +2849,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { super.touchesMoved(touches, with: event) } - override public func touchesEnded(_ touches: Set, with event: UIEvent?) { - self.isTracking = false - + override open func touchesEnded(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { let index = self.itemIndexAtPoint(selectionTouchLocation) if index != self.highlightedItemIndex { @@ -2998,16 +2873,14 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } if let highlightedItemIndex = self.highlightedItemIndex { - self.items[highlightedItemIndex].selected() + self.items[highlightedItemIndex].selected(listView: self) } self.selectionTouchLocation = nil super.touchesEnded(touches, with: event) } - override public func touchesCancelled(_ touches: Set?, with event: UIEvent?) { - self.isTracking = false - + override open func touchesCancelled(_ touches: Set?, with event: UIEvent?) { self.selectionTouchLocation = nil self.selectionTouchDelayTimer?.invalidate() self.selectionTouchDelayTimer = nil @@ -3015,4 +2888,22 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { super.touchesCancelled(touches, with: event) } + + @objc func trackingGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + self.isTracking = true + break + case .changed: + self.touchesPosition = recognizer.location(in: self.view) + case .ended, .cancelled: + self.isTracking = false + default: + break + } + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } } diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 6c22519549..cd9278fe2d 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -15,7 +15,7 @@ public protocol ListViewItem { var floatingAccessoryItem: ListViewAccessoryItem? { get } var selectable: Bool { get } - func selected() + func selected(listView: ListView) } public extension ListViewItem { @@ -35,7 +35,7 @@ public extension ListViewItem { return false } - func selected() { + func selected(listView: ListView) { } func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index a4e3480e2d..6aa5ba6822 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -2,16 +2,16 @@ import Foundation import AsyncDisplayKit var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) -var testSpringFriction: CGFloat = 33.26 +var testSpringFriction: CGFloat = 29.3323 var testSpringConstantLimits: (CGFloat, CGFloat) = (3.0, 450.0) -var testSpringConstant: CGFloat = 450.0 +var testSpringConstant: CGFloat = 353.6746 var testSpringResistanceFreeLimits: (CGFloat, CGFloat) = (0.05, 1.0) -var testSpringFreeResistance: CGFloat = 1.0 +var testSpringFreeResistance: CGFloat = 0.6721 var testSpringResistanceScrollingLimits: (CGFloat, CGFloat) = (0.1, 1.0) -var testSpringScrollingResistance: CGFloat = 0.4 +var testSpringScrollingResistance: CGFloat = 0.6721 struct ListViewItemSpring { let stiffness: CGFloat diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 0316299fa7..39a510c9cb 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -1,7 +1,7 @@ import Foundation import SwiftSignalKit -public typealias ListViewTransaction = @escaping (@escaping (Void) -> Void) -> Void +public typealias ListViewTransaction = (@escaping (Void) -> Void) -> Void public final class ListViewTransactionQueue { private var transactions: [ListViewTransaction] = [] @@ -10,7 +10,7 @@ public final class ListViewTransactionQueue { public init() { } - public func addTransaction(_ transaction: ListViewTransaction) { + public func addTransaction(_ transaction: @escaping ListViewTransaction) { let beginTransaction = self.transactions.count == 0 self.transactions.append(transaction) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 47458f96d7..f5a0b3f6ce 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -176,7 +176,7 @@ public class NavigationBar: ASDisplayNode { } self._previousItem = value - if let previousItem = value { + if let previousItem = value, previousItem.backBarButtonItem == nil { self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] text in if let strongSelf = self { strongSelf.backButtonNode.text = text ?? "Back" @@ -204,7 +204,11 @@ public class NavigationBar: ASDisplayNode { self.leftButtonNode.removeFromSupernode() if let previousItem = self.previousItem { - self.backButtonNode.text = previousItem.title ?? "Back" + if let backBarButtonItem = previousItem.backBarButtonItem { + self.backButtonNode.text = backBarButtonItem.title ?? "Back" + } else { + self.backButtonNode.text = previousItem.title ?? "Back" + } if self.backButtonNode.supernode == nil { self.clippingNode.addSubnode(self.backButtonNode) diff --git a/Display/SystemContainedControllerTransitionCoordinator.swift b/Display/SystemContainedControllerTransitionCoordinator.swift index 723d5e36b5..b861536e24 100644 --- a/Display/SystemContainedControllerTransitionCoordinator.swift +++ b/Display/SystemContainedControllerTransitionCoordinator.swift @@ -55,11 +55,11 @@ final class SystemContainedControllerTransitionCoordinator: NSObject, UIViewCont return CGAffineTransform.identity } - public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: (@escaping (UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { + public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { return false } - public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: (@escaping (UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { + public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { return false } diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h index 6278f5f316..dd2807b5ed 100644 --- a/Display/UIKitUtils.h +++ b/Display/UIKitUtils.h @@ -8,5 +8,6 @@ @end CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath); +CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity); CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t); diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 76acce5862..acae5e4427 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -42,6 +42,17 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { return springAnimation; } +CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity) { + CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath]; + springAnimation.mass = 5.0f; + springAnimation.stiffness = 900.0f; + springAnimation.damping = 88.0f; + springAnimation.initialVelocity = initialVelocity; + springAnimation.duration = springAnimation.settlingDuration; + springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; + return springAnimation; +} + CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t) { return [(CASpringAnimation *)animation _solveForInput:t]; } From e0f3c37e2fbd2e8f2b164ff87d923858b3e8a7e5 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 24 Oct 2016 15:00:28 +0300 Subject: [PATCH 024/245] no message --- Display/ActionSheetButtonItem.swift | 2 +- Display/CATracingLayer.h | 2 + Display/CATracingLayer.m | 21 ++ Display/GenerateImage.swift | 29 +- Display/GridItem.swift | 9 + Display/GridNode.swift | 249 +++++++++++++++--- ...teractiveTransitionGestureRecognizer.swift | 22 ++ Display/ListView.swift | 74 +++++- Display/ListViewItemNode.swift | 9 +- Display/NavigationBackButtonNode.swift | 2 +- Display/NavigationBar.swift | 2 +- Display/NavigationButtonNode.swift | 2 +- Display/NavigationController.swift | 4 +- Display/StatusBar.swift | 8 +- Display/StatusBarManager.swift | 2 +- Display/StatusBarProxyNode.swift | 26 +- Display/TabBarNode.swift | 4 +- Display/Window.swift | 2 + 18 files changed, 382 insertions(+), 87 deletions(-) diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index ccd5ebfd43..109daf1668 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -18,7 +18,7 @@ public class ActionSheetButtonItem: ActionSheetItem { public func node() -> ActionSheetItemNode { let textColorIsAccent = self.color == ActionSheetButtonColor.accent - let textColor = textColorIsAccent ? UIColor(0x1195f2) : UIColor.red + let textColor = textColorIsAccent ? UIColor(0x007ee5) : UIColor.red return ActionSheetButtonNode(title: NSAttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: textColor), action: self.action) } } diff --git a/Display/CATracingLayer.h b/Display/CATracingLayer.h index 4f6b33fc2e..f07f9b9aa2 100644 --- a/Display/CATracingLayer.h +++ b/Display/CATracingLayer.h @@ -6,6 +6,8 @@ @interface UITracingLayerView : UIView +- (void)scheduleWithLayout:(void (^_Nonnull)())block; + @end @interface CALayer (Tracing) diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index a6e905346f..0ea557c54c 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -266,10 +266,31 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic @end +@interface UITracingLayerView () { + void (^_scheduledWithLayout)(); +} + +@end + @implementation UITracingLayerView + (Class)layerClass { return [CATracingLayer class]; } +- (void)scheduleWithLayout:(void (^_Nonnull)())block { + _scheduledWithLayout = [block copy]; + [self setNeedsLayout]; +} + +- (void)layoutSubviews { + [super layoutSubviews]; + + if (_scheduledWithLayout) { + void (^block)() = [_scheduledWithLayout copy]; + _scheduledWithLayout = nil; + block(); + } +} + @end diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 3511197194..87e777b32c 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -29,9 +29,9 @@ public func generateImagePixel(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMu return UIImage(cgImage: image, scale: scale, orientation: .up) } -public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false) -> UIImage? { - let scale = deviceScale - let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) +public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) -> Void, opaque: Bool = false, scale: CGFloat? = nil) -> UIImage? { + let selectedScale = scale ?? deviceScale + let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) let length = bytesPerRow * Int(scaledSize.height) let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) @@ -45,12 +45,11 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) - guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) - else { - return nil + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { + return nil } - context.scaleBy(x: scale, y: scale) + context.scaleBy(x: selectedScale, y: selectedScale) contextGenerator(size, context) @@ -59,7 +58,7 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) return nil } - return UIImage(cgImage: image, scale: scale, orientation: .up) + return UIImage(cgImage: image, scale: selectedScale, orientation: .up) } public func generateFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { @@ -297,7 +296,7 @@ public func readCGFloat(_ index: inout UnsafePointer, end: UnsafePointer< } } -public func drawSvgPath(_ context: CGContext, path: StaticString) throws { +public func drawSvgPath(_ context: CGContext, path: StaticString, strokeOnMove: Bool = false) throws { var index: UnsafePointer = path.utf8Start let end = path.utf8Start.advanced(by: path.utf8CodeUnitCount) while index < end { @@ -316,6 +315,11 @@ public func drawSvgPath(_ context: CGContext, path: StaticString) throws { //print("Line to \(x), \(y)") context.addLine(to: CGPoint(x: x, y: y)) + + if strokeOnMove { + context.strokePath() + context.move(to: CGPoint(x: x, y: y)) + } } else if c == 67 { // C let x1 = try readCGFloat(&index, end: end, separator: 44) let y1 = try readCGFloat(&index, end: end, separator: 32) @@ -326,7 +330,10 @@ public func drawSvgPath(_ context: CGContext, path: StaticString) throws { context.addCurve(to: CGPoint(x: x1, y: y1), control1: CGPoint(x: x2, y: y2), control2: CGPoint(x: x, y: y)) //print("Line to \(x), \(y)") - + if strokeOnMove { + context.strokePath() + context.move(to: CGPoint(x: x, y: y)) + } } else if c == 90 { // Z if index != end && index.pointee != 32 { throw ParsingError.Generic @@ -336,6 +343,8 @@ public func drawSvgPath(_ context: CGContext, path: StaticString) throws { context.fillPath() //CGContextBeginPath(context) //print("Close") + } else { + throw ParsingError.Generic } } } diff --git a/Display/GridItem.swift b/Display/GridItem.swift index bcbe1e9533..a417df781d 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -1,5 +1,14 @@ import Foundation +public protocol GridSection { + var height: CGFloat { get } + var hashValue: Int { get } + + func isEqual(to: GridSection) -> Bool + func node() -> ASDisplayNode +} + public protocol GridItem { + var section: GridSection? { get } func node(layout: GridNodeLayout) -> GridItemNode } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index f89d9a24f1..88bbfa710b 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -44,18 +44,16 @@ public struct GridNodeLayout: Equatable { public let insets: UIEdgeInsets public let preloadSize: CGFloat public let itemSize: CGSize - public let indexOffset: Int - public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, itemSize: CGSize, indexOffset: Int) { + public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, itemSize: CGSize) { self.size = size self.insets = insets self.preloadSize = preloadSize self.itemSize = itemSize - self.indexOffset = indexOffset } public static func ==(lhs: GridNodeLayout, rhs: GridNodeLayout) -> Bool { - return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.itemSize.equalTo(rhs.itemSize) && lhs.indexOffset == rhs.indexOffset + return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.itemSize.equalTo(rhs.itemSize) } } @@ -97,21 +95,29 @@ public struct GridNodeUpdateLayout { } }*/ +public enum GridNodeStationaryItems { + case none + case all + case indices(Set) +} + public struct GridNodeTransaction { public let deleteItems: [Int] public let insertItems: [GridNodeInsertItem] public let updateItems: [GridNodeUpdateItem] public let scrollToItem: GridNodeScrollToItem? public let updateLayout: GridNodeUpdateLayout? - public let stationaryItemRange: (Int, Int)? + public let stationaryItems: GridNodeStationaryItems + public let updateFirstIndexInSectionOffset: Int? - public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, stationaryItemRange: (Int, Int)?) { + public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?) { self.deleteItems = deleteItems self.insertItems = insertItems self.updateItems = updateItems self.scrollToItem = scrollToItem self.updateLayout = updateLayout - self.stationaryItemRange = stationaryItemRange + self.stationaryItems = stationaryItems + self.updateFirstIndexInSectionOffset = updateFirstIndexInSectionOffset } } @@ -120,20 +126,28 @@ private struct GridNodePresentationItem { let frame: CGRect } +private struct GridNodePresentationSection { + let section: GridSection + let frame: CGRect +} + private struct GridNodePresentationLayout { let layout: GridNodeLayout let contentOffset: CGPoint let contentSize: CGSize let items: [GridNodePresentationItem] + let sections: [GridNodePresentationSection] } private final class GridNodeItemLayout { let contentSize: CGSize let items: [GridNodePresentationItem] + let sections: [GridNodePresentationSection] - init(contentSize: CGSize, items: [GridNodePresentationItem]) { + init(contentSize: CGSize, items: [GridNodePresentationItem], sections: [GridNodePresentationSection]) { self.contentSize = contentSize self.items = items + self.sections = sections } } @@ -146,14 +160,42 @@ public struct GridNodeDisplayedItemRange: Equatable { } } +private struct WrappedGridSection: Hashable { + let section: GridSection + + init(_ section: GridSection) { + self.section = section + } + + var hashValue: Int { + return self.section.hashValue + } + + static func ==(lhs: WrappedGridSection, rhs: WrappedGridSection) -> Bool { + return lhs.section.isEqual(to: rhs.section) + } +} + +public struct GridNodeVisibleItems { + public let top: (Int, GridItem)? + public let bottom: (Int, GridItem)? + public let topVisible: (Int, GridItem)? + public let bottomVisible: (Int, GridItem)? + public let count: Int +} + open class GridNode: GridNodeScroller, UIScrollViewDelegate { - private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize(), indexOffset: 0) + private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize()) + private var firstIndexInSectionOffset: Int = 0 private var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] - private var itemLayout = GridNodeItemLayout(contentSize: CGSize(), items: []) + private var sectionNodes: [WrappedGridSection: ASDisplayNode] = [:] + private var itemLayout = GridNodeItemLayout(contentSize: CGSize(), items: [], sections: []) private var applyingContentOffset = false + public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? + public override init() { super.init() @@ -168,11 +210,15 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } public func transaction(_ transaction: GridNodeTransaction, completion: (GridNodeDisplayedItemRange) -> Void) { - if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout) { + if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout && (transaction.updateFirstIndexInSectionOffset == nil || transaction.updateFirstIndexInSectionOffset == self.firstIndexInSectionOffset)) { completion(self.displayedItemRange()) return } + if let updateFirstIndexInSectionOffset = transaction.updateFirstIndexInSectionOffset { + self.firstIndexInSectionOffset = updateFirstIndexInSectionOffset + } + if let updateLayout = transaction.updateLayout { self.gridLayout = updateLayout.layout } @@ -233,9 +279,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.itemNodes = remappedInsertionItemNodes } + var previousLayoutWasEmpty = self.itemLayout.items.isEmpty + self.itemLayout = self.generateItemLayout() - self.applyPresentaionLayout(self.generatePresentationLayout(scrollToItemIndex: 0)) + + self.applyPresentaionLayout(self.generatePresentationLayout(stationaryItems: transaction.stationaryItems, scrollToItemIndex: previousLayoutWasEmpty ? 0 : nil)) completion(self.displayedItemRange()) } @@ -269,16 +318,47 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.items.isEmpty { var contentSize = CGSize(width: gridLayout.size.width, height: 0.0) var items: [GridNodePresentationItem] = [] + var sections: [GridNodePresentationSection] = [] var incrementedCurrentRow = false var nextItemOrigin = CGPoint(x: 0.0, y: 0.0) var index = 0 + var previousSection: GridSection? for item in self.items { + let section = item.section + var keepSection = true + if let previousSection = previousSection, let section = section { + keepSection = previousSection.isEqual(to: section) + } else if (previousSection != nil) != (section != nil) { + keepSection = false + } + + if !keepSection { + if incrementedCurrentRow { + nextItemOrigin.x = 0.0 + nextItemOrigin.y += gridLayout.itemSize.height + incrementedCurrentRow = false + } + + if let section = section { + sections.append(GridNodePresentationSection(section: section, frame: CGRect(origin: CGPoint(x: 0.0, y: nextItemOrigin.y), size: CGSize(width: gridLayout.size.width, height: section.height)))) + nextItemOrigin.y += section.height + contentSize.height += section.height + } + } + previousSection = section + if !incrementedCurrentRow { incrementedCurrentRow = true contentSize.height += gridLayout.itemSize.height } + if index == 0 { + let itemsInRow = Int(gridLayout.size.width) / Int(gridLayout.itemSize.width) + let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow + nextItemOrigin.x += gridLayout.itemSize.width * CGFloat(normalizedIndexOffset) + } + items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: gridLayout.itemSize))) index += 1 @@ -290,31 +370,62 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - return GridNodeItemLayout(contentSize: contentSize, items: items) + return GridNodeItemLayout(contentSize: contentSize, items: items, sections: sections) } else { - return GridNodeItemLayout(contentSize: CGSize(), items: []) + return GridNodeItemLayout(contentSize: CGSize(), items: [], sections: []) } } - private func generatePresentationLayout(scrollToItemIndex: Int? = nil) -> GridNodePresentationLayout { + private func generatePresentationLayout(stationaryItems: GridNodeStationaryItems = .none, scrollToItemIndex: Int? = nil) -> GridNodePresentationLayout { if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.itemLayout.items.isEmpty { let contentOffset: CGPoint - if let scrollToItemIndex = scrollToItemIndex { - let itemFrame = self.itemLayout.items[scrollToItemIndex] - - let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) - var verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) - - if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { - verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height - } - if verticalOffset < -self.gridLayout.insets.top { - verticalOffset = -self.gridLayout.insets.top - } - - contentOffset = CGPoint(x: 0.0, y: verticalOffset) - } else { - contentOffset = self.scrollView.contentOffset + switch stationaryItems { + case .none: + if let scrollToItemIndex = scrollToItemIndex { + let itemFrame = self.itemLayout.items[scrollToItemIndex] + + let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) + var verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + + if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { + verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height + } + if verticalOffset < -self.gridLayout.insets.top { + verticalOffset = -self.gridLayout.insets.top + } + + contentOffset = CGPoint(x: 0.0, y: verticalOffset) + } else { + contentOffset = self.scrollView.contentOffset + } + case let .indices(stationaryItemIndices): + var selectedContentOffset: CGPoint? + for (index, itemNode) in self.itemNodes { + if stationaryItemIndices.contains(index) { + let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y + selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) + break + } + } + + if let selectedContentOffset = selectedContentOffset { + contentOffset = selectedContentOffset + } else { + contentOffset = self.scrollView.contentOffset + } + case .all: + var selectedContentOffset: CGPoint? + for (index, itemNode) in self.itemNodes { + let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y + selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) + break + } + + if let selectedContentOffset = selectedContentOffset { + contentOffset = selectedContentOffset + } else { + contentOffset = self.scrollView.contentOffset + } } let lowerDisplayBound = contentOffset.y - self.gridLayout.preloadSize @@ -331,9 +442,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { presentationItems.append(item) } - return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: contentOffset, contentSize: self.itemLayout.contentSize, items: presentationItems) + var presentationSections: [GridNodePresentationSection] = [] + for section in self.itemLayout.sections { + if section.frame.origin.y < lowerDisplayBound { + continue + } + if section.frame.origin.y + section.frame.size.height > upperDisplayBound { + break + } + presentationSections.append(section) + } + + return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: contentOffset, contentSize: self.itemLayout.contentSize, items: presentationItems, sections: presentationSections) } else { - return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: CGPoint(), contentSize: self.itemLayout.contentSize, items: []) + return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: CGPoint(), contentSize: self.itemLayout.contentSize, items: [], sections: []) } } @@ -342,7 +464,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.scrollView.contentSize = presentationLayout.contentSize self.scrollView.contentInset = presentationLayout.layout.insets if !self.scrollView.contentOffset.equalTo(presentationLayout.contentOffset) { - self.scrollView.setContentOffset(presentationLayout.contentOffset, animated: false) + //self.scrollView.setContentOffset(presentationLayout.contentOffset, animated: false) + self.scrollView.contentOffset = presentationLayout.contentOffset } applyingContentOffset = false @@ -364,6 +487,50 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.removeItemNodeWithIndex(index) } } + + var existingSections = Set() + for section in presentationLayout.sections { + let wrappedSection = WrappedGridSection(section.section) + existingSections.insert(wrappedSection) + + if let sectionNode = self.sectionNodes[wrappedSection] { + sectionNode.frame = section.frame + } else { + let sectionNode = section.section.node() + sectionNode.frame = section.frame + self.addSectionNode(section: wrappedSection, sectionNode: sectionNode) + } + } + + for wrappedSection in self.sectionNodes.keys { + if !existingSections.contains(wrappedSection) { + self.removeSectionNodeWithSection(wrappedSection) + } + } + + if let visibleItemsUpdated = self.visibleItemsUpdated { + if presentationLayout.items.count != 0 { + let topIndex = presentationLayout.items.first!.index + let bottomIndex = presentationLayout.items.last!.index + + var topVisible: (Int, GridItem) = (topIndex, self.items[topIndex]) + var bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) + + let lowerDisplayBound = presentationLayout.contentOffset.y + let upperDisplayBound = presentationLayout.contentOffset.y + self.gridLayout.size.height + + for item in presentationLayout.items { + if item.frame.maxY >= lowerDisplayBound { + topVisible = (item.index, self.items[item.index]) + break + } + } + + visibleItemsUpdated(GridNodeVisibleItems(top: (topIndex, self.items[topIndex]), bottom: (bottomIndex, self.items[bottomIndex]), topVisible: topVisible, bottomVisible: bottomVisible, count: self.items.count)) + } else { + visibleItemsUpdated(GridNodeVisibleItems(top: nil, bottom: nil, topVisible: nil, bottomVisible: nil, count: self.items.count)) + } + } } private func addItemNode(index: Int, itemNode: GridItemNode) { @@ -374,12 +541,26 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + private func addSectionNode(section: WrappedGridSection, sectionNode: ASDisplayNode) { + assert(self.sectionNodes[section] == nil) + self.sectionNodes[section] = sectionNode + if sectionNode.supernode == nil { + self.addSubnode(sectionNode) + } + } + private func removeItemNodeWithIndex(_ index: Int) { if let itemNode = self.itemNodes.removeValue(forKey: index) { itemNode.removeFromSupernode() } } + private func removeSectionNodeWithSection(_ section: WrappedGridSection) { + if let sectionNode = self.sectionNodes.removeValue(forKey: section) { + sectionNode.removeFromSupernode() + } + } + public func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void) { for (_, node) in self.itemNodes { f(node) diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index e6fde67334..f0e5849852 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -1,6 +1,22 @@ import Foundation import UIKit +private func hasHorizontalGestures(_ view: UIView) -> Bool { + if let view = view as? ListViewBackingView { + let transform = view.transform + let angle = Double(atan2f(Float(transform.b), Float(transform.a))) + if abs(angle - M_PI / 2.0) < 0.001 || abs(angle + M_PI / 2.0) < 0.001 || abs(angle - M_PI * 3.0 / 2.0) < 0.001 { + return true + } + } + + if let superview = view.superview { + return hasHorizontalGestures(superview) + } else { + return false + } +} + class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { var validatedGesture = false var firstLocation: CGPoint = CGPoint() @@ -22,6 +38,12 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { let touch = touches.first! self.firstLocation = touch.location(in: self.view) + + if let target = self.view?.hitTest(self.firstLocation, with: event) { + if hasHorizontalGestures(target) { + self.state = .cancelled + } + } } override func touchesMoved(_ touches: Set, with event: UIEvent) { diff --git a/Display/ListView.swift b/Display/ListView.swift index 72aa8f8fa8..dbdf175e18 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -882,7 +882,7 @@ private final class ListViewBackingLayer: CALayer { } } -private final class ListViewBackingView: UIView { +final class ListViewBackingView: UIView { weak var target: ASDisplayNode? override class var layerClass: AnyClass { @@ -1247,13 +1247,29 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var topItemEdge: CGFloat = 0.0 var bottomItemEdge: CGFloat = 0.0 - if itemNodes[0].index == 0 { - topItemFound = true + for i in 0 ..< self.itemNodes.count { + if let index = itemNodes[i].index { + if index == 0 { + topItemFound = true + } + break + } + } + + if topItemFound { topItemEdge = itemNodes[0].apparentFrame.origin.y } - if itemNodes[itemNodes.count - 1].index == self.items.count - 1 { - bottomItemFound = true + for i in (0 ..< self.itemNodes.count).reversed() { + if let index = itemNodes[i].index { + if index == self.items.count - 1 { + bottomItemFound = true + } + break + } + } + + if bottomItemFound { bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY } @@ -1903,10 +1919,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let previousApparentHeight = node.apparentHeight let previousInsets = node.insets - if node.wantsScrollDynamics && previousFrame != nil { - assert(true) - } - node.contentSize = layout.contentSize node.insets = layout.insets node.apparentHeight = animated ? 0.0 : layout.size.height @@ -1958,6 +1970,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if node.index == nil { node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } else if animated { if !takenAnimation { node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in @@ -2015,7 +2028,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel previousApparentFrames.append((itemNode, itemNode.apparentFrame)) } - //var takenPreviousNodes = Set() + var takenPreviousNodes = Set() + for operation in operations { + if case let .InsertNode(_, _, node, _, _) = operation { + takenPreviousNodes.insert(node) + } + } for operation in operations { switch operation { @@ -2028,19 +2046,35 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) - self.addSubnode(node) + if let previousFrame = previousFrame, previousFrame.minY >= self.visibleSize.height || previousFrame.maxY < 0.0 { + self.insertSubnode(node, at: 0) + } else { + if animated { + self.insertSubnode(node, at: 0) + } else { + self.addSubnode(node) + } + } case let .InsertDisappearingPlaceholder(index, referenceNode, offsetDirection): var height: CGFloat? + var previousLayout: ListViewItemNodeLayout? for (node, previousFrame) in previousApparentFrames { if node === referenceNode { height = previousFrame.size.height + previousLayout = ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets) break } } - if let height = height { - self.insertNodeAtIndex(animated: false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + if let height = height, let previousLayout = previousLayout { + if takenPreviousNodes.contains(referenceNode) { + self.insertNodeAtIndex(animated: false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + } else { + referenceNode.index = nil + self.insertNodeAtIndex(animated: false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { }, timestamp: timestamp) + self.addSubnode(referenceNode) + } } else { assertionFailure() } @@ -2103,7 +2137,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.animateFrameTransition(progress) } }) - node.transitionOffset += previousApparentHeight - layout.size.height + + let insetPart: CGFloat = previousInsets.top - layout.insets.top + node.transitionOffset += previousApparentHeight - layout.size.height - insetPart node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } } else { @@ -2833,6 +2869,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public func ensureItemNodeVisible(_ node: ListViewItemNode) { + if let index = node.index { + if node.frame.minY < self.insets.top { + self.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Top, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in }) + } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { + self.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Bottom, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in }) + } + } + } + override open func touchesMoved(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { let location = touches.first!.location(in: self.view) diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 6aa5ba6822..51696a8aef 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -2,13 +2,13 @@ import Foundation import AsyncDisplayKit var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) -var testSpringFriction: CGFloat = 29.3323 +var testSpringFriction: CGFloat = 31.8211269378662 var testSpringConstantLimits: (CGFloat, CGFloat) = (3.0, 450.0) -var testSpringConstant: CGFloat = 353.6746 +var testSpringConstant: CGFloat = 443.704223632812 var testSpringResistanceFreeLimits: (CGFloat, CGFloat) = (0.05, 1.0) -var testSpringFreeResistance: CGFloat = 0.6721 +var testSpringFreeResistance: CGFloat = 0.676197171211243 var testSpringResistanceScrollingLimits: (CGFloat, CGFloat) = (0.1, 1.0) var testSpringScrollingResistance: CGFloat = 0.6721 @@ -410,6 +410,9 @@ open class ListViewItemNode: ASDisplayNode { open func animateAdded(_ currentTimestamp: Double, duration: Double) { } + open func animateRemoved(_ currentTimestamp: Double, duration: Double) { + } + open func setHighlighted(_ highlighted: Bool, animated: Bool) { } diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 780a0b8d67..22077acf36 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -30,7 +30,7 @@ public class NavigationBackButtonNode: ASControlNode { } } - var color: UIColor = UIColor(0x1195f2) { + var color: UIColor = UIColor(0x007ee5) { didSet { self.label.attributedString = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index f5a0b3f6ce..138a7ec870 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -40,7 +40,7 @@ public class NavigationBar: ASDisplayNode { } } - public var accentColor: UIColor = UIColor(0x1195f2) { + public var accentColor: UIColor = UIColor(0x007ee5) { didSet { self.backButtonNode.color = self.accentColor self.leftButtonNode.color = self.accentColor diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index fe0a874573..6cb4611c91 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -38,7 +38,7 @@ public class NavigationButtonNode: ASTextNode { } } - public var color: UIColor = UIColor(0x1195f2) { + public var color: UIColor = UIColor(0x007ee5) { didSet { if let text = self._text { self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 7114d4ca23..60cf8c2c0d 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -68,13 +68,13 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let containedLayout = ContainerViewLayout(size: layout.size, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) - for controller in self.viewControllers { + /*for controller in self.viewControllers { if let controller = controller as? ContainableController { controller.containerLayoutUpdated(containedLayout, transition: transition) } else { controller.viewWillTransition(to: layout.size, with: SystemContainedControllerTransitionCoordinator()) } - } + }*/ if let topViewController = self.topViewController { if let topViewController = topViewController as? ContainableController { diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 7a22cb7959..aa6deb337b 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -25,9 +25,9 @@ public class StatusBarSurface { } public class StatusBar: ASDisplayNode { - public var style: StatusBarStyle = .Black { + public var statusBarStyle: StatusBarStyle = .Black { didSet { - if self.style != oldValue { + if self.statusBarStyle != statusBarStyle { self.layer.invalidateUpTheTree() } } @@ -64,9 +64,9 @@ public class StatusBar: ASDisplayNode { func updateProxyNode(statusBar: UIView) { self.removeProxyNodeScheduled = false if let proxyNode = proxyNode { - proxyNode.style = self.style + proxyNode.statusBarStyle = self.statusBarStyle } else { - self.proxyNode = StatusBarProxyNode(style: self.style, statusBar: statusBar) + self.proxyNode = StatusBarProxyNode(statusBarStyle: self.statusBarStyle, statusBar: statusBar) self.proxyNode!.isHidden = false self.addSubnode(self.proxyNode!) } diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index f8efdf92e1..409ae445be 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -14,7 +14,7 @@ private struct MappedStatusBarSurface { private func mapStatusBar(_ statusBar: StatusBar) -> MappedStatusBar { let frame = CGRect(origin: statusBar.view.convert(CGPoint(), to: nil), size: statusBar.frame.size) - return MappedStatusBar(style: statusBar.style, frame: frame, statusBar: statusBar) + return MappedStatusBar(style: statusBar.statusBarStyle, frame: frame, statusBar: statusBar) } private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurface { diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index b9c2ffc258..0f1e520996 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -11,16 +11,16 @@ private enum StatusBarItemType { case Battery } -func makeStatusBarProxy(_ style: StatusBarStyle, statusBar: UIView) -> StatusBarProxyNode { - return StatusBarProxyNode(style: style, statusBar: statusBar) +func makeStatusBarProxy(_ statusBarStyle: StatusBarStyle, statusBar: UIView) -> StatusBarProxyNode { + return StatusBarProxyNode(statusBarStyle: statusBarStyle, statusBar: statusBar) } private class StatusBarItemNode: ASDisplayNode { - var style: StatusBarStyle + var statusBarStyle: StatusBarStyle var targetView: UIView - init(style: StatusBarStyle, targetView: UIView) { - self.style = style + init(statusBarStyle: StatusBarStyle, targetView: UIView) { + self.statusBarStyle = statusBarStyle self.targetView = targetView super.init() @@ -67,7 +67,7 @@ private class StatusBarItemNode: ASDisplayNode { } let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic - tintStatusBarItem(context, type: type, style: style) + tintStatusBarItem(context, type: type, style: statusBarStyle) self.contents = context.generateImage()?.cgImage self.frame = self.targetView.frame @@ -235,9 +235,9 @@ class StatusBarProxyNode: ASDisplayNode { private let statusBar: UIView var timer: Timer? - var style: StatusBarStyle { + var statusBarStyle: StatusBarStyle { didSet { - if oldValue != self.style { + if oldValue != self.statusBarStyle { if !self.isHidden { self.updateItems() } @@ -268,8 +268,8 @@ class StatusBarProxyNode: ASDisplayNode { } } - init(style: StatusBarStyle, statusBar: UIView) { - self.style = style + init(statusBarStyle: StatusBarStyle, statusBar: UIView) { + self.statusBarStyle = statusBarStyle self.statusBar = statusBar super.init() @@ -280,7 +280,7 @@ class StatusBarProxyNode: ASDisplayNode { //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) for subview in statusBar.subviews { - let itemNode = StatusBarItemNode(style: style, targetView: subview) + let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview) self.itemNodes.append(itemNode) self.addSubnode(itemNode) } @@ -308,7 +308,7 @@ class StatusBarProxyNode: ASDisplayNode { self.itemNodes[i].removeFromSupernode() self.itemNodes.remove(at: i) } else { - self.itemNodes[i].style = self.style + self.itemNodes[i].statusBarStyle = self.statusBarStyle self.itemNodes[i].update() i += 1 } @@ -324,7 +324,7 @@ class StatusBarProxyNode: ASDisplayNode { } if !found { - let itemNode = StatusBarItemNode(style: self.style, targetView: subview) + let itemNode = StatusBarItemNode(statusBarStyle: self.statusBarStyle, targetView: subview) itemNode.update() self.itemNodes.append(itemNode) self.addSubnode(itemNode) diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index a9d51ee2f9..5c02e2ccb7 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -97,7 +97,7 @@ class TabBarNode: ASDisplayNode { node.displayWithoutProcessing = true node.isLayerBacked = true if let selectedIndex = self.selectedIndex , selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x007ee5)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } @@ -116,7 +116,7 @@ class TabBarNode: ASDisplayNode { let item = self.tabBarItems[index] if let selectedIndex = self.selectedIndex , selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x1195f2)) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x007ee5)) } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } diff --git a/Display/Window.swift b/Display/Window.swift index d5ab98880d..9589de34a5 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -131,6 +131,8 @@ public class Window: UIWindow { self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) super.rootViewController = WindowRootViewController() + super.rootViewController?.viewWillAppear(false) + super.rootViewController?.viewDidAppear(false) self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { From 9b2447885e7edcc673619f75be885f9d342b7b41 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 2 Nov 2016 03:13:51 +0300 Subject: [PATCH 025/245] no message --- Display/DisplayLinkDispatcher.swift | 21 +- Display/GridNode.swift | 348 ++++++++++++++++++++++++---- Display/ListView.swift | 56 +++-- Display/UIKitUtils.swift | 4 + 4 files changed, 358 insertions(+), 71 deletions(-) diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index 7e14daed58..6890a87eee 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -10,17 +10,26 @@ public class DisplayLinkDispatcher: NSObject { super.init() - self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) if #available(iOS 10.0, *) { - self.displayLink.preferredFramesPerSecond = 60 + //self.displayLink.preferredFramesPerSecond = 60 + } else { + self.displayLink = CADisplayLink(target: self, selector: #selector(self.run)) + self.displayLink.isPaused = true + self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) } - self.displayLink.isPaused = true - self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) } public func dispatch(f: @escaping (Void) -> Void) { - self.blocksToDispatch.append(f) - self.displayLink.isPaused = false + if self.displayLink == nil { + if Thread.isMainThread { + f() + } else { + DispatchQueue.main.async(execute: f) + } + } else { + self.blocksToDispatch.append(f) + self.displayLink.isPaused = false + } } @objc func run() { diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 88bbfa710b..09abd7f694 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -32,10 +32,16 @@ public enum GridNodeScrollToItemPosition { public struct GridNodeScrollToItem { public let index: Int public let position: GridNodeScrollToItemPosition + public let transition: ContainedViewLayoutTransition + public let directionHint: GridNodePreviousItemsTransitionDirectionHint + public let adjustForSection: Bool - public init(index: Int, position: GridNodeScrollToItemPosition) { + public init(index: Int, position: GridNodeScrollToItemPosition, transition: ContainedViewLayoutTransition, directionHint: GridNodePreviousItemsTransitionDirectionHint, adjustForSection: Bool) { self.index = index self.position = position + self.transition = transition + self.directionHint = directionHint + self.adjustForSection = adjustForSection } } @@ -139,6 +145,17 @@ private struct GridNodePresentationLayout { let sections: [GridNodePresentationSection] } +public enum GridNodePreviousItemsTransitionDirectionHint { + case up + case down +} + +private struct GridNodePresentationLayoutTransition { + let layout: GridNodePresentationLayout + let directionHint: GridNodePreviousItemsTransitionDirectionHint + let transition: ContainedViewLayoutTransition +} + private final class GridNodeItemLayout { let contentSize: CGSize let items: [GridNodePresentationItem] @@ -181,9 +198,22 @@ public struct GridNodeVisibleItems { public let bottom: (Int, GridItem)? public let topVisible: (Int, GridItem)? public let bottomVisible: (Int, GridItem)? + public let topSectionVisible: GridSection? public let count: Int } +private struct WrappedGridItemNode: Hashable { + let node: ASDisplayNode + + var hashValue: Int { + return node.hashValue + } + + static func ==(lhs: WrappedGridItemNode, rhs: WrappedGridItemNode) -> Bool { + return lhs.node === rhs.node + } +} + open class GridNode: GridNodeScroller, UIScrollViewDelegate { private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize()) private var firstIndexInSectionOffset: Int = 0 @@ -230,12 +260,19 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + var removedNodes: [GridItemNode] = [] + if !transaction.deleteItems.isEmpty || !transaction.insertItems.isEmpty { let deleteItems = transaction.deleteItems.sorted() for deleteItemIndex in deleteItems.reversed() { self.items.remove(at: deleteItemIndex) - self.removeItemNodeWithIndex(deleteItemIndex) + if let itemNode = self.itemNodes[deleteItemIndex] { + removedNodes.append(itemNode) + self.removeItemNodeWithIndex(deleteItemIndex, removeNode: false) + } else { + self.removeItemNodeWithIndex(deleteItemIndex, removeNode: true) + } } var remappedDeletionItemNodes: [Int: GridItemNode] = [:] @@ -283,15 +320,23 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.itemLayout = self.generateItemLayout() + let generatedScrollToItem: GridNodeScrollToItem? + if let scrollToItem = transaction.scrollToItem { + generatedScrollToItem = scrollToItem + } else if previousLayoutWasEmpty { + generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .immediate, directionHint: .up, adjustForSection: true) + } else { + generatedScrollToItem = nil + } - self.applyPresentaionLayout(self.generatePresentationLayout(stationaryItems: transaction.stationaryItems, scrollToItemIndex: previousLayoutWasEmpty ? 0 : nil)) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, scrollToItem: generatedScrollToItem), removedNodes: removedNodes) completion(self.displayedItemRange()) } public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayout(self.generatePresentationLayout()) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(), removedNodes: []) } } @@ -320,8 +365,14 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var items: [GridNodePresentationItem] = [] var sections: [GridNodePresentationSection] = [] + let itemsInRow = Int(gridLayout.size.width / gridLayout.itemSize.width) + let itemsInRowWidth = CGFloat(itemsInRow) * gridLayout.itemSize.width + let remainingWidth = gridLayout.size.width - itemsInRowWidth + + let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) + var incrementedCurrentRow = false - var nextItemOrigin = CGPoint(x: 0.0, y: 0.0) + var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) var index = 0 var previousSection: GridSection? for item in self.items { @@ -335,7 +386,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if !keepSection { if incrementedCurrentRow { - nextItemOrigin.x = 0.0 + nextItemOrigin.x = itemSpacing nextItemOrigin.y += gridLayout.itemSize.height incrementedCurrentRow = false } @@ -356,15 +407,15 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if index == 0 { let itemsInRow = Int(gridLayout.size.width) / Int(gridLayout.itemSize.width) let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow - nextItemOrigin.x += gridLayout.itemSize.width * CGFloat(normalizedIndexOffset) + nextItemOrigin.x += (gridLayout.itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) } items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: gridLayout.itemSize))) index += 1 - nextItemOrigin.x += gridLayout.itemSize.width + nextItemOrigin.x += gridLayout.itemSize.width + itemSpacing if nextItemOrigin.x + gridLayout.itemSize.width > gridLayout.size.width { - nextItemOrigin.x = 0.0 + nextItemOrigin.x = itemSpacing nextItemOrigin.y += gridLayout.itemSize.height incrementedCurrentRow = false } @@ -376,16 +427,51 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func generatePresentationLayout(stationaryItems: GridNodeStationaryItems = .none, scrollToItemIndex: Int? = nil) -> GridNodePresentationLayout { + private func generatePresentationLayoutTransition(stationaryItems: GridNodeStationaryItems = .none, scrollToItem: GridNodeScrollToItem? = nil) -> GridNodePresentationLayoutTransition { if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.itemLayout.items.isEmpty { + var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up + var transition: ContainedViewLayoutTransition = .immediate let contentOffset: CGPoint switch stationaryItems { case .none: - if let scrollToItemIndex = scrollToItemIndex { - let itemFrame = self.itemLayout.items[scrollToItemIndex] + if let scrollToItem = scrollToItem { + let itemFrame = self.itemLayout.items[scrollToItem.index] + + var additionalOffset: CGFloat = 0.0 + if scrollToItem.adjustForSection { + var adjustForSection: GridSection? + if scrollToItem.index == 0 { + if let itemSection = self.items[scrollToItem.index].section { + adjustForSection = itemSection + } + } else { + let itemSection = self.items[scrollToItem.index].section + let previousSection = self.items[scrollToItem.index - 1].section + if let itemSection = itemSection, let previousSection = previousSection { + if !itemSection.isEqual(to: previousSection) { + adjustForSection = itemSection + } + } else if let itemSection = itemSection { + adjustForSection = itemSection + } + } + + if let adjustForSection = adjustForSection { + additionalOffset = -adjustForSection.height + } + } let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) - var verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + var verticalOffset: CGFloat + + switch scrollToItem.position { + case .top: + verticalOffset = itemFrame.frame.minY + additionalOffset + case .center: + verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + additionalOffset + case .bottom: + verticalOffset = itemFrame.frame.maxY - displayHeight + additionalOffset + } if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height @@ -394,6 +480,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { verticalOffset = -self.gridLayout.insets.top } + transitionDirectionHint = scrollToItem.directionHint + transition = scrollToItem.transition + contentOffset = CGPoint(x: 0.0, y: verticalOffset) } else { contentOffset = self.scrollView.contentOffset @@ -453,43 +542,55 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { presentationSections.append(section) } - return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: contentOffset, contentSize: self.itemLayout.contentSize, items: presentationItems, sections: presentationSections) + return GridNodePresentationLayoutTransition(layout: GridNodePresentationLayout(layout: self.gridLayout, contentOffset: contentOffset, contentSize: self.itemLayout.contentSize, items: presentationItems, sections: presentationSections), directionHint: transitionDirectionHint, transition: transition) } else { - return GridNodePresentationLayout(layout: self.gridLayout, contentOffset: CGPoint(), contentSize: self.itemLayout.contentSize, items: [], sections: []) + return GridNodePresentationLayoutTransition(layout: GridNodePresentationLayout(layout: self.gridLayout, contentOffset: CGPoint(), contentSize: self.itemLayout.contentSize, items: [], sections: []), directionHint: .up, transition: .immediate) } } - private func applyPresentaionLayout(_ presentationLayout: GridNodePresentationLayout) { + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode]) { + var previousItemFrames: ([WrappedGridItemNode: CGRect])? + switch presentationLayoutTransition.transition { + case .animated: + var itemFrames: [WrappedGridItemNode: CGRect] = [:] + let contentOffset = self.scrollView.contentOffset + for (_, itemNode) in self.itemNodes { + itemFrames[WrappedGridItemNode(node: itemNode)] = itemNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + } + for (_, sectionNode) in self.sectionNodes { + itemFrames[WrappedGridItemNode(node: sectionNode)] = sectionNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + } + for itemNode in removedNodes { + itemFrames[WrappedGridItemNode(node: itemNode)] = itemNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + } + previousItemFrames = itemFrames + case .immediate: + break + } + applyingContentOffset = true - self.scrollView.contentSize = presentationLayout.contentSize - self.scrollView.contentInset = presentationLayout.layout.insets - if !self.scrollView.contentOffset.equalTo(presentationLayout.contentOffset) { - //self.scrollView.setContentOffset(presentationLayout.contentOffset, animated: false) - self.scrollView.contentOffset = presentationLayout.contentOffset + self.scrollView.contentSize = presentationLayoutTransition.layout.contentSize + self.scrollView.contentInset = presentationLayoutTransition.layout.layout.insets + if !self.scrollView.contentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) { + self.scrollView.contentOffset = presentationLayoutTransition.layout.contentOffset } applyingContentOffset = false var existingItemIndices = Set() - for item in presentationLayout.items { + for item in presentationLayoutTransition.layout.items { existingItemIndices.insert(item.index) if let itemNode = self.itemNodes[item.index] { itemNode.frame = item.frame } else { - let itemNode = self.items[item.index].node(layout: presentationLayout.layout) + let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout) itemNode.frame = item.frame self.addItemNode(index: item.index, itemNode: itemNode) } } - for index in self.itemNodes.keys { - if !existingItemIndices.contains(index) { - self.removeItemNodeWithIndex(index) - } - } - var existingSections = Set() - for section in presentationLayout.sections { + for section in presentationLayoutTransition.layout.sections { let wrappedSection = WrappedGridSection(section.section) existingSections.insert(wrappedSection) @@ -502,33 +603,184 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - for wrappedSection in self.sectionNodes.keys { - if !existingSections.contains(wrappedSection) { - self.removeSectionNodeWithSection(wrappedSection) + if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = presentationLayoutTransition.transition { + let contentOffset = presentationLayoutTransition.layout.contentOffset + + var offset: CGFloat? + for (index, itemNode) in self.itemNodes { + if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)], existingItemIndices.contains(index) { + let currentFrame = itemNode.frame.offsetBy(dx: 0.0, dy: -presentationLayoutTransition.layout.contentOffset.y) + offset = previousFrame.origin.y - currentFrame.origin.y + break + } + } + + if offset == nil { + var previousUpperBound: CGFloat? + var previousLowerBound: CGFloat? + for (_, frame) in previousItemFrames { + if previousUpperBound == nil || previousUpperBound! > frame.minY { + previousUpperBound = frame.minY + } + if previousLowerBound == nil || previousLowerBound! < frame.maxY { + previousLowerBound = frame.maxY + } + } + + var updatedUpperBound: CGFloat? + var updatedLowerBound: CGFloat? + for item in presentationLayoutTransition.layout.items { + let frame = item.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + if updatedUpperBound == nil || updatedUpperBound! > frame.minY { + updatedUpperBound = frame.minY + } + if updatedLowerBound == nil || updatedLowerBound! < frame.maxY { + updatedLowerBound = frame.maxY + } + } + for section in presentationLayoutTransition.layout.sections { + let frame = section.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + if updatedUpperBound == nil || updatedUpperBound! > frame.minY { + updatedUpperBound = frame.minY + } + if updatedLowerBound == nil || updatedLowerBound! < frame.maxY { + updatedLowerBound = frame.maxY + } + } + + if let updatedUpperBound = updatedUpperBound, let updatedLowerBound = updatedLowerBound { + switch presentationLayoutTransition.directionHint { + case .up: + offset = -(updatedLowerBound - (previousUpperBound ?? 0.0)) + case .down: + offset = -(updatedUpperBound - (previousLowerBound ?? presentationLayoutTransition.layout.layout.size.height)) + } + } + } + + if let offset = offset { + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + + for (index, itemNode) in self.itemNodes where existingItemIndices.contains(index) { + itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) + } + for (wrappedSection, sectionNode) in self.sectionNodes where existingSections.contains(wrappedSection) { + let position = sectionNode.layer.position + sectionNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) + } + + for index in self.itemNodes.keys { + if !existingItemIndices.contains(index) { + let itemNode = self.itemNodes[index]! + if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { + self.removeItemNodeWithIndex(index, removeNode: false) + let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) + itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } else { + self.removeItemNodeWithIndex(index, removeNode: true) + } + } + } + + for itemNode in removedNodes { + if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { + let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) + itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } else { + itemNode.removeFromSupernode() + } + } + + for wrappedSection in self.sectionNodes.keys { + if !existingSections.contains(wrappedSection) { + let sectionNode = self.sectionNodes[wrappedSection]! + if let previousFrame = previousItemFrames[WrappedGridItemNode(node: sectionNode)] { + self.removeSectionNodeWithSection(wrappedSection, removeNode: false) + let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) + sectionNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak sectionNode] _ in + sectionNode?.removeFromSupernode() + }) + } else { + self.removeSectionNodeWithSection(wrappedSection, removeNode: true) + } + } + } + } else { + for index in self.itemNodes.keys { + if !existingItemIndices.contains(index) { + self.removeItemNodeWithIndex(index) + } + } + + for wrappedSection in self.sectionNodes.keys { + if !existingSections.contains(wrappedSection) { + self.removeSectionNodeWithSection(wrappedSection) + } + } + + for itemNode in removedNodes { + itemNode.removeFromSupernode() + } + } + } else { + for index in self.itemNodes.keys { + if !existingItemIndices.contains(index) { + self.removeItemNodeWithIndex(index) + } + } + + for wrappedSection in self.sectionNodes.keys { + if !existingSections.contains(wrappedSection) { + self.removeSectionNodeWithSection(wrappedSection) + } + } + + for itemNode in removedNodes { + itemNode.removeFromSupernode() } } if let visibleItemsUpdated = self.visibleItemsUpdated { - if presentationLayout.items.count != 0 { - let topIndex = presentationLayout.items.first!.index - let bottomIndex = presentationLayout.items.last!.index + if presentationLayoutTransition.layout.items.count != 0 { + let topIndex = presentationLayoutTransition.layout.items.first!.index + let bottomIndex = presentationLayoutTransition.layout.items.last!.index var topVisible: (Int, GridItem) = (topIndex, self.items[topIndex]) var bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) - let lowerDisplayBound = presentationLayout.contentOffset.y - let upperDisplayBound = presentationLayout.contentOffset.y + self.gridLayout.size.height + let lowerDisplayBound = presentationLayoutTransition.layout.contentOffset.y + let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height - for item in presentationLayout.items { - if item.frame.maxY >= lowerDisplayBound { + for item in presentationLayoutTransition.layout.items { + if lowerDisplayBound.isLess(than: item.frame.maxY) { topVisible = (item.index, self.items[item.index]) break } } - visibleItemsUpdated(GridNodeVisibleItems(top: (topIndex, self.items[topIndex]), bottom: (bottomIndex, self.items[bottomIndex]), topVisible: topVisible, bottomVisible: bottomVisible, count: self.items.count)) + var topSectionVisible: GridSection? + for section in presentationLayoutTransition.layout.sections { + if lowerDisplayBound.isLess(than: section.frame.maxY) { + if self.itemLayout.items[topVisible.0].frame.minY > section.frame.minY { + topSectionVisible = section.section + } + break + } + } + + visibleItemsUpdated(GridNodeVisibleItems(top: (topIndex, self.items[topIndex]), bottom: (bottomIndex, self.items[bottomIndex]), topVisible: topVisible, bottomVisible: bottomVisible, topSectionVisible: topSectionVisible, count: self.items.count)) } else { - visibleItemsUpdated(GridNodeVisibleItems(top: nil, bottom: nil, topVisible: nil, bottomVisible: nil, count: self.items.count)) + visibleItemsUpdated(GridNodeVisibleItems(top: nil, bottom: nil, topVisible: nil, bottomVisible: nil, topSectionVisible: nil, count: self.items.count)) } } } @@ -549,15 +801,19 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func removeItemNodeWithIndex(_ index: Int) { + private func removeItemNodeWithIndex(_ index: Int, removeNode: Bool = true) { if let itemNode = self.itemNodes.removeValue(forKey: index) { - itemNode.removeFromSupernode() + if removeNode { + itemNode.removeFromSupernode() + } } } - private func removeSectionNodeWithSection(_ section: WrappedGridSection) { + private func removeSectionNodeWithSection(_ section: WrappedGridSection, removeNode: Bool = true) { if let sectionNode = self.sectionNodes.removeValue(forKey: section) { - sectionNode.removeFromSupernode() + if removeNode { + sectionNode.removeFromSupernode() + } } } diff --git a/Display/ListView.swift b/Display/ListView.swift index dbdf175e18..600a9df004 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -88,12 +88,14 @@ public struct ListViewInsertItem { public let previousIndex: Int? public let item: ListViewItem public let directionHint: ListViewItemOperationDirectionHint? + public let forceAnimateInsertion: Bool - public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { + public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?, forceAnimateInsertion: Bool = false) { self.index = index self.previousIndex = previousIndex self.item = item self.directionHint = directionHint + self.forceAnimateInsertion = forceAnimateInsertion } } @@ -122,6 +124,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) + public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) } public struct ListViewUpdateSizeAndInsets { @@ -971,9 +974,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final var items: [ListViewItem] = [] private final var itemNodes: [ListViewItemNode] = [] - public final var displayedItemRangeChanged: (ListViewDisplayedItemRange) -> Void = { _ in } + public final var displayedItemRangeChanged: (ListViewDisplayedItemRange, Any?) -> Void = { _, _ in } public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) + private final var opaqueTransactionState: Any? + public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } private final var animations: [ListViewAnimation] = [] @@ -1129,8 +1134,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if !forceNext && self.inVSync { action() } else { - self.actionsForVSync.append(action) - self.setNeedsAnimations() + action() + //self.actionsForVSync.append(action) + //self.setNeedsAnimations() } } } @@ -1448,7 +1454,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) } - public func deleteAndInsertItems(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { + public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil { completion(self.immediateDisplayedItemRange()) return @@ -1457,7 +1463,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.transactionQueue.addTransaction({ [weak self] transactionCompletion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, completion: { [weak strongSelf] in + strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, completion: { [weak strongSelf] in completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)) transactionCompletion() @@ -1466,7 +1472,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, completion: @escaping (Void) -> Void) { + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping (Void) -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { if let updateSizeAndInsets = updateSizeAndInsets , self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { self.visibleSize = updateSizeAndInsets.size @@ -1719,7 +1725,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let stationaryItemIndex = updatedState.stationaryOffset?.0 let next = { - self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, completion: completion) + self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: completion) } if options.contains(.LowLatency) || options.contains(.Synchronous) { @@ -1834,7 +1840,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if self.debugInfo { - print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") + //print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") } if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { @@ -1903,7 +1909,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), timestamp: Double) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), timestamp: Double) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -1995,7 +2001,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } } else if animateAlpha && previousFrame == nil { - node.animateAdded(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + if forceAnimateInsertion { + node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + } else { + node.animateAdded(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + } } if node.apparentHeight > CGFloat(FLT_EPSILON) { @@ -2020,9 +2030,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func replayOperations(animated: Bool, animateAlpha: Bool, operations: [ListViewStateOperation], scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, completion: () -> Void) { + private func replayOperations(animated: Bool, animateAlpha: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { let timestamp = CACurrentMediaTime() + if let updateOpaqueState = updateOpaqueState { + self.opaqueTransactionState = updateOpaqueState + } + var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] for itemNode in self.itemNodes { previousApparentFrames.append((itemNode, itemNode.apparentFrame)) @@ -2045,7 +2059,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel break } } - self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + var forceAnimateInsertion = false + if let index = node.index, requestItemInsertionAnimationsIndices.contains(index) { + forceAnimateInsertion = true + } + self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) if let previousFrame = previousFrame, previousFrame.minY >= self.visibleSize.height || previousFrame.maxY < 0.0 { self.insertSubnode(node, at: 0) } else { @@ -2069,10 +2087,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { - self.insertNodeAtIndex(animated: false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) } else { referenceNode.index = nil - self.insertNodeAtIndex(animated: false, animateAlpha: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { }, timestamp: timestamp) self.addSubnode(referenceNode) } } else { @@ -2644,7 +2662,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) self.dispatchOnVSync { - self.replayOperations(animated: false, animateAlpha: false, operations: updatedOperations, scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) } } } @@ -2654,7 +2672,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if currentRange != self.displayedItemRange || force { self.displayedItemRange = currentRange - self.displayedItemRangeChanged(currentRange) + self.displayedItemRangeChanged(currentRange, self.opaqueTransactionState) } } @@ -2872,9 +2890,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func ensureItemNodeVisible(_ node: ListViewItemNode) { if let index = node.index { if node.frame.minY < self.insets.top { - self.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Top, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Top, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.deleteAndInsertItems(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Bottom, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Bottom, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 93893ee277..ef051adc22 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -84,6 +84,10 @@ public extension CGSize { let scale = UIScreenScale return CGSize(width: self.width / scale, height: self.height / scale) } + + public var integralFloor: CGSize { + return CGSize(width: floor(self.width), height: floor(self.height)) + } } public func assertNotOnMainThread(_ file: String = #file, line: Int = #line) { From ea1d9d38d5949abf4ba592a919ef76a540aa4da7 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 3 Nov 2016 23:00:43 +0300 Subject: [PATCH 026/245] no message --- Display/CAAnimationUtils.swift | 5 +++-- Display/ListView.swift | 23 +++++++++++++++++++---- Display/ListViewItemNode.swift | 2 +- Display/NavigationController.swift | 25 +++++++++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 95a5cff473..5a3c54cf88 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -157,8 +157,9 @@ public extension CALayer { self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double) { - self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: true) + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut) { + //self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: timingFunction, duration: duration, removeOnCompletion: true) + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, additive: true) } public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { diff --git a/Display/ListView.swift b/Display/ListView.swift index 600a9df004..7cb87c5bc0 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1028,6 +1028,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return ListViewBackingView() }, didLoad: nil) + self.clipsToBounds = true + (self.view as! ListViewBackingView).target = self self.transactionQueue.transactionCompleted = { [weak self] in @@ -1997,12 +1999,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.addInsetsAnimationToValue(layout.insets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } } else { - node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: false) } } } else if animateAlpha && previousFrame == nil { if forceAnimateInsertion { - node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) + node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: true) } else { node.animateAdded(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } @@ -2063,9 +2065,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let index = node.index, requestItemInsertionAnimationsIndices.contains(index) { forceAnimateInsertion = true } - self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: previousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + var updatedPreviousFrame = previousFrame if let previousFrame = previousFrame, previousFrame.minY >= self.visibleSize.height || previousFrame.maxY < 0.0 { - self.insertSubnode(node, at: 0) + updatedPreviousFrame = nil + } + + self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + if let updatedPreviousFrame = updatedPreviousFrame { + //self.insertSubnode(node, at: 0) + self.addSubnode(node) } else { if animated { self.insertSubnode(node, at: 0) @@ -2970,4 +2978,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } + + public func withTransaction(_ f: @escaping () -> Void) { + self.transactionQueue.addTransaction { completion in + f() + completion() + } + } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 51696a8aef..6396e309e0 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -404,7 +404,7 @@ open class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("transitionOffset", animation: animation) } - open func animateInsertion(_ currentTimestamp: Double, duration: Double) { + open func animateInsertion(_ currentTimestamp: Double, duration: Double, short: Bool) { } open func animateAdded(_ currentTimestamp: Double, duration: Double) { diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 60cf8c2c0d..0e4dd1d01b 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -226,6 +226,19 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle self.setViewControllers(controllers, animated: animated) } + public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise?) { + controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in + if let strongSelf = self { + ready?.set(true) + var controllers = strongSelf.viewControllers + controllers.removeLast() + controllers.append(controller) + strongSelf.setViewControllers(controllers, animated: animated) + } + })) + } + open override func popViewController(animated: Bool) -> UIViewController? { var controller: UIViewController? var controllers = self.viewControllers @@ -325,14 +338,26 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } else { if let topController = self.viewControllers.last , topController.isViewLoaded { topController.navigation_setNavigationController(nil) + topController.viewWillDisappear(false) topController.view.removeFromSuperview() + topController.viewDidDisappear(false) } self._viewControllers = viewControllers if let topController = viewControllers.last { + if let topController = topController as? ViewController { + if viewControllers.count >= 2 { + topController.navigationBar.previousItem = viewControllers[viewControllers.count - 2].navigationItem + } else { + topController.navigationBar.previousItem = nil + } + } + topController.navigation_setNavigationController(self) + topController.viewWillAppear(false) self.view.addSubview(topController.view) + topController.viewDidAppear(false) } } } From 0682728c2cb763b5cbcf9767a00a9cb76e4bc7ae Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 4 Nov 2016 01:39:09 +0300 Subject: [PATCH 027/245] no message --- Display/GenerateImage.swift | 23 ++++++++++++++++++- Display/NavigationController.swift | 10 +++++--- Display/NavigationTransitionCoordinator.swift | 3 +++ 3 files changed, 32 insertions(+), 4 deletions(-) diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 87e777b32c..d0a75a8b88 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -80,7 +80,28 @@ public func generateFilledCircleImage(radius: CGFloat, color: UIColor?, backgrou } public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { - return generateFilledCircleImage(radius: radius, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)) + let intRadius = Int(radius) + let cap = intRadius == 1 ? 2 : intRadius + return generateFilledCircleImage(radius: radius, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) +} + +public func generateVerticallyStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { + return generateImage(CGSize(width: radius * 2.0, height: radius * 2.0 + radius), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } + + if let color = color { + context.setFillColor(color.cgColor) + } else { + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + } + context.fillEllipse(in: CGRect(origin: CGPoint(), size: CGSize(width: radius + radius, height: radius + radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: 0.0, y: radius), size: CGSize(width: radius + radius, height: radius + radius))) + })?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)) } public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor: UIColor? = nil) -> UIImage? { diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 0e4dd1d01b..ccac1ccfcc 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -129,12 +129,12 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle self.navigationTransitionCoordinator = navigationTransitionCoordinator } case UIGestureRecognizerState.changed: - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { let translation = recognizer.translation(in: self.view).x navigationTransitionCoordinator.progress = max(0.0, min(1.0, translation / self.view.frame.width)) } case UIGestureRecognizerState.ended: - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { let velocity = recognizer.velocity(in: self.view).x if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { @@ -183,7 +183,7 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } } case .cancelled: - if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator, !navigationTransitionCoordinator.animatingCompletion { if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -318,6 +318,8 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator + topView.isUserInteractionEnabled = false + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in if let strongSelf = self { strongSelf.navigationTransitionCoordinator = nil @@ -328,6 +330,8 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle topController.setIgnoreAppearanceMethodInvocations(false) bottomController.setIgnoreAppearanceMethodInvocations(false) + topController.view.isUserInteractionEnabled = true + bottomController.viewDidDisappear(true) topController.viewDidAppear(true) diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 9e38577d36..26858c75ee 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -37,6 +37,8 @@ class NavigationTransitionCoordinator { private let inlineNavigationBarTransition: Bool + private(set) var animatingCompletion = false + init(transition: NavigationTransition, container: UIView, topView: UIView, topNavigationBar: NavigationBar?, bottomView: UIView, bottomNavigationBar: NavigationBar?) { self.transition = transition self.container = container @@ -176,6 +178,7 @@ class NavigationTransitionCoordinator { } func animateCompletion(_ velocity: CGFloat, completion: @escaping () -> ()) { + self.animatingCompletion = true let distance = (1.0 - self.progress) * self.container.bounds.size.width let f = { switch self.transition { From 49f6c6887f3e37c533129e2bf9295c5eb04f2f09 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 4 Nov 2016 12:59:23 +0300 Subject: [PATCH 028/245] no message --- Display/ListView.swift | 8 ++++++++ Display/ListViewItemNode.swift | 4 ++++ 2 files changed, 12 insertions(+) diff --git a/Display/ListView.swift b/Display/ListView.swift index 7cb87c5bc0..69c4e1b68f 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2167,6 +2167,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let insetPart: CGFloat = previousInsets.top - layout.insets.top node.transitionOffset += previousApparentHeight - layout.size.height - insetPart node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } else { + if node.shouldAnimateHorizontalFrameTransition() { + node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in + if let node = node { + node.animateFrameTransition(progress) + } + }) + } } } else { node.apparentHeight = updatedApparentHeight diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 6396e309e0..42fecf85e2 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -419,4 +419,8 @@ open class ListViewItemNode: ASDisplayNode { open func animateFrameTransition(_ progress: CGFloat) { } + + open func shouldAnimateHorizontalFrameTransition() -> Bool { + return false + } } From 962940aae91b73d53b2f6373f66768bf67540c77 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 17 Nov 2016 22:16:33 +0300 Subject: [PATCH 029/245] no message --- Display.xcodeproj/project.pbxproj | 12 +- .../xcschemes/Display.xcscheme | 2 +- .../xcschemes/DisplayTests.xcscheme | 2 +- Display/CAAnimationUtils.swift | 13 +- Display/ContainableController.swift | 17 + Display/GenerateImage.swift | 6 +- Display/ListView.swift | 317 ++++++++++++++++-- Display/ListViewItemHeader.swift | 115 +++++++ Display/ListViewItemNode.swift | 11 +- Display/PresentationContext.swift | 2 +- Display/StatusBarManager.swift | 13 +- Display/UIKitUtils.swift | 9 + 12 files changed, 474 insertions(+), 45 deletions(-) create mode 100644 Display/ListViewItemHeader.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index f73fb852a4..a4bf33483d 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -105,6 +105,7 @@ D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; + D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -220,6 +221,7 @@ D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; + D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -515,15 +517,16 @@ isa = PBXGroup; children = ( D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */, - D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */, D0C2DFBD1CC4431D0044FF83 /* Spring.swift */, D0C2DFBE1CC4431D0044FF83 /* ListView.swift */, D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */, + D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */, D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */, D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */, D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */, D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */, + D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */, ); name = "List Node"; sourceTree = ""; @@ -745,6 +748,7 @@ D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, + D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -877,7 +881,7 @@ PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; }; name = Debug; }; @@ -902,7 +906,7 @@ PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; }; name = Release; }; @@ -990,7 +994,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 3.0.1; }; name = Hockeyapp; }; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme index e81aa39f54..4b34ecd9ef 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme @@ -1,6 +1,6 @@ Void)? = nil) { + public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if timingFunction == kCAMediaTimingFunctionSpring { let animation = makeSpringAnimation(keyPath) animation.fromValue = from @@ -71,7 +71,11 @@ public extension CALayer { animation.fromValue = from animation.toValue = to animation.duration = duration - animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + if let mediaTimingFunction = mediaTimingFunction { + animation.timingFunction = mediaTimingFunction + } else { + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + } animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards animation.speed = speed @@ -158,10 +162,13 @@ public extension CALayer { } public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut) { - //self.animateAdditive(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", key: "boundsOriginYAdditive", timingFunction: timingFunction, duration: duration, removeOnCompletion: true) self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, additive: true) } + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true) + } + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { if let completion = completion { diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 04f1b2fb64..b89468ae18 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -41,6 +41,23 @@ public extension ContainedViewLayoutTransition { } } + func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + break + } + } + func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { switch self { case .immediate: diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index d0a75a8b88..28a3f053d9 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -301,7 +301,7 @@ public func readCGFloat(_ index: inout UnsafePointer, end: UnsafePointer< } } else if c == separator { break - } else if c < 48 || c > 57 { + } else if !((c >= 48 && c <= 57) || c == 45 || c == 101 || c == 69) { throw ParsingError.Generic } } @@ -348,7 +348,7 @@ public func drawSvgPath(_ context: CGContext, path: StaticString, strokeOnMove: let y2 = try readCGFloat(&index, end: end, separator: 32) let x = try readCGFloat(&index, end: end, separator: 44) let y = try readCGFloat(&index, end: end, separator: 32) - context.addCurve(to: CGPoint(x: x1, y: y1), control1: CGPoint(x: x2, y: y2), control2: CGPoint(x: x, y: y)) + context.addCurve(to: CGPoint(x: x, y: y), control1: CGPoint(x: x1, y: y1), control2: CGPoint(x: x2, y: y2)) //print("Line to \(x), \(y)") if strokeOnMove { @@ -364,6 +364,8 @@ public func drawSvgPath(_ context: CGContext, path: StaticString, strokeOnMove: context.fillPath() //CGContextBeginPath(context) //print("Close") + } else if c == 32 { // space + continue } else { throw ParsingError.Generic } diff --git a/Display/ListView.swift b/Display/ListView.swift index 69c4e1b68f..d92e52ed22 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -960,6 +960,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var touchesPosition = CGPoint() private var isTracking = false + private var isDeceleratingAfterTracking = false private final var transactionQueue: ListViewTransactionQueue private final var transactionOffset: CGFloat = 0.0 @@ -973,6 +974,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final var items: [ListViewItem] = [] private final var itemNodes: [ListViewItemNode] = [] + private final var itemHeaderNodes: [Int64: ListViewItemHeaderNode] = [:] public final var displayedItemRangeChanged: (ListViewDisplayedItemRange, Any?) -> Void = { _, _ in } public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) @@ -994,6 +996,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var selectionTouchLocation: CGPoint? private var selectionTouchDelayTimer: Foundation.Timer? + private var flashNodesDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? public func reportDurationInMS(duration: Int, smallDropEvent: Double, largeDropEvent: Double) { @@ -1143,8 +1146,44 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func resetHeaderItemsFlashTimer(start: Bool) { + if let flashNodesDelayTimer = self.flashNodesDelayTimer { + flashNodesDelayTimer.invalidate() + self.flashNodesDelayTimer = nil + } + + if start { + let timer = Timer(timeInterval: 0.3, target: ListViewTimerProxy { [weak self] in + if let strongSelf = self { + if let flashNodesDelayTimer = strongSelf.flashNodesDelayTimer { + flashNodesDelayTimer.invalidate() + strongSelf.flashNodesDelayTimer = nil + strongSelf.updateHeaderItemsFlashing(animated: true) + } + } + }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) + self.flashNodesDelayTimer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + self.updateHeaderItemsFlashing(animated: true) + } + } + + private func headerItemsAreFlashing() -> Bool { + //print("\(self.scroller.isDragging) || (\(self.scroller.isDecelerating) && \(self.isDeceleratingAfterTracking)) || \(self.flashNodesDelayTimer != nil)") + return self.scroller.isDragging || (self.isDeceleratingAfterTracking) || self.flashNodesDelayTimer != nil + } + + private func updateHeaderItemsFlashing(animated: Bool) { + let flashing = self.headerItemsAreFlashing() + for (_, headerNode) in self.itemHeaderNodes { + headerNode.updateFlashingOnScrolling(flashing, animated: animated) + } + } + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.lastContentOffsetTimestamp = 0.0 + self.resetHeaderItemsFlashTimer(start: false) + self.updateHeaderItemsFlashing(animated: true) /*if usePerformanceTracker { self.performanceTracker.start() @@ -1154,7 +1193,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if decelerate { self.lastContentOffsetTimestamp = CACurrentMediaTime() + self.isDeceleratingAfterTracking = true + self.updateHeaderItemsFlashing(animated: true) } else { + self.isDeceleratingAfterTracking = false + self.resetHeaderItemsFlashTimer(start: true) + self.updateHeaderItemsFlashing(animated: true) + self.lastContentOffsetTimestamp = 0.0 /*if usePerformanceTracker { self.performanceTracker.stop() @@ -1164,6 +1209,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.lastContentOffsetTimestamp = 0.0 + self.isDeceleratingAfterTracking = false + self.resetHeaderItemsFlashTimer(start: true) + self.updateHeaderItemsFlashing(animated: true) + /*if usePerformanceTracker { self.performanceTracker.stop() }*/ @@ -1184,15 +1233,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.lastContentOffsetTimestamp = CACurrentMediaTime() } - for itemNode in self.itemNodes { - let position = itemNode.position - itemNode.position = CGPoint(x: position.x, y: position.y - deltaY) - } - self.transactionOffset += -deltaY self.enqueueUpdateVisibleItems() - self.updateScroller() var useScrollDynamics = false @@ -1206,6 +1249,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y - deltaY) + if itemNode.wantsScrollDynamics { useScrollDynamics = true @@ -1227,6 +1273,36 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.snapToBounds() + self.updateScroller() + + self.updateItemHeaders() + + for (headerId, headerNode) in self.itemHeaderNodes { + //let position = headerNode.position + //headerNode.position = CGPoint(x: position.x, y: position.y - deltaY) + + if headerNode.wantsScrollDynamics { + useScrollDynamics = true + + var distance: CGFloat + let itemFrame = headerNode.frame + if anchor < itemFrame.origin.y { + distance = abs(itemFrame.origin.y - anchor) + } else if anchor > itemFrame.origin.y + itemFrame.size.height { + distance = abs(anchor - (itemFrame.origin.y + itemFrame.size.height)) + } else { + distance = 0.0 + } + + let factor: CGFloat = max(0.08, abs(distance) / self.visibleSize.height) + + let resistance: CGFloat = testSpringFreeResistance + + headerNode.addScrollingOffset(deltaY * factor * resistance) + } + } + if useScrollDynamics { self.setNeedsAnimations() } @@ -1292,16 +1368,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) if bottomItemEdge < self.insets.top + areaHeight - overscroll { offset = self.insets.top + areaHeight - overscroll - bottomItemEdge + //print("bottom edge offset \(offset) = \(self.insets.top) + \(areaHeight) - \(overscroll) - \(bottomItemEdge)") } else if topItemEdge > self.insets.top - overscroll && snapTopItem { offset = (self.insets.top - overscroll) - topItemEdge + //print("top edge offset \(offset)") } } else if topItemFound { if topItemEdge > self.insets.top - overscroll && snapTopItem { offset = (self.insets.top - overscroll) - topItemEdge + //print("top only edge offset \(offset)") } } else if bottomItemFound { if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge + //print("bottom only edge offset \(offset)") } } @@ -1842,7 +1922,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if self.debugInfo { - //print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") + print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") } if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { @@ -1992,13 +2072,21 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel assert(true) } - node.transitionOffset += nodeFrame.origin.y - previousFrame.origin.y - previousApparentHeight + layout.size.height + let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y - previousApparentHeight + layout.size.height + if node.rotated { + node.transitionOffset -= transitionOffsetDelta + } else { + node.transitionOffset += transitionOffsetDelta + } node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) if previousInsets != layout.insets { node.insets = previousInsets node.addInsetsAnimationToValue(layout.insets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } } else { + if self.debugInfo { + assert(true) + } node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: false) } } @@ -2032,6 +2120,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func lowestHeaderNode() -> ASDisplayNode? { + var lowestHeaderNode: ASDisplayNode? + var lowestHeaderNodeIndex: Int? + for (_, headerNode) in self.itemHeaderNodes { + if let index = self.subnodes.index(of: headerNode) { + if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! { + lowestHeaderNodeIndex = index + lowestHeaderNode = headerNode + } + } + } + return lowestHeaderNode + } + private func replayOperations(animated: Bool, animateAlpha: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { let timestamp = CACurrentMediaTime() @@ -2051,6 +2153,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + let lowestHeaderNode = self.lowestHeaderNode() + for operation in operations { switch operation { case let .InsertNode(index, offsetDirection, node, layout, apply): @@ -2072,13 +2176,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) if let updatedPreviousFrame = updatedPreviousFrame { - //self.insertSubnode(node, at: 0) - self.addSubnode(node) + if let lowestHeaderNode = lowestHeaderNode { + self.insertSubnode(node, belowSubnode: lowestHeaderNode) + } else { + self.addSubnode(node) + } } else { if animated { self.insertSubnode(node, at: 0) } else { - self.addSubnode(node) + if let lowestHeaderNode = lowestHeaderNode { + self.insertSubnode(node, belowSubnode: lowestHeaderNode) + } else { + self.addSubnode(node) + } } } case let .InsertDisappearingPlaceholder(index, referenceNode, offsetDirection): @@ -2217,7 +2328,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.stopScrolling() for itemNode in self.itemNodes { - if let index = itemNode.index , index == scrollToItem.index { + if let index = itemNode.index, index == scrollToItem.index { let offset: CGFloat switch scrollToItem.position { case .Bottom: @@ -2279,6 +2390,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var sizeAndInsetsOffset: CGFloat = 0.0 + var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) + if let updateSizeAndInsets = updateSizeAndInsets { self.visibleSize = updateSizeAndInsets.size @@ -2299,6 +2412,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let animation: CABasicAnimation switch updateSizeAndInsets.curve { case let .Spring(duration): + headerNodesTransition = (.animated(duration: duration, curve: .spring), false, -completeOffset) let springAnimation = makeSpringAnimation("sublayerTransform") springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) @@ -2314,6 +2428,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel springAnimation.isAdditive = true animation = springAnimation case .Default: + headerNodesTransition = (.animated(duration: updateSizeAndInsets.duration, curve: .easeInOut), false, -completeOffset) let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) basicAnimation.duration = updateSizeAndInsets.duration * UIView.animationDurationFactor() @@ -2378,11 +2493,39 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel offset = offsetValue - sizeAndInsetsOffset } + var previousItemHeaderNodes: [ListViewItemHeaderNode] = [] + let offsetOrZero: CGFloat = offset ?? 0.0 + switch scrollToItem.curve { + case let .Spring(duration): + headerNodesTransition = (.animated(duration: duration, curve: .spring), headerNodesTransition.1, headerNodesTransition.2 - offsetOrZero) + case .Default: + headerNodesTransition = (.animated(duration: 0.5, curve: .easeInOut), true, headerNodesTransition.2 - offsetOrZero) + } + for (_, headerNode) in self.itemHeaderNodes { + previousItemHeaderNodes.append(headerNode) + } + + self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + if let offset = offset , abs(offset) > CGFloat(FLT_EPSILON) { + let lowestHeaderNode = self.lowestHeaderNode() for itemNode in temporaryPreviousNodes { itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) temporaryPreviousNodes.append(itemNode) - self.addSubnode(itemNode) + if let lowestHeaderNode = lowestHeaderNode { + self.insertSubnode(itemNode, belowSubnode: lowestHeaderNode) + } else { + self.addSubnode(itemNode) + } + } + + var temporaryHeaderNodes: [ListViewItemHeaderNode] = [] + for headerNode in previousItemHeaderNodes { + if headerNode.supernode == nil { + headerNode.frame = headerNode.frame.offsetBy(dx: 0.0, dy: offset) + temporaryHeaderNodes.append(headerNode) + self.addSubnode(headerNode) + } } let animation: CABasicAnimation @@ -2418,6 +2561,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for itemNode in temporaryPreviousNodes { itemNode.removeFromSupernode() } + for headerNode in temporaryHeaderNodes { + headerNode.removeFromSupernode() + } } self.layer.add(animation, forKey: nil) } @@ -2434,6 +2580,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } else { + self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + if animated { self.setNeedsAnimations() } @@ -2485,6 +2633,115 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.headerAccessoryItemNode = nil } + private func updateItemHeaders(_ transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { + let upperDisplayBound = self.insets.top + let lowerDisplayBound = self.visibleSize.height - self.insets.bottom + var visibleHeaderNodes = Set() + + let flashing = self.headerItemsAreFlashing() + + let addHeader: (_ id: Int64, _ upperBound: CGFloat, _ lowerBound: CGFloat, _ item: ListViewItemHeader, _ hasValidNodes: Bool) -> Void = { id, upperBound, lowerBound, item, hasValidNodes in + let itemHeaderHeight: CGFloat = item.height + + let headerFrame: CGRect + let stickLocationDistanceFactor: CGFloat + let stickLocationDistance: CGFloat + switch item.stickDirection { + case .top: + headerFrame = CGRect(origin: CGPoint(x: 0.0, y: min(max(upperDisplayBound, upperBound), lowerBound - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight)) + stickLocationDistance = 0.0 + stickLocationDistanceFactor = 0.0 + case .bottom: + headerFrame = CGRect(origin: CGPoint(x: 0.0, y: max(upperBound, min(lowerBound, lowerDisplayBound) - itemHeaderHeight)), size: CGSize(width: self.visibleSize.width, height: itemHeaderHeight)) + stickLocationDistance = lowerBound - headerFrame.maxY + stickLocationDistanceFactor = max(0.0, min(1.0, stickLocationDistance / itemHeaderHeight)) + } + visibleHeaderNodes.insert(id) + if let headerNode = self.itemHeaderNodes[id] { + switch transition.0 { + case .immediate: + headerNode.frame = headerFrame + case let .animated(duration, curve): + let previousFrame = headerNode.frame + headerNode.frame = headerFrame + let offset = -(headerFrame.minY - previousFrame.minY + transition.2) + switch curve { + case .spring: + transition.0.animateOffsetAdditive(node: headerNode, offset: offset) + case .easeInOut: + if transition.1 { + headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99)) + } else { + headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)) + } + } + } + headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true) + headerNode.internalStickLocationDistance = stickLocationDistance + if !hasValidNodes && !headerNode.alpha.isZero { + headerNode.alpha = 0.0 + if animateInsertion { + headerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + headerNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2) + } + } else if hasValidNodes && headerNode.alpha.isZero { + headerNode.alpha = 1.0 + if animateInsertion { + headerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + headerNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.2) + } + } + headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: transition.0) + } else { + let headerNode = item.node() + headerNode.updateFlashingOnScrolling(flashing, animated: false) + headerNode.frame = headerFrame + headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false) + self.itemHeaderNodes[id] = headerNode + self.addSubnode(headerNode) + if animateInsertion { + headerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + headerNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.3) + } + headerNode.updateStickDistanceFactor(stickLocationDistanceFactor, transition: .immediate) + } + } + + var previousHeader: (Int64, CGFloat, CGFloat, ListViewItemHeader, Bool)? + for itemNode in self.itemNodes { + let itemFrame = itemNode.apparentFrame + if let itemHeader = itemNode.header() { + if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader { + if previousHeaderId == itemHeader.id { + previousHeader = (previousHeaderId, previousUpperBound, itemFrame.maxY, previousHeaderItem, hasValidNodes || itemNode.index != nil) + } else { + addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) + + previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.maxY, itemHeader, itemNode.index != nil) + } + } else { + previousHeader = (itemHeader.id, itemFrame.minY, itemFrame.maxY, itemHeader, itemNode.index != nil) + } + } else { + if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader { + addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) + } + previousHeader = nil + } + } + + if let (previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) = previousHeader { + addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) + } + + var currentIds = Set(self.itemHeaderNodes.keys) + for id in currentIds.subtracting(visibleHeaderNodes) { + if let headerNode = self.itemHeaderNodes.removeValue(forKey: id) { + headerNode.removeFromSupernode() + } + } + } + private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { var index = -1 let count = self.itemNodes.count @@ -2644,13 +2901,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel strongSelf.enqueuedUpdateVisibleItems = false } - //dispatch_after(dispatch_time(DISPATCH_TIME_NOW, Int64(2.0 * Double(NSEC_PER_SEC))), dispatch_get_main_queue(), { - completion() - - if repeatUpdate { - strongSelf.enqueueUpdateVisibleItems() - } - //}) + completion() + + if repeatUpdate { + strongSelf.enqueueUpdateVisibleItems() + } }) } }) @@ -2672,13 +2927,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.fillMissingNodes(synchronous: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: self.currentState(), inputPreviousNodes: [:], inputOperations: []) { state, operations in - - var updatedState = state - var updatedOperations = operations - updatedState.removeInvisibleNodes(&updatedOperations) - self.dispatchOnVSync { - self.replayOperations(animated: false, animateAlpha: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + let state = self.currentState() + self.async { + self.fillMissingNodes(synchronous: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in + var updatedState = state + var updatedOperations = operations + updatedState.removeInvisibleNodes(&updatedOperations) + self.dispatchOnVSync { + self.replayOperations(animated: false, animateAlpha: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + } } } } @@ -2813,6 +3070,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel index += 1 } + for (_, headerNode) in self.itemHeaderNodes { + if headerNode.animate(timestamp) { + continueAnimations = true + } + } + if !offsetRanges.offsets.isEmpty { requestUpdateVisibleItems = true var index = 0 diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift new file mode 100644 index 0000000000..e41f6b7866 --- /dev/null +++ b/Display/ListViewItemHeader.swift @@ -0,0 +1,115 @@ +import Foundation +import AsyncDisplayKit + +public enum ListViewItemHeaderStickDirection { + case top + case bottom +} + +public protocol ListViewItemHeader: class { + var id: Int64 { get } + var stickDirection: ListViewItemHeaderStickDirection { get } + var height: CGFloat { get } + + func node() -> ListViewItemHeaderNode +} + +open class ListViewItemHeaderNode: ASDisplayNode { + private final var spring: ListViewItemSpring? + let wantsScrollDynamics: Bool + final private(set) var internalStickLocationDistanceFactor: CGFloat = 0.0 + final var internalStickLocationDistance: CGFloat = 0.0 + private var isFlashingOnScrolling = false + + func updateInternalStickLocationDistanceFactor(_ factor: CGFloat, animated: Bool) { + self.internalStickLocationDistanceFactor = factor + } + + final func updateFlashingOnScrollingInternal(_ isFlashingOnScrolling: Bool, animated: Bool) { + if self.isFlashingOnScrolling != isFlashingOnScrolling { + self.isFlashingOnScrolling = isFlashingOnScrolling + self.updateFlashingOnScrolling(isFlashingOnScrolling, animated: animated) + } + /*if self.isFlashing { + if self.alpha.isZero { + self.alpha = 1.0 + if animated { + self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + } + } + } else { + if !self.alpha.isZero { + self.alpha = 0.0 + if animated { + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + }*/ + } + + open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { + } + + public init(dynamicBounce: Bool = false) { + self.wantsScrollDynamics = dynamicBounce + if dynamicBounce { + self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) + } + + super.init() + } + + open func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { + } + + final func addScrollingOffset(_ scrollingOffset: CGFloat) { + if self.spring != nil && internalStickLocationDistanceFactor.isZero { + let bounds = self.bounds + self.bounds = CGRect(origin: CGPoint(x: 0.0, y: bounds.origin.y + scrollingOffset), size: bounds.size) + } + } + + public func animate(_ timestamp: Double) -> Bool { + var continueAnimations = false + + if let _ = self.spring { + let bounds = self.bounds + var offset = bounds.origin.y + let currentOffset = offset + + let frictionConstant: CGFloat = testSpringFriction + let springConstant: CGFloat = testSpringConstant + let time: CGFloat = 1.0 / 60.0 + + // friction force = velocity * friction constant + let frictionForce = self.spring!.velocity * frictionConstant + // spring force = (target point - current position) * spring constant + let springForce = -currentOffset * springConstant + // force = spring force - friction force + let force = springForce - frictionForce + + // velocity = current velocity + force * time / mass + self.spring!.velocity = self.spring!.velocity + force * time + // position = current position + velocity * time + offset = currentOffset + self.spring!.velocity * time + + offset = offset.isNaN ? 0.0 : offset + + let epsilon: CGFloat = 0.1 + if abs(offset) < epsilon && abs(self.spring!.velocity) < epsilon { + offset = 0.0 + self.spring!.velocity = 0.0 + } else { + continueAnimations = true + } + + if abs(offset) > 250.0 { + offset = offset < 0.0 ? -250.0 : 250.0 + } + + self.bounds = CGRect(origin: CGPoint(x: 0.0, y: offset), size: bounds.size) + } + + return continueAnimations + } +} diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 42fecf85e2..15912c8c1f 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -52,9 +52,10 @@ public struct ListViewItemNodeLayout { } open class ListViewItemNode: ASDisplayNode { + let rotated: Bool final var index: Int? - final var accessoryItemNode: ListViewAccessoryItemNode? { + public final var accessoryItemNode: ListViewAccessoryItemNode? { didSet { if let accessoryItemNode = self.accessoryItemNode { self.layoutAccessoryItemNode(accessoryItemNode) @@ -132,7 +133,7 @@ open class ListViewItemNode: ASDisplayNode { return ListViewItemNodeLayout(contentSize: contentSize, insets: insets) } - public init(layerBacked: Bool, dynamicBounce: Bool = true) { + public init(layerBacked: Bool, dynamicBounce: Bool = true, rotated: Bool = false) { if true { if dynamicBounce { self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) @@ -142,6 +143,8 @@ open class ListViewItemNode: ASDisplayNode { self.wantsScrollDynamics = false } + self.rotated = rotated + //super.init() //self.layerBacked = layerBacked @@ -423,4 +426,8 @@ open class ListViewItemNode: ASDisplayNode { open func shouldAnimateHorizontalFrameTransition() -> Bool { return false } + + open func header() -> ListViewItemHeader? { + return nil + } } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index aad06a904a..85df924276 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -130,7 +130,7 @@ final class PresentationContext { } func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - for controller in self.controllers { + for controller in self.controllers.reversed() { if controller.isViewLoaded { if let result = controller.view.hitTest(point, with: event) { return result diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 409ae445be..cf75310654 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -71,17 +71,22 @@ class StatusBarManager { var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) }) var reduceSurfaces = true - var reduceSurfacesStatusBarStyle: StatusBarStyle? + var reduceSurfacesStatusBarStyleAndAlpha: (StatusBarStyle, CGFloat)? outer: for surface in mappedSurfaces { for mappedStatusBar in surface.statusBars { if mappedStatusBar.frame.origin.equalTo(CGPoint()) { - if let reduceSurfacesStatusBarStyle = reduceSurfacesStatusBarStyle { - if mappedStatusBar.style != reduceSurfacesStatusBarStyle { + let statusBarAlpha = mappedStatusBar.statusBar?.alpha ?? 1.0 + if let reduceSurfacesStatusBarStyleAndAlpha = reduceSurfacesStatusBarStyleAndAlpha { + if mappedStatusBar.style != reduceSurfacesStatusBarStyleAndAlpha.0 { + reduceSurfaces = false + break outer + } + if !statusBarAlpha.isEqual(to: reduceSurfacesStatusBarStyleAndAlpha.1) { reduceSurfaces = false break outer } } else { - reduceSurfacesStatusBarStyle = mappedStatusBar.style + reduceSurfacesStatusBarStyleAndAlpha = (mappedStatusBar.style, statusBarAlpha) } } } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index ef051adc22..3ae3835f18 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -55,6 +55,10 @@ public extension CGSize { return fittedSize } + public func cropped(_ size: CGSize) -> CGSize { + return CGSize(width: min(size.width, self.width), height: min(size.height, self.height)) + } + public func fittedToArea(_ area: CGFloat) -> CGSize { if self.height < 1.0 || self.width < 1.0 { return CGSize() @@ -75,6 +79,11 @@ public extension CGSize { return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } + public func fittedToWidthOrSmaller(_ width: CGFloat) -> CGSize { + let scale = min(1.0, width / max(1.0, self.width)) + return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) + } + public func multipliedByScreenScale() -> CGSize { let scale = UIScreenScale return CGSize(width: self.width * scale, height: self.height * scale) From 28457a2e165d44b1f04deda5b80f871d1b7a4614 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 22 Nov 2016 21:29:16 +0300 Subject: [PATCH 030/245] no message --- Display.xcodeproj/project.pbxproj | 16 + Display/HighlightTrackingButton.swift | 12 +- Display/HighlightableButton.swift | 26 + Display/LegacyPresentedController.swift | 149 +++ Display/LegacyPresentedControllerNode.swift | 38 + Display/ListView.swift | 1100 ++++--------------- Display/ListViewIntermediateState.swift | 869 +++++++++++++++ Display/NavigationController.swift | 4 +- Display/PresentationContext.swift | 6 +- Display/UIViewController+Navigation.h | 3 +- Display/UIViewController+Navigation.m | 20 +- Display/ViewController.swift | 4 +- Display/Window.swift | 40 +- 13 files changed, 1351 insertions(+), 936 deletions(-) create mode 100644 Display/HighlightableButton.swift create mode 100644 Display/LegacyPresentedController.swift create mode 100644 Display/LegacyPresentedControllerNode.swift create mode 100644 Display/ListViewIntermediateState.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index a4bf33483d..747514cf83 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE31D904A000066BF65 /* GridItem.swift */; }; + D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */; }; + D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */; }; + D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; @@ -103,6 +106,7 @@ D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; + D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A021DE473B900BC6096 /* HighlightableButton.swift */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; @@ -129,6 +133,9 @@ D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; D01E2BE31D904A000066BF65 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; + D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedController.swift; sourceTree = ""; }; + D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedControllerNode.swift; sourceTree = ""; }; + D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; @@ -219,6 +226,7 @@ D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0E35A021DE473B900BC6096 /* HighlightableButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableButton.swift; sourceTree = ""; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; @@ -303,6 +311,7 @@ isa = PBXGroup; children = ( D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */, + D0E35A021DE473B900BC6096 /* HighlightableButton.swift */, ); name = Nodes; sourceTree = ""; @@ -504,6 +513,8 @@ D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */, D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */, D05CC2E21B69552C00E235A3 /* ViewController.swift */, + D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */, + D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */, D05BE4A71D1F1DCC002BD72C /* Master */, D081229A1D19A9EB005F7395 /* Navigation */, D015F7551D1B142300E269B5 /* Tab Bar */, @@ -519,6 +530,7 @@ D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */, D0C2DFBD1CC4431D0044FF83 /* Spring.swift */, D0C2DFBE1CC4431D0044FF83 /* ListView.swift */, + D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */, D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */, D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */, D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */, @@ -731,8 +743,10 @@ D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, + D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, + D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, @@ -742,11 +756,13 @@ D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, + D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, + D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */, ); diff --git a/Display/HighlightTrackingButton.swift b/Display/HighlightTrackingButton.swift index 9f8d5bbfeb..f50385e127 100644 --- a/Display/HighlightTrackingButton.swift +++ b/Display/HighlightTrackingButton.swift @@ -1,22 +1,26 @@ import UIKit -public class HighlightTrackingButton: UIButton { +open class HighlightTrackingButton: UIButton { + public var internalHighligthedChanged: (Bool) -> Void = { _ in } public var highligthedChanged: (Bool) -> Void = { _ in } - public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { self.highligthedChanged(true) + self.internalHighligthedChanged(true) return super.beginTracking(touch, with: event) } - public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { self.highligthedChanged(false) + self.internalHighligthedChanged(false) super.endTracking(touch, with: event) } - public override func cancelTracking(with event: UIEvent?) { + open override func cancelTracking(with event: UIEvent?) { self.highligthedChanged(false) + self.internalHighligthedChanged(false) super.cancelTracking(with: event) } diff --git a/Display/HighlightableButton.swift b/Display/HighlightableButton.swift new file mode 100644 index 0000000000..44c0071958 --- /dev/null +++ b/Display/HighlightableButton.swift @@ -0,0 +1,26 @@ +import Foundation +import UIKit + +open class HighlightableButton: HighlightTrackingButton { + override public init(frame: CGRect) { + super.init(frame: frame) + + self.adjustsImageWhenHighlighted = false + self.adjustsImageWhenDisabled = false + self.internalHighligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.layer.removeAnimation(forKey: "opacity") + strongSelf.alpha = 0.4 + } else { + strongSelf.alpha = 1.0 + strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift new file mode 100644 index 0000000000..f1d399e6e5 --- /dev/null +++ b/Display/LegacyPresentedController.swift @@ -0,0 +1,149 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +public enum LegacyPresentedControllerPresentation { + case custom + case modal +} + +private func passControllerAppearanceAnimated(presentation: LegacyPresentedControllerPresentation) -> Bool { + switch presentation { + case .custom: + return false + case .modal: + return true + } +} + +open class LegacyPresentedController: ViewController { + private let legacyController: UIViewController + private let presentation: LegacyPresentedControllerPresentation + + private var controllerNode: LegacyPresentedControllerNode { + return self.displayNode as! LegacyPresentedControllerNode + } + private var loadedController = false + + var controllerLoaded: (() -> Void)? + + private let asPresentable = true + + public init(legacyController: UIViewController, presentation: LegacyPresentedControllerPresentation) { + self.legacyController = legacyController + self.presentation = presentation + + super.init() + + self.navigationBar.isHidden = true + /*legacyController.navigation_setDismiss { [weak self] in + self?.dismiss() + }*/ + if !asPresentable { + self.addChildViewController(legacyController) + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override open func loadDisplayNode() { + self.displayNode = LegacyPresentedControllerNode() + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if self.ignoreAppearanceMethodInvocations() { + return + } + + if !loadedController && !asPresentable { + loadedController = true + + self.controllerNode.controllerView = self.legacyController.view + self.controllerNode.view.addSubview(self.legacyController.view) + self.legacyController.didMove(toParentViewController: self) + + if let controllerLoaded = self.controllerLoaded { + controllerLoaded() + } + } + + if !asPresentable { + self.legacyController.viewWillAppear(animated && passControllerAppearanceAnimated(presentation: self.presentation)) + } + } + + override open func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if self.ignoreAppearanceMethodInvocations() { + return + } + + if !asPresentable { + self.legacyController.viewWillDisappear(animated && passControllerAppearanceAnimated(presentation: self.presentation)) + } + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if self.ignoreAppearanceMethodInvocations() { + return + } + + if asPresentable { + if !loadedController { + loadedController = true + //self.legacyController.modalPresentationStyle = .currentContext + self.present(self.legacyController, animated: false, completion: nil) + } + } else { + switch self.presentation { + case .modal: + self.controllerNode.animateModalIn() + self.legacyController.viewDidAppear(true) + case .custom: + self.legacyController.viewDidAppear(animated) + } + } + } + + override open func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + if !self.asPresentable { + self.legacyController.viewDidDisappear(animated && passControllerAppearanceAnimated(presentation: self.presentation)) + } + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition) + } + + public func dismiss() { + switch self.presentation { + case .modal: + self.controllerNode.animateModalOut { [weak self] in + /*if let controller = self?.legacyController as? TGViewController { + controller.didDismiss() + } else if let controller = self?.legacyController as? TGNavigationController { + controller.didDismiss() + }*/ + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + case .custom: + /*if let controller = self.legacyController as? TGViewController { + controller.didDismiss() + } else if let controller = self.legacyController as? TGNavigationController { + controller.didDismiss() + }*/ + self.presentingViewController?.dismiss(animated: false, completion: nil) + } + } +} diff --git a/Display/LegacyPresentedControllerNode.swift b/Display/LegacyPresentedControllerNode.swift new file mode 100644 index 0000000000..f853701566 --- /dev/null +++ b/Display/LegacyPresentedControllerNode.swift @@ -0,0 +1,38 @@ +import Foundation +import AsyncDisplayKit +import Display + +final class LegacyPresentedControllerNode: ASDisplayNode { + private var containerLayout: ContainerViewLayout? + + var controllerView: UIView? { + didSet { + if let controllerView = self.controllerView, let containerLayout = self.containerLayout { + controllerView.frame = CGRect(origin: CGPoint(), size: containerLayout.size) + } + } + } + + override init() { + super.init(viewBlock: { + return UITracingLayerView() + }, didLoad: nil) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.containerLayout = layout + if let controllerView = self.controllerView { + controllerView.frame = CGRect(origin: CGPoint(), size: layout.size) + } + } + + func animateModalIn() { + self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + + func animateModalOut(completion: @escaping () -> Void) { + self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in + completion() + }) + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index d92e52ed22..45e2611db7 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -5,875 +5,6 @@ import SwiftSignalKit private let usePerformanceTracker = false private let useDynamicTuning = false -public enum ListViewCenterScrollPositionOverflow { - case Top - case Bottom -} - -public enum ListViewScrollPosition: Equatable { - case Top - case Bottom - case Center(ListViewCenterScrollPositionOverflow) -} - -public func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { - switch lhs { - case .Top: - switch rhs { - case .Top: - return true - default: - return false - } - case .Bottom: - switch rhs { - case .Bottom: - return true - default: - return false - } - case let .Center(lhsOverflow): - switch rhs { - case let .Center(rhsOverflow) where lhsOverflow == rhsOverflow: - return true - default: - return false - } - } -} - -public enum ListViewScrollToItemDirectionHint { - case Up - case Down -} - -public enum ListViewAnimationCurve { - case Spring(duration: Double) - case Default -} - -public struct ListViewScrollToItem { - public let index: Int - public let position: ListViewScrollPosition - public let animated: Bool - public let curve: ListViewAnimationCurve - public let directionHint: ListViewScrollToItemDirectionHint - - public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) { - self.index = index - self.position = position - self.animated = animated - self.curve = curve - self.directionHint = directionHint - } -} - -public enum ListViewItemOperationDirectionHint { - case Up - case Down -} - -public struct ListViewDeleteItem { - public let index: Int - public let directionHint: ListViewItemOperationDirectionHint? - - public init(index: Int, directionHint: ListViewItemOperationDirectionHint?) { - self.index = index - self.directionHint = directionHint - } -} - -public struct ListViewInsertItem { - public let index: Int - public let previousIndex: Int? - public let item: ListViewItem - public let directionHint: ListViewItemOperationDirectionHint? - public let forceAnimateInsertion: Bool - - public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?, forceAnimateInsertion: Bool = false) { - self.index = index - self.previousIndex = previousIndex - self.item = item - self.directionHint = directionHint - self.forceAnimateInsertion = forceAnimateInsertion - } -} - -public struct ListViewUpdateItem { - public let index: Int - public let previousIndex: Int - public let item: ListViewItem - public let directionHint: ListViewItemOperationDirectionHint? - - public init(index: Int, previousIndex: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { - self.index = index - self.previousIndex = previousIndex - self.item = item - self.directionHint = directionHint - } -} - -public struct ListViewDeleteAndInsertOptions: OptionSet { - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1) - public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) - public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) - public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) - public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) -} - -public struct ListViewUpdateSizeAndInsets { - public let size: CGSize - public let insets: UIEdgeInsets - public let duration: Double - public let curve: ListViewAnimationCurve - - public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve) { - self.size = size - self.insets = insets - self.duration = duration - self.curve = curve - } -} - -public struct ListViewItemRange: Equatable { - public let firstIndex: Int - public let lastIndex: Int -} - -public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { - return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex -} - -public struct ListViewDisplayedItemRange: Equatable { - public let loadedRange: ListViewItemRange? - public let visibleRange: ListViewItemRange? -} - -public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { - return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange -} - -private struct IndexRange { - let first: Int - let last: Int - - func contains(_ index: Int) -> Bool { - return index >= first && index <= last - } - - var empty: Bool { - return first > last - } -} - -private struct OffsetRanges { - var offsets: [(IndexRange, CGFloat)] = [] - - mutating func append(_ other: OffsetRanges) { - self.offsets.append(contentsOf: other.offsets) - } - - mutating func offset(_ indexRange: IndexRange, offset: CGFloat) { - self.offsets.append((indexRange, offset)) - } - - func offsetForIndex(_ index: Int) -> CGFloat { - var result: CGFloat = 0.0 - for offset in self.offsets { - if offset.0.contains(index) { - result += offset.1 - } - } - return result - } -} - -private func binarySearch(_ inputArr: [Int], searchItem: Int) -> Int? { - var lowerIndex = 0; - var upperIndex = inputArr.count - 1 - - if lowerIndex > upperIndex { - return nil - } - - while (true) { - let currentIndex = (lowerIndex + upperIndex) / 2 - if (inputArr[currentIndex] == searchItem) { - return currentIndex - } else if (lowerIndex > upperIndex) { - return nil - } else { - if (inputArr[currentIndex] > searchItem) { - upperIndex = currentIndex - 1 - } else { - lowerIndex = currentIndex + 1 - } - } - } -} - -private struct TransactionState { - let visibleSize: CGSize - let items: [ListViewItem] -} - -private struct PendingNode { - let index: Int - let node: ListViewItemNode - let apply: () -> () - let frame: CGRect - let apparentHeight: CGFloat -} - -private enum ListViewStateNode { - case Node(index: Int, frame: CGRect, referenceNode: ListViewItemNode?) - case Placeholder(frame: CGRect) - - var index: Int? { - switch self { - case .Node(let index, _, _): - return index - case .Placeholder(_): - return nil - } - } - - var frame: CGRect { - get { - switch self { - case .Node(_, let frame, _): - return frame - case .Placeholder(let frame): - return frame - } - } set(value) { - switch self { - case let .Node(index, _, referenceNode): - self = .Node(index: index, frame: value, referenceNode: referenceNode) - case .Placeholder(_): - self = .Placeholder(frame: value) - } - } - } -} - -private enum ListViewInsertionOffsetDirection { - case Up - case Down - - init(_ hint: ListViewItemOperationDirectionHint) { - switch hint { - case .Up: - self = .Up - case .Down: - self = .Down - } - } - - func inverted() -> ListViewInsertionOffsetDirection { - switch self { - case .Up: - return .Down - case .Down: - return .Up - } - } -} - -private struct ListViewInsertionPoint { - let index: Int - let point: CGPoint - let direction: ListViewInsertionOffsetDirection -} - -private struct ListViewState { - var insets: UIEdgeInsets - var visibleSize: CGSize - let invisibleInset: CGFloat - var nodes: [ListViewStateNode] - var scrollPosition: (Int, ListViewScrollPosition)? - var stationaryOffset: (Int, CGFloat)? - - mutating func fixScrollPostition(_ itemCount: Int) { - if let (fixedIndex, fixedPosition) = self.scrollPosition { - for node in self.nodes { - if let index = node.index , index == fixedIndex { - let offset: CGFloat - switch fixedPosition { - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY - case .Top: - offset = self.insets.top - node.frame.minY - case let .Center(overflow): - let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top - if node.frame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { - offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY - } else { - switch overflow { - case .Top: - offset = self.insets.top - node.frame.minY - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY - } - } - } - - var minY: CGFloat = CGFloat.greatestFiniteMagnitude - var maxY: CGFloat = 0.0 - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame = frame.offsetBy(dx: 0.0, dy: offset) - self.nodes[i].frame = frame - - minY = min(minY, frame.minY) - maxY = max(maxY, frame.maxY) - } - - var additionalOffset: CGFloat = 0.0 - if minY > self.insets.top { - additionalOffset = self.insets.top - minY - } - - if abs(additionalOffset) > CGFloat(FLT_EPSILON) { - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame = frame.offsetBy(dx: 0.0, dy: additionalOffset) - self.nodes[i].frame = frame - } - } - - self.snapToBounds(itemCount, snapTopItem: true) - - break - } - } - } else if let (stationaryIndex, stationaryOffset) = self.stationaryOffset { - for node in self.nodes { - if node.index == stationaryIndex { - let offset = stationaryOffset - node.frame.minY - - if abs(offset) > CGFloat(FLT_EPSILON) { - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame = frame.offsetBy(dx: 0.0, dy: offset) - self.nodes[i].frame = frame - } - } - - break - } - } - - //self.snapToBounds(itemCount, snapTopItem: true) - } - } - - mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) { - if index < boundary { - for node in self.nodes { - if let nodeIndex = node.index , nodeIndex >= index { - if let frame = frames[nodeIndex] { - self.stationaryOffset = (nodeIndex, frame.minY) - break - } - } - } - } else { - for node in self.nodes.reversed() { - if let nodeIndex = node.index , nodeIndex <= index { - if let frame = frames[nodeIndex] { - self.stationaryOffset = (nodeIndex, frame.minY) - break - } - } - } - } - } - - mutating func snapToBounds(_ itemCount: Int, snapTopItem: Bool) { - var completeHeight: CGFloat = 0.0 - var topItemFound = false - var bottomItemFound = false - var topItemEdge: CGFloat = 0.0 - var bottomItemEdge: CGFloat = 0.0 - - for node in self.nodes { - if let index = node.index { - if index == 0 { - topItemFound = true - topItemEdge = node.frame.minY - } - break - } - } - - for node in self.nodes.reversed() { - if let index = node.index { - if index == itemCount - 1 { - bottomItemFound = true - bottomItemEdge = node.frame.maxY - } - break - } - } - - if topItemFound && bottomItemFound { - for node in self.nodes { - completeHeight += node.frame.size.height - } - } - - let overscroll: CGFloat = 0.0 - - var offset: CGFloat = 0.0 - if topItemFound && bottomItemFound { - let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) - if bottomItemEdge < self.insets.top + areaHeight - overscroll { - offset = self.insets.top + areaHeight - overscroll - bottomItemEdge - } else if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - } - } else if topItemFound { - if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - } - } else if bottomItemFound { - if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { - offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge - } - } - - if abs(offset) > CGFloat(FLT_EPSILON) { - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y += offset - self.nodes[i].frame = frame - } - } - } - - func insertionPoint(_ insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { - var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)? - - if let (fixedIndex, _) = self.scrollPosition { - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , index == fixedIndex { - fixedNode = (i, index, node.frame) - break - } - } - - if fixedNode == nil { - return ListViewInsertionPoint(index: fixedIndex, point: CGPoint(), direction: .Down) - } - } - - if fixedNode == nil { - if let (fixedIndex, _) = self.stationaryOffset { - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , index == fixedIndex { - fixedNode = (i, index, node.frame) - break - } - } - } - } - - if fixedNode == nil { - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , node.frame.maxY >= self.insets.top { - fixedNode = (i, index, node.frame) - break - } - } - } - - if fixedNode == nil && self.nodes.count != 0 { - for i in (0 ..< self.nodes.count).reversed() { - let node = self.nodes[i] - if let index = node.index { - fixedNode = (i, index, node.frame) - break - } - } - } - - if let fixedNode = fixedNode { - var currentUpperNode = fixedNode - for i in (0 ..< fixedNode.nodeIndex).reversed() { - let node = self.nodes[i] - if let index = node.index { - if index != currentUpperNode.index - 1 { - if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) - } else { - break - } - } - currentUpperNode = (i, index, node.frame) - } - } - - if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - - return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) - } - - var currentLowerNode = fixedNode - if fixedNode.nodeIndex + 1 < self.nodes.count { - for i in (fixedNode.nodeIndex + 1) ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index { - if index != currentLowerNode.index + 1 { - if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) - } else { - break - } - } - currentLowerNode = (i, index, node.frame) - } - } - } - - if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) - } - } else if itemCount != 0 { - return ListViewInsertionPoint(index: 0, point: CGPoint(x: 0.0, y: self.insets.top), direction: .Down) - } - - return nil - } - - mutating func removeInvisibleNodes(_ operations: inout [ListViewStateOperation]) { - var i = 0 - var visibleItemNodeHeight: CGFloat = 0.0 - while i < self.nodes.count { - visibleItemNodeHeight += self.nodes[i].frame.height - i += 1 - } - - if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { - i = self.nodes.count - 1 - while i >= 0 { - let itemNode = self.nodes[i] - let frame = itemNode.frame - //print("node \(i) frame \(frame)") - if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { - //print("remove invisible 1 \(i) frame \(frame)") - operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) - self.nodes.remove(at: i) - } - - i -= 1 - } - } - - let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , node.frame.maxY > upperBound { - if i != 0 { - var previousIndex = index - for j in (0 ..< i).reversed() { - if self.nodes[j].frame.maxY < upperBound { - if let index = self.nodes[j].index { - if index != previousIndex - 1 { - //print("remove monotonity \(j) (\(index))") - operations.append(.Remove(index: j, offsetDirection: .Down)) - self.nodes.remove(at: j) - } else { - previousIndex = index - } - } - } - } - } - break - } - } - - let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) - for i in (0 ..< self.nodes.count).reversed() { - let node = self.nodes[i] - if let index = node.index , node.frame.minY < lowerBound { - if i != self.nodes.count - 1 { - var previousIndex = index - var removeIndices: [Int] = [] - for j in (i + 1) ..< self.nodes.count { - if self.nodes[j].frame.minY > lowerBound { - if let index = self.nodes[j].index { - if index != previousIndex + 1 { - removeIndices.append(j) - } else { - previousIndex = index - } - } - } - } - if !removeIndices.isEmpty { - for i in removeIndices.reversed() { - //print("remove monotonity \(i) (\(self.nodes[i].index!))") - operations.append(.Remove(index: i, offsetDirection: .Up)) - self.nodes.remove(at: i) - } - } - } - break - } - } - } - - func nodeInsertionPointAndIndex(_ itemIndex: Int) -> (CGPoint, Int) { - if self.nodes.count == 0 { - return (CGPoint(x: 0.0, y: self.insets.top), 0) - } else { - var index = 0 - var lastNodeWithIndex = -1 - for node in self.nodes { - if let nodeItemIndex = node.index { - if nodeItemIndex > itemIndex { - break - } - lastNodeWithIndex = index - } - index += 1 - } - lastNodeWithIndex += 1 - return (CGPoint(x: 0.0, y: lastNodeWithIndex == 0 ? self.nodes[0].frame.minY : self.nodes[lastNodeWithIndex - 1].frame.maxY), lastNodeWithIndex) - } - } - - func continuousHeightRelativeToNodeIndex(_ fixedNodeIndex: Int) -> CGFloat { - let fixedIndex = self.nodes[fixedNodeIndex].index! - - var height: CGFloat = 0.0 - - if fixedNodeIndex != 0 { - var upperIndex = fixedIndex - for i in (0 ..< fixedNodeIndex).reversed() { - if let index = self.nodes[i].index { - if index == upperIndex - 1 { - height += self.nodes[i].frame.size.height - upperIndex = index - } else { - break - } - } - } - } - - if fixedNodeIndex != self.nodes.count - 1 { - var lowerIndex = fixedIndex - for i in (fixedNodeIndex + 1) ..< self.nodes.count { - if let index = self.nodes[i].index { - if index == lowerIndex + 1 { - height += self.nodes[i].frame.size.height - lowerIndex = index - } else { - break - } - } - } - } - - return height - } - - mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { - let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) - - let nodeOrigin: CGPoint - switch offsetDirection { - case .Up: - nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height)) - case .Down: - nodeOrigin = insertionOrigin - } - - let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) - - operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) - self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex) - - if !animated { - switch offsetDirection { - case .Up: - var i = insertionIndex - 1 - while i >= 0 { - var frame = self.nodes[i].frame - frame.origin.y -= nodeFrame.size.height - self.nodes[i].frame = frame - i -= 1 - } - case .Down: - var i = insertionIndex + 1 - while i < self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y += nodeFrame.size.height - self.nodes[i].frame = frame - i += 1 - } - } - } - - var previousIndex: Int? - for node in self.nodes { - if let index = node.index { - if let currentPreviousIndex = previousIndex { - if index <= currentPreviousIndex { - print("index <= previousIndex + 1") - break - } - previousIndex = index - } else { - previousIndex = index - } - } - } - - if let _ = self.scrollPosition { - self.fixScrollPostition(itemCount) - } - } - - mutating func removeNodeAtIndex(_ index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, operations: inout [ListViewStateOperation]) { - let node = self.nodes[index] - if case let .Node(_, _, referenceNode) = node { - let nodeFrame = node.frame - self.nodes.remove(at: index) - let offsetDirection: ListViewInsertionOffsetDirection - if let direction = direction { - offsetDirection = ListViewInsertionOffsetDirection(direction) - } else { - if nodeFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { - offsetDirection = .Down - } else { - offsetDirection = .Up - } - } - operations.append(.Remove(index: index, offsetDirection: offsetDirection)) - - if let referenceNode = referenceNode , animated { - self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) - operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) - } else { - if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { - if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - for i in (0 ..< index).reversed() { - var frame = self.nodes[i].frame - frame.origin.y += nodeFrame.size.height - self.nodes[i].frame = frame - } - } else { - for i in index ..< self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y -= nodeFrame.size.height - self.nodes[i].frame = frame - } - } - } else if index != 0 { - for i in (0 ..< index).reversed() { - var frame = self.nodes[i].frame - frame.origin.y += nodeFrame.size.height - self.nodes[i].frame = frame - } - } - } - } else { - assertionFailure() - } - } - - mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> Void, operations: inout [ListViewStateOperation]) { - var i = -1 - for node in self.nodes { - i += 1 - if node.index == itemIndex { - switch animation { - case .None: - let offsetDirection: ListViewInsertionOffsetDirection - if let direction = direction { - offsetDirection = ListViewInsertionOffsetDirection(direction) - } else { - if node.frame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { - offsetDirection = .Down - } else { - offsetDirection = .Up - } - } - - switch offsetDirection { - case .Up: - let offsetDelta = -(layout.size.height - node.frame.size.height) - var updatedFrame = node.frame - updatedFrame.origin.y += offsetDelta - updatedFrame.size.height = layout.size.height - self.nodes[i].frame = updatedFrame - - for j in 0 ..< i { - var frame = self.nodes[j].frame - frame.origin.y += offsetDelta - self.nodes[j].frame = frame - } - case .Down: - let offsetDelta = layout.size.height - node.frame.size.height - var updatedFrame = node.frame - updatedFrame.size.height = layout.size.height - self.nodes[i].frame = updatedFrame - - for j in i + 1 ..< self.nodes.count { - var frame = self.nodes[j].frame - frame.origin.y += offsetDelta - self.nodes[j].frame = frame - } - } - - operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) - case .System: - operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) - } - - break - } - } - } -} - -private enum ListViewStateOperation { - case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) - case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) - case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) - case Remap([Int: Int]) - case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) -} - private let infiniteScrollSize: CGFloat = 10000.0 private let insertionAnimationDuration: Double = 0.4 @@ -886,7 +17,7 @@ private final class ListViewBackingLayer: CALayer { } final class ListViewBackingView: UIView { - weak var target: ASDisplayNode? + weak var target: ListView? override class var layerClass: AnyClass { return ListViewBackingLayer.self @@ -913,6 +44,15 @@ final class ListViewBackingView: UIView { override func touchesEnded(_ touches: Set, with event: UIEvent?) { self.target?.touchesEnded(touches, with: event) } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let target = target, target.limitHitTestToNodes { + if !target.internalHitTest(point, with: event) { + return nil + } + } + return super.hitTest(point, with: event) + } } private final class ListViewTimerProxy: NSObject { @@ -958,6 +98,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public final var stackFromBottom: Bool = false + public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 + public final var limitHitTestToNodes: Bool = false + private var touchesPosition = CGPoint() private var isTracking = false private var isDeceleratingAfterTracking = false @@ -1273,7 +417,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.snapToBounds() + if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } self.updateScroller() self.updateItemHeaders() @@ -1313,9 +459,33 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel //CATransaction.commit() } - private func snapToBounds(_ snapTopItem: Bool = false) { + private func calculateAdditionalTopInverseInset() -> CGFloat { + var additionalInverseTopInset: CGFloat = 0.0 + if !self.stackFromBottomInsetItemFactor.isZero { + var remainingFactor = self.stackFromBottomInsetItemFactor + for itemNode in self.itemNodes { + if remainingFactor.isLessThanOrEqualTo(0.0) { + break + } + + let itemFactor: CGFloat + if CGFloat(1.0).isLessThanOrEqualTo(remainingFactor) { + itemFactor = 1.0 + } else { + itemFactor = remainingFactor + } + + additionalInverseTopInset += floor(itemNode.apparentBounds.height * itemFactor) + + remainingFactor -= 1.0 + } + } + return additionalInverseTopInset + } + + private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool) -> (snappedTopInset: CGFloat, offset: CGFloat) { if self.itemNodes.count == 0 { - return + return (0.0, 0.0) } var overscroll: CGFloat = 0.0 @@ -1340,6 +510,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + var effectiveInsets = self.insets + if topItemFound && !self.stackFromBottomInsetItemFactor.isZero { + let additionalInverseTopInset = self.calculateAdditionalTopInverseInset() + effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset) + } + if topItemFound { topItemEdge = itemNodes[0].apparentFrame.origin.y } @@ -1365,23 +541,38 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var offset: CGFloat = 0.0 if topItemFound && bottomItemFound { - let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) - if bottomItemEdge < self.insets.top + areaHeight - overscroll { - offset = self.insets.top + areaHeight - overscroll - bottomItemEdge - //print("bottom edge offset \(offset) = \(self.insets.top) + \(areaHeight) - \(overscroll) - \(bottomItemEdge)") - } else if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - //print("top edge offset \(offset)") + let visibleAreaHeight = self.visibleSize.height - effectiveInsets.bottom - effectiveInsets.top + if self.stackFromBottom { + if visibleAreaHeight > completeHeight { + let areaHeight = completeHeight + if topItemEdge < self.visibleSize.height - effectiveInsets.bottom - areaHeight - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - areaHeight - overscroll - topItemEdge + } else if bottomItemEdge > self.visibleSize.height - effectiveInsets.bottom - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - overscroll - bottomItemEdge + } + } else { + let areaHeight = min(completeHeight, visibleAreaHeight) + if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll { + offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > effectiveInsets.top - overscroll { + offset = (effectiveInsets.top - overscroll) - topItemEdge + } + } + } else { + let areaHeight = min(completeHeight, visibleAreaHeight) + if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll { + offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { + offset = (effectiveInsets.top - overscroll) - topItemEdge + } } } else if topItemFound { - if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - //print("top only edge offset \(offset)") + if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { + offset = (effectiveInsets.top - overscroll) - topItemEdge } } else if bottomItemFound { - if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { - offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge - //print("bottom only edge offset \(offset)") + if bottomItemEdge < self.visibleSize.height - effectiveInsets.bottom - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - overscroll - bottomItemEdge } } @@ -1391,9 +582,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel frame.origin.y += offset itemNode.frame = frame } - - self.updateVisibleContentOffset() } + + var snappedTopInset: CGFloat = 0.0 + if !self.stackFromBottomInsetItemFactor.isZero && topItemFound { + snappedTopInset = max(0.0, (effectiveInsets.top - self.insets.top) - (topItemEdge + offset)) + } + + return (snappedTopInset, offset) } private func updateVisibleContentOffset() { @@ -1426,36 +622,61 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return } - var completeHeight = self.insets.top + self.insets.bottom var topItemFound = false var bottomItemFound = false var topItemEdge: CGFloat = 0.0 var bottomItemEdge: CGFloat = 0.0 - if itemNodes[0].index == 0 { - topItemFound = true - topItemEdge = itemNodes[0].apparentFrame.origin.y + for i in 0 ..< self.itemNodes.count { + if let index = itemNodes[i].index { + if index == 0 { + topItemFound = true + topItemEdge = itemNodes[0].apparentFrame.origin.y + break + } + } } + var effectiveInsets = self.insets + if topItemFound && !self.stackFromBottomInsetItemFactor.isZero { + let additionalInverseTopInset = self.calculateAdditionalTopInverseInset() + effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset) + } + + var completeHeight = effectiveInsets.top + effectiveInsets.bottom + if itemNodes[itemNodes.count - 1].index == self.items.count - 1 { bottomItemFound = true bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY } + topItemEdge -= effectiveInsets.top + bottomItemEdge += effectiveInsets.bottom + if topItemFound && bottomItemFound { for itemNode in self.itemNodes { completeHeight += itemNode.apparentBounds.height } + + if self.stackFromBottom { + let updatedCompleteHeight = max(completeHeight, self.visibleSize.height) + let deltaCompleteHeight = updatedCompleteHeight - completeHeight + topItemEdge -= deltaCompleteHeight + bottomItemEdge -= deltaCompleteHeight + completeHeight = updatedCompleteHeight + } } - topItemEdge -= self.insets.top - bottomItemEdge += self.insets.bottom - + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true if topItemFound && bottomItemFound { + if self.stackFromBottom { + self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) + } else { + self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) + } self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight) - self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) - self.scroller.contentOffset = self.lastContentOffset; + self.scroller.contentOffset = self.lastContentOffset } else if topItemFound { self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) @@ -1471,8 +692,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset } - - self.ignoreScrollingEvents = false + self.ignoreScrollingEvents = wasIgnoringScrollingEvents } private func async(_ f: @escaping () -> Void) { @@ -1533,7 +753,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel nodes.append(.Placeholder(frame: node.apparentFrame)) } } - return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) + return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom) } public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { @@ -1556,7 +776,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping (Void) -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { - if let updateSizeAndInsets = updateSizeAndInsets , self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { + if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets @@ -1568,11 +788,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.scrollingResistanceSlider.frame = CGRect(x: 10.0, y: self.freeResistanceSlider.frame.minY - self.scrollingResistanceSlider.bounds.height, width: size.width - 20.0, height: self.scrollingResistanceSlider.bounds.height) } + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size) self.scroller.contentSize = CGSize(width: updateSizeAndInsets.size.width, height: infiniteScrollSize * 2.0) self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset + self.ignoreScrollingEvents = wasIgnoringScrollingEvents self.updateScroller() @@ -2393,21 +1615,40 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) if let updateSizeAndInsets = updateSizeAndInsets { - self.visibleSize = updateSizeAndInsets.size - - if self.insets != updateSizeAndInsets.insets { + if self.insets != updateSizeAndInsets.insets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { + let previousVisibleSize = self.visibleSize + self.visibleSize = updateSizeAndInsets.size + var offsetFix = updateSizeAndInsets.insets.top - self.insets.top self.insets = updateSizeAndInsets.insets - - var completeOffset = offsetFix - sizeAndInsetsOffset = offsetFix + self.visibleSize = updateSizeAndInsets.size for itemNode in self.itemNodes { let position = itemNode.position itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) } + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom) + + if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) { + offsetFix += snappedTopInset + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) + } + } + + var completeOffset = offsetFix + + if !snapToBoundsOffset.isZero { + self.updateVisibleContentOffset() + } + + sizeAndInsetsOffset = offsetFix + completeOffset += snapToBoundsOffset + if updateSizeAndInsets.duration > DBL_EPSILON { let animation: CABasicAnimation switch updateSizeAndInsets.curve { @@ -2441,17 +1682,38 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.layer.add(animation, forKey: nil) } + } else { + self.visibleSize = updateSizeAndInsets.size + + if !self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } } + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset + self.ignoreScrollingEvents = wasIgnoringScrollingEvents + } else { + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom) + + if !snappedTopInset.isZero && previousApparentFrames.isEmpty { + let offsetFix = snappedTopInset + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) + } + } + + if !snapToBoundsOffset.isZero { + self.updateVisibleContentOffset() + } } - self.snapToBounds(scrollToItem != nil) - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) self.updateFloatingAccessoryNodes(animated: animated, currentTimestamp: timestamp) @@ -3090,7 +2352,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel index += 1 } - self.snapToBounds() + if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } } self.debugCheckMonotonity() @@ -3256,4 +2520,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } } + + fileprivate func internalHitTest(_ point: CGPoint, with event: UIEvent?) -> Bool { + if self.limitHitTestToNodes { + var foundHit = false + for itemNode in self.itemNodes { + if itemNode.frame.contains(point) { + foundHit = true + break + } + } + if !foundHit { + return false + } + } + return true + } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift new file mode 100644 index 0000000000..ae42d2562b --- /dev/null +++ b/Display/ListViewIntermediateState.swift @@ -0,0 +1,869 @@ +import Foundation + +public enum ListViewCenterScrollPositionOverflow { + case Top + case Bottom +} + +public enum ListViewScrollPosition: Equatable { + case Top + case Bottom + case Center(ListViewCenterScrollPositionOverflow) +} + +public func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { + switch lhs { + case .Top: + switch rhs { + case .Top: + return true + default: + return false + } + case .Bottom: + switch rhs { + case .Bottom: + return true + default: + return false + } + case let .Center(lhsOverflow): + switch rhs { + case let .Center(rhsOverflow) where lhsOverflow == rhsOverflow: + return true + default: + return false + } + } +} + +public enum ListViewScrollToItemDirectionHint { + case Up + case Down +} + +public enum ListViewAnimationCurve { + case Spring(duration: Double) + case Default +} + +public struct ListViewScrollToItem { + public let index: Int + public let position: ListViewScrollPosition + public let animated: Bool + public let curve: ListViewAnimationCurve + public let directionHint: ListViewScrollToItemDirectionHint + + public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) { + self.index = index + self.position = position + self.animated = animated + self.curve = curve + self.directionHint = directionHint + } +} + +public enum ListViewItemOperationDirectionHint { + case Up + case Down +} + +public struct ListViewDeleteItem { + public let index: Int + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.directionHint = directionHint + } +} + +public struct ListViewInsertItem { + public let index: Int + public let previousIndex: Int? + public let item: ListViewItem + public let directionHint: ListViewItemOperationDirectionHint? + public let forceAnimateInsertion: Bool + + public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?, forceAnimateInsertion: Bool = false) { + self.index = index + self.previousIndex = previousIndex + self.item = item + self.directionHint = directionHint + self.forceAnimateInsertion = forceAnimateInsertion + } +} + +public struct ListViewUpdateItem { + public let index: Int + public let previousIndex: Int + public let item: ListViewItem + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, previousIndex: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.previousIndex = previousIndex + self.item = item + self.directionHint = directionHint + } +} + +public struct ListViewDeleteAndInsertOptions: OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1) + public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) + public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) + public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) + public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) +} + +public struct ListViewUpdateSizeAndInsets { + public let size: CGSize + public let insets: UIEdgeInsets + public let duration: Double + public let curve: ListViewAnimationCurve + + public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve) { + self.size = size + self.insets = insets + self.duration = duration + self.curve = curve + } +} + +public struct ListViewItemRange: Equatable { + public let firstIndex: Int + public let lastIndex: Int +} + +public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { + return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex +} + +public struct ListViewDisplayedItemRange: Equatable { + public let loadedRange: ListViewItemRange? + public let visibleRange: ListViewItemRange? +} + +public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { + return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange +} + +struct IndexRange { + let first: Int + let last: Int + + func contains(_ index: Int) -> Bool { + return index >= first && index <= last + } + + var empty: Bool { + return first > last + } +} + +struct OffsetRanges { + var offsets: [(IndexRange, CGFloat)] = [] + + mutating func append(_ other: OffsetRanges) { + self.offsets.append(contentsOf: other.offsets) + } + + mutating func offset(_ indexRange: IndexRange, offset: CGFloat) { + self.offsets.append((indexRange, offset)) + } + + func offsetForIndex(_ index: Int) -> CGFloat { + var result: CGFloat = 0.0 + for offset in self.offsets { + if offset.0.contains(index) { + result += offset.1 + } + } + return result + } +} + +func binarySearch(_ inputArr: [Int], searchItem: Int) -> Int? { + var lowerIndex = 0; + var upperIndex = inputArr.count - 1 + + if lowerIndex > upperIndex { + return nil + } + + while (true) { + let currentIndex = (lowerIndex + upperIndex) / 2 + if (inputArr[currentIndex] == searchItem) { + return currentIndex + } else if (lowerIndex > upperIndex) { + return nil + } else { + if (inputArr[currentIndex] > searchItem) { + upperIndex = currentIndex - 1 + } else { + lowerIndex = currentIndex + 1 + } + } + } +} + +struct TransactionState { + let visibleSize: CGSize + let items: [ListViewItem] +} + +struct PendingNode { + let index: Int + let node: ListViewItemNode + let apply: () -> () + let frame: CGRect + let apparentHeight: CGFloat +} + +enum ListViewStateNode { + case Node(index: Int, frame: CGRect, referenceNode: ListViewItemNode?) + case Placeholder(frame: CGRect) + + var index: Int? { + switch self { + case .Node(let index, _, _): + return index + case .Placeholder(_): + return nil + } + } + + var frame: CGRect { + get { + switch self { + case .Node(_, let frame, _): + return frame + case .Placeholder(let frame): + return frame + } + } set(value) { + switch self { + case let .Node(index, _, referenceNode): + self = .Node(index: index, frame: value, referenceNode: referenceNode) + case .Placeholder(_): + self = .Placeholder(frame: value) + } + } + } +} + +enum ListViewInsertionOffsetDirection { + case Up + case Down + + init(_ hint: ListViewItemOperationDirectionHint) { + switch hint { + case .Up: + self = .Up + case .Down: + self = .Down + } + } + + func inverted() -> ListViewInsertionOffsetDirection { + switch self { + case .Up: + return .Down + case .Down: + return .Up + } + } +} + +struct ListViewInsertionPoint { + let index: Int + let point: CGPoint + let direction: ListViewInsertionOffsetDirection +} + +struct ListViewState { + var insets: UIEdgeInsets + var visibleSize: CGSize + let invisibleInset: CGFloat + var nodes: [ListViewStateNode] + var scrollPosition: (Int, ListViewScrollPosition)? + var stationaryOffset: (Int, CGFloat)? + let stackFromBottom: Bool + + mutating func fixScrollPostition(_ itemCount: Int) { + if let (fixedIndex, fixedPosition) = self.scrollPosition { + for node in self.nodes { + if let index = node.index , index == fixedIndex { + let offset: CGFloat + switch fixedPosition { + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + case .Top: + offset = self.insets.top - node.frame.minY + case let .Center(overflow): + let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top + if node.frame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { + offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY + } else { + switch overflow { + case .Top: + offset = self.insets.top - node.frame.minY + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + } + } + } + + var minY: CGFloat = CGFloat.greatestFiniteMagnitude + var maxY: CGFloat = 0.0 + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame = frame.offsetBy(dx: 0.0, dy: offset) + self.nodes[i].frame = frame + + minY = min(minY, frame.minY) + maxY = max(maxY, frame.maxY) + } + + var additionalOffset: CGFloat = 0.0 + if minY > self.insets.top { + additionalOffset = self.insets.top - minY + } + + if abs(additionalOffset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame = frame.offsetBy(dx: 0.0, dy: additionalOffset) + self.nodes[i].frame = frame + } + } + + self.snapToBounds(itemCount, snapTopItem: true, stackFromBottom: self.stackFromBottom) + + break + } + } + } else if let (stationaryIndex, stationaryOffset) = self.stationaryOffset { + for node in self.nodes { + if node.index == stationaryIndex { + let offset = stationaryOffset - node.frame.minY + + if abs(offset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame = frame.offsetBy(dx: 0.0, dy: offset) + self.nodes[i].frame = frame + } + } + + break + } + } + } + } + + mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) { + if index < boundary { + for node in self.nodes { + if let nodeIndex = node.index , nodeIndex >= index { + if let frame = frames[nodeIndex] { + self.stationaryOffset = (nodeIndex, frame.minY) + break + } + } + } + } else { + for node in self.nodes.reversed() { + if let nodeIndex = node.index , nodeIndex <= index { + if let frame = frames[nodeIndex] { + self.stationaryOffset = (nodeIndex, frame.minY) + break + } + } + } + } + } + + mutating func snapToBounds(_ itemCount: Int, snapTopItem: Bool, stackFromBottom: Bool) { + var completeHeight: CGFloat = 0.0 + var topItemFound = false + var bottomItemFound = false + var topItemEdge: CGFloat = 0.0 + var bottomItemEdge: CGFloat = 0.0 + + for node in self.nodes { + if let index = node.index { + if index == 0 { + topItemFound = true + topItemEdge = node.frame.minY + } + break + } + } + + for node in self.nodes.reversed() { + if let index = node.index { + if index == itemCount - 1 { + bottomItemFound = true + bottomItemEdge = node.frame.maxY + } + break + } + } + + if topItemFound && bottomItemFound { + for node in self.nodes { + completeHeight += node.frame.size.height + } + } + + let overscroll: CGFloat = 0.0 + + var offset: CGFloat = 0.0 + if topItemFound && bottomItemFound { + let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) + if bottomItemEdge < self.insets.top + areaHeight - overscroll { + offset = self.insets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge + } + } else if topItemFound { + if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge + } + } else if bottomItemFound { + if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { + offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge + } + } + + if abs(offset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y += offset + self.nodes[i].frame = frame + } + } + } + + func insertionPoint(_ insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { + var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)? + + if let (fixedIndex, _) = self.scrollPosition { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , index == fixedIndex { + fixedNode = (i, index, node.frame) + break + } + } + + if fixedNode == nil { + return ListViewInsertionPoint(index: fixedIndex, point: CGPoint(), direction: .Down) + } + } + + if fixedNode == nil { + if let (fixedIndex, _) = self.stationaryOffset { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , index == fixedIndex { + fixedNode = (i, index, node.frame) + break + } + } + } + } + + if fixedNode == nil { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , node.frame.maxY >= self.insets.top { + fixedNode = (i, index, node.frame) + break + } + } + } + + if fixedNode == nil && self.nodes.count != 0 { + for i in (0 ..< self.nodes.count).reversed() { + let node = self.nodes[i] + if let index = node.index { + fixedNode = (i, index, node.frame) + break + } + } + } + + if let fixedNode = fixedNode { + var currentUpperNode = fixedNode + for i in (0 ..< fixedNode.nodeIndex).reversed() { + let node = self.nodes[i] + if let index = node.index { + if index != currentUpperNode.index - 1 { + if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) + } else { + break + } + } + currentUpperNode = (i, index, node.frame) + } + } + + if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + + return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) + } + + var currentLowerNode = fixedNode + if fixedNode.nodeIndex + 1 < self.nodes.count { + for i in (fixedNode.nodeIndex + 1) ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index { + if index != currentLowerNode.index + 1 { + if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) + } else { + break + } + } + currentLowerNode = (i, index, node.frame) + } + } + } + + if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) + } + } else if itemCount != 0 { + return ListViewInsertionPoint(index: 0, point: CGPoint(x: 0.0, y: self.insets.top), direction: .Down) + } + + return nil + } + + mutating func removeInvisibleNodes(_ operations: inout [ListViewStateOperation]) { + var i = 0 + var visibleItemNodeHeight: CGFloat = 0.0 + while i < self.nodes.count { + visibleItemNodeHeight += self.nodes[i].frame.height + i += 1 + } + + if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { + i = self.nodes.count - 1 + while i >= 0 { + let itemNode = self.nodes[i] + let frame = itemNode.frame + //print("node \(i) frame \(frame)") + if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { + //print("remove invisible 1 \(i) frame \(frame)") + operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) + self.nodes.remove(at: i) + } + + i -= 1 + } + } + + let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , node.frame.maxY > upperBound { + if i != 0 { + var previousIndex = index + for j in (0 ..< i).reversed() { + if self.nodes[j].frame.maxY < upperBound { + if let index = self.nodes[j].index { + if index != previousIndex - 1 { + //print("remove monotonity \(j) (\(index))") + operations.append(.Remove(index: j, offsetDirection: .Down)) + self.nodes.remove(at: j) + } else { + previousIndex = index + } + } + } + } + } + break + } + } + + let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) + for i in (0 ..< self.nodes.count).reversed() { + let node = self.nodes[i] + if let index = node.index , node.frame.minY < lowerBound { + if i != self.nodes.count - 1 { + var previousIndex = index + var removeIndices: [Int] = [] + for j in (i + 1) ..< self.nodes.count { + if self.nodes[j].frame.minY > lowerBound { + if let index = self.nodes[j].index { + if index != previousIndex + 1 { + removeIndices.append(j) + } else { + previousIndex = index + } + } + } + } + if !removeIndices.isEmpty { + for i in removeIndices.reversed() { + //print("remove monotonity \(i) (\(self.nodes[i].index!))") + operations.append(.Remove(index: i, offsetDirection: .Up)) + self.nodes.remove(at: i) + } + } + } + break + } + } + } + + func nodeInsertionPointAndIndex(_ itemIndex: Int) -> (CGPoint, Int) { + if self.nodes.count == 0 { + return (CGPoint(x: 0.0, y: self.insets.top), 0) + } else { + var index = 0 + var lastNodeWithIndex = -1 + for node in self.nodes { + if let nodeItemIndex = node.index { + if nodeItemIndex > itemIndex { + break + } + lastNodeWithIndex = index + } + index += 1 + } + lastNodeWithIndex += 1 + return (CGPoint(x: 0.0, y: lastNodeWithIndex == 0 ? self.nodes[0].frame.minY : self.nodes[lastNodeWithIndex - 1].frame.maxY), lastNodeWithIndex) + } + } + + func continuousHeightRelativeToNodeIndex(_ fixedNodeIndex: Int) -> CGFloat { + let fixedIndex = self.nodes[fixedNodeIndex].index! + + var height: CGFloat = 0.0 + + if fixedNodeIndex != 0 { + var upperIndex = fixedIndex + for i in (0 ..< fixedNodeIndex).reversed() { + if let index = self.nodes[i].index { + if index == upperIndex - 1 { + height += self.nodes[i].frame.size.height + upperIndex = index + } else { + break + } + } + } + } + + if fixedNodeIndex != self.nodes.count - 1 { + var lowerIndex = fixedIndex + for i in (fixedNodeIndex + 1) ..< self.nodes.count { + if let index = self.nodes[i].index { + if index == lowerIndex + 1 { + height += self.nodes[i].frame.size.height + lowerIndex = index + } else { + break + } + } + } + } + + return height + } + + mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { + let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) + + let nodeOrigin: CGPoint + switch offsetDirection { + case .Up: + nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height)) + case .Down: + nodeOrigin = insertionOrigin + } + + let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) + + operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) + self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex) + + if !animated { + switch offsetDirection { + case .Up: + var i = insertionIndex - 1 + while i >= 0 { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + i -= 1 + } + case .Down: + var i = insertionIndex + 1 + while i < self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + i += 1 + } + } + } + + var previousIndex: Int? + for node in self.nodes { + if let index = node.index { + if let currentPreviousIndex = previousIndex { + if index <= currentPreviousIndex { + print("index <= previousIndex + 1") + break + } + previousIndex = index + } else { + previousIndex = index + } + } + } + + if let _ = self.scrollPosition { + self.fixScrollPostition(itemCount) + } + } + + mutating func removeNodeAtIndex(_ index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, operations: inout [ListViewStateOperation]) { + let node = self.nodes[index] + if case let .Node(_, _, referenceNode) = node { + let nodeFrame = node.frame + self.nodes.remove(at: index) + let offsetDirection: ListViewInsertionOffsetDirection + if let direction = direction { + offsetDirection = ListViewInsertionOffsetDirection(direction) + } else { + if nodeFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetDirection = .Down + } else { + offsetDirection = .Up + } + } + operations.append(.Remove(index: index, offsetDirection: offsetDirection)) + + if let referenceNode = referenceNode , animated { + self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) + operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) + } else { + if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { + if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + for i in (0 ..< index).reversed() { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + } + } else { + for i in index ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + } + } + } else if index != 0 { + for i in (0 ..< index).reversed() { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + } + } + } + } else { + assertionFailure() + } + } + + mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> Void, operations: inout [ListViewStateOperation]) { + var i = -1 + for node in self.nodes { + i += 1 + if node.index == itemIndex { + switch animation { + case .None: + let offsetDirection: ListViewInsertionOffsetDirection + if let direction = direction { + offsetDirection = ListViewInsertionOffsetDirection(direction) + } else { + if node.frame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetDirection = .Down + } else { + offsetDirection = .Up + } + } + + switch offsetDirection { + case .Up: + let offsetDelta = -(layout.size.height - node.frame.size.height) + var updatedFrame = node.frame + updatedFrame.origin.y += offsetDelta + updatedFrame.size.height = layout.size.height + self.nodes[i].frame = updatedFrame + + for j in 0 ..< i { + var frame = self.nodes[j].frame + frame.origin.y += offsetDelta + self.nodes[j].frame = frame + } + case .Down: + let offsetDelta = layout.size.height - node.frame.size.height + var updatedFrame = node.frame + updatedFrame.size.height = layout.size.height + self.nodes[i].frame = updatedFrame + + for j in i + 1 ..< self.nodes.count { + var frame = self.nodes[j].frame + frame.origin.y += offsetDelta + self.nodes[j].frame = frame + } + } + + operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) + case .System: + operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) + } + + break + } + } + } +} + +enum ListViewStateOperation { + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) + case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) + case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) + case Remap([Int: Int]) + case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) +} diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index ccac1ccfcc..bc081974b9 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -368,11 +368,11 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = viewControllerToPresent as? NavigationController { - controller.navigation_setDismiss { [weak self] in + controller.navigation_setDismiss({ [weak self] in if let strongSelf = self { strongSelf.dismiss(animated: false, completion: nil) } - } + }, rootController: self.view!.window!.rootViewController) self._presentedViewController = controller self.view.endEditing(true) diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 85df924276..02cb60bf55 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -52,11 +52,12 @@ final class PresentationContext { strongSelf.controllers.append(controller) if let view = strongSelf.view, let layout = strongSelf.layout { - controller.navigation_setDismiss { [weak strongSelf, weak controller] in + controller.navigation_setDismiss({ [weak strongSelf, weak controller] in if let strongSelf = strongSelf, let controller = controller { strongSelf.dismiss(controller) } - } + }, rootController: nil) + controller.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) view.addSubview(controller.view) @@ -64,6 +65,7 @@ final class PresentationContext { } else { view.addSubview(controller.view) } + controller.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 474b3a6858..8f9f226ef0 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -3,9 +3,10 @@ @interface UIViewController (Navigation) - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; +- (BOOL)ignoreAppearanceMethodInvocations; - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; - (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; -- (void)navigation_setDismiss:(void (^_Nullable)())dismiss; +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:(UIViewController *)rootController; @end diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 128296b26d..c1a81a4c5c 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -8,12 +8,14 @@ @interface UIViewControllerPresentingProxy : UIViewController @property (nonatomic, copy) void (^dismiss)(); +@property (nonatomic, strong, readonly) UIViewController *rootController; @end @implementation UIViewControllerPresentingProxy -- (instancetype)init { +- (instancetype)initWithRootController:(UIViewController *)rootController { + _rootController = rootController; return self; } @@ -92,6 +94,7 @@ static bool notyfyingShiftState = false; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidDisappear:) newSelector:@selector(_65087dc8_viewDidDisappear:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(navigationController) newSelector:@selector(_65087dc8_navigationController)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)]; //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)]; //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)]; @@ -156,8 +159,8 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:presentingViewController] forKey:UIViewControllerPresentingControllerKey]; } -- (void)navigation_setDismiss:(void (^_Nullable)())dismiss { - UIViewControllerPresentingProxy *proxy = [[UIViewControllerPresentingProxy alloc] init]; +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:(UIViewController *)rootController { + UIViewControllerPresentingProxy *proxy = [[UIViewControllerPresentingProxy alloc] initWithRootController:rootController]; proxy.dismiss = dismiss; [self setAssociatedObject:proxy forKey:UIViewControllerPresentingProxyControllerKey]; } @@ -173,7 +176,16 @@ static bool notyfyingShiftState = false; return controller; } - return [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; + UIView *result = [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; + if (result != nil) { + return result; + } + + return [self _65087dc8_presentingViewController]; +} + +- (void)_65087dc8_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { + [self _65087dc8_presentViewController:viewControllerToPresent animated:flag completion:completion]; } @end diff --git a/Display/ViewController.swift b/Display/ViewController.swift index aaa22f1f77..4fd5e3d7ca 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -174,9 +174,9 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { } } - override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + /*override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { preconditionFailure("use present(_:in)") - } + }*/ override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { diff --git a/Display/Window.swift b/Display/Window.swift index 9589de34a5..4ab3fd3c2b 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -2,6 +2,8 @@ import Foundation import AsyncDisplayKit private class WindowRootViewController: UIViewController { + var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? + override var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -9,6 +11,12 @@ private class WindowRootViewController: UIViewController { override var prefersStatusBarHidden: Bool { return false } + + /*override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + if let presentController = self.presentController { + presentController(viewControllerToPresent, flag, completion) + } + }*/ } private struct WindowLayout: Equatable { @@ -130,9 +138,20 @@ public class Window: UIWindow { self.presentationContext.view = self self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - super.rootViewController = WindowRootViewController() - super.rootViewController?.viewWillAppear(false) - super.rootViewController?.viewDidAppear(false) + let rootViewController = WindowRootViewController() + super.rootViewController = rootViewController + rootViewController.viewWillAppear(false) + rootViewController.viewDidAppear(false) + rootViewController.view.isHidden = true + + rootViewController.presentController = { [weak self] controller, animated, completion in + if let strongSelf = self { + strongSelf.present(LegacyPresentedController(legacyController: controller, presentation: .custom)) + if let completion = completion { + completion() + } + } + } self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { @@ -230,18 +249,18 @@ public class Window: UIWindow { } } - private var rootController: ContainableController? + private var _rootController: ContainableController? public var viewController: ContainableController? { get { - return rootController + return _rootController } set(value) { - if let rootController = self.rootController { + if let rootController = self._rootController { rootController.view.removeFromSuperview() } - self.rootController = value + self._rootController = value - if let rootController = self.rootController { + if let rootController = self._rootController { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) self.addSubview(rootController.view) @@ -343,14 +362,13 @@ public class Window: UIWindow { } self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight) - self.rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) - + self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) } } } - func present(_ controller: ViewController) { + public func present(_ controller: ViewController) { self.presentationContext.present(controller) } } From bed02f5c56410551709e4ffd05ddf7454be04eab Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 6 Dec 2016 12:12:28 +0300 Subject: [PATCH 031/245] no message --- Display.xcodeproj/project.pbxproj | 9 +- Display/ActionSheetController.swift | 8 + Display/ContainableController.swift | 88 +++++++++++ Display/GenerateImage.swift | 15 +- Display/HighlightableButton.swift | 41 +++++ ...teractiveTransitionGestureRecognizer.swift | 4 + Display/LegacyPresentedController.swift | 2 +- Display/ListView.swift | 144 ++++++++++++++++-- Display/ListViewIntermediateState.swift | 1 + Display/NavigationBar.swift | 8 +- Display/TabBarController.swift | 10 +- Display/UIViewController+Navigation.h | 6 + Display/UIViewController+Navigation.m | 13 ++ Display/UniversalMasterController.swift | 4 +- Display/ViewController.swift | 12 +- 15 files changed, 332 insertions(+), 33 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 747514cf83..d153af1160 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -897,7 +897,8 @@ PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0.1; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 3.0; }; name = Debug; }; @@ -922,7 +923,8 @@ PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 3.0.1; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 3.0; }; name = Release; }; @@ -1010,7 +1012,8 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0.1; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 3.0; }; name = Hockeyapp; }; diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 0b6191f424..e47ecbbe5a 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -7,6 +7,14 @@ open class ActionSheetController: ViewController { private var groups: [ActionSheetItemGroup] = [] + public init() { + super.init(navigationBar: NavigationBar()) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + public func dismissAnimated() { self.actionSheetNode.animateOut() } diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index b89468ae18..9417b2afee 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -20,6 +20,14 @@ public extension ContainedViewLayoutTransitionCurve { public enum ContainedViewLayoutTransition { case immediate case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve) + + public var isAnimated: Bool { + if case .immediate = self { + return false + } else { + return true + } + } } public extension ContainedViewLayoutTransition { @@ -41,6 +49,54 @@ public extension ContainedViewLayoutTransition { } } + func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + node.position = position + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousPosition = node.position + node.position = position + node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { switch self { case .immediate: @@ -77,6 +133,13 @@ public extension ContainedViewLayoutTransition { } func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + if node.alpha.isEqual(to: alpha) { + if let completion = completion { + completion(true) + } + return + } + switch self { case .immediate: node.alpha = alpha @@ -93,6 +156,31 @@ public extension ContainedViewLayoutTransition { }) } } + + func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + if layer.opacity.isEqual(to: Float(alpha)) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.opacity = Float(alpha) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = layer.opacity + layer.opacity = Float(alpha) + layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } } public protocol ContainableController: class { diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 28a3f053d9..f64d202553 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -61,8 +61,8 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) return UIImage(cgImage: image, scale: selectedScale, orientation: .up) } -public func generateFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { - return generateImage(CGSize(width: radius * 2.0, height: radius * 2.0), contextGenerator: { size, context in +public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { + return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) if let backgroundColor = backgroundColor { context.setFillColor(backgroundColor.cgColor) @@ -82,7 +82,13 @@ public func generateFilledCircleImage(radius: CGFloat, color: UIColor?, backgrou public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { let intRadius = Int(radius) let cap = intRadius == 1 ? 2 : intRadius - return generateFilledCircleImage(radius: radius, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) + return generateFilledCircleImage(diameter: radius * 2.0, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) +} + +public func generateStretchableFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { + let intRadius = Int(diameter / 2.0) + let cap = intRadius == 1 ? 2 : intRadius + return generateFilledCircleImage(diameter: diameter, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) } public func generateVerticallyStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { @@ -258,11 +264,12 @@ public class DrawingContext { let dstPixel = dstLine + dx let baseColor = dstPixel.pointee + let baseAlpha = (baseColor >> 24) & 0xff let baseR = (baseColor >> 16) & 0xff let baseG = (baseColor >> 8) & 0xff let baseB = baseColor & 0xff - let alpha = srcPixel.pointee >> 24 + let alpha = min(baseAlpha, srcPixel.pointee >> 24) let r = (baseR * alpha) / 255 let g = (baseG * alpha) / 255 diff --git a/Display/HighlightableButton.swift b/Display/HighlightableButton.swift index 44c0071958..60c844e3de 100644 --- a/Display/HighlightableButton.swift +++ b/Display/HighlightableButton.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import AsyncDisplayKit open class HighlightableButton: HighlightTrackingButton { override public init(frame: CGRect) { @@ -24,3 +25,43 @@ open class HighlightableButton: HighlightTrackingButton { fatalError("init(coder:) has not been implemented") } } + +open class HighlightTrackingButtonNode: ASButtonNode { + public var highligthedChanged: (Bool) -> Void = { _ in } + + open override func beginTracking(with touch: UITouch, with event: UIEvent?) -> Bool { + self.highligthedChanged(true) + + return super.beginTracking(with: touch, with: event) + } + + open override func endTracking(with touch: UITouch?, with event: UIEvent?) { + self.highligthedChanged(false) + + super.endTracking(with: touch, with: event) + } + + open override func cancelTracking(with event: UIEvent?) { + self.highligthedChanged(false) + + super.cancelTracking(with: event) + } +} + +open class HighlightableButtonNode: HighlightTrackingButtonNode { + override public init() { + super.init() + + self.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.layer.removeAnimation(forKey: "opacity") + strongSelf.alpha = 0.4 + } else { + strongSelf.alpha = 1.0 + strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } +} diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index f0e5849852..fdc25bc829 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -2,6 +2,10 @@ import Foundation import UIKit private func hasHorizontalGestures(_ view: UIView) -> Bool { + if view.disablesInteractiveTransitionGestureRecognizer { + return true + } + if let view = view as? ListViewBackingView { let transform = view.transform let angle = Double(atan2f(Float(transform.b), Float(transform.a))) diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index f1d399e6e5..f2c4b1b798 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -123,7 +123,7 @@ open class LegacyPresentedController: ViewController { override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition) + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } public func dismiss() { diff --git a/Display/ListView.swift b/Display/ListView.swift index 45e2611db7..a4f35b25f3 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -101,6 +101,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var stackFromBottom: Bool = false public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 public final var limitHitTestToNodes: Bool = false + public final var keepBottomItemOverscrollBackground: Bool = false + + private var bottomItemOverscrollBackground: ASDisplayNode? private var touchesPosition = CGPoint() private var isTracking = false @@ -617,6 +620,40 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.ignoreScrollingEvents = wasIgnoringScrollingEvents } + private func updateBottomItemOverscrollBackground() { + if self.keepBottomItemOverscrollBackground { + var bottomItemFound = false + if self.itemNodes[itemNodes.count - 1].index == self.items.count - 1 { + bottomItemFound = true + } + + let bottomItemOverscrollBackground: ASDisplayNode + if let currentBottomItemOverscrollBackground = self.bottomItemOverscrollBackground { + bottomItemOverscrollBackground = currentBottomItemOverscrollBackground + } else { + bottomItemOverscrollBackground = ASDisplayNode() + bottomItemOverscrollBackground.backgroundColor = .white + bottomItemOverscrollBackground.isLayerBacked = true + self.insertSubnode(bottomItemOverscrollBackground, at: 0) + self.bottomItemOverscrollBackground = bottomItemOverscrollBackground + } + + if bottomItemFound { + let realBottomItemEdge = itemNodes.last!.apparentFrame.origin.y + let realBottomItemEdgeOffset = max(0.0, self.visibleSize.height - realBottomItemEdge) + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: self.visibleSize.height - realBottomItemEdgeOffset), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height)) + if !backgroundFrame.equalTo(bottomItemOverscrollBackground.frame) { + bottomItemOverscrollBackground.frame = backgroundFrame + } + } else { + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: self.visibleSize.height), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height)) + if !backgroundFrame.equalTo(bottomItemOverscrollBackground.frame) { + bottomItemOverscrollBackground.frame = backgroundFrame + } + } + } + } + private func updateScroller() { if itemNodes.count == 0 { return @@ -667,6 +704,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.updateBottomItemOverscrollBackground() + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true if topItemFound && bottomItemFound { @@ -1029,7 +1068,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let stationaryItemIndex = updatedState.stationaryOffset?.0 let next = { - self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: completion) + self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: completion) } if options.contains(.LowLatency) || options.contains(.Synchronous) { @@ -1356,13 +1395,60 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return lowestHeaderNode } - private func replayOperations(animated: Bool, animateAlpha: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + private func topItemVerticalOrigin() -> CGFloat? { + var topItemFound = false + + for i in 0 ..< self.itemNodes.count { + if let index = itemNodes[i].index { + if index == 0 { + topItemFound = true + } + break + } + } + + if topItemFound { + return itemNodes[0].apparentFrame.origin.y + } else { + return nil + } + } + + private func bottomItemMaxY() -> CGFloat? { + var bottomItemFound = false + + for i in (0 ..< self.itemNodes.count).reversed() { + if let index = itemNodes[i].index { + if index == self.items.count - 1 { + bottomItemFound = true + break + } + } + } + + if bottomItemFound { + return itemNodes.last!.apparentFrame.maxY + } else { + return nil + } + } + + private func replayOperations(animated: Bool, animateAlpha: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { let timestamp = CACurrentMediaTime() if let updateOpaqueState = updateOpaqueState { self.opaqueTransactionState = updateOpaqueState } + var previousTopItemVerticalOrigin: CGFloat? + var previousBottomItemMaxY: CGFloat? + var snapshotView: UIView? + if animateTopItemVerticalOrigin { + previousTopItemVerticalOrigin = self.topItemVerticalOrigin() + previousBottomItemMaxY = self.bottomItemMaxY() + snapshotView = self.view.snapshotView(afterScreenUpdates: false) + } + var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] for itemNode in self.itemNodes { previousApparentFrames.append((itemNode, itemNode.apparentFrame)) @@ -1580,10 +1666,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel break } } - - /*for itemNode in self.itemNodes { - print("item \(itemNode.index) frame \(itemNode.frame)") - }*/ } else if let stationaryItemIndex = stationaryItemIndex { for itemNode in self.itemNodes { if let index = itemNode.index , index == stationaryItemIndex { @@ -1690,6 +1772,44 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + if let updatedTopItemVerticalOrigin = self.topItemVerticalOrigin(), let previousTopItemVerticalOrigin = previousTopItemVerticalOrigin, animateTopItemVerticalOrigin, !updatedTopItemVerticalOrigin.isEqual(to: previousTopItemVerticalOrigin) { + self.stopScrolling() + + let completeOffset = updatedTopItemVerticalOrigin - previousTopItemVerticalOrigin + let duration: Double = 0.4 + + if let snapshotView = snapshotView { + snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: completeOffset), size: snapshotView.frame.size) + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + + let springAnimation = makeSpringAnimation("sublayerTransform") + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + + springAnimation.isAdditive = true + self.layer.add(springAnimation, forKey: nil) + } else { + if let snapshotView = snapshotView { + snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: snapshotView.frame.size) + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) @@ -1831,6 +1951,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.updateScroller() self.setNeedsAnimations() self.updateVisibleContentOffset() @@ -1848,6 +1969,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.setNeedsAnimations() } + self.updateScroller() self.updateVisibleContentOffset() if self.debugInfo { @@ -2196,7 +2318,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) self.dispatchOnVSync { - self.replayOperations(animated: false, animateAlpha: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) } } } @@ -2382,10 +2504,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { if itemNode.index == index { - if !itemNode.isLayerBacked { - strongSelf.view.bringSubview(toFront: itemNode.view) + if true { //!(itemNode.hitTest(CGPoint(x: strongSelf.touchesPosition.x - itemNode.frame.minX, y: strongSelf.touchesPosition.y - itemNode.frame.minY), with: event) is UIControl) { + if !itemNode.isLayerBacked { + strongSelf.view.bringSubview(toFront: itemNode.view) + } + itemNode.setHighlighted(true, animated: false) } - itemNode.setHighlighted(true, animated: false) break } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index ae42d2562b..083efe4612 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -120,6 +120,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) + public static let AnimateTopItemPosition = ListViewDeleteAndInsertOptions(rawValue: 32) } public struct ListViewUpdateSizeAndInsets { diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 138a7ec870..076206af53 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -31,8 +31,8 @@ private func backArrowImage(color: UIColor) -> UIImage? { } } -public class NavigationBar: ASDisplayNode { - public var foregroundColor: UIColor = UIColor.black { +open class NavigationBar: ASDisplayNode { + open var foregroundColor: UIColor = UIColor.black { didSet { if let title = self.title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) @@ -40,7 +40,7 @@ public class NavigationBar: ASDisplayNode { } } - public var accentColor: UIColor = UIColor(0x007ee5) { + open var accentColor: UIColor = UIColor(0x007ee5) { didSet { self.backButtonNode.color = self.accentColor self.leftButtonNode.color = self.accentColor @@ -361,7 +361,7 @@ public class NavigationBar: ASDisplayNode { } } - public override func layout() { + open override func layout() { var size = self.bounds.size let leftButtonInset: CGFloat = 8.0 diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 09d7fc06f8..c366e88cd5 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -2,7 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit -public class TabBarController: ViewController { +open class TabBarController: ViewController { private var containerLayout = ContainerViewLayout() private var tabBarControllerNode: TabBarControllerNode { @@ -37,15 +37,15 @@ public class TabBarController: ViewController { var currentController: ViewController? - override public init() { - super.init() + override public init(navigationBar: NavigationBar = NavigationBar()) { + super.init(navigationBar: navigationBar) } required public init(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } - override public func loadDisplayNode() { + override open func loadDisplayNode() { self.displayNode = TabBarControllerNode(itemSelected: { [weak self] index in if let strongSelf = self { strongSelf.selectedIndex = index @@ -100,7 +100,7 @@ public class TabBarController: ViewController { } } - override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) self.containerLayout = layout diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 8f9f226ef0..12c7dda690 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -10,4 +10,10 @@ @end +@interface UIView (Navigation) + +@property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer; + +@end + void applyKeyboardAutocorrection(); diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index c1a81a4c5c..247bf14f26 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -34,6 +34,7 @@ static const void *UIViewControllerIgnoreAppearanceMethodInvocationsKey = &UIVie static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNavigationControllerKey; static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey; static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey; +static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey; static bool notyfyingShiftState = false; @@ -190,6 +191,18 @@ static bool notyfyingShiftState = false; @end +@implementation UIView (Navigation) + +- (bool)disablesInteractiveTransitionGestureRecognizer { + return [[self associatedObjectForKey:disablesInteractiveTransitionGestureRecognizerKey] boolValue]; +} + +- (void)setDisablesInteractiveTransitionGestureRecognizer:(bool)disablesInteractiveTransitionGestureRecognizer { + [self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey]; +} + +@end + static NSString *TGEncodeText(NSString *string, int key) { NSMutableString *result = [[NSMutableString alloc] init]; diff --git a/Display/UniversalMasterController.swift b/Display/UniversalMasterController.swift index b751a92391..117e2b1ec1 100644 --- a/Display/UniversalMasterController.swift +++ b/Display/UniversalMasterController.swift @@ -6,8 +6,8 @@ import SwiftSignalKit class UniversalMasterController: ViewController { private var controllers: [ViewController] = [] - public override init() { - super.init() + public init() { + super.init(navigationBar: NavigationBar()) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 4fd5e3d7ca..f5f7b51c25 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -53,6 +53,10 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { private weak var activeInputViewCandidate: UIResponder? private weak var activeInputView: UIResponder? + open var navigationHeight: CGFloat { + return self.navigationBar.frame.maxY + } + private let _ready = Promise(true) open var ready: Promise { return self._ready @@ -85,9 +89,9 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { } } - public init() { + public init(navigationBar: NavigationBar = NavigationBar()) { self.statusBar = StatusBar() - self.navigationBar = NavigationBar() + self.navigationBar = navigationBar self.presentationContext = PresentationContext() super.init(nibName: nil, bundle: nil) @@ -174,9 +178,9 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { } } - /*override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { preconditionFailure("use present(_:in)") - }*/ + } override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { From 269fccdcc9de734bf950a6f0ede2d2c466961aad Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 22 Dec 2016 03:19:59 +0300 Subject: [PATCH 032/245] no message --- Display/ContainableController.swift | 108 +++++++++------- Display/Font.swift | 12 ++ Display/GenerateImage.swift | 11 +- Display/GridNodeScroller.swift | 9 ++ Display/ListView.swift | 116 +++++++++++++++--- Display/ListViewIntermediateState.swift | 12 +- Display/ListViewItem.swift | 20 ++- Display/ListViewItemNode.swift | 11 ++ Display/NavigationTransitionCoordinator.swift | 8 +- 9 files changed, 231 insertions(+), 76 deletions(-) diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 9417b2afee..4ed6f3296e 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -32,38 +32,46 @@ public enum ContainedViewLayoutTransition { public extension ContainedViewLayoutTransition { func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - node.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = node.frame - node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if node.frame.equalTo(frame) { + completion?(true) + } else { + switch self { + case .immediate: + node.frame = frame if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + let previousFrame = node.frame + node.frame = frame + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } } } func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - node.position = position - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousPosition = node.position - node.position = position - node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if node.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + node.position = position if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + let previousPosition = node.position + node.position = position + node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } } } @@ -83,17 +91,21 @@ public extension ContainedViewLayoutTransition { } func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) + if node.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) } - case let .animated(duration, curve): - node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) } } @@ -115,20 +127,24 @@ public extension ContainedViewLayoutTransition { } func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - layer.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = layer.frame - layer.frame = frame - layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if layer.frame.equalTo(frame) { + completion?(true) + } else { + switch self { + case .immediate: + layer.frame = frame if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + let previousFrame = layer.frame + layer.frame = frame + layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } } } diff --git a/Display/Font.swift b/Display/Font.swift index 295dd1e4f2..99372d913d 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -13,6 +13,18 @@ public struct Font { return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) } } + + public static func bold(_ size: CGFloat) -> UIFont { + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: UIFontWeightBold) + } else { + return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, size, nil) + } + } + + public static func italic(_ size: CGFloat) -> UIFont { + return UIFont.italicSystemFont(ofSize: size) + } } public extension NSAttributedString { diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index f64d202553..220e7cf83b 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -87,7 +87,16 @@ public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor public func generateStretchableFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { let intRadius = Int(diameter / 2.0) - let cap = intRadius == 1 ? 2 : intRadius + let intDiameter = Int(diameter) + let cap: Int + if intDiameter == 3 { + cap = 1 + } else if intRadius == 1 { + cap = 2 + } else { + cap = intRadius + } + return generateFilledCircleImage(diameter: diameter, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) } diff --git a/Display/GridNodeScroller.swift b/Display/GridNodeScroller.swift index 6ad5e93348..b4a8c5e6fb 100644 --- a/Display/GridNodeScroller.swift +++ b/Display/GridNodeScroller.swift @@ -1,6 +1,15 @@ import UIKit +private class GridNodeScrollerLayer: CALayer { + override func setNeedsDisplay() { + } +} + private class GridNodeScrollerView: UIScrollView { + override class var layerClass: AnyClass { + return GridNodeScrollerLayer.self + } + override func touchesShouldCancel(in view: UIView) -> Bool { return true } diff --git a/Display/ListView.swift b/Display/ListView.swift index a4f35b25f3..30b036fef6 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -14,6 +14,9 @@ private final class ListViewBackingLayer: CALayer { override func layoutSublayers() { } + + override func setNeedsDisplay() { + } } final class ListViewBackingView: UIView { @@ -146,6 +149,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var flashNodesDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? + private let waitingForNodesDisposable = MetaDisposable() + public func reportDurationInMS(duration: Int, smallDropEvent: Double, largeDropEvent: Double) { print("reportDurationInMS duration: \(duration), smallDropEvent: \(smallDropEvent), largeDropEvent: \(largeDropEvent)") } @@ -241,6 +246,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel deinit { self.pauseAnimations() self.displayLink.invalidate() + + for itemNode in self.itemNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + } + for itemHeaderNode in self.itemHeaderNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemHeaderNode) + } + + self.waitingForNodesDisposable.dispose() } @objc func frictionSliderChanged(_ slider: UISlider) { @@ -738,7 +752,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> Void) -> Void) { + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -750,21 +764,27 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if Thread.isMainThread { if synchronous { completion(previousNode, layout, { - previousNode.index = index - apply() + return (nil, { + previousNode.index = index + apply() + }) }) } else { self.async { completion(previousNode, layout, { - previousNode.index = index - apply() + return (nil, { + previousNode.index = index + apply() + }) }) } } } else { completion(previousNode, layout, { - previousNode.index = index - apply() + return (nil, { + previousNode.index = index + apply() + }) }) } }) @@ -1068,7 +1088,53 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let stationaryItemIndex = updatedState.stationaryOffset?.0 let next = { - self.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: completion) + var updatedOperations = updatedOperations + + var readySignals: [Signal]? + + if options.contains(.PreferSynchronousResourceLoading) { + var currentReadySignals: [Signal] = [] + for i in 0 ..< updatedOperations.count { + if case let .InsertNode(index, offsetDirection, node, layout, apply) = updatedOperations[i] { + let (ready, commitApply) = apply() + updatedOperations[i] = .InsertNode(index: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: { + return (nil, commitApply) + }) + if let ready = ready { + currentReadySignals.append(ready) + } + } + } + readySignals = currentReadySignals + } + + let beginReplay = { [weak self] in + if let strongSelf = self { + strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { + if options.contains(.PreferSynchronousResourceLoading) { + let startTime = CACurrentMediaTime() + self?.recursivelyEnsureDisplaySynchronously(true) + let deltaTime = CACurrentMediaTime() - startTime + print("ListView: waited \(deltaTime * 1000.0) ms for nodes to display") + } + completion() + }) + } + } + + if let readySignals = readySignals, !readySignals.isEmpty && false { + let readyWithTimeout = combineLatest(readySignals) + |> deliverOnMainQueue + |> timeout(0.2, queue: Queue.mainQueue(), alternate: .single([])) + let startTime = CACurrentMediaTime() + self.waitingForNodesDisposable.set(readyWithTimeout.start(completed: { + let deltaTime = CACurrentMediaTime() - startTime + print("ListView: waited \(deltaTime * 1000.0) ms for nodes to load") + beginReplay() + })) + } else { + beginReplay() + } } if options.contains(.LowLatency) || options.contains(.Synchronous) { @@ -1123,7 +1189,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let heightDelta = layout.size.height - updatedState.nodes[i].frame.size.height - updatedOperations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) + updatedOperations.append(.UpdateLayout(index: i, layout: layout, apply: { + return (nil, apply) + })) if !animated { let previousFrame = updatedState.nodes[i].frame @@ -1252,7 +1320,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (), timestamp: Double) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void), timestamp: Double) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -1272,7 +1340,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.insets = layout.insets node.apparentHeight = animated ? 0.0 : layout.size.height node.frame = nodeFrame - apply() + apply().1() self.itemNodes.insert(node, at: nodeIndex) if useDynamicTuning { @@ -1514,10 +1582,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp) } else { referenceNode.index = nil - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp) self.addSubnode(referenceNode) } } else { @@ -1562,7 +1630,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.contentSize = layout.contentSize node.insets = layout.insets - apply() + apply().1() let updatedApparentHeight = node.bounds.size.height let updatedInsets = node.insets @@ -1893,7 +1961,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let lowestHeaderNode = self.lowestHeaderNode() for itemNode in temporaryPreviousNodes { itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) - temporaryPreviousNodes.append(itemNode) if let lowestHeaderNode = lowestHeaderNode { self.insertSubnode(itemNode, belowSubnode: lowestHeaderNode) } else { @@ -1942,12 +2009,18 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel animation.completion = { _ in for itemNode in temporaryPreviousNodes { itemNode.removeFromSupernode() + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) } for headerNode in temporaryHeaderNodes { headerNode.removeFromSupernode() + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) } } self.layer.add(animation, forKey: nil) + } else { + for itemNode in temporaryPreviousNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + } } } @@ -1977,6 +2050,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel //print("replayOperations \(delta * 1000.0) ms") } + for (previousNode, _) in previousApparentFrames { + if previousNode.supernode == nil { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: previousNode) + } + } + completion() } }) @@ -2306,6 +2385,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let node = self.itemNodes[i] if node.index == nil && node.apparentHeight <= CGFloat(FLT_EPSILON) { self.removeItemNodeAtIndex(i) + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) } else { i += 1 } @@ -2507,6 +2587,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if true { //!(itemNode.hitTest(CGPoint(x: strongSelf.touchesPosition.x - itemNode.frame.minX, y: strongSelf.touchesPosition.y - itemNode.frame.minY), with: event) is UIControl) { if !itemNode.isLayerBacked { strongSelf.view.bringSubview(toFront: itemNode.view) + for (_, headerNode) in strongSelf.itemHeaderNodes { + strongSelf.view.bringSubview(toFront: headerNode.view) + } } itemNode.setHighlighted(true, animated: false) } @@ -2594,6 +2677,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if itemNode.index == index { if !itemNode.isLayerBacked { self.view.bringSubview(toFront: itemNode.view) + for (_, headerNode) in self.itemHeaderNodes { + self.view.bringSubview(toFront: headerNode.view) + } } itemNode.setHighlighted(true, animated: false) break diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 083efe4612..bac1aa5eb7 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -1,4 +1,5 @@ import Foundation +import SwiftSignalKit public enum ListViewCenterScrollPositionOverflow { case Top @@ -121,6 +122,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) public static let AnimateTopItemPosition = ListViewDeleteAndInsertOptions(rawValue: 32) + public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 64) } public struct ListViewUpdateSizeAndInsets { @@ -222,7 +224,7 @@ struct TransactionState { struct PendingNode { let index: Int let node: ListViewItemNode - let apply: () -> () + let apply: () -> (Signal?, () -> Void) let frame: CGRect let apparentHeight: CGFloat } @@ -701,7 +703,7 @@ struct ListViewState { return height } - mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { + mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (Signal?, () -> Void), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) let nodeOrigin: CGPoint @@ -806,7 +808,7 @@ struct ListViewState { } } - mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> Void, operations: inout [ListViewStateOperation]) { + mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> (Signal?, () -> Void), operations: inout [ListViewStateOperation]) { var i = -1 for node in self.nodes { i += 1 @@ -862,9 +864,9 @@ struct ListViewState { } enum ListViewStateOperation { - case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) case Remap([Int: Int]) - case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) + case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) } diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index cd9278fe2d..153847d2ad 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -6,8 +6,22 @@ public enum ListViewItemUpdateAnimation { case System(duration: Double) } +public struct ListViewItemConfigureNodeFlags: OptionSet { + public var rawValue: Int32 + + public init() { + self.rawValue = 0 + } + + public init(rawValue: Int32) { + self.rawValue = rawValue + } + + public static let preferSynchronousResourceLoading = ListViewItemConfigureNodeFlags(rawValue: 1 << 0) +} + public protocol ListViewItem { - func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> Void) -> Void) + func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } @@ -37,8 +51,4 @@ public extension ListViewItem { func selected(listView: ListView) { } - - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) { - completion(ListViewItemNodeLayout(contentSize: node.contentSize, insets: node.insets), {}) - } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 15912c8c1f..8c97ec0400 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -1,5 +1,6 @@ import Foundation import AsyncDisplayKit +import SwiftSignalKit var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) var testSpringFriction: CGFloat = 31.8211269378662 @@ -133,6 +134,10 @@ open class ListViewItemNode: ASDisplayNode { return ListViewItemNodeLayout(contentSize: contentSize, insets: insets) } + public var displayResourcesReady: Signal { + return .complete() + } + public init(layerBacked: Bool, dynamicBounce: Bool = true, rotated: Bool = false) { if true { if dynamicBounce { @@ -163,6 +168,12 @@ open class ListViewItemNode: ASDisplayNode { self.isLayerBacked = layerBacked } + /*deinit { + if Thread.isMainThread { + print("deallocating on main thread") + } + }*/ + var apparentHeight: CGFloat = 0.0 private var _bounds: CGRect = CGRect() private var _position: CGPoint = CGPoint() diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 26858c75ee..d1fe4b5e0c 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -56,7 +56,7 @@ class NavigationTransitionCoordinator { self.dimView.backgroundColor = UIColor.black self.shadowView = UIImageView(image: shadowImage) - if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar { + if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden { var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container) var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container) topFrame.origin.x = 0.0 @@ -111,7 +111,7 @@ class NavigationTransitionCoordinator { } func updateNavigationBarTransition() { - if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { + if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition { let position: CGFloat switch self.transition { case .Push: @@ -126,7 +126,7 @@ class NavigationTransitionCoordinator { } func maybeCreateNavigationBarTransition() { - if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { + if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition { let position: CGFloat switch self.transition { case .Push: @@ -141,7 +141,7 @@ class NavigationTransitionCoordinator { } func endNavigationBarTransition() { - if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar { + if let topNavigationBar = self.topNavigationBar, let bottomNavigationBar = self.bottomNavigationBar, self.inlineNavigationBarTransition { topNavigationBar.transitionState = nil bottomNavigationBar.transitionState = nil } From a4ea9ca8cf58fb61a1deb36ef47e569af21219f2 Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 5 Jan 2017 01:14:04 +0400 Subject: [PATCH 033/245] no message --- Display/ContainableController.swift | 22 ++++++ Display/GenerateImage.swift | 4 +- Display/ListView.swift | 52 ++++++++++--- Display/ListViewItemNode.swift | 6 ++ Display/NavigationBar.swift | 2 +- Display/TabBarController.swift | 6 +- Display/TabBarNode.swift | 109 ++++++++++++++++++++++++---- Display/UINavigationItem+Proxy.h | 11 +++ Display/UINavigationItem+Proxy.m | 107 ++++++++++++++++++++++++--- 9 files changed, 277 insertions(+), 42 deletions(-) diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 4ed6f3296e..c734b5ac35 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -53,6 +53,28 @@ public extension ContainedViewLayoutTransition { } } + func updateBounds(node: ASDisplayNode, bounds: CGRect, completion: ((Bool) -> Void)? = nil) { + if node.bounds.equalTo(bounds) { + completion?(true) + } else { + switch self { + case .immediate: + node.bounds = bounds + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousBounds = node.bounds + node.bounds = bounds + node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { if node.position.equalTo(position) { completion?(true) diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 220e7cf83b..aaad94b53b 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -263,8 +263,8 @@ public class DrawingContext { switch mode { case .Alpha: while dstY < maxDstY { - let srcLine = other.bytes.advanced(by: srcY * other.bytesPerRow).assumingMemoryBound(to: UInt32.self) - let dstLine = self.bytes.advanced(by: dstY * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) + let srcLine = other.bytes.advanced(by: max(0, srcY) * other.bytesPerRow).assumingMemoryBound(to: UInt32.self) + let dstLine = self.bytes.advanced(by: max(0, dstY) * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) var dx = dstX var sx = srcX diff --git a/Display/ListView.swift b/Display/ListView.swift index 30b036fef6..2fa306a7bd 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -500,7 +500,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return additionalInverseTopInset } - private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool) -> (snappedTopInset: CGFloat, offset: CGFloat) { + private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, scrollToItem: ListViewScrollToItem? = nil) -> (snappedTopInset: CGFloat, offset: CGFloat) { if self.itemNodes.count == 0 { return (0.0, 0.0) } @@ -556,6 +556,25 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + var transition: ContainedViewLayoutTransition = .immediate + if let updateSizeAndInsets = updateSizeAndInsets { + if updateSizeAndInsets.duration > DBL_EPSILON { + switch updateSizeAndInsets.curve { + case let .Spring(duration): + transition = .animated(duration: duration, curve: .spring) + case .Default: + transition = .animated(duration: updateSizeAndInsets.duration, curve: .easeInOut) + } + } + } else if let scrollToItem = scrollToItem { + switch scrollToItem.curve { + case let .Spring(duration): + transition = .animated(duration: duration, curve: .spring) + case .Default: + transition = .animated(duration: 0.5, curve: .easeInOut) + } + } + var offset: CGFloat = 0.0 if topItemFound && bottomItemFound { let visibleAreaHeight = self.visibleSize.height - effectiveInsets.bottom - effectiveInsets.top @@ -583,13 +602,28 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel offset = (effectiveInsets.top - overscroll) - topItemEdge } } - } else if topItemFound { - if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { - offset = (effectiveInsets.top - overscroll) - topItemEdge + + if visibleAreaHeight > completeHeight { + if let itemNode = self.itemNodes.last, itemNode.wantsTrailingItemSpaceUpdates { + itemNode.updateTrailingItemSpace(visibleAreaHeight - completeHeight, transition: transition) + } + } else { + if let itemNode = self.itemNodes.last, itemNode.wantsTrailingItemSpaceUpdates { + itemNode.updateTrailingItemSpace(0.0, transition: transition) + } } - } else if bottomItemFound { - if bottomItemEdge < self.visibleSize.height - effectiveInsets.bottom - overscroll { - offset = self.visibleSize.height - effectiveInsets.bottom - overscroll - bottomItemEdge + } else { + if let itemNode = self.itemNodes.last, itemNode.wantsTrailingItemSpaceUpdates { + itemNode.updateTrailingItemSpace(0.0, transition: transition) + } + if topItemFound { + if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { + offset = (effectiveInsets.top - overscroll) - topItemEdge + } + } else if bottomItemFound { + if bottomItemEdge < self.visibleSize.height - effectiveInsets.bottom - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - overscroll - bottomItemEdge + } } } @@ -1779,7 +1813,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) } - let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom) + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets) if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) { offsetFix += snappedTopInset @@ -1886,7 +1920,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.scroller.contentOffset = self.lastContentOffset self.ignoreScrollingEvents = wasIgnoringScrollingEvents } else { - let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom) + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem) if !snappedTopInset.isZero && previousApparentFrames.isEmpty { let offsetFix = snappedTopInset diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 8c97ec0400..38296f0aad 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -79,6 +79,8 @@ open class ListViewItemNode: ASDisplayNode { final let wantsScrollDynamics: Bool + public final var wantsTrailingItemSpaceUpdates: Bool = false + public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets() public final var insets: UIEdgeInsets = UIEdgeInsets() { @@ -441,4 +443,8 @@ open class ListViewItemNode: ASDisplayNode { open func header() -> ListViewItemHeader? { return nil } + + open func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) { + + } } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 076206af53..f48a44128c 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -71,7 +71,7 @@ open class NavigationBar: ASDisplayNode { private var itemLeftButtonListenerKey: Int? private var itemRightButtonListenerKey: Int? private var _item: UINavigationItem? - var item: UINavigationItem? { + public var item: UINavigationItem? { get { return self._item } set(value) { diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index c366e88cd5..cd504c07c3 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -85,14 +85,14 @@ open class TabBarController: ViewController { self.addChildViewController(currentController) currentController.didMove(toParentViewController: self) - self.navigationItem.title = currentController.navigationItem.title - self.navigationItem.leftBarButtonItem = currentController.navigationItem.leftBarButtonItem - self.navigationItem.rightBarButtonItem = currentController.navigationItem.rightBarButtonItem + currentController.navigationItem.setTarget(self.navigationItem) displayNavigationBar = currentController.displayNavigationBar + //currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) } else { self.navigationItem.title = nil self.navigationItem.leftBarButtonItem = nil self.navigationItem.rightBarButtonItem = nil + self.navigationItem.titleView = nil displayNavigationBar = false } if self.displayNavigationBar != displayNavigationBar { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 5c02e2ccb7..dd7f0895df 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -42,6 +42,47 @@ private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColo return image } +private let badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: UIColor(0xff3b30), backgroundColor: nil) +private let badgeFont = Font.regular(13.0) + +private final class TabBarNodeContainer { + let item: UITabBarItem + let updateBadgeListenerIndex: Int + + let imageNode: ASImageNode + let badgeBackgroundNode: ASImageNode + let badgeTextNode: ASTextNode + + var badgeValue: String? + var appliedBadgeValue: String? + + init(item: UITabBarItem, imageNode: ASImageNode, updateBadge: @escaping (String) -> Void) { + self.item = item + + self.imageNode = imageNode + + self.badgeBackgroundNode = ASImageNode() + self.badgeBackgroundNode.isLayerBacked = true + self.badgeBackgroundNode.displayWithoutProcessing = true + self.badgeBackgroundNode.displaysAsynchronously = false + self.badgeBackgroundNode.image = badgeImage + + self.badgeTextNode = ASTextNode() + self.badgeTextNode.maximumNumberOfLines = 1 + self.badgeTextNode.isLayerBacked = true + self.badgeTextNode.displaysAsynchronously = false + + self.badgeValue = item.badgeValue ?? "" + self.updateBadgeListenerIndex = UITabBarItem_addSetBadgeListener(item, { value in + updateBadge(value ?? "") + }) + } + + deinit { + item.removeSetBadgeListener(self.updateBadgeListenerIndex) + } +} + class TabBarNode: ASDisplayNode { var tabBarItems: [UITabBarItem] = [] { didSet { @@ -66,7 +107,7 @@ class TabBarNode: ASDisplayNode { private let itemSelected: (Int) -> Void let separatorNode: ASDisplayNode - private var tabBarNodes: [ASImageNode] = [] + private var tabBarNodeContainers: [TabBarNodeContainer] = [] init(itemSelected: @escaping (Int) -> Void) { self.itemSelected = itemSelected @@ -85,11 +126,13 @@ class TabBarNode: ASDisplayNode { } private func reloadTabBarItems() { - for node in self.tabBarNodes { - node.removeFromSupernode() + for node in self.tabBarNodeContainers { + node.imageNode.removeFromSupernode() + node.badgeBackgroundNode.removeFromSupernode() + node.badgeTextNode.removeFromSupernode() } - var tabBarNodes: [ASImageNode] = [] + var tabBarNodeContainers: [TabBarNodeContainer] = [] for i in 0 ..< self.tabBarItems.count { let item = self.tabBarItems[i] let node = ASImageNode() @@ -101,18 +144,26 @@ class TabBarNode: ASDisplayNode { } else { node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) } - tabBarNodes.append(node) + let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in + self?.updateNodeBadge(i, value: value) + }) + tabBarNodeContainers.append(container) self.addSubnode(node) } - self.tabBarNodes = tabBarNodes + for container in tabBarNodeContainers { + self.addSubnode(container.badgeBackgroundNode) + self.addSubnode(container.badgeTextNode) + } + + self.tabBarNodeContainers = tabBarNodeContainers self.setNeedsLayout() } private func updateNodeImage(_ index: Int) { - if index < self.tabBarNodes.count && index < self.tabBarItems.count { - let node = self.tabBarNodes[index] + if index < self.tabBarNodeContainers.count && index < self.tabBarItems.count { + let node = self.tabBarNodeContainers[index].imageNode let item = self.tabBarItems[index] if let selectedIndex = self.selectedIndex , selectedIndex == index { @@ -123,6 +174,13 @@ class TabBarNode: ASDisplayNode { } } + private func updateNodeBadge(_ index: Int, value: String) { + self.tabBarNodeContainers[index].badgeValue = value + if self.tabBarNodeContainers[index].badgeValue != self.tabBarNodeContainers[index].appliedBadgeValue { + self.layout() + } + } + override func layout() { super.layout() @@ -130,18 +188,39 @@ class TabBarNode: ASDisplayNode { self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight), size: CGSize(width: size.width, height: separatorHeight)) - if self.tabBarNodes.count != 0 { - let distanceBetweenNodes = size.width / CGFloat(self.tabBarNodes.count) + if self.tabBarNodeContainers.count != 0 { + let distanceBetweenNodes = size.width / CGFloat(self.tabBarNodeContainers.count) - let internalWidth = distanceBetweenNodes * CGFloat(self.tabBarNodes.count - 1) + let internalWidth = distanceBetweenNodes * CGFloat(self.tabBarNodeContainers.count - 1) let leftNodeOriginX = (size.width - internalWidth) / 2.0 - for i in 0 ..< self.tabBarNodes.count { - let node = self.tabBarNodes[i] + for i in 0 ..< self.tabBarNodeContainers.count { + let container = self.tabBarNodeContainers[i] + let node = container.imageNode node.measure(CGSize(width: internalWidth, height: size.height)) let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - node.calculatedSize.width / 2.0) node.frame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: node.calculatedSize) + + if container.badgeValue != container.appliedBadgeValue { + container.appliedBadgeValue = container.badgeValue + if let badgeValue = container.badgeValue, !badgeValue.isEmpty { + container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: .white) + container.badgeBackgroundNode.isHidden = false + container.badgeTextNode.isHidden = false + } else { + container.badgeBackgroundNode.isHidden = true + container.badgeTextNode.isHidden = true + } + } + + if !container.badgeBackgroundNode.isHidden { + let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) + let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) + let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.calculatedSize.width / 2.0) - 5.0, y: 2.0), size: backgroundSize) + container.badgeBackgroundNode.frame = backgroundFrame + container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: 3.0), size: badgeSize) + } } } } @@ -153,8 +232,8 @@ class TabBarNode: ASDisplayNode { let location = touch.location(in: self.view) var closestNode: (Int, CGFloat)? - for i in 0 ..< self.tabBarNodes.count { - let node = self.tabBarNodes[i] + for i in 0 ..< self.tabBarNodeContainers.count { + let node = self.tabBarNodeContainers[i].imageNode let distance = abs(location.x - node.position.x) if let previousClosestNode = closestNode { if previousClosestNode.1 > distance { diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 4fd81f16e2..8f3b22d0ab 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -3,9 +3,12 @@ typedef void (^UINavigationItemSetTitleListener)(NSString *); typedef void (^UINavigationItemSetTitleViewListener)(UIView *); typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL); +typedef void (^UITabBarItemSetBadgeListener)(NSString *); @interface UINavigationItem (Proxy) +- (void)setTargetItem:(UINavigationItem *)targetItem; + - (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; - (void)removeSetTitleListener:(NSInteger)key; - (NSInteger)addSetTitleViewListener:(UINavigationItemSetTitleViewListener)listener; @@ -16,3 +19,11 @@ typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL - (void)removeSetRightBarButtonItemListener:(NSInteger)key; @end + +NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBadgeListener listener); + +@interface UITabBarItem (Proxy) + +- (void)removeSetBadgeListener:(NSInteger)key; + +@end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index e8ad9c81a8..b77620575f 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -2,11 +2,15 @@ #import "NSBag.h" #import "RuntimeUtils.h" +#import "NSWeakReference.h" +static const void *sourceItemKey = &sourceItemKey; +static const void *targetItemKey = &targetItemKey; static const void *setTitleListenerBagKey = &setTitleListenerBagKey; static const void *setTitleViewListenerBagKey = &setTitleViewListenerBagKey; static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemListenerBagKey; static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; +static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; @implementation UINavigationItem (Proxy) @@ -28,18 +32,28 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [self _ac91f40f_setTitle:title]; - [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { - listener(title); - }]; + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setTitle:title]; + } else { + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { + listener(title); + }]; + } } - (void)_ac91f40f_setTitleView:(UIView *)titleView { [self _ac91f40f_setTitleView:titleView]; - [(NSBag *)[self associatedObjectForKey:setTitleViewListenerBagKey] enumerateItems:^(UINavigationItemSetTitleViewListener listener) { - listener(titleView); - }]; + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setTitleView:titleView]; + } else { + [(NSBag *)[self associatedObjectForKey:setTitleViewListenerBagKey] enumerateItems:^(UINavigationItemSetTitleViewListener listener) { + listener(titleView); + }]; + } } - (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem { @@ -50,9 +64,14 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; - [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { - listener(leftBarButtonItem, animated); - }]; + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setLeftBarButtonItem:leftBarButtonItem animated:animated]; + } else { + [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { + listener(leftBarButtonItem, animated); + }]; + } } - (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem { @@ -63,9 +82,38 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; - [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { - listener(rightBarButtonItem, animated); - }]; + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setRightBarButtonItem:rightBarButtonItem animated:animated]; + } else { + [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { + listener(rightBarButtonItem, animated); + }]; + } +} + +- (void)setTargetItem:(UINavigationItem *)targetItem { + NSWeakReference *previousSourceItem = [targetItem associatedObjectForKey:sourceItemKey]; + [(UINavigationItem *)previousSourceItem.value setAssociatedObject:nil forKey:targetItemKey associationPolicy:NSObjectAssociationPolicyRetain]; + + [self setAssociatedObject:targetItem forKey:targetItemKey associationPolicy:NSObjectAssociationPolicyRetain]; + [targetItem setAssociatedObject:[[NSWeakReference alloc] initWithValue:self] forKey:sourceItemKey associationPolicy:NSObjectAssociationPolicyRetain]; + + if ((targetItem.title != nil) != (self.title != nil) || ![targetItem.title isEqualToString:self.title]) { + targetItem.title = self.title; + } + if (targetItem.titleView != self.titleView) { + [targetItem setTitleView:self.titleView]; + } + if (targetItem.leftBarButtonItem != self.leftBarButtonItem) { + [targetItem setLeftBarButtonItem:self.leftBarButtonItem]; + } + if (targetItem.rightBarButtonItem != self.rightBarButtonItem) { + [targetItem setRightBarButtonItem:self.rightBarButtonItem]; + } + if (targetItem.backBarButtonItem != self.backBarButtonItem) { + [targetItem setBackBarButtonItem:self.backBarButtonItem]; + } } - (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener @@ -133,3 +181,38 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL } @end + +@implementation UITabBarItem (Proxy) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UITabBarItem class] currentSelector:@selector(setBadgeValue:) newSelector:@selector(_ac91f40f_setBadgeValue:)]; + }); +} + +NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBadgeListener listener) { + NSBag *bag = [item associatedObjectForKey:setBadgeListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [item setAssociatedObject:bag forKey:setBadgeListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetBadgeListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setBadgeListenerBagKey] removeItem:key]; +} + +- (void)_ac91f40f_setBadgeValue:(NSString *)value { + [self _ac91f40f_setBadgeValue:value]; + + [(NSBag *)[self associatedObjectForKey:setBadgeListenerBagKey] enumerateItems:^(UITabBarItemSetBadgeListener listener) { + listener(value); + }]; +} + +@end From 457a6ef8eef0c86dbe3c6f5a40a739eebfbc52bb Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 11 Feb 2017 17:02:35 +0300 Subject: [PATCH 034/245] no message --- Display.xcodeproj/project.pbxproj | 32 +++ .../xcschemes/xcschememanagement.plist | 4 +- Display/AlertContentNode.swift | 10 + Display/AlertController.swift | 57 +++++ Display/AlertControllerNode.swift | 80 +++++++ Display/CAAnimationUtils.swift | 58 ++++- Display/CATracingLayer.m | 6 + Display/ContainableController.swift | 32 +++ Display/Font.swift | 20 +- Display/GenerateImage.swift | 35 +++ Display/GridItem.swift | 1 + Display/GridNode.swift | 2 +- Display/ListView.swift | 123 ++++++---- Display/ListViewItem.swift | 8 + Display/ListViewItemHeader.swift | 4 +- Display/ListViewItemNode.swift | 21 +- Display/NavigationBar.swift | 48 +++- Display/NavigationButtonNode.swift | 39 +++- Display/NavigationController.swift | 19 +- Display/StatusBarManager.swift | 4 +- Display/SwitchNode.swift | 10 + Display/TabBarController.swift | 7 +- Display/TextAlertController.swift | 221 ++++++++++++++++++ Display/TextFieldNode.swift | 33 +++ Display/UIBarButtonItem+Proxy.m | 2 +- Display/UINavigationItem+Proxy.h | 2 +- Display/UINavigationItem+Proxy.m | 8 +- Display/UIViewController+Navigation.h | 2 +- Display/ViewController.swift | 13 ++ 29 files changed, 830 insertions(+), 71 deletions(-) create mode 100644 Display/AlertContentNode.swift create mode 100644 Display/AlertController.swift create mode 100644 Display/AlertControllerNode.swift create mode 100644 Display/SwitchNode.swift create mode 100644 Display/TextAlertController.swift create mode 100644 Display/TextFieldNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index d153af1160..8ff689f0b6 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */; }; + D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; @@ -80,6 +81,7 @@ D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; + D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; @@ -102,6 +104,10 @@ D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; }; + D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; }; + D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */; }; + D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444F1E4DCBDE005FDCA7 /* AlertContentNode.swift */; }; + D0DA44521E4DCC11005FDCA7 /* TextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44511E4DCC11005FDCA7 /* TextAlertController.swift */; }; D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; }; D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; @@ -125,6 +131,7 @@ /* Begin PBXFileReference section */ D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentableViewController.swift; sourceTree = ""; }; + D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = ""; }; D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; @@ -199,6 +206,7 @@ D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; + D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; @@ -221,6 +229,10 @@ D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonNode.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = ""; }; + D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; + D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertControllerNode.swift; sourceTree = ""; }; + D0DA444F1E4DCBDE005FDCA7 /* AlertContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertContentNode.swift; sourceTree = ""; }; + D0DA44511E4DCC11005FDCA7 /* TextAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextAlertController.swift; sourceTree = ""; }; D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = ""; }; D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = ""; }; D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; @@ -312,6 +324,8 @@ children = ( D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */, D0E35A021DE473B900BC6096 /* HighlightableButton.swift */, + D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */, + D0A749941E3A9E7B00AD786E /* SwitchNode.swift */, ); name = Nodes; sourceTree = ""; @@ -520,6 +534,7 @@ D015F7551D1B142300E269B5 /* Tab Bar */, D015F7561D1B465600E269B5 /* Action Sheet */, D03725BF1D6DF57B007FC290 /* Context Menu */, + D0DA444A1E4DCA36005FDCA7 /* Alert */, ); name = Controllers; sourceTree = ""; @@ -543,6 +558,17 @@ name = "List Node"; sourceTree = ""; }; + D0DA444A1E4DCA36005FDCA7 /* Alert */ = { + isa = PBXGroup; + children = ( + D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */, + D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */, + D0DA444F1E4DCBDE005FDCA7 /* AlertContentNode.swift */, + D0DA44511E4DCC11005FDCA7 /* TextAlertController.swift */, + ); + name = Alert; + sourceTree = ""; + }; D0DC48521BF93D7C00F672FD /* Tabs */ = { isa = PBXGroup; children = ( @@ -708,8 +734,10 @@ D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */, D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, + D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */, + D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */, D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, @@ -727,9 +755,11 @@ D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */, + D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, + D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */, @@ -737,6 +767,7 @@ D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */, + D0DA44521E4DCC11005FDCA7 /* TextAlertController.swift in Sources */, D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */, D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */, @@ -757,6 +788,7 @@ D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, + D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index b987a65bba..8f0ec3539d 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ Display.xcscheme orderHint - 20 + 23 DisplayTests.xcscheme orderHint - 14 + 24 SuppressBuildableAutocreation diff --git a/Display/AlertContentNode.swift b/Display/AlertContentNode.swift new file mode 100644 index 0000000000..c95d14ac95 --- /dev/null +++ b/Display/AlertContentNode.swift @@ -0,0 +1,10 @@ +import Foundation +import AsyncDisplayKit + +open class AlertContentNode: ASDisplayNode { + open func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + assertionFailure() + + return CGSize() + } +} diff --git a/Display/AlertController.swift b/Display/AlertController.swift new file mode 100644 index 0000000000..2e2ff29541 --- /dev/null +++ b/Display/AlertController.swift @@ -0,0 +1,57 @@ +import Foundation +import AsyncDisplayKit + +open class AlertController: ViewController { + private var controllerNode: AlertControllerNode { + return self.displayNode as! AlertControllerNode + } + + private let contentNode: AlertContentNode + + public init(contentNode: AlertContentNode) { + self.contentNode = contentNode + + super.init(navigationBar: NavigationBar()) + + self.navigationBar.isHidden = true + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override open func loadDisplayNode() { + self.displayNode = AlertControllerNode(contentNode: self.contentNode) + self.displayNodeDidLoad() + + self.controllerNode.dismiss = { [weak self] in + if let strongSelf = self { + strongSelf.controllerNode.animateOut { + self?.dismiss() + } + } + } + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.controllerNode.animateIn() + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, transition: transition) + } + + public func dismiss() { + self.presentingViewController?.dismiss(animated: false, completion: nil) + } + + public func dismissAnimated() { + self.controllerNode.animateOut { [weak self] in + self?.dismiss() + } + } +} diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift new file mode 100644 index 0000000000..3655342629 --- /dev/null +++ b/Display/AlertControllerNode.swift @@ -0,0 +1,80 @@ +import Foundation +import AsyncDisplayKit + +final class AlertControllerNode: ASDisplayNode { + private let dimmingNode: ASDisplayNode + private let containerNode: ASDisplayNode + private let effectNode: ASDisplayNode + private let contentNode: AlertContentNode + + var dismiss: (() -> Void)? + + init(contentNode: AlertContentNode) { + self.dimmingNode = ASDisplayNode() + self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + + self.containerNode = ASDisplayNode() + self.containerNode.backgroundColor = .white + self.containerNode.layer.cornerRadius = 14.0 + self.containerNode.layer.masksToBounds = true + + self.effectNode = ASDisplayNode(viewBlock: { + let view = UIView()//UIVisualEffectView(effect: UIBlurEffect(style: .light)) + return view + }, didLoad: nil) + + self.contentNode = contentNode + + super.init() + + self.addSubnode(self.dimmingNode) + + self.addSubnode(self.effectNode) + + self.containerNode.addSubnode(self.contentNode) + self.addSubnode(self.containerNode) + } + + override func didLoad() { + super.didLoad() + + self.dimmingNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) + } + + func animateIn() { + self.dimmingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) + } + + func animateOut(completion: @escaping () -> Void) { + self.dimmingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.containerNode.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false, completion: { _ in + completion() + }) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + + var insets = layout.insets(options: [.statusBar, .input]) + let maxWidth = min(340.0, layout.size.width - 70.0) + insets.left = floor((layout.size.width - maxWidth) / 2.0) + insets.right = floor((layout.size.width - maxWidth) / 2.0) + let contentAvailableFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: layout.size.width - insets.right, height: layout.size.height - insets.top - insets.bottom)) + let contentSize = self.contentNode.updateLayout(size: contentAvailableFrame.size, transition: transition) + let containerSize = CGSize(width: contentSize.width, height: contentSize.height) + let containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: contentAvailableFrame.minY + floor((contentAvailableFrame.size.height - containerSize.height) / 2.0)), size: containerSize) + + transition.updateFrame(node: self.containerNode, frame: containerFrame) + transition.updateFrame(node: self.effectNode, frame: containerFrame) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) + } + + @objc func dimmingNodeTapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.dismiss?() + } + } +} diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 1ccd26ca10..93c89b6d99 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -39,7 +39,7 @@ public extension CAAnimation { } public extension CALayer { - public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + public func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation { if timingFunction == kCAMediaTimingFunctionSpring { let animation = makeSpringAnimation(keyPath) animation.fromValue = from @@ -59,7 +59,7 @@ public extension CALayer { animation.speed = speed * Float(animation.duration / duration) animation.isAdditive = additive - self.add(animation, forKey: keyPath) + return animation } else { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 @@ -84,9 +84,55 @@ public extension CALayer { animation.delegate = CALayerAnimationDelegate(completion: completion) } - self.add(animation, forKey: keyPath) + return animation } } + + public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + self.add(animation, forKey: keyPath) + } + + public func animateGroup(_ animations: [CAAnimation], key: String) { + let animationGroup = CAAnimationGroup() + var timeOffset = 0.0 + for animation in animations { + animation.beginTime = animation.beginTime + timeOffset + timeOffset += animation.duration / Double(animation.speed) + } + animationGroup.animations = animations + animationGroup.duration = timeOffset + self.add(animationGroup, forKey: key) + } + + public func animateKeyframes(values: [AnyObject], duration: Double, keyPath: String, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + + let animation = CAKeyframeAnimation(keyPath: keyPath) + animation.values = values + var keyTimes: [NSNumber] = [] + for i in 0 ..< values.count { + if i == 0 { + keyTimes.append(0.0) + } else if i == values.count - 1 { + keyTimes.append(1.0) + } else { + keyTimes.append((Double(i) / Double(values.count - 1)) as NSNumber) + } + } + animation.keyTimes = keyTimes + animation.speed = speed + animation.duration = duration + if let completion = completion { + animation.delegate = CALayerAnimationDelegate(completion: completion) + } + + self.add(animation, forKey: keyPath) + } public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { let animation = makeSpringBounceAnimation(keyPath, initialVelocity) @@ -169,6 +215,10 @@ public extension CALayer { self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true) } + public func animatePositionKeyframes(values: [CGPoint], duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position") + } + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to { if let completion = completion { @@ -179,7 +229,7 @@ public extension CALayer { var interrupted = false var completedPosition = false var completedBounds = false - var partialCompletion: () -> Void = { + let partialCompletion: () -> Void = { if interrupted || (completedPosition && completedBounds) { if let completion = completion { completion(!interrupted) diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 0ea557c54c..bd8f209477 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -142,6 +142,12 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic @implementation CATracingLayer +- (void)setNeedsDisplay { +} + +- (void)displayIfNeeded { +} + - (bool)isInvalidated { return [[self associatedObjectForKey:CATracingLayerInvalidatedKey] intValue] != 0; } diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index c734b5ac35..85182f228b 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -219,6 +219,38 @@ public extension ContainedViewLayoutTransition { }) } } + + func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) { + if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.backgroundColor = color + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + if let nodeColor = node.backgroundColor { + node.backgroundColor = color + node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in + if let completion = completion { + completion(result) + } + }) + } else { + node.backgroundColor = color + if let completion = completion { + completion(true) + } + } + } + } + } public protocol ContainableController: class { diff --git a/Display/Font.swift b/Display/Font.swift index 99372d913d..9093a22637 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -22,13 +22,29 @@ public struct Font { } } + public static func light(_ size: CGFloat) -> UIFont { + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: UIFontWeightLight) + } else { + return CTFontCreateWithName("HelveticaNeue-Light" as CFString, size, nil) + } + } + public static func italic(_ size: CGFloat) -> UIFont { return UIFont.italicSystemFont(ofSize: size) } } public extension NSAttributedString { - convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black) { - self.init(string: string, attributes: [NSFontAttributeName: font, NSForegroundColorAttributeName as String: textColor]) + convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black, paragraphAlignment: NSTextAlignment? = nil) { + var attributes: [String: AnyObject] = [:] + attributes[NSFontAttributeName] = font + attributes[NSForegroundColorAttributeName] = textColor + if let paragraphAlignment = paragraphAlignment { + let paragraphStyle = NSMutableParagraphStyle() + paragraphStyle.alignment = paragraphAlignment + attributes[NSParagraphStyleAttributeName] = paragraphStyle + } + self.init(string: string, attributes: attributes) } } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index aaad94b53b..6119a70611 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -61,6 +61,41 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext) return UIImage(cgImage: image, scale: selectedScale, orientation: .up) } +public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? = nil, rotatedContext: (CGSize, CGContext) -> Void) -> UIImage? { + let selectedScale = scale ?? deviceScale + let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale) + let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) + let length = bytesPerRow * Int(scaledSize.height) + let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self) + + guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in + free(bytes) + }) + else { + return nil + } + + let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue)) + + guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else { + return nil + } + + context.scaleBy(x: selectedScale, y: selectedScale) + context.translateBy(x: size.width / 2.0, y: size.height / 2.0) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0) + + rotatedContext(size, context) + + guard let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider, decode: nil, shouldInterpolate: false, intent: .defaultIntent) + else { + return nil + } + + return UIImage(cgImage: image, scale: selectedScale, orientation: .up) +} + public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) diff --git a/Display/GridItem.swift b/Display/GridItem.swift index a417df781d..cd112ce5c0 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -11,4 +11,5 @@ public protocol GridSection { public protocol GridItem { var section: GridSection? { get } func node(layout: GridNodeLayout) -> GridItemNode + func update(node: GridItemNode) } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 09abd7f694..057b1454fb 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -256,7 +256,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { for updatedItem in transaction.updateItems { self.items[updatedItem.index] = updatedItem.item if let itemNode = self.itemNodes[updatedItem.index] { - //update node + updatedItem.item.update(node: itemNode) } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 2fa306a7bd..8a6b34e8b7 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -17,6 +17,16 @@ private final class ListViewBackingLayer: CALayer { override func setNeedsDisplay() { } + + override func displayIfNeeded() { + } + + override func needsDisplay() -> Bool { + return false + } + + override func display() { + } } final class ListViewBackingView: UIView { @@ -32,6 +42,9 @@ final class ListViewBackingView: UIView { override func layoutSubviews() { } + override func setNeedsDisplay() { + } + override func touchesBegan(_ touches: Set, with event: UIEvent?) { self.target?.touchesBegan(touches, with: event) } @@ -1199,6 +1212,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if updateAdjacentItemsIndices.isEmpty { completion(state, operations) } else { + let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None + var updatedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices let nodeIndex = updateAdjacentItemsIndices.first! @@ -1217,7 +1232,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: .None, completion: { layout, apply in + }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in var updatedState = state var updatedOperations = operations @@ -1321,20 +1336,24 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var operations = inputOperations var updateIndicesAndItems = updateIndicesAndItems - if updateIndicesAndItems.isEmpty { - completion(state, operations) - } else { - var updateItem = updateIndicesAndItems[0] - if let previousNode = previousNodes[updateItem.index] { - self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in - state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations) - - updateIndicesAndItems.remove(at: 0) - self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) - }) + while true { + if updateIndicesAndItems.isEmpty { + completion(state, operations) + break } else { - updateIndicesAndItems.remove(at: 0) - self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + let updateItem = updateIndicesAndItems[0] + if let previousNode = previousNodes[updateItem.index] { + self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in + state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations) + + updateIndicesAndItems.remove(at: 0) + self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + }) + break + } else { + updateIndicesAndItems.remove(at: 0) + //self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + } } } } @@ -1406,13 +1425,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel takenAnimation = true if abs(layout.size.height - previousApparentHeight) > CGFloat(FLT_EPSILON) { - node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in + node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in if let node = node { - node.animateFrameTransition(progress) + node.animateFrameTransition(progress, currentValue) } }) - node.transitionOffset += previousApparentHeight - layout.size.height - node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + if node.rotated { + node.transitionOffset += previousApparentHeight - layout.size.height + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } } } } @@ -1424,20 +1445,22 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } else if animated { if !takenAnimation { - node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in - if let node = node { - node.animateFrameTransition(progress) - } - }) + if !nodeFrame.size.height.isEqual(to: node.apparentHeight) { + node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in + if let node = node { + node.animateFrameTransition(progress, currentValue) + } + }) + } if let previousFrame = previousFrame { if self.debugInfo { assert(true) } - let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y - previousApparentHeight + layout.size.height + let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y if node.rotated { - node.transitionOffset -= transitionOffsetDelta + node.transitionOffset -= transitionOffsetDelta - previousApparentHeight + layout.size.height } else { node.transitionOffset += transitionOffsetDelta } @@ -1450,6 +1473,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if self.debugInfo { assert(true) } + if !node.rotated { + if !node.insets.top.isZero { + node.transitionOffset += node.insets.top + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + } node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: false) } } @@ -1679,20 +1708,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if abs(updatedApparentHeight - previousApparentHeight) > CGFloat(FLT_EPSILON) { node.apparentHeight = previousApparentHeight - node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in + node.animateFrameTransition(0.0, previousApparentHeight) + node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in if let node = node { - node.animateFrameTransition(progress) + node.animateFrameTransition(progress, currentValue) } }) let insetPart: CGFloat = previousInsets.top - layout.insets.top - node.transitionOffset += previousApparentHeight - layout.size.height - insetPart - node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + if node.rotated { + node.transitionOffset += previousApparentHeight - layout.size.height - insetPart + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } } else { if node.shouldAnimateHorizontalFrameTransition() { - node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in + node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in if let node = node { - node.animateFrameTransition(progress) + node.animateFrameTransition(progress, currentValue) } }) } @@ -1957,13 +1989,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel previousLowerBound = previousFrame.maxY } } else { - offset = previousNode.apparentFrame.minY - previousFrame.minY + if previousNode.canBeUsedAsScrollToItemAnchor { + offset = previousNode.apparentFrame.minY - previousFrame.minY + } } } if offset == nil { let updatedUpperBound = self.itemNodes[0].apparentFrame.minY - let updatedLowerBound = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY + let updatedLowerBound = max(self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY, self.visibleSize.height) switch scrollToItem.directionHint { case .Up: @@ -2161,7 +2195,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel case let .animated(duration, curve): let previousFrame = headerNode.frame headerNode.frame = headerFrame - let offset = -(headerFrame.minY - previousFrame.minY + transition.2) + var offset = headerFrame.minY - previousFrame.minY + transition.2 + if headerNode.isRotated { + offset = -offset + } switch curve { case .spring: transition.0.animateOffsetAdditive(node: headerNode, offset: offset) @@ -2617,7 +2654,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if strongSelf.items[index].selectable { strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { - if itemNode.index == index { + if itemNode.index == index && itemNode.canBeSelected { if true { //!(itemNode.hitTest(CGPoint(x: strongSelf.touchesPosition.x - itemNode.frame.minX, y: strongSelf.touchesPosition.y - itemNode.frame.minY), with: event) is UIControl) { if !itemNode.isLayerBacked { strongSelf.view.bringSubview(toFront: itemNode.view) @@ -2663,7 +2700,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return nil } - public func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void) { + public func forEachItemNode(_ f: (ASDisplayNode) -> Void) { for itemNode in self.itemNodes { if itemNode.index != nil { f(itemNode) @@ -2709,13 +2746,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.highlightedItemIndex = index for itemNode in self.itemNodes { if itemNode.index == index { - if !itemNode.isLayerBacked { - self.view.bringSubview(toFront: itemNode.view) - for (_, headerNode) in self.itemHeaderNodes { - self.view.bringSubview(toFront: headerNode.view) + if itemNode.canBeSelected { + if !itemNode.isLayerBacked { + self.view.bringSubview(toFront: itemNode.view) + for (_, headerNode) in self.itemHeaderNodes { + self.view.bringSubview(toFront: headerNode.view) + } } + itemNode.setHighlighted(true, animated: false) + } else { + self.highlightedItemIndex = nil } - itemNode.setHighlighted(true, animated: false) break } } diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 153847d2ad..7f5db6fe76 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -4,6 +4,14 @@ import SwiftSignalKit public enum ListViewItemUpdateAnimation { case None case System(duration: Double) + + public var isAnimated: Bool { + if case .None = self { + return false + } else { + return true + } + } } public struct ListViewItemConfigureNodeFlags: OptionSet { diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index e41f6b7866..25e851f184 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -17,6 +17,7 @@ public protocol ListViewItemHeader: class { open class ListViewItemHeaderNode: ASDisplayNode { private final var spring: ListViewItemSpring? let wantsScrollDynamics: Bool + let isRotated: Bool final private(set) var internalStickLocationDistanceFactor: CGFloat = 0.0 final var internalStickLocationDistance: CGFloat = 0.0 private var isFlashingOnScrolling = false @@ -50,8 +51,9 @@ open class ListViewItemHeaderNode: ASDisplayNode { open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { } - public init(dynamicBounce: Bool = false) { + public init(dynamicBounce: Bool = false, isRotated: Bool = false) { self.wantsScrollDynamics = dynamicBounce + self.isRotated = isRotated if dynamicBounce { self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 38296f0aad..72e0c8c6a2 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -83,11 +83,18 @@ open class ListViewItemNode: ASDisplayNode { public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets() + public final var canBeUsedAsScrollToItemAnchor: Bool = true + + open var canBeSelected: Bool { + return true + } + public final var insets: UIEdgeInsets = UIEdgeInsets() { didSet { let effectiveInsets = self.insets self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: self.contentSize.width, height: self.contentSize.height + effectiveInsets.top + effectiveInsets.bottom)) - self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size) + let bounds = self.bounds + self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size) if oldValue != self.insets { if let accessoryItemNode = self.accessoryItemNode { @@ -110,14 +117,16 @@ open class ListViewItemNode: ASDisplayNode { private var contentOffset: CGFloat = 0.0 { didSet { let effectiveInsets = self.insets - self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size) + let bounds = self.bounds + self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size) } } public var transitionOffset: CGFloat = 0.0 { didSet { let effectiveInsets = self.insets - self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size) + let bounds = self.bounds + self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size) } } @@ -378,12 +387,12 @@ open class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("insets", animation: animation) } - public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat) -> Void)? = nil) { + public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { strongSelf.apparentHeight = currentValue if let update = update { - update(progress) + update(progress, currentValue) } } }) @@ -432,7 +441,7 @@ open class ListViewItemNode: ASDisplayNode { open func setHighlighted(_ highlighted: Bool, animated: Bool) { } - open func animateFrameTransition(_ progress: CGFloat) { + open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index f48a44128c..e4fdf024b3 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -68,8 +68,13 @@ open class NavigationBar: ASDisplayNode { private var itemTitleListenerKey: Int? private var itemTitleViewListenerKey: Int? + private var itemLeftButtonListenerKey: Int? + private var itemLeftButtonSetEnabledListenerKey: Int? + private var itemRightButtonListenerKey: Int? + private var itemRightButtonSetEnabledListenerKey: Int? + private var _item: UINavigationItem? public var item: UINavigationItem? { get { @@ -80,10 +85,26 @@ open class NavigationBar: ASDisplayNode { previousValue.removeSetTitleListener(itemTitleListenerKey) self.itemTitleListenerKey = nil } + if let itemLeftButtonListenerKey = self.itemLeftButtonListenerKey { previousValue.removeSetLeftBarButtonItemListener(itemLeftButtonListenerKey) self.itemLeftButtonListenerKey = nil } + + if let itemLeftButtonSetEnabledListenerKey = self.itemLeftButtonSetEnabledListenerKey { + previousValue.leftBarButtonItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) + self.itemLeftButtonSetEnabledListenerKey = nil + } + + if let itemRightButtonListenerKey = self.itemRightButtonListenerKey { + previousValue.removeSetRightBarButtonItemListener(itemRightButtonListenerKey) + self.itemRightButtonListenerKey = nil + } + + if let itemRightButtonSetEnabledListenerKey = self.itemRightButtonSetEnabledListenerKey { + previousValue.rightBarButtonItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey) + self.itemRightButtonSetEnabledListenerKey = nil + } } self._item = value @@ -105,16 +126,34 @@ open class NavigationBar: ASDisplayNode { } } - self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] _, _ in + self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] previousItem, _, _ in if let strongSelf = self { + if let itemLeftButtonSetEnabledListenerKey = strongSelf.itemLeftButtonSetEnabledListenerKey { + previousItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) + strongSelf.itemLeftButtonSetEnabledListenerKey = nil + } + strongSelf.updateLeftButton() strongSelf.invalidateCalculatedLayout() strongSelf.setNeedsLayout() } } - self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] _, _ in + self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, _ in if let strongSelf = self { + if let itemRightButtonSetEnabledListenerKey = strongSelf.itemRightButtonSetEnabledListenerKey { + previousItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey) + strongSelf.itemRightButtonSetEnabledListenerKey = nil + } + + if let currentItem = currentItem { + strongSelf.itemRightButtonSetEnabledListenerKey = currentItem.addSetEnabledListener { _ in + if let strongSelf = self { + strongSelf.updateRightButton() + } + } + } + strongSelf.updateRightButton() strongSelf.invalidateCalculatedLayout() strongSelf.setNeedsLayout() @@ -197,6 +236,8 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.removeFromSupernode() self.leftButtonNode.text = leftBarButtonItem.title ?? "" + self.leftButtonNode.bold = leftBarButtonItem.style == .done + self.leftButtonNode.isEnabled = leftBarButtonItem.isEnabled if self.leftButtonNode.supernode == nil { self.clippingNode.addSubnode(self.leftButtonNode) } @@ -230,6 +271,9 @@ open class NavigationBar: ASDisplayNode { if let item = self.item { if let rightBarButtonItem = item.rightBarButtonItem { self.rightButtonNode.text = rightBarButtonItem.title ?? "" + self.rightButtonNode.image = rightBarButtonItem.image + self.rightButtonNode.bold = rightBarButtonItem.style == .done + self.rightButtonNode.isEnabled = rightBarButtonItem.isEnabled self.rightButtonNode.node = rightBarButtonItem.customDisplayNode if self.rightButtonNode.supernode == nil { self.clippingNode.addSubnode(self.rightButtonNode) diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 6cb4611c91..10247d8e0f 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -21,7 +21,35 @@ public class NavigationButtonNode: ASTextNode { set(value) { _text = value - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + + private var imageNode: ASImageNode? + + private var _image: UIImage? + public var image: UIImage? { + get { + return _image + } set(value) { + _image = value + + if let _ = value { + if self.imageNode == nil { + let imageNode = ASImageNode() + imageNode.displayWithoutProcessing = true + imageNode.displaysAsynchronously = false + self.imageNode = imageNode + self.addSubnode(imageNode) + } + self.imageNode?.image = image + } else if let imageNode = self.imageNode { + imageNode.removeFromSupernode() + self.imageNode = nil + } + + self.invalidateCalculatedLayout() + self.setNeedsLayout() } } @@ -41,7 +69,7 @@ public class NavigationButtonNode: ASTextNode { public var color: UIColor = UIColor(0x007ee5) { didSet { if let text = self._text { - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } @@ -55,7 +83,7 @@ public class NavigationButtonNode: ASTextNode { if _bold != value { _bold = value - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } @@ -78,6 +106,11 @@ public class NavigationButtonNode: ASTextNode { if let node = self.node { let nodeSize = node.measure(constrainedSize) return CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + } else if let imageNode = self.imageNode { + let nodeSize = imageNode.measure(constrainedSize) + let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0), y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize) + return size } return superSize } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index bc081974b9..3f8dd36518 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -226,9 +226,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle self.setViewControllers(controllers, animated: animated) } - public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise?) { + public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) - self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in + self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { ready?.set(true) var controllers = strongSelf.viewControllers @@ -239,6 +239,21 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle })) } + public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { + controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in + if let strongSelf = self { + ready?.set(true) + var controllers = strongSelf.viewControllers + while controllers.count > 1 { + controllers.removeLast() + } + controllers.append(controller) + strongSelf.setViewControllers(controllers, animated: animated) + } + })) + } + open override func popViewController(animated: Bool) -> UIViewController? { var controller: UIViewController? var controllers = self.viewControllers diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index cf75310654..693435cc4d 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -66,7 +66,9 @@ class StatusBarManager { private func updateSurfaces(_ previousSurfaces: [StatusBarSurface]) { let statusBarFrame = self.host.statusBarFrame - let statusBarView = self.host.statusBarView! + guard let statusBarView = self.host.statusBarView else { + return + } var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) }) diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift new file mode 100644 index 0000000000..b3515a33eb --- /dev/null +++ b/Display/SwitchNode.swift @@ -0,0 +1,10 @@ +import Foundation +import AsyncDisplayKit + +open class SwitchNode: ASDisplayNode { + override public init() { + super.init(viewBlock: { + return UISwitch() + }, didLoad: nil) + } +} diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index cd504c07c3..b24a87c480 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -31,6 +31,10 @@ open class TabBarController: ViewController { _selectedIndex = index self.updateSelectedIndex() + } else { + if let controller = self.currentController { + controller.scrollToTop?() + } } } } @@ -87,12 +91,13 @@ open class TabBarController: ViewController { currentController.navigationItem.setTarget(self.navigationItem) displayNavigationBar = currentController.displayNavigationBar - //currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) + currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) } else { self.navigationItem.title = nil self.navigationItem.leftBarButtonItem = nil self.navigationItem.rightBarButtonItem = nil self.navigationItem.titleView = nil + self.navigationItem.backBarButtonItem = nil displayNavigationBar = false } if self.displayNavigationBar != displayNavigationBar { diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift new file mode 100644 index 0000000000..dc833c1c35 --- /dev/null +++ b/Display/TextAlertController.swift @@ -0,0 +1,221 @@ +import Foundation +import AsyncDisplayKit + +public enum TextAlertActionType { + case genericAction + case defaultAction +} + +public struct TextAlertAction { + public let type: TextAlertActionType + public let title: String + public let action: () -> Void + + public init(type: TextAlertActionType, title: String, action: @escaping () -> Void) { + self.type = type + self.title = title + self.action = action + } +} + +private final class TextAlertContentActionNode: HighlightableButtonNode { + private let backgroundNode: ASDisplayNode + + let action: TextAlertAction + + init(action: TextAlertAction) { + self.backgroundNode = ASDisplayNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.backgroundColor = UIColor(0xe0e5e6) + self.backgroundNode.alpha = 0.0 + + self.action = action + + super.init() + + self.setTitle(action.title, with: action.type == .defaultAction ? Font.medium(17.0) : Font.regular(17.0), with: UIColor(0x007ee5), for: []) + + self.highligthedChanged = { [weak self] value in + if let strongSelf = self { + if value { + if strongSelf.backgroundNode.supernode == nil { + strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0) + } + strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity") + strongSelf.backgroundNode.alpha = 1.0 + } else if !strongSelf.backgroundNode.alpha.isZero { + strongSelf.backgroundNode.alpha = 0.0 + strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25) + } + } + } + } + + override func didLoad() { + super.didLoad() + + self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) + } + + @objc func pressed() { + self.action.action() + } + + override func layout() { + super.layout() + + self.backgroundNode.frame = self.bounds + } +} + +final class TextAlertContentNode: AlertContentNode { + private let titleNode: ASTextNode? + private let textNode: ASTextNode + + private let actionNodesSeparator: ASDisplayNode + private let actionNodes: [TextAlertContentActionNode] + private let actionVerticalSeparators: [ASDisplayNode] + + init(title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) { + if let title = title { + let titleNode = ASTextNode() + titleNode.attributedText = title + titleNode.displaysAsynchronously = false + titleNode.isLayerBacked = true + titleNode.maximumNumberOfLines = 1 + titleNode.truncationMode = .byTruncatingTail + self.titleNode = titleNode + } else { + self.titleNode = nil + } + + self.textNode = ASTextNode() + self.textNode.attributedText = text + self.textNode.displaysAsynchronously = false + self.textNode.isLayerBacked = true + + self.actionNodesSeparator = ASDisplayNode() + self.actionNodesSeparator.isLayerBacked = true + self.actionNodesSeparator.backgroundColor = UIColor(0xc9cdd7) + + self.actionNodes = actions.map { action -> TextAlertContentActionNode in + return TextAlertContentActionNode(action: action) + } + + var actionVerticalSeparators: [ASDisplayNode] = [] + if actions.count > 1 { + for _ in 0 ..< actions.count - 1 { + let separatorNode = ASDisplayNode() + separatorNode.isLayerBacked = true + separatorNode.backgroundColor = UIColor(0xc9cdd7) + actionVerticalSeparators.append(separatorNode) + } + } + self.actionVerticalSeparators = actionVerticalSeparators + + super.init() + + if let titleNode = self.titleNode { + self.addSubnode(titleNode) + } + self.addSubnode(self.textNode) + + self.addSubnode(self.actionNodesSeparator) + + for actionNode in self.actionNodes { + self.addSubnode(actionNode) + } + + for separatorNode in self.actionVerticalSeparators { + self.addSubnode(separatorNode) + } + } + + override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + + var titleSize: CGSize? + if let titleNode = self.titleNode { + titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) + } + let textSize = self.textNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) + + let actionsHeight: CGFloat = 44.0 + + var minActionsWidth: CGFloat = 0.0 + let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) + let actionTitleInsets: CGFloat = 8.0 + for actionNode in self.actionNodes { + let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionsHeight)) + minActionsWidth += actionTitleSize.width + actionTitleInsets + } + + let resultSize: CGSize + + if let titleNode = titleNode, let titleSize = titleSize { + let contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) + + let spacing: CGFloat = 6.0 + let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize) + transition.updateFrame(node: titleNode, frame: titleFrame) + + let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: titleFrame.maxY + spacing), size: textSize) + transition.updateFrame(node: self.textNode, frame: textFrame) + + resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom) + } else { + let textFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize) + transition.updateFrame(node: self.textNode, frame: textFrame) + + resultSize = CGSize(width: textSize.width + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom) + } + + self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) + + var actionOffset: CGFloat = 0.0 + let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count)) + var separatorIndex = -1 + var nodeIndex = 0 + for actionNode in self.actionNodes { + if separatorIndex >= 0 { + let separatorNode = self.actionVerticalSeparators[separatorIndex] + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + } + separatorIndex += 1 + + let currentActionWidth: CGFloat + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + + let actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight)) + + actionOffset += currentActionWidth + transition.updateFrame(node: actionNode, frame: actionNodeFrame) + + nodeIndex += 1 + } + + return resultSize + } +} + +public func textAlertController(title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) -> AlertController { + return AlertController(contentNode: TextAlertContentNode(title: title, text: text, actions: actions)) +} + +public func standardTextAlertController(title: String?, text: String, actions: [TextAlertAction]) -> AlertController { + var dismissImpl: (() -> Void)? + let controller = AlertController(contentNode: TextAlertContentNode(title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: .black, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.medium(17.0) : Font.regular(13.0), textColor: .black, paragraphAlignment: .center), actions: actions.map { action in + return TextAlertAction(type: action.type, title: action.title, action: { + dismissImpl?() + action.action() + }) + })) + dismissImpl = { [weak controller] in + controller?.dismissAnimated() + } + return controller +} diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift new file mode 100644 index 0000000000..8522926cc3 --- /dev/null +++ b/Display/TextFieldNode.swift @@ -0,0 +1,33 @@ +import Foundation +import AsyncDisplayKit + +public final class TextFieldNodeView: UITextField { + public var didDeleteBackwardWhileEmpty: (() -> Void)? + + override public func editingRect(forBounds bounds: CGRect) -> CGRect { + return bounds.offsetBy(dx: 0.0, dy: -UIScreenPixel) + } + + override public func placeholderRect(forBounds bounds: CGRect) -> CGRect { + return self.editingRect(forBounds: bounds) + } + + override public func deleteBackward() { + if self.text == nil || self.text!.isEmpty { + self.didDeleteBackwardWhileEmpty?() + } + super.deleteBackward() + } +} + +public class TextFieldNode: ASDisplayNode { + public var textField: TextFieldNodeView { + return self.view as! TextFieldNodeView + } + + override public init() { + super.init(viewBlock: { + return TextFieldNodeView() + }, didLoad: nil) + } +} diff --git a/Display/UIBarButtonItem+Proxy.m b/Display/UIBarButtonItem+Proxy.m index e9d486bc7a..945c12411a 100644 --- a/Display/UIBarButtonItem+Proxy.m +++ b/Display/UIBarButtonItem+Proxy.m @@ -20,7 +20,7 @@ static const void *customDisplayNodeKey = &customDisplayNodeKey; } - (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode { - self = [super init]; + self = [self init]; if (self != nil) { [self setAssociatedObject:customDisplayNode forKey:customDisplayNodeKey]; } diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 8f3b22d0ab..a32d4b0199 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -2,7 +2,7 @@ typedef void (^UINavigationItemSetTitleListener)(NSString *); typedef void (^UINavigationItemSetTitleViewListener)(UIView *); -typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL); +typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, UIBarButtonItem *, BOOL); typedef void (^UITabBarItemSetBadgeListener)(NSString *); @interface UINavigationItem (Proxy) diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index b77620575f..daa72e99c5 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -62,6 +62,8 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; - (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated { + UIBarButtonItem *previousItem = self.leftBarButtonItem; + [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; @@ -69,7 +71,7 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; [targetItem setLeftBarButtonItem:leftBarButtonItem animated:animated]; } else { [(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { - listener(leftBarButtonItem, animated); + listener(previousItem, leftBarButtonItem, animated); }]; } } @@ -80,6 +82,8 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; - (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem animated:(BOOL)animated { + UIBarButtonItem *previousItem = self.rightBarButtonItem; + [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; @@ -87,7 +91,7 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; [targetItem setRightBarButtonItem:rightBarButtonItem animated:animated]; } else { [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { - listener(rightBarButtonItem, animated); + listener(previousItem, rightBarButtonItem, animated); }]; } } diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 12c7dda690..1cbc548127 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -6,7 +6,7 @@ - (BOOL)ignoreAppearanceMethodInvocations; - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; - (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; -- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:(UIViewController *)rootController; +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:( UIViewController * _Nullable )rootController; @end diff --git a/Display/ViewController.swift b/Display/ViewController.swift index f5f7b51c25..497e2141bf 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -16,6 +16,19 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { } } +public enum ViewControllerPresentationAnimation { + case none + case modalSheet +} + +open class ViewControllerPresentationArguments { + public let presentationAnimation: ViewControllerPresentationAnimation + + public init(presentationAnimation: ViewControllerPresentationAnimation) { + self.presentationAnimation = presentationAnimation + } +} + @objc open class ViewController: UIViewController, ContainableController { private var containerLayout = ContainerViewLayout() private let presentationContext: PresentationContext From a9c96e7712e43e52461010ac3d95d920d75c35c5 Mon Sep 17 00:00:00 2001 From: Peter Date: Sun, 19 Feb 2017 23:21:31 +0300 Subject: [PATCH 035/245] no message --- Display/AlertControllerNode.swift | 2 +- Display/Font.swift | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 3655342629..02c9719d36 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -59,7 +59,7 @@ final class AlertControllerNode: ASDisplayNode { transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) var insets = layout.insets(options: [.statusBar, .input]) - let maxWidth = min(340.0, layout.size.width - 70.0) + let maxWidth = min(240.0, layout.size.width - 70.0) insets.left = floor((layout.size.width - maxWidth) / 2.0) insets.right = floor((layout.size.width - maxWidth) / 2.0) let contentAvailableFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: layout.size.width - insets.right, height: layout.size.height - insets.top - insets.bottom)) diff --git a/Display/Font.swift b/Display/Font.swift index 9093a22637..25ab867276 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -36,9 +36,11 @@ public struct Font { } public extension NSAttributedString { - convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black, paragraphAlignment: NSTextAlignment? = nil) { + convenience init(string: String, font: UIFont? = nil, textColor: UIColor = UIColor.black, paragraphAlignment: NSTextAlignment? = nil) { var attributes: [String: AnyObject] = [:] - attributes[NSFontAttributeName] = font + if let font = font { + attributes[NSFontAttributeName] = font + } attributes[NSForegroundColorAttributeName] = textColor if let paragraphAlignment = paragraphAlignment { let paragraphStyle = NSMutableParagraphStyle() From 6b542e9029e74edbe8518d3336a85a3f2fd42e94 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 6 Mar 2017 02:10:29 +0300 Subject: [PATCH 036/245] no message --- Display/CATracingLayer.m | 2 -- Display/ContextMenuController.swift | 4 +++ Display/ListView.swift | 2 +- Display/ListViewIntermediateState.swift | 3 +- Display/ListViewItemNode.swift | 6 ++++ Display/NavigationController.swift | 8 +++++ Display/SwitchNode.swift | 39 +++++++++++++++++++++++++ Display/TabBarController.swift | 12 ++++++++ Display/UIKitUtils.m | 12 ++++++-- Display/UIKitUtils.swift | 4 +++ 10 files changed, 85 insertions(+), 7 deletions(-) diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index bd8f209477..554655d57f 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -33,8 +33,6 @@ static void *CATracingLayerTraceablInfoKey = &CATracingLayerTraceablInfoKey; } static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth) { - NSMutableArray *result = nil; - bool hadTraceableSublayers = false; for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { if ([sublayer traceableInfo] != nil) { diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 3572374577..14d2eb5d55 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -18,6 +18,8 @@ public final class ContextMenuController: ViewController { private var layout: ContainerViewLayout? + public var dismissed: (() -> Void)? + public init(actions: [ContextMenuAction]) { self.actions = actions @@ -30,6 +32,7 @@ public final class ContextMenuController: ViewController { open override func loadDisplayNode() { self.displayNode = ContextMenuNode(actions: self.actions, dismiss: { [weak self] in + self?.dismissed?() self?.contextMenuNode.animateOut { [weak self] in self?.presentingViewController?.dismiss(animated: false) } @@ -48,6 +51,7 @@ public final class ContextMenuController: ViewController { super.containerLayoutUpdated(layout, transition: transition) if self.layout != nil && self.layout! != layout { + self.dismissed?() self.contextMenuNode.animateOut { [weak self] in self?.presentingViewController?.dismiss(animated: false) } diff --git a/Display/ListView.swift b/Display/ListView.swift index 8a6b34e8b7..cd32b3223f 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1158,7 +1158,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let beginReplay = { [weak self] in if let strongSelf = self { strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { - if options.contains(.PreferSynchronousResourceLoading) { + if options.contains(.PreferSynchronousDrawing) { let startTime = CACurrentMediaTime() self?.recursivelyEnsureDisplaySynchronously(true) let deltaTime = CACurrentMediaTime() - startTime diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index bac1aa5eb7..99e60d79d9 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -122,7 +122,8 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) public static let AnimateTopItemPosition = ListViewDeleteAndInsertOptions(rawValue: 32) - public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 64) + public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64) + public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128) } public struct ListViewUpdateSizeAndInsets { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 72e0c8c6a2..e7994277c4 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -234,6 +234,12 @@ open class ListViewItemNode: ASDisplayNode { } } + public var contentBounds: CGRect { + let bounds = self.bounds + let effectiveInsets = self.insets + return CGRect(origin: CGPoint(x: 0.0, y: bounds.origin.y + effectiveInsets.top), size: CGSize(width: bounds.size.width, height: bounds.size.height - effectiveInsets.top - effectiveInsets.bottom)) + } + open override var position: CGPoint { get { return self._position diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 3f8dd36518..c90f621782 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -253,6 +253,14 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } })) } + + public func popToRoot(animated: Bool) { + var controllers = self.viewControllers + while controllers.count > 1 { + controllers.removeLast() + } + self.setViewControllers(controllers, animated: animated) + } open override func popViewController(animated: Bool) -> UIViewController? { var controller: UIViewController? diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index b3515a33eb..c1048ff36c 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -2,9 +2,48 @@ import Foundation import AsyncDisplayKit open class SwitchNode: ASDisplayNode { + public var valueUpdated: ((Bool) -> Void)? + + private var _isOn: Bool = false + public var isOn: Bool { + get { + return self._isOn + } set(value) { + if (value != self._isOn) { + self._isOn = value + if self.isNodeLoaded { + (self.view as! UISwitch).setOn(value, animated: false) + } + } + } + } + override public init() { super.init(viewBlock: { return UISwitch() }, didLoad: nil) } + + override open func didLoad() { + super.didLoad() + + (self.view as! UISwitch).setOn(self._isOn, animated: false) + + (self.view as! UISwitch).addTarget(self, action: #selector(switchValueChanged(_:)), for: .valueChanged) + } + + public func setOn(_ value: Bool, animated: Bool) { + self._isOn = value + if self.isNodeLoaded { + (self.view as! UISwitch).setOn(value, animated: animated) + } + } + + override open func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: 51.0, height: 31.0) + } + + @objc func switchValueChanged(_ view: UISwitch) { + self.valueUpdated?(view.isOn) + } } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index b24a87c480..e2587b3198 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -118,4 +118,16 @@ open class TabBarController: ViewController { currentController.containerLayoutUpdated(layout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: transition) } } + + override open func viewDidAppear(_ animated: Bool) { + if let currentController = self.currentController { + currentController.viewDidAppear(animated) + } + } + + override open func viewDidDisappear(_ animated: Bool) { + if let currentController = self.currentController { + currentController.viewDidDisappear(animated) + } + } } diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index acae5e4427..484ac2549f 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -36,8 +36,7 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { springAnimation.mass = 3.0f; springAnimation.stiffness = 1000.0f; springAnimation.damping = 500.0f; - springAnimation.initialVelocity = 0.0f; - springAnimation.duration = 0.5;//springAnimation.settlingDuration; + springAnimation.duration = 0.5; springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; } @@ -47,7 +46,14 @@ CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPat springAnimation.mass = 5.0f; springAnimation.stiffness = 900.0f; springAnimation.damping = 88.0f; - springAnimation.initialVelocity = initialVelocity; + static bool canSetInitialVelocity = true; + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^{ + canSetInitialVelocity = [springAnimation respondsToSelector:@selector(setInitialVelocity:)]; + }); + if (canSetInitialVelocity) { + springAnimation.initialVelocity = initialVelocity; + } springAnimation.duration = springAnimation.settlingDuration; springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 3ae3835f18..f49fafaef0 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -119,6 +119,10 @@ public extension UIImage { private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { let view = UIView() view.layer.contents = layer.contents + view.layer.contentsRect = layer.contentsRect + view.layer.contentsScale = layer.contentsScale + view.layer.contentsCenter = layer.contentsCenter + view.layer.contentsGravity = layer.contentsGravity if let sublayers = layer.sublayers { for sublayer in sublayers { let subtree = makeSubtreeSnapshot(layer: sublayer) From daa642eacb4ecaf5c44c8e01e2301cbc2633bae5 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 21 Mar 2017 19:58:45 +0300 Subject: [PATCH 037/245] no message --- Display.xcodeproj/project.pbxproj | 16 ++ Display/ActionSheetItemGroupNode.swift | 2 +- Display/AlertController.swift | 2 +- Display/CAAnimationUtils.swift | 13 +- Display/CATracingLayer.h | 19 ++- Display/CATracingLayer.m | 83 ++++++++-- Display/ContainerViewLayout.swift | 4 + Display/ContextMenuNode.swift | 2 +- Display/Font.swift | 2 +- Display/GenerateImage.swift | 21 ++- Display/GridNode.swift | 70 +++++++-- Display/KeyboardManager.swift | 147 ++++++++++++++++++ Display/LegacyPresentedController.swift | 2 +- Display/ListView.swift | 52 ++++--- Display/ListViewAnimation.swift | 4 +- Display/ListViewIntermediateState.swift | 40 ++--- Display/ListViewItemNode.swift | 2 +- .../MinimizeKeyboardGestureRecognizer.swift | 20 +++ Display/NavigationController.swift | 29 +++- Display/NavigationTransitionCoordinator.swift | 2 +- Display/StatusBar.swift | 2 +- Display/StatusBarHost.swift | 1 + Display/StatusBarManager.swift | 2 +- Display/Theme.swift | 6 +- Display/UIKitUtils.m | 2 +- Display/UIViewController+Navigation.m | 2 +- Display/ViewController.swift | 6 + Display/Window.swift | 77 +++++++-- 28 files changed, 527 insertions(+), 103 deletions(-) create mode 100644 Display/KeyboardManager.swift create mode 100644 Display/MinimizeKeyboardGestureRecognizer.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 8ff689f0b6..7007bb8f87 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -23,6 +23,7 @@ D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */; }; @@ -116,6 +117,7 @@ D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; + D0FF9B301E7196F6000C66DB /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -145,6 +147,7 @@ D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimizeKeyboardGestureRecognizer.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuController.swift; sourceTree = ""; }; @@ -242,6 +245,7 @@ D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; + D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -397,6 +401,7 @@ D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, + D0FF9B2E1E7196E2000C66DB /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, @@ -584,6 +589,15 @@ name = "Image Cache"; sourceTree = ""; }; + D0FF9B2E1E7196E2000C66DB /* Keyboard */ = { + isa = PBXGroup; + children = ( + D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */, + D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */, + ); + name = Keyboard; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -770,6 +784,7 @@ D0DA44521E4DCC11005FDCA7 /* TextAlertController.swift in Sources */, D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */, D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, + D0FF9B301E7196F6000C66DB /* KeyboardManager.swift in Sources */, D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */, D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, @@ -784,6 +799,7 @@ D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, + D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift index 9ac3c0230c..685e9bafa5 100644 --- a/Display/ActionSheetItemGroupNode.swift +++ b/Display/ActionSheetItemGroupNode.swift @@ -145,7 +145,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { if !self.scrollView.contentSize.equalTo(scrollViewContentSize) { self.scrollView.contentSize = scrollViewContentSize } - var scrollViewContentInsets = UIEdgeInsets(top: max(0.0, self.calculatedSize.height - leadingVisibleNodeSize), left: 0.0, bottom: 0.0, right: 0.0) + let scrollViewContentInsets = UIEdgeInsets(top: max(0.0, self.calculatedSize.height - leadingVisibleNodeSize), left: 0.0, bottom: 0.0, right: 0.0) if !UIEdgeInsetsEqualToEdgeInsets(self.scrollView.contentInset, scrollViewContentInsets) { self.scrollView.contentInset = scrollViewContentInsets diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 2e2ff29541..5e37e021c6 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -45,7 +45,7 @@ open class AlertController: ViewController { self.controllerNode.containerLayoutUpdated(layout, transition: transition) } - public func dismiss() { + override open func dismiss() { self.presentingViewController?.dismiss(animated: false, completion: nil) } diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 93c89b6d99..0a54ce4bd9 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -207,8 +207,8 @@ public extension CALayer { self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut) { - self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, additive: true) + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) { @@ -251,4 +251,13 @@ public extension CALayer { partialCompletion() }) } + + public func cancelAnimationsRecursive(key: String) { + self.removeAnimation(forKey: key) + if let sublayers = self.sublayers { + for layer in sublayers { + layer.cancelAnimationsRecursive(key: key) + } + } + } } diff --git a/Display/CATracingLayer.h b/Display/CATracingLayer.h index f07f9b9aa2..0ca59876d3 100644 --- a/Display/CATracingLayer.h +++ b/Display/CATracingLayer.h @@ -4,6 +4,16 @@ @end +@interface CATracingLayerInfo : NSObject + +@property (nonatomic, readonly) bool shouldBeAdjustedToInverseTransform; +@property (nonatomic, weak, readonly) id _Nullable userData; +@property (nonatomic, readonly) int32_t tracingTag; + +- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag; + +@end + @interface UITracingLayerView : UIView - (void)scheduleWithLayout:(void (^_Nonnull)())block; @@ -12,15 +22,18 @@ @interface CALayer (Tracing) -- (id _Nullable)traceableInfo; -- (void)setTraceableInfo:(id _Nullable)info; +- (CATracingLayerInfo * _Nullable)traceableInfo; +- (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info; - (bool)hasPositionOrOpacityAnimations; +- (bool)hasPositionAnimations; - (void)setInvalidateTracingSublayers:(void (^_Nullable)())block; -- (NSArray *> * _Nonnull)traceableLayerSurfaces; +- (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag; - (void)adjustTraceableLayerTransforms:(CGSize)offset; +- (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget; + - (void)invalidateUpTheTree; @end diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 554655d57f..6bbc6fbd6e 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -4,7 +4,8 @@ static void *CATracingLayerInvalidatedKey = &CATracingLayerInvalidatedKey; static void *CATracingLayerIsInvalidatedBlock = &CATracingLayerIsInvalidatedBlock; -static void *CATracingLayerTraceablInfoKey = &CATracingLayerTraceablInfoKey; +static void *CATracingLayerTraceableInfoKey = &CATracingLayerTraceableInfoKey; +static void *CATracingLayerPositionAnimationMirrorTarget = &CATracingLayerPositionAnimationMirrorTarget; @implementation CALayer (Tracing) @@ -17,25 +18,30 @@ static void *CATracingLayerTraceablInfoKey = &CATracingLayerTraceablInfoKey; } - (bool)isTraceable { - return [self associatedObjectForKey:CATracingLayerTraceablInfoKey] != nil || [self isKindOfClass:[CATracingLayer class]]; + return [self associatedObjectForKey:CATracingLayerTraceableInfoKey] != nil || [self isKindOfClass:[CATracingLayer class]]; } -- (id _Nullable)traceableInfo { - return [self associatedObjectForKey:CATracingLayerTraceablInfoKey]; +- (CATracingLayerInfo * _Nullable)traceableInfo { + return [self associatedObjectForKey:CATracingLayerTraceableInfoKey]; } -- (void)setTraceableInfo:(id _Nullable)info { - [self setAssociatedObject:info forKey:CATracingLayerTraceablInfoKey]; +- (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info { + [self setAssociatedObject:info forKey:CATracingLayerTraceableInfoKey]; } - (bool)hasPositionOrOpacityAnimations { return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil || [self animationForKey:@"sublayerTransform"] != nil || [self animationForKey:@"opacity"] != nil; } -static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth) { +- (bool)hasPositionAnimations { + return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil; +} + +static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth) { bool hadTraceableSublayers = false; for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { - if ([sublayer traceableInfo] != nil) { + CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; + if (sublayerTraceableInfo != nil && sublayerTraceableInfo.tracingTag == tracingTag) { NSMutableArray *array = layersByDepth[@(depth)]; if (array == nil) { array = [[NSMutableArray alloc] init]; @@ -49,16 +55,16 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic if (!hadTraceableSublayers) { for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { if ([sublayer isKindOfClass:[CATracingLayer class]]) { - traceLayerSurfaces(depth + 1, sublayer, layersByDepth); + traceLayerSurfaces(tracingTag, depth + 1, sublayer, layersByDepth); } } } } -- (NSArray *> *)traceableLayerSurfaces { +- (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag { NSMutableDictionary *> *layersByDepth = [[NSMutableDictionary alloc] init]; - traceLayerSurfaces(0, self, layersByDepth); + traceLayerSurfaces(tracingTag, 0, self, layersByDepth); NSMutableArray *> *result = [[NSMutableArray alloc] init]; @@ -73,7 +79,8 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic CGRect frame = self.frame; CGSize sublayerOffset = CGSizeMake(frame.origin.x + offset.width, frame.origin.y + offset.height); for (CALayer *sublayer in self.sublayers) { - if ([sublayer traceableInfo] != nil) { + CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; + if (sublayerTraceableInfo != nil && sublayerTraceableInfo.shouldBeAdjustedToInverseTransform) { sublayer.sublayerTransform = CATransform3DMakeTranslation(-sublayerOffset.width, -sublayerOffset.height, 0.0f); } else if ([sublayer isKindOfClass:[CATracingLayer class]]) { [(CATracingLayer *)sublayer adjustTraceableLayerTransforms:sublayerOffset]; @@ -81,6 +88,14 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic } } +- (CALayer * _Nullable)animationMirrorTarget { + return [self associatedObjectForKey:CATracingLayerPositionAnimationMirrorTarget]; +} + +- (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget { + [self setAssociatedObject:animationMirrorTarget forKey:CATracingLayerPositionAnimationMirrorTarget associationPolicy:NSObjectAssociationPolicyRetain]; +} + - (void)invalidateUpTheTree { CALayer *superlayer = self; while (true) { @@ -235,9 +250,21 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic [super addAnimation:anim forKey:key]; + CABasicAnimation *positionAnimCopy = [animCopy copy]; + positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(-to.x + from.x, 0.0, 0.0f)]; + positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + positionAnimCopy.additive = true; + positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + [self invalidateUpTheTree]; [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; + [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; } else if ([key isEqualToString:@"opacity"]) { __weak CATracingLayer *weakSelf = self; anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ @@ -258,11 +285,25 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic } } +- (void)mirrorPositionAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { + if ([animation isKindOfClass:[CABasicAnimation class]]) { + if ([((CABasicAnimation *)animation).keyPath isEqualToString:@"sublayerTransform"]) { + CALayer *positionAnimationMirrorTarget = [self animationMirrorTarget]; + if (positionAnimationMirrorTarget != nil) { + [positionAnimationMirrorTarget addAnimation:[animation copy] forKey:key]; + } + } + } +} + - (void)mirrorAnimationDownTheTree:(CAAnimation *)animation key:(NSString *)key { for (CALayer *sublayer in self.sublayers) { - if ([sublayer traceableInfo] != nil) { + CATracingLayerInfo *traceableInfo = [sublayer traceableInfo]; + if (traceableInfo != nil && traceableInfo.shouldBeAdjustedToInverseTransform) { [sublayer addAnimation:[animation copy] forKey:key]; - } else if ([sublayer isKindOfClass:[CATracingLayer class]]) { + } + + if ([sublayer isKindOfClass:[CATracingLayer class]]) { [(CATracingLayer *)sublayer mirrorAnimationDownTheTree:animation key:key]; } } @@ -270,6 +311,20 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic @end +@implementation CATracingLayerInfo + +- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag { + self = [super init]; + if (self != nil) { + _shouldBeAdjustedToInverseTransform = shouldBeAdjustedToInverseTransform; + _userData = userData; + _tracingTag = tracingTag; + } + return self; +} + +@end + @interface UITracingLayerView () { void (^_scheduledWithLayout)(); } diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 74a06eda9b..134a038ed8 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -48,6 +48,10 @@ public struct ContainerViewLayout: Equatable { public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout { return ContainerViewLayout(size: self.size, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) } + + public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout { + return ContainerViewLayout(size: self.size, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight) + } } public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index 8f8aabcbad..06e8df983a 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -27,7 +27,7 @@ final class ContextMenuNode: ASDisplayNode { super.init() self.addSubnode(self.containerNode) - let dismissNode = { [weak self] in + let dismissNode = { dismiss() } for actionNode in self.actionNodes { diff --git a/Display/Font.swift b/Display/Font.swift index 25ab867276..d118f534af 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -16,7 +16,7 @@ public struct Font { public static func bold(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: size, weight: UIFontWeightBold) + return UIFont.boldSystemFont(ofSize: size) } else { return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, size, nil) } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 6119a70611..2e0a7e3cb1 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -114,6 +114,25 @@ public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, backgr }) } +public func generateCircleImage(diameter: CGFloat, lineWidth: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { + return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: size)) + } + + if let color = color { + context.setStrokeColor(color.cgColor) + } else { + context.setStrokeColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + } + context.setLineWidth(lineWidth) + context.strokeEllipse(in: CGRect(origin: CGPoint(x: lineWidth / 2.0, y: lineWidth / 2.0), size: CGSize(width: size.width - lineWidth, height: size.height - lineWidth))) + }) +} + public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { let intRadius = Int(radius) let cap = intRadius == 1 ? 2 : intRadius @@ -283,7 +302,7 @@ public class DrawingContext { } public func blt(_ other: DrawingContext, at: CGPoint, mode: DrawingContextBltMode = .Alpha) { - if abs(other.scale - self.scale) < CGFloat(FLT_EPSILON) { + if abs(other.scale - self.scale) < CGFloat.ulpOfOne { let srcX = 0 var srcY = 0 let dstX = Int(at.x * self.scale) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 057b1454fb..0d8adc8e6d 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -35,13 +35,15 @@ public struct GridNodeScrollToItem { public let transition: ContainedViewLayoutTransition public let directionHint: GridNodePreviousItemsTransitionDirectionHint public let adjustForSection: Bool + public let adjustForTopInset: Bool - public init(index: Int, position: GridNodeScrollToItemPosition, transition: ContainedViewLayoutTransition, directionHint: GridNodePreviousItemsTransitionDirectionHint, adjustForSection: Bool) { + public init(index: Int, position: GridNodeScrollToItemPosition, transition: ContainedViewLayoutTransition, directionHint: GridNodePreviousItemsTransitionDirectionHint, adjustForSection: Bool, adjustForTopInset: Bool = false) { self.index = index self.position = position self.transition = transition self.directionHint = directionHint self.adjustForSection = adjustForSection + self.adjustForTopInset = adjustForTopInset } } @@ -156,6 +158,12 @@ private struct GridNodePresentationLayoutTransition { let transition: ContainedViewLayoutTransition } +public struct GridNodeCurrentPresentationLayout { + public let layout: GridNodeLayout + public let contentOffset: CGPoint + public let contentSize: CGSize +} + private final class GridNodeItemLayout { let contentSize: CGSize let items: [GridNodePresentationItem] @@ -225,6 +233,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { private var applyingContentOffset = false public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? + public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? public override init() { super.init() @@ -241,6 +250,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func transaction(_ transaction: GridNodeTransaction, completion: (GridNodeDisplayedItemRange) -> Void) { if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout && (transaction.updateFirstIndexInSectionOffset == nil || transaction.updateFirstIndexInSectionOffset == self.firstIndexInSectionOffset)) { + if let presentationLayoutUpdated = self.presentationLayoutUpdated { + presentationLayoutUpdated(GridNodeCurrentPresentationLayout(layout: self.gridLayout, contentOffset: self.scrollView.contentOffset, contentSize: self.itemLayout.contentSize), .immediate) + } completion(self.displayedItemRange()) return } @@ -249,7 +261,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.firstIndexInSectionOffset = updateFirstIndexInSectionOffset } + var layoutTransactionOffset: CGFloat = 0.0 if let updateLayout = transaction.updateLayout { + layoutTransactionOffset += updateLayout.layout.insets.top - self.gridLayout.insets.top self.gridLayout = updateLayout.layout } @@ -316,7 +330,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.itemNodes = remappedInsertionItemNodes } - var previousLayoutWasEmpty = self.itemLayout.items.isEmpty + let previousLayoutWasEmpty = self.itemLayout.items.isEmpty self.itemLayout = self.generateItemLayout() @@ -324,19 +338,19 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let scrollToItem = transaction.scrollToItem { generatedScrollToItem = scrollToItem } else if previousLayoutWasEmpty { - generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .immediate, directionHint: .up, adjustForSection: true) + generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .immediate, directionHint: .up, adjustForSection: true, adjustForTopInset: true) } else { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, scrollToItem: generatedScrollToItem), removedNodes: removedNodes) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition) completion(self.displayedItemRange()) } public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(), removedNodes: []) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil) } } @@ -427,7 +441,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func generatePresentationLayoutTransition(stationaryItems: GridNodeStationaryItems = .none, scrollToItem: GridNodeScrollToItem? = nil) -> GridNodePresentationLayoutTransition { + private func generatePresentationLayoutTransition(stationaryItems: GridNodeStationaryItems = .none, layoutTransactionOffset: CGFloat, scrollToItem: GridNodeScrollToItem? = nil) -> GridNodePresentationLayoutTransition { if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.itemLayout.items.isEmpty { var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up var transition: ContainedViewLayoutTransition = .immediate @@ -438,7 +452,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let itemFrame = self.itemLayout.items[scrollToItem.index] var additionalOffset: CGFloat = 0.0 - if scrollToItem.adjustForSection { + if scrollToItem.adjustForTopInset { + additionalOffset = -gridLayout.insets.top + } else if scrollToItem.adjustForSection { var adjustForSection: GridSection? if scrollToItem.index == 0 { if let itemSection = self.items[scrollToItem.index].section { @@ -485,7 +501,18 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { contentOffset = CGPoint(x: 0.0, y: verticalOffset) } else { - contentOffset = self.scrollView.contentOffset + if !layoutTransactionOffset.isZero { + var verticalOffset = self.scrollView.contentOffset.y - layoutTransactionOffset + if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { + verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height + } + if verticalOffset < -self.gridLayout.insets.top { + verticalOffset = -self.gridLayout.insets.top + } + contentOffset = CGPoint(x: 0.0, y: verticalOffset) + } else { + contentOffset = self.scrollView.contentOffset + } } case let .indices(stationaryItemIndices): var selectedContentOffset: CGPoint? @@ -548,7 +575,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode]) { + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?) { var previousItemFrames: ([WrappedGridItemNode: CGRect])? switch presentationLayoutTransition.transition { case .animated: @@ -783,6 +810,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { visibleItemsUpdated(GridNodeVisibleItems(top: nil, bottom: nil, topVisible: nil, bottomVisible: nil, topSectionVisible: nil, count: self.items.count)) } } + + if let presentationLayoutUpdated = self.presentationLayoutUpdated { + presentationLayoutUpdated(GridNodeCurrentPresentationLayout(layout: presentationLayoutTransition.layout.layout, contentOffset: presentationLayoutTransition.layout.contentOffset, contentSize: presentationLayoutTransition.layout.contentSize), updateLayoutTransition ?? presentationLayoutTransition.transition) + } } private func addItemNode(index: Int, itemNode: GridItemNode) { @@ -817,9 +848,28 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - public func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void) { + public func forEachItemNode(_ f: (ASDisplayNode) -> Void) { for (_, node) in self.itemNodes { f(node) } } + + public func forEachRow(_ f: ([ASDisplayNode]) -> Void) { + var row: [ASDisplayNode] = [] + var previousMinY: CGFloat? + for index in self.itemNodes.keys.sorted() { + let itemNode = self.itemNodes[index]! + if let previousMinY = previousMinY, !previousMinY.isEqual(to: itemNode.frame.minY) { + if !row.isEmpty { + f(row) + row.removeAll() + } + } + previousMinY = itemNode.frame.minY + row.append(itemNode) + } + if !row.isEmpty { + f(row) + } + } } diff --git a/Display/KeyboardManager.swift b/Display/KeyboardManager.swift new file mode 100644 index 0000000000..6abee24933 --- /dev/null +++ b/Display/KeyboardManager.swift @@ -0,0 +1,147 @@ +import Foundation +import AsyncDisplayKit + +struct KeyboardSurface { + let host: UIView +} + +private func hasFirstResponder(_ view: UIView) -> Bool { + if view.isFirstResponder { + return true + } else { + for subview in view.subviews { + if hasFirstResponder(subview) { + return true + } + } + return false + } +} + +private func findKeyboardBackdrop(_ view: UIView) -> UIView? { + if NSStringFromClass(type(of: view)) == "UIKBInputBackdropView" { + return view + } + for subview in view.subviews { + if let result = findKeyboardBackdrop(subview) { + return result + } + } + return nil +} + +class KeyboardManager { + private let host: StatusBarHost + + private weak var previousPositionAnimationMirrorSource: CATracingLayer? + private weak var previousFirstResponderView: UIView? + + var gestureRecognizer: MinimizeKeyboardGestureRecognizer? = nil + + var minimized: Bool = false + var minimizedUpdated: (() -> Void)? + + var updatedMinimizedBackdrop = false + + var surfaces: [KeyboardSurface] = [] { + didSet { + self.updateSurfaces(oldValue) + } + } + + init(host: StatusBarHost) { + self.host = host + } + + private func updateSurfaces(_ previousSurfaces: [KeyboardSurface]) { + guard let keyboardWindow = self.host.keyboardWindow else { + return + } + + if let keyboardView = self.host.keyboardView { + if self.minimized { + let normalizedHeight = floor(0.85 * keyboardView.frame.size.height) + let factor = normalizedHeight / keyboardView.frame.size.height + let scaleTransform = CATransform3DMakeScale(factor, factor, 1.0) + let horizontalOffset = (keyboardView.frame.size.width - keyboardView.frame.size.width * factor) / 2.0 + let verticalOffset = (keyboardView.frame.size.height - keyboardView.frame.size.height * factor) / 2.0 + let translate = CATransform3DMakeTranslation(horizontalOffset, verticalOffset, 0.0) + keyboardView.layer.sublayerTransform = CATransform3DConcat(scaleTransform, translate) + + self.updatedMinimizedBackdrop = false + + if let backdrop = findKeyboardBackdrop(keyboardView) { + let scale = CATransform3DMakeScale(1.0 / factor, 1.0, 0.0) + let translate = CATransform3DMakeTranslation(-horizontalOffset * (1.0 / factor), 0.0, 0.0) + backdrop.layer.sublayerTransform = CATransform3DConcat(scale, translate) + } + } else { + keyboardView.layer.sublayerTransform = CATransform3DIdentity + if !self.updatedMinimizedBackdrop { + if let backdrop = findKeyboardBackdrop(keyboardView) { + backdrop.layer.sublayerTransform = CATransform3DIdentity + } + + self.updatedMinimizedBackdrop = true + } + } + } + + if let gestureRecognizer = self.gestureRecognizer { + if keyboardWindow.gestureRecognizers == nil || !keyboardWindow.gestureRecognizers!.contains(gestureRecognizer) { + keyboardWindow.addGestureRecognizer(gestureRecognizer) + } + } else { + let gestureRecognizer = MinimizeKeyboardGestureRecognizer(target: self, action: #selector(self.minimizeGesture(_:))) + self.gestureRecognizer = gestureRecognizer + keyboardWindow.addGestureRecognizer(gestureRecognizer) + } + + var firstResponderView: UIView? + for surface in surfaces { + if hasFirstResponder(surface.host) { + firstResponderView = surface.host + break + } + } + + if let firstResponderView = firstResponderView { + let containerOrigin = firstResponderView.convert(CGPoint(), to: nil) + let horizontalTranslation = CATransform3DMakeTranslation(containerOrigin.x, 0.0, 0.0) + keyboardWindow.layer.sublayerTransform = horizontalTranslation + if let tracingLayer = firstResponderView.layer as? CATracingLayer { + if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer { + previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) + } + tracingLayer.setPositionAnimationMirrorTarget(keyboardWindow.layer) + self.previousPositionAnimationMirrorSource = tracingLayer + } else if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource { + previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) + self.previousPositionAnimationMirrorSource = nil + } + } else { + keyboardWindow.layer.sublayerTransform = CATransform3DIdentity + if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource { + previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) + self.previousPositionAnimationMirrorSource = nil + } + if let previousFirstResponderView = previousFirstResponderView { + if previousFirstResponderView.window == nil { + keyboardWindow.isHidden = true + keyboardWindow.layer.cancelAnimationsRecursive(key: "position") + keyboardWindow.layer.cancelAnimationsRecursive(key: "bounds") + keyboardWindow.isHidden = false + } + } + } + + self.previousFirstResponderView = firstResponderView + } + + @objc func minimizeGesture(_ recognizer: UISwipeGestureRecognizer) { + if case .ended = recognizer.state { + self.minimized = !self.minimized + self.minimizedUpdated?() + } + } +} diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index f2c4b1b798..eec8e3ada7 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -126,7 +126,7 @@ open class LegacyPresentedController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } - public func dismiss() { + override open func dismiss() { switch self.presentation { case .modal: self.controllerNode.animateModalOut { [weak self] in diff --git a/Display/ListView.swift b/Display/ListView.swift index cd32b3223f..2b48c1b71b 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -118,6 +118,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 public final var limitHitTestToNodes: Bool = false public final var keepBottomItemOverscrollBackground: Bool = false + public final var snapToBottomInsetUntilFirstInteraction: Bool = false private var bottomItemOverscrollBackground: ASDisplayNode? @@ -359,6 +360,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.resetHeaderItemsFlashTimer(start: false) self.updateHeaderItemsFlashing(animated: true) + if self.snapToBottomInsetUntilFirstInteraction { + self.snapToBottomInsetUntilFirstInteraction = false + } + /*if usePerformanceTracker { self.performanceTracker.start() }*/ @@ -640,7 +645,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - if abs(offset) > CGFloat(FLT_EPSILON) { + if abs(offset) > CGFloat.ulpOfOne { for itemNode in self.itemNodes { var frame = itemNode.frame frame.origin.y += offset @@ -914,7 +919,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let widthUpdated: Bool if let updateSizeAndInsets = updateSizeAndInsets { - widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat(FLT_EPSILON) + widthUpdated = abs(state.visibleSize.width - updateSizeAndInsets.size.width) > CGFloat.ulpOfOne state.visibleSize = updateSizeAndInsets.size state.insets = updateSizeAndInsets.insets @@ -1142,9 +1147,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if options.contains(.PreferSynchronousResourceLoading) { var currentReadySignals: [Signal] = [] for i in 0 ..< updatedOperations.count { - if case let .InsertNode(index, offsetDirection, node, layout, apply) = updatedOperations[i] { + if case let .InsertNode(index, offsetDirection, nodeAnimated, node, layout, apply) = updatedOperations[i] { let (ready, commitApply) = apply() - updatedOperations[i] = .InsertNode(index: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: { + updatedOperations[i] = .InsertNode(index: index, offsetDirection: offsetDirection, animated: nodeAnimated, node: node, layout: layout, apply: { return (nil, commitApply) }) if let ready = ready { @@ -1409,7 +1414,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let nextNode = self.itemNodes[nodeIndex + 1] if nextNode.index == nil { let nextHeight = nextNode.apparentHeight - if abs(nextHeight - previousApparentHeight) < CGFloat(FLT_EPSILON) { + if abs(nextHeight - previousApparentHeight) < CGFloat.ulpOfOne { if let animation = nextNode.animationForKey("apparentHeight") { node.apparentHeight = previousApparentHeight @@ -1424,7 +1429,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel takenAnimation = true - if abs(layout.size.height - previousApparentHeight) > CGFloat(FLT_EPSILON) { + if abs(layout.size.height - previousApparentHeight) > CGFloat.ulpOfOne { node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in if let node = node { node.animateFrameTransition(progress, currentValue) @@ -1490,7 +1495,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - if node.apparentHeight > CGFloat(FLT_EPSILON) { + if node.apparentHeight > CGFloat.ulpOfOne { switch offsetDirection { case .Up: var i = nodeIndex - 1 @@ -1587,7 +1592,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var takenPreviousNodes = Set() for operation in operations { - if case let .InsertNode(_, _, node, _, _) = operation { + if case let .InsertNode(_, _, _, node, _, _) = operation { takenPreviousNodes.insert(node) } } @@ -1596,7 +1601,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for operation in operations { switch operation { - case let .InsertNode(index, offsetDirection, node, layout, apply): + case let .InsertNode(index, offsetDirection, nodeAnimated, node, layout, apply): var previousFrame: CGRect? for (previousNode, frame) in previousApparentFrames { if previousNode === node { @@ -1613,8 +1618,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel updatedPreviousFrame = nil } - self.insertNodeAtIndex(animated: animated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) - if let updatedPreviousFrame = updatedPreviousFrame { + self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + if let _ = updatedPreviousFrame { if let lowestHeaderNode = lowestHeaderNode { self.insertSubnode(node, belowSubnode: lowestHeaderNode) } else { @@ -1706,7 +1711,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.addInsetsAnimationToValue(updatedInsets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } - if abs(updatedApparentHeight - previousApparentHeight) > CGFloat(FLT_EPSILON) { + if abs(updatedApparentHeight - previousApparentHeight) > CGFloat.ulpOfOne { node.apparentHeight = previousApparentHeight node.animateFrameTransition(0.0, previousApparentHeight) node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in @@ -1779,7 +1784,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top case let .Center(overflow): let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top - if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { + if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { offset = self.insets.top + floor(((self.visibleSize.height - self.insets.bottom - self.insets.top) - itemNode.frame.size.height) / 2.0) - itemNode.apparentFrame.minY } else { switch overflow { @@ -1807,7 +1812,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if previousNode === itemNode { let offset = previousFrame.minY - itemNode.frame.minY - if abs(offset) > CGFloat(FLT_EPSILON) { + if abs(offset) > CGFloat.ulpOfOne { for itemNode in self.itemNodes { var frame = itemNode.frame frame.origin.y += offset @@ -1835,7 +1840,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let previousVisibleSize = self.visibleSize self.visibleSize = updateSizeAndInsets.size - var offsetFix = updateSizeAndInsets.insets.top - self.insets.top + var offsetFix: CGFloat + if self.snapToBottomInsetUntilFirstInteraction { + offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom + } else { + offsetFix = updateSizeAndInsets.insets.top - self.insets.top + } self.insets = updateSizeAndInsets.insets self.visibleSize = updateSizeAndInsets.size @@ -2025,7 +2035,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) - if let offset = offset , abs(offset) > CGFloat(FLT_EPSILON) { + if let offset = offset , abs(offset) > CGFloat.ulpOfOne { let lowestHeaderNode = self.lowestHeaderNode() for itemNode in temporaryPreviousNodes { itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) @@ -2144,7 +2154,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if self.debugInfo { var previousMaxY: CGFloat? for node in self.itemNodes { - if let previousMaxY = previousMaxY , abs(previousMaxY - node.apparentFrame.minY) > CGFloat(FLT_EPSILON) { + if let previousMaxY = previousMaxY , abs(previousMaxY - node.apparentFrame.minY) > CGFloat.ulpOfOne { print("monotonity violated") break } @@ -2454,7 +2464,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var i = 0 while i < self.itemNodes.count { let node = self.itemNodes[i] - if node.index == nil && node.apparentHeight <= CGFloat(FLT_EPSILON) { + if node.index == nil && node.apparentHeight <= CGFloat.ulpOfOne { self.removeItemNodeAtIndex(i) ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) } else { @@ -2590,15 +2600,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } let updatedApparentHeight = itemNode.apparentHeight let apparentHeightDelta = updatedApparentHeight - previousApparentHeight - if abs(apparentHeightDelta) > CGFloat(FLT_EPSILON) { - if itemNode.apparentFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + if abs(apparentHeightDelta) > CGFloat.ulpOfOne { + if itemNode.apparentFrame.maxY < self.insets.top + CGFloat.ulpOfOne { offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta) } else { offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta) } } - if itemNode.index == nil && updatedApparentHeight <= CGFloat(FLT_EPSILON) { + if itemNode.index == nil && updatedApparentHeight <= CGFloat.ulpOfOne { requestUpdateVisibleItems = true } diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 91b91d9c0e..2d7d954a98 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -133,10 +133,10 @@ public final class ListViewAnimation { public func applyAt(_ timestamp: Double) { var t = CGFloat((timestamp - self.startTime) / self.duration) let ct: CGFloat - if t <= 0.0 + CGFloat(FLT_EPSILON) { + if t <= 0.0 + CGFloat.ulpOfOne { t = 0.0 ct = 0.0 - } else if t >= 1.0 - CGFloat(FLT_EPSILON) { + } else if t >= 1.0 - CGFloat.ulpOfOne { t = 1.0 ct = 1.0 } else { diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 99e60d79d9..e59bf1dadf 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -312,7 +312,7 @@ struct ListViewState { offset = self.insets.top - node.frame.minY case let .Center(overflow): let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top - if node.frame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { + if node.frame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY } else { switch overflow { @@ -340,7 +340,7 @@ struct ListViewState { additionalOffset = self.insets.top - minY } - if abs(additionalOffset) > CGFloat(FLT_EPSILON) { + if abs(additionalOffset) > CGFloat.ulpOfOne { for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame frame = frame.offsetBy(dx: 0.0, dy: additionalOffset) @@ -358,7 +358,7 @@ struct ListViewState { if node.index == stationaryIndex { let offset = stationaryOffset - node.frame.minY - if abs(offset) > CGFloat(FLT_EPSILON) { + if abs(offset) > CGFloat.ulpOfOne { for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame frame = frame.offsetBy(dx: 0.0, dy: offset) @@ -447,7 +447,7 @@ struct ListViewState { } } - if abs(offset) > CGFloat(FLT_EPSILON) { + if abs(offset) > CGFloat.ulpOfOne { for i in 0 ..< self.nodes.count { var frame = self.nodes[i].frame frame.origin.y += offset @@ -511,9 +511,9 @@ struct ListViewState { let node = self.nodes[i] if let index = node.index { if index != currentUpperNode.index - 1 { - if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat.ulpOfOne { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat.ulpOfOne { directionHint = ListViewInsertionOffsetDirection(hint) } return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) @@ -525,9 +525,9 @@ struct ListViewState { } } - if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat.ulpOfOne { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat.ulpOfOne { directionHint = ListViewInsertionOffsetDirection(hint) } @@ -540,9 +540,9 @@ struct ListViewState { let node = self.nodes[i] if let index = node.index { if index != currentLowerNode.index + 1 { - if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat.ulpOfOne { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat.ulpOfOne { directionHint = ListViewInsertionOffsetDirection(hint) } return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) @@ -555,9 +555,9 @@ struct ListViewState { } } - if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat.ulpOfOne { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat.ulpOfOne { directionHint = ListViewInsertionOffsetDirection(hint) } return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) @@ -593,7 +593,7 @@ struct ListViewState { } } - let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) + let upperBound = -self.invisibleInset + CGFloat.ulpOfOne for i in 0 ..< self.nodes.count { let node = self.nodes[i] if let index = node.index , node.frame.maxY > upperBound { @@ -617,7 +617,7 @@ struct ListViewState { } } - let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) + let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat.ulpOfOne for i in (0 ..< self.nodes.count).reversed() { let node = self.nodes[i] if let index = node.index , node.frame.minY < lowerBound { @@ -717,7 +717,7 @@ struct ListViewState { let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) - operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) + operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, animated: animated, node: node, layout: layout, apply: apply)) self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex) if !animated { @@ -770,7 +770,7 @@ struct ListViewState { if let direction = direction { offsetDirection = ListViewInsertionOffsetDirection(direction) } else { - if nodeFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + if nodeFrame.maxY < self.insets.top + CGFloat.ulpOfOne { offsetDirection = .Down } else { offsetDirection = .Up @@ -782,8 +782,8 @@ struct ListViewState { self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) } else { - if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { - if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + if nodeFrame.maxY > self.insets.top - CGFloat.ulpOfOne { + if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat.ulpOfOne { for i in (0 ..< index).reversed() { var frame = self.nodes[i].frame frame.origin.y += nodeFrame.size.height @@ -820,7 +820,7 @@ struct ListViewState { if let direction = direction { offsetDirection = ListViewInsertionOffsetDirection(direction) } else { - if node.frame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + if node.frame.maxY < self.insets.top + CGFloat.ulpOfOne { offsetDirection = .Down } else { offsetDirection = .Up @@ -865,7 +865,7 @@ struct ListViewState { } enum ListViewStateOperation { - case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) case Remap([Int: Int]) diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index e7994277c4..59580cf9c8 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -408,7 +408,7 @@ open class ListViewItemNode: ASDisplayNode { public func modifyApparentHeightAnimation(_ value: CGFloat, beginAt: Double) { if let previousAnimation = self.animationForKey("apparentHeight") { var duration = previousAnimation.startTime + previousAnimation.duration - beginAt - if abs(self.apparentHeight - value) < CGFloat(FLT_EPSILON) { + if abs(self.apparentHeight - value) < CGFloat.ulpOfOne { duration = 0.0 } diff --git a/Display/MinimizeKeyboardGestureRecognizer.swift b/Display/MinimizeKeyboardGestureRecognizer.swift new file mode 100644 index 0000000000..b8c2ed10c5 --- /dev/null +++ b/Display/MinimizeKeyboardGestureRecognizer.swift @@ -0,0 +1,20 @@ +import Foundation +import UIKit + +final class MinimizeKeyboardGestureRecognizer: UISwipeGestureRecognizer, UIGestureRecognizerDelegate { + override init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.cancelsTouchesInView = false + self.delaysTouchesBegan = false + self.delaysTouchesEnded = false + self.delegate = self + + self.direction = [.left, .right] + self.numberOfTouchesRequired = 2 + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } +} diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index c90f621782..33d6396360 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -210,9 +210,14 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } public func pushViewController(_ controller: ViewController) { - controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + self.view.endEditing(true) + let appliedLayout = self.containerLayout.withUpdatedInputHeight(nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in if let strongSelf = self { + if strongSelf.containerLayout.withUpdatedInputHeight(nil) != appliedLayout { + controller.containerLayoutUpdated(strongSelf.containerLayout.withUpdatedInputHeight(nil), transition: .immediate) + } strongSelf.pushViewController(controller, animated: true) } })) @@ -227,6 +232,7 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { + self.view.endEditing(true) controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { @@ -240,6 +246,7 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { + self.view.endEditing(true) controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { @@ -262,6 +269,26 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle self.setViewControllers(controllers, animated: animated) } + override open func popToViewController(_ viewController: UIViewController, animated: Bool) -> [UIViewController]? { + var poppedControllers: [UIViewController] = [] + var found = false + var controllers = self.viewControllers + while !controllers.isEmpty { + if controllers[controllers.count - 1] === viewController { + found = true + break + } + poppedControllers.insert(controllers[controllers.count - 1], at: 0) + controllers.removeLast() + } + if found { + self.setViewControllers(controllers, animated: animated) + return poppedControllers + } else { + return nil + } + } + open override func popViewController(animated: Bool) -> UIViewController? { var controller: UIViewController? var controllers = self.viewControllers diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index d1fe4b5e0c..104743d4bf 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -204,7 +204,7 @@ class NavigationTransitionCoordinator { completion() } - if abs(velocity) < CGFloat(FLT_EPSILON) && abs(self.progress) < CGFloat(FLT_EPSILON) { + if abs(velocity) < CGFloat.ulpOfOne && abs(self.progress) < CGFloat.ulpOfOne { UIView.animate(withDuration: 0.5, delay: 0.0, options: UIViewAnimationOptions(rawValue: 7 << 16), animations: { self.progress = 1.0 }, completion: { _ in diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index aa6deb337b..c5aa85610f 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -40,7 +40,7 @@ public class StatusBar: ASDisplayNode { return UITracingLayerView() }, didLoad: nil) - self.layer.setTraceableInfo(NSWeakReference(value: self)) + self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: Window.statusBarTracingTag)) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/Display/StatusBarHost.swift b/Display/StatusBarHost.swift index 073aeaf6c9..4b79aa1845 100644 --- a/Display/StatusBarHost.swift +++ b/Display/StatusBarHost.swift @@ -6,5 +6,6 @@ public protocol StatusBarHost { var statusBarWindow: UIView? { get } var statusBarView: UIView? { get } + var keyboardWindow: UIWindow? { get } var keyboardView: UIView? { get } } diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 693435cc4d..69af044c0b 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -24,7 +24,7 @@ private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurfac private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface) -> MappedStatusBarSurface { if surface.statusBars.count > 1 { for i in 1 ..< surface.statusBars.count { - if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat(FLT_EPSILON) { + if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat.ulpOfOne { return surface } if let lhsStatusBar = surface.statusBars[i - 1].statusBar, let rhsStatusBar = surface.statusBars[i].statusBar , !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) { diff --git a/Display/Theme.swift b/Display/Theme.swift index cf8ea4883c..fd448fb1fb 100644 --- a/Display/Theme.swift +++ b/Display/Theme.swift @@ -1,9 +1,9 @@ import Foundation public class Theme { - public let tintColor: UIColor + public let accentColor: UIColor - public init(tintColor: UIColor) { - self.tintColor = tintColor + public init(accentColor: UIColor) { + self.accentColor = accentColor } } diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 484ac2549f..e236dab2c7 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -53,8 +53,8 @@ CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPat }); if (canSetInitialVelocity) { springAnimation.initialVelocity = initialVelocity; + springAnimation.duration = springAnimation.settlingDuration; } - springAnimation.duration = springAnimation.settlingDuration; springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; } diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 247bf14f26..dade219ffc 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -177,7 +177,7 @@ static bool notyfyingShiftState = false; return controller; } - UIView *result = [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; + UIViewController *result = [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; if (result != nil) { return result; } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 497e2141bf..daf8347c18 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -168,6 +168,9 @@ open class ViewControllerPresentationArguments { } open func displayNodeDidLoad() { + if let layer = self.displayNode.layer as? CATracingLayer { + layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: Window.keyboardTracingTag)) + } self.updateScrollToTopView() } @@ -243,4 +246,7 @@ open class ViewControllerPresentationArguments { super.viewDidAppear(animated) } + + open func dismiss() { + } } diff --git a/Display/Window.swift b/Display/Window.swift index 4ab3fd3c2b..60c55e5a8a 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -23,6 +23,7 @@ private struct WindowLayout: Equatable { public let size: CGSize public let statusBarHeight: CGFloat? public let inputHeight: CGFloat? + public let inputMinimized: Bool } private func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { @@ -54,6 +55,10 @@ private func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { return false } + if lhs.inputMinimized != rhs.inputMinimized { + return false + } + return true } @@ -76,19 +81,25 @@ private struct UpdatingLayout { mutating func update(size: CGSize, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: size, statusBarHeight: self.layout.statusBarHeight, inputHeight: self.layout.inputHeight) + self.layout = WindowLayout(size: size, statusBarHeight: self.layout.statusBarHeight, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) } mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, statusBarHeight: statusBarHeight, inputHeight: self.layout.inputHeight) + self.layout = WindowLayout(size: self.layout.size, statusBarHeight: statusBarHeight, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) } mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, statusBarHeight: self.layout.statusBarHeight, inputHeight: inputHeight) + self.layout = WindowLayout(size: self.layout.size, statusBarHeight: self.layout.statusBarHeight, inputHeight: inputHeight, inputMinimized: self.layout.inputMinimized) + } + + mutating func update(inputMinimized: Bool, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: self.layout.size, statusBarHeight: self.layout.statusBarHeight, inputHeight: self.layout.inputHeight, inputMinimized: inputMinimized) } } @@ -96,12 +107,21 @@ private let orientationChangeDuration: Double = UIDevice.current.userInterfaceId private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout { - return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) + var inputHeight: CGFloat? = layout.inputHeight + if let inputHeightValue = inputHeight, layout.inputMinimized { + inputHeight = floor(0.85 * inputHeightValue) + } + + return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: inputHeight) } public class Window: UIWindow { + public static let statusBarTracingTag: Int32 = 0 + public static let keyboardTracingTag: Int32 = 1 + private let statusBarHost: StatusBarHost? private let statusBarManager: StatusBarManager? + private let keyboardManager: KeyboardManager? private var statusBarChangeObserver: AnyObject? private var keyboardFrameChangeObserver: AnyObject? @@ -122,11 +142,21 @@ public class Window: UIWindow { if let statusBarHost = statusBarHost { self.statusBarManager = StatusBarManager(host: statusBarHost) statusBarHeight = statusBarHost.statusBarFrame.size.height + self.keyboardManager = KeyboardManager(host: statusBarHost) } else { self.statusBarManager = nil + self.keyboardManager = nil statusBarHeight = 20.0 } - self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: statusBarHeight, inputHeight: 0.0) + + let minimized: Bool + if let keyboardManager = self.keyboardManager { + minimized = keyboardManager.minimized + } else { + minimized = false + } + + self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized) self.presentationContext = PresentationContext() super.init(frame: frame) @@ -135,6 +165,14 @@ public class Window: UIWindow { self?.invalidateTracingStatusBars() } + self.keyboardManager?.minimizedUpdated = { [weak self] in + if let strongSelf = self { + strongSelf.updateLayout { current in + current.update(inputMinimized: strongSelf.keyboardManager!.minimized, transition: .immediate, overrideTransition: false) + } + } + } + self.presentationContext.view = self self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) @@ -170,7 +208,7 @@ public class Window: UIWindow { if duration > DBL_EPSILON { duration = 0.5 } - var curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7 + let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7 let transitionCurve: ContainedViewLayoutTransitionCurve if curve == 7 { @@ -271,20 +309,19 @@ public class Window: UIWindow { override public func layoutSubviews() { super.layoutSubviews() - if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager { + if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager { self.tracingStatusBarsInvalidated = false if self.statusBarHidden { statusBarManager.surfaces = [] } else { var statusBarSurfaces: [StatusBarSurface] = [] - for layers in self.layer.traceableLayerSurfaces() { + for layers in self.layer.traceableLayerSurfaces(withTag: Window.statusBarTracingTag) { let surface = StatusBarSurface() for layer in layers { - if let weakInfo = layer.traceableInfo() as? NSWeakReference { - if let statusBar = weakInfo.value as? StatusBar { - surface.addStatusBar(statusBar) - } + let traceableInfo = layer.traceableInfo() + if let statusBar = traceableInfo?.userData as? StatusBar { + surface.addStatusBar(statusBar) } } statusBarSurfaces.append(surface) @@ -292,6 +329,16 @@ public class Window: UIWindow { self.layer.adjustTraceableLayerTransforms(CGSize()) statusBarManager.surfaces = statusBarSurfaces } + + var keyboardSurfaces: [KeyboardSurface] = [] + for layers in self.layer.traceableLayerSurfaces(withTag: Window.keyboardTracingTag) { + for layer in layers { + if let view = layer.delegate as? UITracingLayerView { + keyboardSurfaces.append(KeyboardSurface(host: view)) + } + } + } + keyboardManager.surfaces = keyboardSurfaces } if !Window.isDeviceRotating() { @@ -331,7 +378,7 @@ public class Window: UIWindow { postUpdateToInterfaceOrientationBlocks.append(f) } - private func updateLayout(_ update: @noescape(inout UpdatingLayout) -> ()) { + private func updateLayout(_ update: (inout UpdatingLayout) -> ()) { if self.updatingLayout == nil { self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) } @@ -349,7 +396,7 @@ public class Window: UIWindow { } else { statusBarHeight = 20.0 } - var statusBarWasHidden = self.statusBarHidden + let statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { statusBarHeight = 0.0 self.statusBarHidden = true @@ -360,7 +407,7 @@ public class Window: UIWindow { self.tracingStatusBarsInvalidated = true self.setNeedsLayout() } - self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight) + self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized) self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) From a4de6e5fb65e685fc7133c09b1750dd323ebf84b Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 23 Mar 2017 21:26:08 +0300 Subject: [PATCH 038/245] no message --- Display/ListView.swift | 8 +++++--- Display/NavigationController.swift | 27 +++++++++++++++++++-------- Display/TabBarController.swift | 14 +++++++++++++- 3 files changed, 37 insertions(+), 12 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 2b48c1b71b..a1536f9862 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -555,9 +555,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel topItemEdge = itemNodes[0].apparentFrame.origin.y } + var bottomItemNode: ListViewItemNode? for i in (0 ..< self.itemNodes.count).reversed() { if let index = itemNodes[i].index { if index == self.items.count - 1 { + bottomItemNode = itemNodes[i] bottomItemFound = true } break @@ -622,16 +624,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if visibleAreaHeight > completeHeight { - if let itemNode = self.itemNodes.last, itemNode.wantsTrailingItemSpaceUpdates { + if let itemNode = bottomItemNode, itemNode.wantsTrailingItemSpaceUpdates { itemNode.updateTrailingItemSpace(visibleAreaHeight - completeHeight, transition: transition) } } else { - if let itemNode = self.itemNodes.last, itemNode.wantsTrailingItemSpaceUpdates { + if let itemNode = bottomItemNode, itemNode.wantsTrailingItemSpaceUpdates { itemNode.updateTrailingItemSpace(0.0, transition: transition) } } } else { - if let itemNode = self.itemNodes.last, itemNode.wantsTrailingItemSpaceUpdates { + if let itemNode = bottomItemNode, itemNode.wantsTrailingItemSpaceUpdates { itemNode.updateTrailingItemSpace(0.0, transition: transition) } if topItemFound { diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 33d6396360..2fdffca162 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -4,9 +4,18 @@ import AsyncDisplayKit import SwiftSignalKit private class NavigationControllerView: UIView { + var inTransition = false + override class var layerClass: AnyClass { return CATracingLayer.self } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) && self.inTransition { + return self + } + return super.hitTest(point, with: event) + } } open class NavigationController: NavigationControllerProxy, ContainableController, UIGestureRecognizerDelegate { @@ -68,14 +77,6 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let containedLayout = ContainerViewLayout(size: layout.size, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) - /*for controller in self.viewControllers { - if let controller = controller as? ContainableController { - controller.containerLayoutUpdated(containedLayout, transition: transition) - } else { - controller.viewWillTransition(to: layout.size, with: SystemContainedControllerTransitionCoordinator()) - } - }*/ - if let topViewController = self.topViewController { if let topViewController = topViewController as? ContainableController { topViewController.containerLayoutUpdated(containedLayout, transition: transition) @@ -138,7 +139,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let velocity = recognizer.velocity(in: self.view).x if velocity > 1000 || navigationTransitionCoordinator.progress > 0.2 { + (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCompletion(velocity, completion: { + (self.view as! NavigationControllerView).inTransition = false self.navigationTransitionCoordinator = nil //self._navigationBar.endInteractivePopProgress() @@ -167,7 +170,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle bottomController.viewWillDisappear(true) } + (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCancel({ + (self.view as! NavigationControllerView).inTransition = false self.navigationTransitionCoordinator = nil //self._navigationBar.endInteractivePopProgress() @@ -192,7 +197,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle bottomController.viewWillDisappear(true) } + (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCancel({ + (self.view as! NavigationControllerView).inTransition = false self.navigationTransitionCoordinator = nil if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { @@ -336,8 +343,10 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator + (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in if let strongSelf = self { + (strongSelf.view as! NavigationControllerView).inTransition = false strongSelf.navigationTransitionCoordinator = nil topController.setIgnoreAppearanceMethodInvocations(true) @@ -370,8 +379,10 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle topView.isUserInteractionEnabled = false + (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in if let strongSelf = self { + (strongSelf.view as! NavigationControllerView).inTransition = false strongSelf.navigationTransitionCoordinator = nil topController.setIgnoreAppearanceMethodInvocations(true) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index e2587b3198..988b23678f 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import AsyncDisplayKit +import SwiftSignalKit open class TabBarController: ViewController { private var containerLayout = ContainerViewLayout() @@ -41,6 +42,8 @@ open class TabBarController: ViewController { var currentController: ViewController? + private let pendingControllerDisposable = MetaDisposable() + override public init(navigationBar: NavigationBar = NavigationBar()) { super.init(navigationBar: navigationBar) } @@ -49,10 +52,19 @@ open class TabBarController: ViewController { fatalError("init(coder:) has not been implemented") } + deinit { + self.pendingControllerDisposable.dispose() + } + override open func loadDisplayNode() { self.displayNode = TabBarControllerNode(itemSelected: { [weak self] index in if let strongSelf = self { - strongSelf.selectedIndex = index + strongSelf.controllers[index].containerLayoutUpdated(strongSelf.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) + strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in + if let strongSelf = self { + strongSelf.selectedIndex = index + } + })) } }) From 124e621ee282179ea7147ea3528da206b2ebf7ca Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 18 Apr 2017 19:52:18 +0300 Subject: [PATCH 039/245] no message --- Display.xcodeproj/project.pbxproj | 20 +- Display/ActionSheetButtonItem.swift | 100 +++- Display/ActionSheetButtonNode.swift | 63 --- Display/ActionSheetCheckboxItem.swift | 130 +++++ Display/ActionSheetController.swift | 6 + Display/ActionSheetControllerNode.swift | 4 + Display/ActionSheetItem.swift | 1 + Display/ActionSheetItemGroupNode.swift | 4 + .../ActionSheetItemGroupsContainerNode.swift | 15 + Display/AlertController.swift | 4 +- Display/CAAnimationUtils.swift | 20 +- Display/CATracingLayer.m | 36 +- Display/ContainableController.swift | 6 +- Display/Font.swift | 8 + Display/GridItem.swift | 7 + Display/GridItemNode.swift | 13 +- Display/GridNode.swift | 470 +++++++++++++++--- Display/LegacyPresentedController.swift | 6 +- Display/ListView.swift | 24 +- Display/ListViewItemNode.swift | 8 + Display/NativeWindowHostView.swift | 149 ++++++ Display/NavigationBar.swift | 8 +- Display/NavigationController.swift | 13 + Display/PresentationContext.swift | 32 +- Display/StatusBar.swift | 4 +- Display/StatusBarManager.swift | 51 +- Display/StatusBarProxyNode.swift | 6 +- Display/SwitchNode.swift | 2 + Display/UIKitUtils.h | 2 +- Display/UIKitUtils.m | 4 +- Display/UIKitUtils.swift | 4 + Display/ViewController.swift | 30 +- Display/{Window.swift => WindowContent.swift} | 207 ++++---- 33 files changed, 1156 insertions(+), 301 deletions(-) delete mode 100644 Display/ActionSheetButtonNode.swift create mode 100644 Display/ActionSheetCheckboxItem.swift create mode 100644 Display/NativeWindowHostView.swift rename Display/{Window.swift => WindowContent.swift} (73%) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 7007bb8f87..6cb0ea93f5 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; - D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; + D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* WindowContent.swift */; }; D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; }; @@ -82,10 +82,12 @@ D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; + D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */; }; D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; + D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */; }; D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; }; @@ -102,7 +104,6 @@ D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; }; D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; - D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; }; D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; }; @@ -171,7 +172,7 @@ D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = ""; }; D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; - D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; + D05CC2A11B69326C00E235A3 /* WindowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowContent.swift; sourceTree = ""; }; D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; @@ -209,10 +210,12 @@ D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; + D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetCheckboxItem.swift; sourceTree = ""; }; D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; + D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWindowHostView.swift; sourceTree = ""; }; D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTransformLayerNode.swift; sourceTree = ""; }; @@ -229,7 +232,6 @@ D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = ""; }; D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; - D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonNode.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = ""; }; D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; @@ -272,7 +274,8 @@ D015F7501D1ADC6800E269B5 /* Window */ = { isa = PBXGroup; children = ( - D05CC2A11B69326C00E235A3 /* Window.swift */, + D05CC2A11B69326C00E235A3 /* WindowContent.swift */, + D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */, ); name = Window; sourceTree = ""; @@ -298,7 +301,7 @@ D08E90391D24159200533158 /* ActionSheetItem.swift */, D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */, D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */, - D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */, + D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */, ); name = "Action Sheet"; sourceTree = ""; @@ -737,6 +740,7 @@ D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, + D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, @@ -746,7 +750,6 @@ D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */, D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */, - D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, @@ -772,6 +775,7 @@ D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, + D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, @@ -806,7 +810,7 @@ D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, - D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, + D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */, D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index 109daf1668..52bd2ef7df 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -1,24 +1,116 @@ import Foundation +import AsyncDisplayKit public enum ActionSheetButtonColor { case accent case destructive + case disabled } public class ActionSheetButtonItem: ActionSheetItem { public let title: String public let color: ActionSheetButtonColor + public let enabled: Bool public let action: () -> Void - public init(title: String, color: ActionSheetButtonColor = .accent, action: @escaping () -> Void) { + public init(title: String, color: ActionSheetButtonColor = .accent, enabled: Bool = true, action: @escaping () -> Void) { self.title = title self.color = color + self.enabled = enabled self.action = action } public func node() -> ActionSheetItemNode { - let textColorIsAccent = self.color == ActionSheetButtonColor.accent - let textColor = textColorIsAccent ? UIColor(0x007ee5) : UIColor.red - return ActionSheetButtonNode(title: NSAttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: textColor), action: self.action) + let node = ActionSheetButtonNode() + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? ActionSheetButtonNode else { + assertionFailure() + return + } + + node.setItem(self) + } +} + +public class ActionSheetButtonNode: ActionSheetItemNode { + public static let defaultFont: UIFont = Font.regular(20.0) + + private var item: ActionSheetButtonItem? + + private let button: HighlightTrackingButton + private let label: ASTextNode + + override public init() { + self.button = HighlightTrackingButton() + + self.label = ASTextNode() + self.label.isLayerBacked = true + self.label.maximumNumberOfLines = 1 + self.label.displaysAsynchronously = false + + super.init() + + self.view.addSubview(self.button) + + self.label.isUserInteractionEnabled = false + self.addSubnode(self.label) + + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + } else { + UIView.animate(withDuration: 0.3, animations: { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + }) + } + } + } + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + func setItem(_ item: ActionSheetButtonItem) { + self.item = item + + let textColor: UIColor + switch item.color { + case .accent: + textColor = UIColor(0x007ee5) + case .destructive: + textColor = .red + case .disabled: + textColor = .gray + } + self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: textColor) + + self.button.isEnabled = item.enabled + + self.setNeedsLayout() + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + let size = self.bounds.size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + + let labelSize = self.label.measure(size) + self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + } + + @objc func buttonPressed() { + if let item = self.item { + item.action() + } } } diff --git a/Display/ActionSheetButtonNode.swift b/Display/ActionSheetButtonNode.swift deleted file mode 100644 index 396d6623bf..0000000000 --- a/Display/ActionSheetButtonNode.swift +++ /dev/null @@ -1,63 +0,0 @@ -import UIKit -import AsyncDisplayKit - -public class ActionSheetButtonNode: ActionSheetItemNode { - public static let defaultFont: UIFont = Font.regular(20.0) - - private let action: () -> Void - - private let button: HighlightTrackingButton - private let label: UILabel - private var calculatedLabelSize: CGSize? - - public init(title: NSAttributedString, action: @escaping () -> Void) { - self.action = action - - self.button = HighlightTrackingButton() - self.label = UILabel() - - super.init() - - self.view.addSubview(self.button) - - self.label.attributedText = title - self.label.numberOfLines = 1 - self.label.isUserInteractionEnabled = false - self.view.addSubview(self.label) - - self.button.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor - }) - } - } - } - - self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - } - - public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - self.label.sizeToFit() - self.calculatedLabelSize = self.label.frame.size - - return CGSize(width: constrainedSize.width, height: 57.0) - } - - public override func layout() { - super.layout() - - self.button.frame = CGRect(origin: CGPoint(), size: self.calculatedSize) - - if let calculatedLabelSize = self.calculatedLabelSize { - self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.calculatedSize.width - calculatedLabelSize.width) / 2.0), y: floorToScreenPixels((self.calculatedSize.height - calculatedLabelSize.height) / 2.0)), size: calculatedLabelSize) - } - } - - @objc func buttonPressed() { - self.action() - } -} diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift new file mode 100644 index 0000000000..651e72e28f --- /dev/null +++ b/Display/ActionSheetCheckboxItem.swift @@ -0,0 +1,130 @@ +import Foundation +import AsyncDisplayKit + +private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor(0x007ee5).cgColor) + context.setLineWidth(2.0) + context.move(to: CGPoint(x: 12.0, y: 1.0)) + context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) + context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) + context.strokePath() +}) + +public class ActionSheetCheckboxItem: ActionSheetItem { + public let title: String + public let label: String + public let value: Bool + public let action: (Bool) -> Void + + public init(title: String, label: String, value: Bool, action: @escaping (Bool) -> Void) { + self.title = title + self.label = label + self.value = value + self.action = action + } + + public func node() -> ActionSheetItemNode { + let node = ActionSheetCheckboxItemNode() + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? ActionSheetCheckboxItemNode else { + assertionFailure() + return + } + + node.setItem(self) + } +} + +public class ActionSheetCheckboxItemNode: ActionSheetItemNode { + public static let defaultFont: UIFont = Font.regular(20.0) + + private var item: ActionSheetCheckboxItem? + + private let button: HighlightTrackingButton + private let titleNode: ASTextNode + private let labelNode: ASTextNode + private let checkNode: ASImageNode + + public override init() { + self.button = HighlightTrackingButton() + + self.titleNode = ASTextNode() + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.isUserInteractionEnabled = false + self.titleNode.displaysAsynchronously = false + + self.labelNode = ASTextNode() + self.labelNode.maximumNumberOfLines = 1 + self.labelNode.isUserInteractionEnabled = false + self.labelNode.displaysAsynchronously = false + + self.checkNode = ASImageNode() + self.checkNode.isUserInteractionEnabled = false + self.checkNode.displayWithoutProcessing = true + self.checkNode.displaysAsynchronously = false + self.checkNode.image = checkIcon + + super.init() + + self.view.addSubview(self.button) + self.addSubnode(self.titleNode) + self.addSubnode(self.labelNode) + self.addSubnode(self.checkNode) + + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + } else { + UIView.animate(withDuration: 0.3, animations: { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + }) + } + } + } + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + func setItem(_ item: ActionSheetCheckboxItem) { + self.item = item + + self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: .black) + self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: UIColor(0x8e8e93)) + self.checkNode.isHidden = !item.value + + self.setNeedsLayout() + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + let size = self.bounds.size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + + let labelSize = self.labelNode.measure(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height)) + let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height)) + self.titleNode.frame = CGRect(origin: CGPoint(x: 44.0, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + + if let image = self.checkNode.image { + self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) + } + } + + @objc func buttonPressed() { + if let item = self.item { + item.action(!item.value) + } + } +} diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index e47ecbbe5a..2302f01a9d 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -49,4 +49,10 @@ open class ActionSheetController: ViewController { self.actionSheetNode.setGroups(groups) } } + + public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) { + if self.isViewLoaded { + self.actionSheetNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f) + } + } } diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 5db30c1173..f6d8914ec5 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -155,4 +155,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func setGroups(_ groups: [ActionSheetItemGroup]) { self.itemGroupsContainerNode.setGroups(groups) } + + public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) { + self.itemGroupsContainerNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f) + } } diff --git a/Display/ActionSheetItem.swift b/Display/ActionSheetItem.swift index 469f36888c..fdac4f2386 100644 --- a/Display/ActionSheetItem.swift +++ b/Display/ActionSheetItem.swift @@ -2,4 +2,5 @@ import Foundation public protocol ActionSheetItem { func node() -> ActionSheetItemNode + func updateNode(_ node: ActionSheetItemNode) -> Void } diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift index 685e9bafa5..09d9ae277a 100644 --- a/Display/ActionSheetItemGroupNode.swift +++ b/Display/ActionSheetItemGroupNode.swift @@ -209,4 +209,8 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { node.layer.animateAlpha(from: from, to: to, duration: duration) } } + + func itemNode(at index: Int) -> ActionSheetItemNode { + return self.itemNodes[index] + } } diff --git a/Display/ActionSheetItemGroupsContainerNode.swift b/Display/ActionSheetItemGroupsContainerNode.swift index e385cc73ef..21805872d0 100644 --- a/Display/ActionSheetItemGroupsContainerNode.swift +++ b/Display/ActionSheetItemGroupsContainerNode.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit private let groupSpacing: CGFloat = 16.0 final class ActionSheetItemGroupsContainerNode: ASDisplayNode { + private var groups: [ActionSheetItemGroup] = [] private var groupNodes: [ActionSheetItemGroupNode] = [] override init() { @@ -11,6 +12,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { } func setGroups(_ groups: [ActionSheetItemGroup]) { + self.groups = groups + for groupNode in self.groupNodes { groupNode.removeFromSupernode() } @@ -72,4 +75,16 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { node.animateDimViewsAlpha(from: from, to: to, duration: duration) } } + + public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) { + var item = self.groups[groupIndex].items[itemIndex] + let itemNode = self.groupNodes[groupIndex].itemNode(at: itemIndex) + item = f(item) + item.updateNode(itemNode) + + var groupItems = self.groups[groupIndex].items + groupItems[itemIndex] = item + + self.groups[groupIndex] = ActionSheetItemGroup(items: groupItems) + } } diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 5e37e021c6..79340826bf 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -45,8 +45,8 @@ open class AlertController: ViewController { self.controllerNode.containerLayoutUpdated(layout, transition: transition) } - override open func dismiss() { - self.presentingViewController?.dismiss(animated: false, completion: nil) + override open func dismiss(completion: (() -> Void)? = nil) { + self.presentingViewController?.dismiss(animated: false, completion: completion) } public func dismissAnimated() { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 0a54ce4bd9..0756b29372 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -134,8 +134,8 @@ public extension CALayer { self.add(animation, forKey: keyPath) } - public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation = makeSpringBounceAnimation(keyPath, initialVelocity) + public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) animation.fromValue = from animation.toValue = to animation.isRemovedOnCompletion = removeOnCompletion @@ -187,8 +187,8 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { if let completion = completion { completion(true) } @@ -197,8 +197,8 @@ public extension CALayer { self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { + func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { if let completion = completion { completion(true) } @@ -219,8 +219,8 @@ public extension CALayer { self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position") } - public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { if let completion = completion { completion(true) } @@ -236,14 +236,14 @@ public extension CALayer { } } } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } completedPosition = true partialCompletion() }) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 6bbc6fbd6e..18b8828867 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -231,7 +231,41 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull - (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { if ([anim isKindOfClass:[CABasicAnimation class]]) { - if ([key isEqualToString:@"position"]) { + if (false && [key isEqualToString:@"bounds.origin.y"]) { + CABasicAnimation *animCopy = [anim copy]; + CGFloat from = [animCopy.fromValue floatValue]; + CGFloat to = [animCopy.toValue floatValue]; + + animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, to - from, 0.0f)]; + animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + animCopy.keyPath = @"sublayerTransform"; + + __weak CATracingLayer *weakSelf = self; + anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + + [super addAnimation:anim forKey:key]; + + CABasicAnimation *positionAnimCopy = [animCopy copy]; + positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0f)]; + positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + positionAnimCopy.additive = true; + positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + + [self invalidateUpTheTree]; + + [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; + [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; + } else if ([key isEqualToString:@"position"]) { CABasicAnimation *animCopy = [anim copy]; CGPoint from = [animCopy.fromValue CGPointValue]; CGPoint to = [animCopy.toValue CGPointValue]; diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 85182f228b..a5a02e8400 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -31,8 +31,8 @@ public enum ContainedViewLayoutTransition { } public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) { - if node.frame.equalTo(frame) { + func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.frame.equalTo(frame) && !force { completion?(true) } else { switch self { @@ -44,7 +44,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousFrame = node.frame node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in if let completion = completion { completion(result) } diff --git a/Display/Font.swift b/Display/Font.swift index d118f534af..39bdf5b419 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -14,6 +14,14 @@ public struct Font { } } + public static func semibold(_ size: CGFloat) -> UIFont { + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: UIFontWeightSemibold) + } else { + return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) + } + } + public static func bold(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { return UIFont.boldSystemFont(ofSize: size) diff --git a/Display/GridItem.swift b/Display/GridItem.swift index cd112ce5c0..d5707c3d86 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -12,4 +12,11 @@ public protocol GridItem { var section: GridSection? { get } func node(layout: GridNodeLayout) -> GridItemNode func update(node: GridItemNode) + var aspectRatio: CGFloat { get } +} + +public extension GridItem { + var aspectRatio: CGFloat { + return 1.0 + } } diff --git a/Display/GridItemNode.swift b/Display/GridItemNode.swift index 8a4de56269..5acb7a1aa4 100644 --- a/Display/GridItemNode.swift +++ b/Display/GridItemNode.swift @@ -2,5 +2,16 @@ import Foundation import AsyncDisplayKit open class GridItemNode: ASDisplayNode { - + open var isVisibleInGrid = false + open var isGridScrolling = false + + final var cachedFrame: CGRect = CGRect() + override open var frame: CGRect { + get { + return self.cachedFrame + } set(value) { + self.cachedFrame = value + super.frame = value + } + } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 0d8adc8e6d..485a83b182 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -47,21 +47,43 @@ public struct GridNodeScrollToItem { } } +public enum GridNodeLayoutType: Equatable { + case fixed(itemSize: CGSize) + case balanced(idealHeight: CGFloat) + + public static func ==(lhs: GridNodeLayoutType, rhs: GridNodeLayoutType) -> Bool { + switch lhs { + case let .fixed(itemSize): + if case .fixed(itemSize) = rhs { + return true + } else { + return false + } + case let .balanced(idealHeight): + if case .balanced(idealHeight) = rhs { + return true + } else { + return false + } + } + } +} + public struct GridNodeLayout: Equatable { public let size: CGSize public let insets: UIEdgeInsets public let preloadSize: CGFloat - public let itemSize: CGSize + public let type: GridNodeLayoutType - public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, itemSize: CGSize) { + public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, type: GridNodeLayoutType) { self.size = size self.insets = insets self.preloadSize = preloadSize - self.itemSize = itemSize + self.type = type } public static func ==(lhs: GridNodeLayout, rhs: GridNodeLayout) -> Bool { - return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.itemSize.equalTo(rhs.itemSize) + return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.type == rhs.type } } @@ -223,7 +245,7 @@ private struct WrappedGridItemNode: Hashable { } open class GridNode: GridNodeScroller, UIScrollViewDelegate { - private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize()) + private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize())) private var firstIndexInSectionOffset: Int = 0 private var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] @@ -235,6 +257,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? + public final var floatingSections = false + public override init() { super.init() @@ -315,10 +339,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.items.insert(insertedItem.item, at: insertedItem.index) } + let sortedInsertItems = transaction.insertItems.sorted(by: { $0.index < $1.index }) + var remappedInsertionItemNodes: [Int: GridItemNode] = [:] for (index, itemNode) in remappedDeletionItemNodes { var indexOffset = 0 - for insertedItem in transaction.insertItems { + for insertedItem in sortedInsertItems { if insertedItem.index <= index + indexOffset { indexOffset += 1 } @@ -343,14 +369,26 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition) - - completion(self.displayedItemRange()) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, completion: completion) + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.updateItemNodeVisibilititesAndScrolling() + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.updateItemNodeVisibilititesAndScrolling() + } + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.updateItemNodeVisibilititesAndScrolling() } public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, completion: { _ in }) } } @@ -379,60 +417,144 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var items: [GridNodePresentationItem] = [] var sections: [GridNodePresentationSection] = [] - let itemsInRow = Int(gridLayout.size.width / gridLayout.itemSize.width) - let itemsInRowWidth = CGFloat(itemsInRow) * gridLayout.itemSize.width - let remainingWidth = gridLayout.size.width - itemsInRowWidth - - let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) - - var incrementedCurrentRow = false - var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) - var index = 0 - var previousSection: GridSection? - for item in self.items { - let section = item.section - var keepSection = true - if let previousSection = previousSection, let section = section { - keepSection = previousSection.isEqual(to: section) - } else if (previousSection != nil) != (section != nil) { - keepSection = false - } - - if !keepSection { - if incrementedCurrentRow { - nextItemOrigin.x = itemSpacing - nextItemOrigin.y += gridLayout.itemSize.height - incrementedCurrentRow = false + switch gridLayout.type { + case let .fixed(itemSize): + let itemsInRow = Int(gridLayout.size.width / itemSize.width) + let itemsInRowWidth = CGFloat(itemsInRow) * itemSize.width + let remainingWidth = gridLayout.size.width - itemsInRowWidth + + let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) + + var incrementedCurrentRow = false + var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) + var index = 0 + var previousSection: GridSection? + for item in self.items { + let section = item.section + var keepSection = true + if let previousSection = previousSection, let section = section { + keepSection = previousSection.isEqual(to: section) + } else if (previousSection != nil) != (section != nil) { + keepSection = false + } + + if !keepSection { + if incrementedCurrentRow { + nextItemOrigin.x = itemSpacing + nextItemOrigin.y += itemSize.height + incrementedCurrentRow = false + } + + if let section = section { + sections.append(GridNodePresentationSection(section: section, frame: CGRect(origin: CGPoint(x: 0.0, y: nextItemOrigin.y), size: CGSize(width: gridLayout.size.width, height: section.height)))) + nextItemOrigin.y += section.height + contentSize.height += section.height + } + } + previousSection = section + + if !incrementedCurrentRow { + incrementedCurrentRow = true + contentSize.height += itemSize.height + } + + if index == 0 { + let itemsInRow = Int(gridLayout.size.width) / Int(itemSize.width) + let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow + nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) + } + + items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: itemSize))) + index += 1 + + nextItemOrigin.x += itemSize.width + itemSpacing + if nextItemOrigin.x + itemSize.width > gridLayout.size.width { + nextItemOrigin.x = itemSpacing + nextItemOrigin.y += itemSize.height + incrementedCurrentRow = false + } + } + case let .balanced(idealHeight): + var weights: [Int] = [] + for item in self.items { + weights.append(Int(item.aspectRatio * 100)) } - if let section = section { - sections.append(GridNodePresentationSection(section: section, frame: CGRect(origin: CGPoint(x: 0.0, y: nextItemOrigin.y), size: CGSize(width: gridLayout.size.width, height: section.height)))) - nextItemOrigin.y += section.height - contentSize.height += section.height + var totalItemSize: CGFloat = 0.0 + for i in 0 ..< self.items.count { + totalItemSize += self.items[i].aspectRatio * idealHeight } - } - previousSection = section - - if !incrementedCurrentRow { - incrementedCurrentRow = true - contentSize.height += gridLayout.itemSize.height - } - - if index == 0 { - let itemsInRow = Int(gridLayout.size.width) / Int(gridLayout.itemSize.width) - let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow - nextItemOrigin.x += (gridLayout.itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) - } - - items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: gridLayout.itemSize))) - index += 1 - - nextItemOrigin.x += gridLayout.itemSize.width + itemSpacing - if nextItemOrigin.x + gridLayout.itemSize.width > gridLayout.size.width { - nextItemOrigin.x = itemSpacing - nextItemOrigin.y += gridLayout.itemSize.height - incrementedCurrentRow = false - } + let numberOfRows = max(Int(round(totalItemSize / gridLayout.size.width)), 1) + + let partition = linearPartitionForWeights(weights, numberOfPartitions:numberOfRows) + + var i = 0 + var offset = CGPoint(x: 0.0, y: 0.0) + var previousItemSize: CGFloat = 0.0 + var contentMaxValueInScrollDirection: CGFloat = 0.0 + let maxWidth = gridLayout.size.width + + let minimumInteritemSpacing: CGFloat = 1.0 + let minimumLineSpacing: CGFloat = 1.0 + + let viewportWidth: CGFloat = gridLayout.size.width + + let preferredRowSize = idealHeight + + var rowIndex = -1 + for row in partition { + rowIndex += 1 + + var summedRatios: CGFloat = 0.0 + + var j = i + var n = i + row.count + + while j < n { + summedRatios += self.items[j].aspectRatio + + j += 1 + } + + var rowSize = gridLayout.size.width - (CGFloat(row.count - 1) * minimumInteritemSpacing) + + if rowIndex == partition.count - 1 { + if row.count < 2 { + rowSize = floor(viewportWidth / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing) + } else if row.count < 3 { + rowSize = floor(viewportWidth * 2.0 / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing) + } + } + + j = i + n = i + row.count + + while j < n { + let preferredAspectRatio = self.items[j].aspectRatio + + let actualSize = CGSize(width: round(rowSize / summedRatios * (preferredAspectRatio)), height: preferredRowSize) + + var frame = CGRect(x: offset.x, y: offset.y, width: actualSize.width, height: actualSize.height) + if frame.origin.x + frame.size.width >= maxWidth - 2.0 { + frame.size.width = max(1.0, maxWidth - frame.origin.x) + } + + items.append(GridNodePresentationItem(index: j, frame: frame)) + + offset.x += actualSize.width + minimumInteritemSpacing + previousItemSize = actualSize.height + contentMaxValueInScrollDirection = frame.maxY + + j += 1 + } + + if row.count > 0 { + offset = CGPoint(x: 0.0, y: offset.y + previousItemSize + minimumLineSpacing) + } + + i += row.count + } + contentSize = CGSize(width: gridLayout.size.width, height: contentMaxValueInScrollDirection) } return GridNodeItemLayout(contentSize: contentSize, items: items, sections: sections) @@ -446,15 +568,17 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up var transition: ContainedViewLayoutTransition = .immediate let contentOffset: CGPoint - switch stationaryItems { + var updatedStationaryItems = stationaryItems + if scrollToItem != nil { + updatedStationaryItems = .none + } + switch updatedStationaryItems { case .none: if let scrollToItem = scrollToItem { let itemFrame = self.itemLayout.items[scrollToItem.index] var additionalOffset: CGFloat = 0.0 - if scrollToItem.adjustForTopInset { - additionalOffset = -gridLayout.insets.top - } else if scrollToItem.adjustForSection { + if scrollToItem.adjustForSection { var adjustForSection: GridSection? if scrollToItem.index == 0 { if let itemSection = self.items[scrollToItem.index].section { @@ -475,6 +599,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let adjustForSection = adjustForSection { additionalOffset = -adjustForSection.height } + + if scrollToItem.adjustForTopInset { + additionalOffset += -gridLayout.insets.top + } + } else if scrollToItem.adjustForTopInset { + additionalOffset = -gridLayout.insets.top } let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) @@ -518,7 +648,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var selectedContentOffset: CGPoint? for (index, itemNode) in self.itemNodes { if stationaryItemIndices.contains(index) { - let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y + //let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) break } @@ -532,7 +662,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { case .all: var selectedContentOffset: CGPoint? for (index, itemNode) in self.itemNodes { - let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y + //let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) break } @@ -548,6 +678,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let upperDisplayBound = contentOffset.y + self.gridLayout.size.height + self.gridLayout.preloadSize var presentationItems: [GridNodePresentationItem] = [] + + var validSections = Set() for item in self.itemLayout.items { if item.frame.origin.y < lowerDisplayBound { continue @@ -556,12 +688,19 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { break } presentationItems.append(item) + if self.floatingSections { + if let section = self.items[item.index].section { + validSections.insert(WrappedGridSection(section)) + } + } } var presentationSections: [GridNodePresentationSection] = [] for section in self.itemLayout.sections { if section.frame.origin.y < lowerDisplayBound { - continue + if !validSections.contains(WrappedGridSection(section.section)) { + continue + } } if section.frame.origin.y + section.frame.size.height > upperDisplayBound { break @@ -575,7 +714,21 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?) { + private func lowestSectionNode() -> ASDisplayNode? { + var lowestHeaderNode: ASDisplayNode? + var lowestHeaderNodeIndex: Int? + for (_, headerNode) in self.sectionNodes { + if let index = self.subnodes.index(of: headerNode) { + if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! { + lowestHeaderNodeIndex = index + lowestHeaderNode = headerNode + } + } + } + return lowestHeaderNode + } + + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, completion: (GridNodeDisplayedItemRange) -> Void) { var previousItemFrames: ([WrappedGridItemNode: CGRect])? switch presentationLayoutTransition.transition { case .animated: @@ -603,29 +756,44 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } applyingContentOffset = false + let lowestSectionNode: ASDisplayNode? = self.lowestSectionNode() + var existingItemIndices = Set() for item in presentationLayoutTransition.layout.items { existingItemIndices.insert(item.index) if let itemNode = self.itemNodes[item.index] { - itemNode.frame = item.frame + if itemNode.frame != item.frame { + itemNode.frame = item.frame + } } else { let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout) itemNode.frame = item.frame - self.addItemNode(index: item.index, itemNode: itemNode) + self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) } } var existingSections = Set() - for section in presentationLayoutTransition.layout.sections { + for i in 0 ..< presentationLayoutTransition.layout.sections.count { + let section = presentationLayoutTransition.layout.sections[i] + let wrappedSection = WrappedGridSection(section.section) existingSections.insert(wrappedSection) + var sectionFrame = section.frame + if self.floatingSections { + var maxY = CGFloat.greatestFiniteMagnitude + if i != presentationLayoutTransition.layout.sections.count - 1 { + maxY = presentationLayoutTransition.layout.sections[i + 1].frame.minY - sectionFrame.height + } + sectionFrame.origin.y = max(sectionFrame.minY, min(maxY, presentationLayoutTransition.layout.contentOffset.y + presentationLayoutTransition.layout.layout.insets.top)) + } + if let sectionNode = self.sectionNodes[wrappedSection] { - sectionNode.frame = section.frame + sectionNode.frame = sectionFrame } else { let sectionNode = section.section.node() - sectionNode.frame = section.frame + sectionNode.frame = sectionFrame self.addSectionNode(section: wrappedSection, sectionNode: sectionNode) } } @@ -698,7 +866,6 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) } for (wrappedSection, sectionNode) in self.sectionNodes where existingSections.contains(wrappedSection) { - let position = sectionNode.layer.position sectionNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) } @@ -777,16 +944,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + completion(self.displayedItemRange()) + + self.updateItemNodeVisibilititesAndScrolling() + if let visibleItemsUpdated = self.visibleItemsUpdated { if presentationLayoutTransition.layout.items.count != 0 { let topIndex = presentationLayoutTransition.layout.items.first!.index let bottomIndex = presentationLayoutTransition.layout.items.last!.index var topVisible: (Int, GridItem) = (topIndex, self.items[topIndex]) - var bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) + let bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) let lowerDisplayBound = presentationLayoutTransition.layout.contentOffset.y - let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height + //let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height for item in presentationLayoutTransition.layout.items { if lowerDisplayBound.isLess(than: item.frame.maxY) { @@ -816,11 +987,15 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func addItemNode(index: Int, itemNode: GridItemNode) { + private func addItemNode(index: Int, itemNode: GridItemNode, lowestSectionNode: ASDisplayNode?) { assert(self.itemNodes[index] == nil) self.itemNodes[index] = itemNode if itemNode.supernode == nil { - self.addSubnode(itemNode) + if let lowestSectionNode = lowestSectionNode { + self.insertSubnode(itemNode, belowSubnode: lowestSectionNode) + } else { + self.addSubnode(itemNode) + } } } @@ -848,6 +1023,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + private func updateItemNodeVisibilititesAndScrolling() { + let visibleRect = self.scrollView.bounds + let isScrolling = self.scrollView.isDragging || self.scrollView.isDecelerating + for (_, itemNode) in self.itemNodes { + let visible = itemNode.frame.intersects(visibleRect) + if itemNode.isVisibleInGrid != visible { + itemNode.isVisibleInGrid = visible + } + if itemNode.isGridScrolling != isScrolling { + itemNode.isGridScrolling = isScrolling + } + } + } + public func forEachItemNode(_ f: (ASDisplayNode) -> Void) { for (_, node) in self.itemNodes { f(node) @@ -872,4 +1061,127 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { f(row) } } + + public func itemNodeAtPoint(_ point: CGPoint) -> ASDisplayNode? { + for (_, node) in self.itemNodes { + if node.frame.contains(point) { + return node + } + } + return nil + } } + +private func NH_LP_TABLE_LOOKUP(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int) -> Int { + return table[i * rowsize + j] +} + +private func NH_LP_TABLE_LOOKUP_SET(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int, _ value: Int) { + table[i * rowsize + j] = value +} + +private func linearPartitionTable(_ weights: [Int], numberOfPartitions: Int) -> [Int] { + let n = weights.count + let k = numberOfPartitions + + let tableSize = n * k; + var tmpTable = Array(repeatElement(0, count: tableSize)) + + let solutionSize = (n - 1) * (k - 1) + var solution = Array(repeatElement(0, count: solutionSize)) + + for i in 0 ..< n { + let offset = i != 0 ? NH_LP_TABLE_LOOKUP(&tmpTable, i - 1, 0, k) : 0 + NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, 0, k, Int(weights[i]) + offset) + } + + for j in 0 ..< k { + NH_LP_TABLE_LOOKUP_SET(&tmpTable, 0, j, k, Int(weights[0])) + } + + for i in 1 ..< n { + for j in 1 ..< k { + var currentMin = 0 + var minX = Int.max + + for x in 0 ..< i { + let c1 = NH_LP_TABLE_LOOKUP(&tmpTable, x, j - 1, k) + let c2 = NH_LP_TABLE_LOOKUP(&tmpTable, i, 0, k) - NH_LP_TABLE_LOOKUP(&tmpTable, x, 0, k) + let cost = max(c1, c2) + + if x == 0 || cost < currentMin { + currentMin = cost; + minX = x + } + } + + NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, j, k, currentMin) + NH_LP_TABLE_LOOKUP_SET(&solution, i - 1, j - 1, k - 1, minX) + } + } + + return solution +} + +private func linearPartitionForWeights(_ weights: [Int], numberOfPartitions: Int) -> [[Int]] { + var n = weights.count + var k = numberOfPartitions + + if k <= 0 { + return [] + } + + if k >= n { + var partition: [[Int]] = [] + for weight in weights { + partition.append([weight]) + } + return partition + } + + if n == 1 { + return [weights] + } + + var solution = linearPartitionTable(weights, numberOfPartitions: numberOfPartitions) + let solutionRowSize = numberOfPartitions - 1 + + k = k - 2; + n = n - 1; + + var answer: [[Int]] = [] + + while k >= 0 { + if n < 1 { + answer.insert([], at: 0) + } else { + var currentAnswer: [Int] = [] + + var i = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) + 1 + let range = n + 1 + while i < range { + currentAnswer.append(weights[i]) + i += 1 + } + + answer.insert(currentAnswer, at: 0) + + n = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) + } + + k = k - 1 + } + + var currentAnswer: [Int] = [] + var i = 0 + let range = n + 1 + while i < range { + currentAnswer.append(weights[i]) + i += 1 + } + + answer.insert(currentAnswer, at: 0) + + return answer +} + diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index eec8e3ada7..2a3bd70b83 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -126,7 +126,7 @@ open class LegacyPresentedController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } - override open func dismiss() { + override open func dismiss(completion: (() -> Void)? = nil) { switch self.presentation { case .modal: self.controllerNode.animateModalOut { [weak self] in @@ -135,7 +135,7 @@ open class LegacyPresentedController: ViewController { } else if let controller = self?.legacyController as? TGNavigationController { controller.didDismiss() }*/ - self?.presentingViewController?.dismiss(animated: false, completion: nil) + self?.presentingViewController?.dismiss(animated: false, completion: completion) } case .custom: /*if let controller = self.legacyController as? TGViewController { @@ -143,7 +143,7 @@ open class LegacyPresentedController: ViewController { } else if let controller = self.legacyController as? TGNavigationController { controller.didDismiss() }*/ - self.presentingViewController?.dismiss(animated: false, completion: nil) + self.presentingViewController?.dismiss(animated: false, completion: completion) } } } diff --git a/Display/ListView.swift b/Display/ListView.swift index a1536f9862..12a64822e1 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -172,7 +172,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func reportStackTrace(stack: String!, withSlide slide: String!) { NSLog("reportStackTrace stack: \(stack)\n\nslide: \(slide)") } - + override public init() { class DisplayLinkProxy: NSObject { weak var target: ListView? @@ -459,7 +459,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemHeaders() - for (headerId, headerNode) in self.itemHeaderNodes { + for (_, headerNode) in self.itemHeaderNodes { //let position = headerNode.position //headerNode.position = CGPoint(x: position.x, y: position.y - deltaY) @@ -490,6 +490,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateVisibleContentOffset() self.updateVisibleItemRange() + self.updateItemNodesVisibilities() //CATransaction.commit() } @@ -2104,6 +2105,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.updateItemNodesVisibilities() + self.updateScroller() self.setNeedsAnimations() @@ -2117,6 +2120,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } else { self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemNodesVisibilities() if animated { self.setNeedsAnimations() @@ -2280,7 +2284,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) } - var currentIds = Set(self.itemHeaderNodes.keys) + let currentIds = Set(self.itemHeaderNodes.keys) for id in currentIds.subtracting(visibleHeaderNodes) { if let headerNode = self.itemHeaderNodes.removeValue(forKey: id) { headerNode.removeFromSupernode() @@ -2288,6 +2292,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func updateItemNodesVisibilities() { + let visibilityRect = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height - self.insets.top - self.insets.bottom)) + for itemNode in self.itemNodes { + let itemFrame = itemNode.apparentFrame + var visibility: ListViewItemNodeVisibility = .none + if visibilityRect.intersects(itemFrame) { + visibility = .visible + } + if visibility != itemNode.visibility { + itemNode.visibility = visibility + } + } + } + private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { var index = -1 let count = self.itemNodes.count diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 59580cf9c8..40bf158e44 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -52,6 +52,12 @@ public struct ListViewItemNodeLayout { } } +public enum ListViewItemNodeVisibility { + case none + case nearlyVisible + case visible +} + open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? @@ -85,6 +91,8 @@ open class ListViewItemNode: ASDisplayNode { public final var canBeUsedAsScrollToItemAnchor: Bool = true + open var visibility: ListViewItemNodeVisibility = .none + open var canBeSelected: Bool { return true } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift new file mode 100644 index 0000000000..d2d78ae42a --- /dev/null +++ b/Display/NativeWindowHostView.swift @@ -0,0 +1,149 @@ +import Foundation +import SwiftSignalKit + +private let defaultOrientations: UIInterfaceOrientationMask = { + if UIDevice.current.userInterfaceIdiom == .pad { + return .all + } else { + return .allButUpsideDown + } +}() + +private class WindowRootViewController: UIViewController { + var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? + var orientations: UIInterfaceOrientationMask = defaultOrientations { + didSet { + if oldValue != self.orientations { + if self.orientations == .portrait { + if UIDevice.current.orientation != .portrait { + let value = UIInterfaceOrientation.portrait.rawValue + UIDevice.current.setValue(value, forKey: "orientation") + } + } else { + UIViewController.attemptRotationToDeviceOrientation() + } + } + } + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .default + } + + override var prefersStatusBarHidden: Bool { + return false + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return orientations + } +} + +private final class NativeWindow: UIWindow, WindowHost { + var updateSize: ((CGSize) -> Void)? + var layoutSubviewsEvent: (() -> Void)? + var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? + var updateToInterfaceOrientation: (() -> Void)? + var presentController: ((ViewController) -> Void)? + var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? + + override var frame: CGRect { + get { + return super.frame + } set(value) { + let sizeUpdated = super.frame.size != value.size + super.frame = value + + if sizeUpdated { + self.updateSize?(value.size) + } + } + } + + override var bounds: CGRect { + get { + return super.bounds + } + set(value) { + let sizeUpdated = super.bounds.size != value.size + super.bounds = value + + if sizeUpdated { + self.updateSize?(value.size) + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layoutSubviewsEvent?() + } + + override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { + self.updateIsUpdatingOrientationLayout?(true) + super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) + self.updateIsUpdatingOrientationLayout?(false) + + self.updateToInterfaceOrientation?() + } + + func present(_ controller: ViewController) { + self.presentController?(controller) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.hitTestImpl?(point, event) + } +} + +public func nativeWindowHostView() -> WindowHostView { + let window = NativeWindow(frame: UIScreen.main.bounds) + + let rootViewController = WindowRootViewController() + window.rootViewController = rootViewController + rootViewController.viewWillAppear(false) + rootViewController.viewDidAppear(false) + rootViewController.view.isHidden = true + + let hostView = WindowHostView(view: window, isRotating: { + return window.isRotating() + }, updateSupportedInterfaceOrientations: { orientations in + rootViewController.orientations = orientations + }) + + window.updateSize = { [weak hostView] size in + hostView?.updateSize?(size) + } + + window.layoutSubviewsEvent = { [weak hostView] in + hostView?.layoutSubviews?() + } + + window.updateIsUpdatingOrientationLayout = { [weak hostView] value in + hostView?.isUpdatingOrientationLayout = value + } + + window.updateToInterfaceOrientation = { [weak hostView] in + hostView?.updateToInterfaceOrientation?() + } + + window.presentController = { [weak hostView] controller in + hostView?.present?(controller) + } + + window.hitTestImpl = { [weak hostView] point, event in + return hostView?.hitTest?(point, event) + } + + rootViewController.presentController = { [weak hostView] controller, animated, completion in + if let strongSelf = hostView { + strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom)) + if let completion = completion { + completion() + } + } + } + + return hostView +} diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index e4fdf024b3..b3dc8370c9 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -35,7 +35,7 @@ open class NavigationBar: ASDisplayNode { open var foregroundColor: UIColor = UIColor.black { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor) } } } @@ -53,7 +53,7 @@ open class NavigationBar: ASDisplayNode { private var collapsed: Bool { get { - return self.frame.size.height < (20.0 + 44.0) + return self.frame.size.height.isLess(than: 44.0) } } @@ -173,7 +173,7 @@ open class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -544,7 +544,7 @@ open class NavigationBar: ASDisplayNode { public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? { if let title = self.title { let node = ASTextNode() - node.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: foregroundColor) + node.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: foregroundColor) return node } else { return nil diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 2fdffca162..d03e82a29b 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -504,4 +504,17 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } return false } + + public final var window: WindowHost? { + if let window = self.view.window as? WindowHost { + return window + } else if let superwindow = self.view.window { + for subview in superwindow.subviews { + if let subview = subview as? WindowHost { + return subview + } + } + } + return nil + } } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 02cb60bf55..5b94f1113e 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -33,6 +33,8 @@ final class PresentationContext { private var presentationDisposables = DisposableSet() + var topLevelSubview: UIView? + public func present(_ controller: ViewController) { let controllerReady = controller.ready.get() |> filter({ $0 }) @@ -40,7 +42,7 @@ final class PresentationContext { |> deliverOnMainQueue |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) - if let view = self.view, let initialLayout = self.layout { + if let _ = self.view, let initialLayout = self.layout { controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) @@ -60,10 +62,18 @@ final class PresentationContext { controller.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) - view.addSubview(controller.view) + if let topLevelSubview = strongSelf.topLevelSubview { + view.insertSubview(controller.view, belowSubview: topLevelSubview) + } else { + view.addSubview(controller.view) + } controller.containerLayoutUpdated(layout, transition: .immediate) } else { - view.addSubview(controller.view) + if let topLevelSubview = strongSelf.topLevelSubview { + view.insertSubview(controller.view, belowSubview: topLevelSubview) + } else { + view.addSubview(controller.view) + } } controller.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() @@ -115,7 +125,11 @@ final class PresentationContext { if let view = self.view, let layout = self.layout { for controller in self.controllers { controller.viewWillAppear(false) - view.addSubview(controller.view) + if let topLevelSubview = self.topLevelSubview { + view.insertSubview(controller.view, belowSubview: topLevelSubview) + } else { + view.addSubview(controller.view) + } controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) controller.containerLayoutUpdated(layout, transition: .immediate) controller.viewDidAppear(false) @@ -141,4 +155,14 @@ final class PresentationContext { } return nil } + + func combinedSupportedOrientations() -> UIInterfaceOrientationMask { + var mask: UIInterfaceOrientationMask = .all + + for controller in self.controllers { + mask = mask.intersection(controller.supportedInterfaceOrientations) + } + + return mask + } } diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index c5aa85610f..6bc02aec96 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -27,7 +27,7 @@ public class StatusBarSurface { public class StatusBar: ASDisplayNode { public var statusBarStyle: StatusBarStyle = .Black { didSet { - if self.statusBarStyle != statusBarStyle { + if self.statusBarStyle != oldValue { self.layer.invalidateUpTheTree() } } @@ -40,7 +40,7 @@ public class StatusBar: ASDisplayNode { return UITracingLayerView() }, didLoad: nil) - self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: Window.statusBarTracingTag)) + self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar)) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 69af044c0b..f75b236c13 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -18,7 +18,13 @@ private func mapStatusBar(_ statusBar: StatusBar) -> MappedStatusBar { } private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurface { - return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface) + var statusBars: [MappedStatusBar] = [] + for statusBar in surface.statusBars { + if statusBar.statusBarStyle != .Ignore { + statusBars.append(mapStatusBar(statusBar)) + } + } + return MappedStatusBarSurface(statusBars: statusBars, surface: surface) } private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface) -> MappedStatusBarSurface { @@ -70,12 +76,36 @@ class StatusBarManager { return } - var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) }) + var mappedSurfaces: [MappedStatusBarSurface] = [] + var mapIndex = 0 + var doNotOptimize = false + for surface in self.surfaces { + inner: for statusBar in surface.statusBars { + if statusBar.statusBarStyle == .Hide { + doNotOptimize = true + break inner + } + } + + let mapped = mappedSurface(surface) + + if doNotOptimize { + mappedSurfaces.append(mapped) + } else { + mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped)) + } + mapIndex += 1 + } var reduceSurfaces = true var reduceSurfacesStatusBarStyleAndAlpha: (StatusBarStyle, CGFloat)? + var reduceIndex = 0 outer: for surface in mappedSurfaces { for mappedStatusBar in surface.statusBars { + if reduceIndex == 0 && mappedStatusBar.style == .Hide { + reduceSurfaces = false + break outer + } if mappedStatusBar.frame.origin.equalTo(CGPoint()) { let statusBarAlpha = mappedStatusBar.statusBar?.alpha ?? 1.0 if let reduceSurfacesStatusBarStyleAndAlpha = reduceSurfacesStatusBarStyleAndAlpha { @@ -92,6 +122,7 @@ class StatusBarManager { } } } + reduceIndex += 1 } if reduceSurfaces { @@ -112,16 +143,19 @@ class StatusBarManager { var globalStatusBar: (StatusBarStyle, CGFloat)? var coveredIdentity = false + var statusBarIndex = 0 for i in 0 ..< mappedSurfaces.count { for mappedStatusBar in mappedSurfaces[i].statusBars { if let statusBar = mappedStatusBar.statusBar { if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() { if !coveredIdentity { - coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) - if i == 0 && globalStatusBar == nil { - globalStatusBar = (mappedStatusBar.style, statusBar.alpha) - } else { - visibleStatusBars.append(statusBar) + if statusBar.statusBarStyle != .Hide { + coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) + if statusBarIndex == 0 && globalStatusBar == nil { + globalStatusBar = (mappedStatusBar.style, statusBar.alpha) + } else { + visibleStatusBars.append(statusBar) + } } } } else { @@ -130,11 +164,12 @@ class StatusBarManager { } else { if !coveredIdentity { coveredIdentity = true - if i == 0 && globalStatusBar == nil { + if statusBarIndex == 0 && globalStatusBar == nil { globalStatusBar = (mappedStatusBar.style, 1.0) } } } + statusBarIndex += 1 } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 0f1e520996..e77e3d58e2 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -4,6 +4,8 @@ import AsyncDisplayKit public enum StatusBarStyle { case Black case White + case Ignore + case Hide } private enum StatusBarItemType { @@ -142,7 +144,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseColor: UInt32 switch style { - case .Black: + case .Black, .Ignore, .Hide: baseColor = 0x000000 case .White: baseColor = 0xffffff @@ -193,7 +195,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseColor: UInt32 switch style { - case .Black: + case .Black, .Ignore, .Hide: baseColor = 0x000000 case .White: baseColor = 0xffffff diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index c1048ff36c..0a1a3c6797 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -27,6 +27,8 @@ open class SwitchNode: ASDisplayNode { override open func didLoad() { super.didLoad() + (self.view as! UISwitch).backgroundColor = .white + (self.view as! UISwitch).setOn(self._isOn, animated: false) (self.view as! UISwitch).addTarget(self, action: #selector(switchValueChanged(_:)), for: .valueChanged) diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h index dd2807b5ed..292a972750 100644 --- a/Display/UIKitUtils.h +++ b/Display/UIKitUtils.h @@ -8,6 +8,6 @@ @end CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath); -CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity); +CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping); CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t); diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index e236dab2c7..195d4a7dcf 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -41,11 +41,11 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { return springAnimation; } -CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity) { +CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) { CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath]; springAnimation.mass = 5.0f; springAnimation.stiffness = 900.0f; - springAnimation.damping = 88.0f; + springAnimation.damping = damping; static bool canSetInitialVelocity = true; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index f49fafaef0..999fe5d312 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -118,11 +118,15 @@ public extension UIImage { private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { let view = UIView() + //view.layer.isHidden = layer.isHidden + view.layer.opacity = layer.opacity view.layer.contents = layer.contents view.layer.contentsRect = layer.contentsRect view.layer.contentsScale = layer.contentsScale view.layer.contentsCenter = layer.contentsCenter view.layer.contentsGravity = layer.contentsGravity + view.layer.masksToBounds = layer.masksToBounds + view.layer.cornerRadius = layer.cornerRadius if let sublayers = layer.sublayers { for sublayer in sublayers { let subtree = makeSubtreeSnapshot(layer: sublayer) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index daf8347c18..0dcfd25ff1 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -33,6 +33,11 @@ open class ViewControllerPresentationArguments { private var containerLayout = ContainerViewLayout() private let presentationContext: PresentationContext + public final var supportedOrientations: UIInterfaceOrientationMask = .all + override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return self.supportedOrientations + } + public private(set) var presentationArguments: Any? private var _displayNode: ASDisplayNode? @@ -137,9 +142,9 @@ open class ViewControllerPresentationArguments { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0)) - if statusBarHeight.isLessThanOrEqualTo(0.0) { - navigationBarFrame.origin.y -= 20.0 - navigationBarFrame.size.height = 20.0 + 32.0 + if layout.statusBarHeight == nil { + //navigationBarFrame.origin.y -= 20.0 + navigationBarFrame.size.height = 44.0 } if !self.displayNavigationBar { @@ -169,7 +174,7 @@ open class ViewControllerPresentationArguments { open func displayNodeDidLoad() { if let layer = self.displayNode.layer as? CATracingLayer { - layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: Window.keyboardTracingTag)) + layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard)) } self.updateScrollToTopView() } @@ -195,7 +200,14 @@ open class ViewControllerPresentationArguments { } override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { - preconditionFailure("use present(_:in)") + super.present(viewControllerToPresent, animated: flag, completion: completion) + return + + if let controller = viewControllerToPresent as? ViewController { + self.present(controller, in: .window) + } else { + preconditionFailure("use present(_:in) for \(viewControllerToPresent)") + } } override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { @@ -206,12 +218,12 @@ open class ViewControllerPresentationArguments { } } - private var window: Window? { - if let window = self.view.window as? Window { + public final var window: WindowHost? { + if let window = self.view.window as? WindowHost { return window } else if let superwindow = self.view.window { for subview in superwindow.subviews { - if let subview = subview as? Window { + if let subview = subview as? WindowHost { return subview } } @@ -247,6 +259,6 @@ open class ViewControllerPresentationArguments { super.viewDidAppear(animated) } - open func dismiss() { + open func dismiss(completion: (() -> Void)? = nil) { } } diff --git a/Display/Window.swift b/Display/WindowContent.swift similarity index 73% rename from Display/Window.swift rename to Display/WindowContent.swift index 60c55e5a8a..386d85fd3c 100644 --- a/Display/Window.swift +++ b/Display/WindowContent.swift @@ -11,12 +11,6 @@ private class WindowRootViewController: UIViewController { override var prefersStatusBarHidden: Bool { return false } - - /*override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { - if let presentController = self.presentController { - presentController(viewControllerToPresent, flag, completion) - } - }*/ } private struct WindowLayout: Equatable { @@ -115,9 +109,37 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: inputHeight) } -public class Window: UIWindow { - public static let statusBarTracingTag: Int32 = 0 - public static let keyboardTracingTag: Int32 = 1 +public final class WindowHostView { + public let view: UIView + public let isRotating: () -> Bool + + let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void + + var present: ((ViewController) -> Void)? + var updateSize: ((CGSize) -> Void)? + var layoutSubviews: (() -> Void)? + var updateToInterfaceOrientation: (() -> Void)? + var isUpdatingOrientationLayout = false + var hitTest: ((CGPoint, UIEvent?) -> UIView?)? + + init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void) { + self.view = view + self.isRotating = isRotating + self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations + } +} + +public struct WindowTracingTags { + public static let statusBar: Int32 = 0 + public static let keyboard: Int32 = 1 +} + +public protocol WindowHost { + func present(_ controller: ViewController) +} + +public class Window1 { + public let hostView: WindowHostView private let statusBarHost: StatusBarHost? private let statusBarManager: StatusBarManager? @@ -128,15 +150,15 @@ public class Window: UIWindow { private var windowLayout: WindowLayout private var updatingLayout: UpdatingLayout? - public var isUpdatingOrientationLayout = false - private let presentationContext: PresentationContext private var tracingStatusBarsInvalidated = false private var statusBarHidden = false - public init(frame: CGRect, statusBarHost: StatusBarHost?) { + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { + self.hostView = hostView + self.statusBarHost = statusBarHost let statusBarHeight: CGFloat if let statusBarHost = statusBarHost { @@ -156,15 +178,33 @@ public class Window: UIWindow { minimized = false } - self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized) + self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized) self.presentationContext = PresentationContext() - super.init(frame: frame) + self.hostView.present = { [weak self] controller in + self?.present(controller) + } - self.layer.setInvalidateTracingSublayers { [weak self] in + self.hostView.updateSize = { [weak self] size in + self?.updateSize(size) + } + + self.hostView.view.layer.setInvalidateTracingSublayers { [weak self] in self?.invalidateTracingStatusBars() } + self.hostView.layoutSubviews = { [weak self] in + self?.layoutSubviews() + } + + self.hostView.updateToInterfaceOrientation = { [weak self] in + self?.updateToInterfaceOrientation() + } + + self.hostView.hitTest = { [weak self] point, event in + return self?.hitTest(point, with: event) + } + self.keyboardManager?.minimizedUpdated = { [weak self] in if let strongSelf = self { strongSelf.updateLayout { current in @@ -173,24 +213,9 @@ public class Window: UIWindow { } } - self.presentationContext.view = self + self.presentationContext.view = self.hostView.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - let rootViewController = WindowRootViewController() - super.rootViewController = rootViewController - rootViewController.viewWillAppear(false) - rootViewController.viewDidAppear(false) - rootViewController.view.isHidden = true - - rootViewController.presentController = { [weak self] controller, animated, completion in - if let strongSelf = self { - strongSelf.present(LegacyPresentedController(legacyController: controller, presentation: .custom)) - if let completion = completion { - completion() - } - } - } - self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? 20.0) @@ -205,7 +230,7 @@ public class Window: UIWindow { let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect() let keyboardHeight = max(0.0, UIScreen.main.bounds.size.height - keyboardFrame.minY) var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 - if duration > DBL_EPSILON { + if duration > Double.ulpOfOne { duration = 0.5 } let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7 @@ -237,54 +262,36 @@ public class Window: UIWindow { private func invalidateTracingStatusBars() { self.tracingStatusBarsInvalidated = true - self.setNeedsLayout() + self.hostView.view.setNeedsLayout() } - public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for view in self.hostView.view.subviews.reversed() { + if NSStringFromClass(type(of: view)) == "UITransitionView" { + if let result = view.hitTest(point, with: event) { + return result + } + } + } + + if let result = self._topLevelOverlayController?.view.hitTest(point, with: event) { + return result + } + if let result = self.presentationContext.hitTest(point, with: event) { return result } return self.viewController?.view.hitTest(point, with: event) } - public override var frame: CGRect { - get { - return super.frame - } - set(value) { - let sizeUpdated = super.frame.size != value.size - super.frame = value - - if sizeUpdated { - let transition: ContainedViewLayoutTransition - if self.isRotating() { - transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) - } else { - transition = .immediate - } - self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) } - } - } - } - - public override var bounds: CGRect { - get { - return super.frame - } - set(value) { - let sizeUpdated = super.bounds.size != value.size - super.bounds = value - - if sizeUpdated { - let transition: ContainedViewLayoutTransition - if self.isRotating() { - transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) - } else { - transition = .immediate - } - self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) } - } + func updateSize(_ value: CGSize) { + let transition: ContainedViewLayoutTransition + if self.hostView.isRotating() { + transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) + } else { + transition = .immediate } + self.updateLayout { $0.update(size: value, transition: transition, overrideTransition: true) } } private var _rootController: ContainableController? @@ -301,14 +308,33 @@ public class Window: UIWindow { if let rootController = self._rootController { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - self.addSubview(rootController.view) + self.hostView.view.addSubview(rootController.view) } } } - override public func layoutSubviews() { - super.layoutSubviews() - + private var _topLevelOverlayController: ContainableController? + public var topLevelOverlayController: ContainableController? { + get { + return _topLevelOverlayController + } + set(value) { + if let topLevelOverlayController = self._topLevelOverlayController { + topLevelOverlayController.view.removeFromSuperview() + } + self._topLevelOverlayController = value + + if let topLevelOverlayController = self._topLevelOverlayController { + topLevelOverlayController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + + self.hostView.view.addSubview(topLevelOverlayController.view) + } + + self.presentationContext.topLevelSubview = self._topLevelOverlayController?.view + } + } + + private func layoutSubviews() { if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager { self.tracingStatusBarsInvalidated = false @@ -316,7 +342,7 @@ public class Window: UIWindow { statusBarManager.surfaces = [] } else { var statusBarSurfaces: [StatusBarSurface] = [] - for layers in self.layer.traceableLayerSurfaces(withTag: Window.statusBarTracingTag) { + for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { let surface = StatusBarSurface() for layer in layers { let traceableInfo = layer.traceableInfo() @@ -326,12 +352,12 @@ public class Window: UIWindow { } statusBarSurfaces.append(surface) } - self.layer.adjustTraceableLayerTransforms(CGSize()) + self.hostView.view.layer.adjustTraceableLayerTransforms(CGSize()) statusBarManager.surfaces = statusBarSurfaces } var keyboardSurfaces: [KeyboardSurface] = [] - for layers in self.layer.traceableLayerSurfaces(withTag: Window.keyboardTracingTag) { + for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.keyboard) { for layer in layers { if let view = layer.delegate as? UITracingLayerView { keyboardSurfaces.append(KeyboardSurface(host: view)) @@ -339,22 +365,23 @@ public class Window: UIWindow { } } keyboardManager.surfaces = keyboardSurfaces + self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) } - if !Window.isDeviceRotating() { - if !self.isUpdatingOrientationLayout { + if !UIWindow.isDeviceRotating() { + if !self.hostView.isUpdatingOrientationLayout { self.commitUpdatingLayout() } else { self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in if let strongSelf = self { - strongSelf.setNeedsLayout() + strongSelf.hostView.view.setNeedsLayout() } }) } } else { - Window.addPostDeviceOrientationDidChange({ [weak self] in + UIWindow.addPostDeviceOrientationDidChange({ [weak self] in if let strongSelf = self { - strongSelf.setNeedsLayout() + strongSelf.hostView.view.setNeedsLayout() } }) } @@ -362,11 +389,7 @@ public class Window: UIWindow { var postUpdateToInterfaceOrientationBlocks: [(Void) -> Void] = [] - override public func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { - self.isUpdatingOrientationLayout = true - super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) - self.isUpdatingOrientationLayout = false - + private func updateToInterfaceOrientation() { let blocks = self.postUpdateToInterfaceOrientationBlocks self.postUpdateToInterfaceOrientationBlocks = [] for f in blocks { @@ -383,14 +406,14 @@ public class Window: UIWindow { self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) } update(&self.updatingLayout!) - self.setNeedsLayout() + self.hostView.view.setNeedsLayout() } private func commitUpdatingLayout() { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil if updatingLayout.layout != self.windowLayout { - var statusBarHeight: CGFloat + var statusBarHeight: CGFloat? if let statusBarHost = self.statusBarHost { statusBarHeight = statusBarHost.statusBarFrame.size.height } else { @@ -398,14 +421,14 @@ public class Window: UIWindow { } let statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { - statusBarHeight = 0.0 + statusBarHeight = nil self.statusBarHidden = true } else { self.statusBarHidden = false } if self.statusBarHidden != statusBarWasHidden { self.tracingStatusBarsInvalidated = true - self.setNeedsLayout() + self.hostView.view.setNeedsLayout() } self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized) From 38f701bfc984c023688a39dee16a5356e0d6d0e6 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 16 Jun 2017 12:17:02 +0300 Subject: [PATCH 040/245] no message --- Display.xcodeproj/project.pbxproj | 36 ++-- .../xcschemes/Display.xcscheme | 2 +- .../xcschemes/DisplayTests.xcscheme | 2 +- Display/ActionSheetButtonItem.swift | 2 +- Display/ActionSheetCheckboxItem.swift | 4 +- Display/ActionSheetController.swift | 3 +- Display/ActionSheetTextItem.swift | 69 +++++++ Display/AlertController.swift | 4 +- Display/CAAnimationUtils.swift | 7 +- Display/CASeeThroughTracingLayer.h | 11 + Display/CASeeThroughTracingLayer.m | 61 ++++++ Display/CATracingLayer.m | 8 +- Display/ContainerViewLayout.swift | 41 +++- Display/ContextMenuContainerNode.swift | 2 +- Display/ContextMenuController.swift | 3 +- Display/Display.h | 1 + Display/GenerateImage.swift | 6 +- Display/GridNode.swift | 39 ++-- Display/HighlightTrackingButton.swift | 33 ++- Display/HighlightableButton.swift | 26 ++- Display/KeyboardManager.swift | 2 +- Display/LegacyPresentedController.swift | 3 +- Display/LegacyPresentedControllerNode.swift | 1 - Display/ListView.swift | 23 +-- Display/ListViewAnimation.swift | 15 +- Display/ListViewItem.swift | 5 - Display/ListViewItemHeader.swift | 22 +- Display/ListViewItemNode.swift | 25 ++- Display/NavigationBackButtonNode.swift | 10 +- Display/NavigationBar.swift | 173 ++++++++++++---- Display/NavigationBarContentNode.swift | 6 + Display/NavigationButtonNode.swift | 2 +- Display/NavigationController.swift | 24 ++- Display/NavigationControllerProxy.m | 3 +- Display/NavigationTransitionCoordinator.swift | 2 +- Display/PresentableViewController.swift | 6 - Display/StatusBar.swift | 195 ++++++++++++++++-- Display/StatusBarManager.swift | 63 ++++-- Display/StatusBarProxyNode.swift | 6 +- Display/SwitchNode.swift | 27 ++- Display/TabBarContollerNode.swift | 12 +- Display/TabBarController.swift | 45 +++- Display/TabBarNode.swift | 110 ++++++++-- Display/TextAlertController.swift | 8 +- Display/UIBarButtonItem+Proxy.h | 2 + Display/UIBarButtonItem+Proxy.m | 13 ++ Display/UIKitUtils.m | 2 + Display/UIKitUtils.swift | 42 +++- Display/UINavigationItem+Proxy.h | 12 ++ Display/UINavigationItem+Proxy.m | 103 +++++++++ Display/UniversalMasterController.swift | 22 -- Display/ViewController.swift | 28 ++- Display/ViewControllerTracingNode.swift | 34 +++ Display/WindowContent.swift | 155 +++++++++----- 54 files changed, 1241 insertions(+), 320 deletions(-) create mode 100644 Display/ActionSheetTextItem.swift create mode 100644 Display/CASeeThroughTracingLayer.h create mode 100644 Display/CASeeThroughTracingLayer.m create mode 100644 Display/NavigationBarContentNode.swift delete mode 100644 Display/PresentableViewController.swift create mode 100644 Display/ViewControllerTracingNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 6cb0ea93f5..634b1f8735 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -8,7 +8,6 @@ /* Begin PBXBuildFile section */ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; - D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; @@ -35,9 +34,10 @@ D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DF71C96C5F200C07816 /* NSWeakReference.m */; }; D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */; }; D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */; }; + D05174B31EAA833200A1BF36 /* CASeeThroughTracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */; }; D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */; }; - D05BE4A91D1F1DDD002BD72C /* UniversalMasterController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4A81D1F1DDD002BD72C /* UniversalMasterController.swift */; }; D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */; }; D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -78,6 +78,7 @@ D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; + D08CAA7B1ED73C990000FDA8 /* ViewControllerTracingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */; }; D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; @@ -88,6 +89,8 @@ D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */; }; + D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */; }; + D0C0B59D1EE022CC000F4D2C /* NavigationBarContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */; }; D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; }; @@ -133,7 +136,6 @@ /* Begin PBXFileReference section */ D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; - D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentableViewController.swift; sourceTree = ""; }; D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = ""; }; D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; @@ -160,9 +162,10 @@ D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = ""; }; D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = ""; }; D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; + D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CASeeThroughTracingLayer.h; sourceTree = ""; }; + D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CASeeThroughTracingLayer.m; sourceTree = ""; }; D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CATracingLayer.h; sourceTree = ""; }; D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CATracingLayer.m; sourceTree = ""; }; - D05BE4A81D1F1DDD002BD72C /* UniversalMasterController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalMasterController.swift; sourceTree = ""; }; D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = ""; }; D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergedLayoutEvents.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -206,6 +209,7 @@ D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; + D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerTracingNode.swift; sourceTree = ""; }; D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; @@ -216,6 +220,8 @@ D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWindowHostView.swift; sourceTree = ""; }; + D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetTextItem.swift; sourceTree = ""; }; + D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarContentNode.swift; sourceTree = ""; }; D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTransformLayerNode.swift; sourceTree = ""; }; @@ -302,6 +308,7 @@ D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */, D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */, D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */, + D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */, ); name = "Action Sheet"; sourceTree = ""; @@ -357,14 +364,6 @@ name = Theme; sourceTree = ""; }; - D05BE4A71D1F1DCC002BD72C /* Master */ = { - isa = PBXGroup; - children = ( - D05BE4A81D1F1DDD002BD72C /* UniversalMasterController.swift */, - ); - name = Master; - sourceTree = ""; - }; D05BE4AC1D217F33002BD72C /* Utils */ = { isa = PBXGroup; children = ( @@ -373,6 +372,8 @@ D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */, D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */, D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */, + D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */, + D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */, ); name = Utils; sourceTree = ""; @@ -488,6 +489,7 @@ D05CC3091B695A9500E235A3 /* NavigationTransitionCoordinator.swift */, D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */, D05CC30A1B695A9500E235A3 /* NavigationBar.swift */, + D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */, D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */, D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */, @@ -532,12 +534,11 @@ children = ( D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */, D015F7511D1AE08D00E269B5 /* ContainableController.swift */, - D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */, D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */, D05CC2E21B69552C00E235A3 /* ViewController.swift */, + D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */, D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */, D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */, - D05BE4A71D1F1DCC002BD72C /* Master */, D081229A1D19A9EB005F7395 /* Navigation */, D015F7551D1B142300E269B5 /* Tab Bar */, D015F7561D1B465600E269B5 /* Action Sheet */, @@ -618,6 +619,7 @@ D05CC2FF1B6955D000E235A3 /* UIWindow+OrientationChange.h in Headers */, D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */, D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, + D05174B31EAA833200A1BF36 /* CASeeThroughTracingLayer.h in Headers */, D05CC3081B69575900E235A3 /* NSBag.h in Headers */, D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */, D05CC2671B69316F00E235A3 /* Display.h in Headers */, @@ -742,6 +744,7 @@ D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, + D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, @@ -763,7 +766,6 @@ D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */, D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, - D05BE4A91D1F1DDD002BD72C /* UniversalMasterController.swift in Sources */, D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */, D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */, D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, @@ -780,7 +782,6 @@ D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, - D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, @@ -806,6 +807,8 @@ D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, + D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */, + D0C0B59D1EE022CC000F4D2C /* NavigationBarContentNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, @@ -817,6 +820,7 @@ D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */, + D08CAA7B1ED73C990000FDA8 /* ViewControllerTracingNode.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme index 4b34ecd9ef..aebd30386b 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme @@ -1,6 +1,6 @@ ActionSheetItemNode { + let node = ActionSheetTextNode() + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? ActionSheetTextNode else { + assertionFailure() + return + } + + node.setItem(self) + } +} + +public class ActionSheetTextNode: ActionSheetItemNode { + public static let defaultFont: UIFont = Font.regular(13.0) + + private var item: ActionSheetTextItem? + + private let label: ASTextNode + + override public init() { + self.label = ASTextNode() + self.label.isLayerBacked = true + self.label.maximumNumberOfLines = 1 + self.label.displaysAsynchronously = false + self.label.truncationMode = .byTruncatingTail + + super.init() + + self.label.isUserInteractionEnabled = false + self.addSubnode(self.label) + } + + func setItem(_ item: ActionSheetTextItem) { + self.item = item + + let textColor = UIColor(rgb: 0x7c7c7c) + + self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: textColor) + + self.setNeedsLayout() + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + let size = self.bounds.size + + let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 20.0), height: size.height)) + self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + } +} diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 79340826bf..37bcb5f2d3 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -11,9 +11,7 @@ open class AlertController: ViewController { public init(contentNode: AlertContentNode) { self.contentNode = contentNode - super.init(navigationBar: NavigationBar()) - - self.navigationBar.isHidden = true + super.init(navigationBarTheme: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 0756b29372..b1cff306af 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -135,7 +135,12 @@ public extension CALayer { } public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) + let animation: CABasicAnimation + if #available(iOS 9.0, *) { + animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) + } else { + animation = makeSpringAnimation(keyPath) + } animation.fromValue = from animation.toValue = to animation.isRemovedOnCompletion = removeOnCompletion diff --git a/Display/CASeeThroughTracingLayer.h b/Display/CASeeThroughTracingLayer.h new file mode 100644 index 0000000000..231170d8b5 --- /dev/null +++ b/Display/CASeeThroughTracingLayer.h @@ -0,0 +1,11 @@ +#import + +@interface CASeeThroughTracingLayer : CALayer + +@property (nonatomic, copy) void (^updateRelativePosition)(CGPoint); + +@end + +@interface CASeeThroughTracingView : UIView + +@end diff --git a/Display/CASeeThroughTracingLayer.m b/Display/CASeeThroughTracingLayer.m new file mode 100644 index 0000000000..b3c01da78b --- /dev/null +++ b/Display/CASeeThroughTracingLayer.m @@ -0,0 +1,61 @@ +#import "CASeeThroughTracingLayer.h" + +@interface CASeeThroughTracingLayer () { + CGPoint _parentOffset; +} + +@end + +@implementation CASeeThroughTracingLayer + +- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { + [super addAnimation:anim forKey:key]; +} + +- (void)setFrame:(CGRect)frame { + [super setFrame:frame]; + + [self _mirrorTransformToSublayers]; +} + +- (void)setBounds:(CGRect)bounds { + [super setBounds:bounds]; + + [self _mirrorTransformToSublayers]; +} + +- (void)setPosition:(CGPoint)position { + [super setPosition:position]; + + [self _mirrorTransformToSublayers]; +} + +- (void)_mirrorTransformToSublayers { + CGRect bounds = self.bounds; + CGPoint position = self.position; + + CGPoint sublayerParentOffset = _parentOffset; + sublayerParentOffset.x += position.x - (bounds.size.width) / 2.0f; + sublayerParentOffset.y += position.y - (bounds.size.width) / 2.0f; + + for (CALayer *sublayer in self.sublayers) { + if ([sublayer isKindOfClass:[CASeeThroughTracingLayer class]]) { + ((CASeeThroughTracingLayer *)sublayer)->_parentOffset = sublayerParentOffset; + [(CASeeThroughTracingLayer *)sublayer _mirrorTransformToSublayers]; + } + } + + if (_updateRelativePosition) { + _updateRelativePosition(sublayerParentOffset); + } +} + +@end + +@implementation CASeeThroughTracingView + ++ (Class)layerClass { + return [CASeeThroughTracingLayer class]; +} + +@end diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 18b8828867..bc8d2668bb 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -37,7 +37,7 @@ static void *CATracingLayerPositionAnimationMirrorTarget = &CATracingLayerPositi return [self animationForKey:@"position"] != nil || [self animationForKey:@"bounds"] != nil; } -static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth) { +static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull layer, NSMutableDictionary *> *layersByDepth, bool skipIfNoTraceableSublayers) { bool hadTraceableSublayers = false; for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { CATracingLayerInfo *sublayerTraceableInfo = [sublayer traceableInfo]; @@ -52,10 +52,10 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull } } - if (!hadTraceableSublayers) { + if (!skipIfNoTraceableSublayers || !hadTraceableSublayers) { for (CALayer *sublayer in layer.sublayers.reverseObjectEnumerator) { if ([sublayer isKindOfClass:[CATracingLayer class]]) { - traceLayerSurfaces(tracingTag, depth + 1, sublayer, layersByDepth); + traceLayerSurfaces(tracingTag, depth + 1, sublayer, layersByDepth, hadTraceableSublayers); } } } @@ -64,7 +64,7 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull - (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag { NSMutableDictionary *> *layersByDepth = [[NSMutableDictionary alloc] init]; - traceLayerSurfaces(tracingTag, 0, self, layersByDepth); + traceLayerSurfaces(tracingTag, 0, self, layersByDepth, false); NSMutableArray *> *result = [[NSMutableArray alloc] init]; diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 134a038ed8..d1c9b3d372 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -14,21 +14,48 @@ public struct ContainerViewLayoutInsetOptions: OptionSet { public static let input = ContainerViewLayoutInsetOptions(rawValue: 1 << 1) } +public enum ContainerViewLayoutSizeClass { + case compact + case regular +} + +public struct LayoutMetrics: Equatable { + public let widthClass: ContainerViewLayoutSizeClass + public let heightClass: ContainerViewLayoutSizeClass + + public init(widthClass: ContainerViewLayoutSizeClass, heightClass: ContainerViewLayoutSizeClass) { + self.widthClass = widthClass + self.heightClass = heightClass + } + + public init() { + self.widthClass = .compact + self.heightClass = .compact + } + + public static func ==(lhs: LayoutMetrics, rhs: LayoutMetrics) -> Bool { + return lhs.widthClass == rhs.widthClass && lhs.heightClass == rhs.heightClass + } +} + public struct ContainerViewLayout: Equatable { public let size: CGSize + public let metrics: LayoutMetrics public let intrinsicInsets: UIEdgeInsets public let statusBarHeight: CGFloat? public let inputHeight: CGFloat? public init() { self.size = CGSize() + self.metrics = LayoutMetrics() self.intrinsicInsets = UIEdgeInsets() self.statusBarHeight = nil self.inputHeight = nil } - public init(size: CGSize, intrinsicInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?) { + public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?) { self.size = size + self.metrics = metrics self.intrinsicInsets = intrinsicInsets self.statusBarHeight = statusBarHeight self.inputHeight = inputHeight @@ -46,11 +73,15 @@ public struct ContainerViewLayout: Equatable { } public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) } public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight) + } + + public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout { + return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) } } @@ -59,6 +90,10 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { return false } + if lhs.metrics != rhs.metrics { + return false + } + if lhs.intrinsicInsets != rhs.intrinsicInsets { return false } diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift index c7d22be218..02c6b9f7b9 100644 --- a/Display/ContextMenuContainerNode.swift +++ b/Display/ContextMenuContainerNode.swift @@ -30,7 +30,7 @@ final class ContextMenuContainerNode: ASDisplayNode { super.init() - self.backgroundColor = UIColor(0xeaecec) + self.backgroundColor = UIColor(rgb: 0xeaecec) //self.view.addSubview(self.effectView) //self.effectView.mask = self.maskView self.view.mask = self.maskView diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 14d2eb5d55..a845ca5a34 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -23,7 +23,7 @@ public final class ContextMenuController: ViewController { public init(actions: [ContextMenuAction]) { self.actions = actions - super.init() + super.init(navigationBarTheme: nil) } required public init(coder aDecoder: NSCoder) { @@ -38,7 +38,6 @@ public final class ContextMenuController: ViewController { } }) self.displayNodeDidLoad() - self.navigationBar.isHidden = true } override public func viewDidAppear(_ animated: Bool) { diff --git a/Display/Display.h b/Display/Display.h index 801c52ac4e..49c0e1fb5b 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -30,3 +30,4 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import +#import diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 2e0a7e3cb1..3c3e82e34b 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -227,7 +227,7 @@ public class DrawingContext { private var _context: CGContext? - public func withContext(_ f: @noescape(CGContext) -> ()) { + public func withContext(_ f: (CGContext) -> ()) { if self._context == nil { if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { c.scaleBy(x: scale, y: scale) @@ -248,7 +248,7 @@ public class DrawingContext { } } - public func withFlippedContext(_ f: @noescape(CGContext) -> ()) { + public func withFlippedContext(_ f: (CGContext) -> ()) { if self._context == nil { if let c = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: self.bitmapInfo.rawValue) { c.scaleBy(x: scale, y: scale) @@ -295,7 +295,7 @@ public class DrawingContext { let srcLine = self.bytes.advanced(by: y * self.bytesPerRow).assumingMemoryBound(to: UInt32.self) let pixel = srcLine + x let colorValue = pixel.pointee - return UIColor(UInt32(colorValue)) + return UIColor(rgb: UInt32(colorValue)) } else { return UIColor.clear } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 485a83b182..e5f0697de3 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -48,13 +48,13 @@ public struct GridNodeScrollToItem { } public enum GridNodeLayoutType: Equatable { - case fixed(itemSize: CGSize) + case fixed(itemSize: CGSize, lineSpacing: CGFloat) case balanced(idealHeight: CGFloat) public static func ==(lhs: GridNodeLayoutType, rhs: GridNodeLayoutType) -> Bool { switch lhs { - case let .fixed(itemSize): - if case .fixed(itemSize) = rhs { + case let .fixed(itemSize, lineSpacing): + if case .fixed(itemSize, lineSpacing) = rhs { return true } else { return false @@ -72,12 +72,14 @@ public enum GridNodeLayoutType: Equatable { public struct GridNodeLayout: Equatable { public let size: CGSize public let insets: UIEdgeInsets + public let scrollIndicatorInsets: UIEdgeInsets? public let preloadSize: CGFloat public let type: GridNodeLayoutType - public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, type: GridNodeLayoutType) { + public init(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets? = nil, preloadSize: CGFloat, type: GridNodeLayoutType) { self.size = size self.insets = insets + self.scrollIndicatorInsets = scrollIndicatorInsets self.preloadSize = preloadSize self.type = type } @@ -245,7 +247,7 @@ private struct WrappedGridItemNode: Hashable { } open class GridNode: GridNodeScroller, UIScrollViewDelegate { - private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize())) + private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), lineSpacing: 0.0)) private var firstIndexInSectionOffset: Int = 0 private var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] @@ -259,6 +261,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public final var floatingSections = false + public var showVerticalScrollIndicator: Bool = false { + didSet { + self.scrollView.showsVerticalScrollIndicator = self.showVerticalScrollIndicator + } + } + public override init() { super.init() @@ -418,10 +426,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var sections: [GridNodePresentationSection] = [] switch gridLayout.type { - case let .fixed(itemSize): + case let .fixed(itemSize, lineSpacing): let itemsInRow = Int(gridLayout.size.width / itemSize.width) let itemsInRowWidth = CGFloat(itemsInRow) * itemSize.width - let remainingWidth = gridLayout.size.width - itemsInRowWidth + let remainingWidth = max(0.0, gridLayout.size.width - itemsInRowWidth) let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) @@ -441,7 +449,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if !keepSection { if incrementedCurrentRow { nextItemOrigin.x = itemSpacing - nextItemOrigin.y += itemSize.height + nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false } @@ -455,7 +463,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if !incrementedCurrentRow { incrementedCurrentRow = true - contentSize.height += itemSize.height + contentSize.height += itemSize.height + lineSpacing } if index == 0 { @@ -470,7 +478,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { nextItemOrigin.x += itemSize.width + itemSpacing if nextItemOrigin.x + itemSize.width > gridLayout.size.width { nextItemOrigin.x = itemSpacing - nextItemOrigin.y += itemSize.height + nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false } } @@ -749,10 +757,17 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } applyingContentOffset = true + self.scrollView.contentSize = presentationLayoutTransition.layout.contentSize self.scrollView.contentInset = presentationLayoutTransition.layout.layout.insets - if !self.scrollView.contentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) { - self.scrollView.contentOffset = presentationLayoutTransition.layout.contentOffset + if let scrollIndicatorInsets = presentationLayoutTransition.layout.layout.scrollIndicatorInsets { + self.scrollView.scrollIndicatorInsets = scrollIndicatorInsets + } else { + self.scrollView.scrollIndicatorInsets = presentationLayoutTransition.layout.layout.insets + } + if !self.scrollView.contentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) || self.bounds.size != presentationLayoutTransition.layout.layout.size { + //self.scrollView.contentOffset = presentationLayoutTransition.layout.contentOffset + self.bounds = CGRect(origin: presentationLayoutTransition.layout.contentOffset, size: presentationLayoutTransition.layout.layout.size) } applyingContentOffset = false diff --git a/Display/HighlightTrackingButton.swift b/Display/HighlightTrackingButton.swift index f50385e127..c48a9b2b33 100644 --- a/Display/HighlightTrackingButton.swift +++ b/Display/HighlightTrackingButton.swift @@ -1,27 +1,48 @@ import UIKit open class HighlightTrackingButton: UIButton { + private var internalHighlighted = false + public var internalHighligthedChanged: (Bool) -> Void = { _ in } public var highligthedChanged: (Bool) -> Void = { _ in } open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { - self.highligthedChanged(true) - self.internalHighligthedChanged(true) + if !self.internalHighlighted { + self.internalHighlighted = true + self.highligthedChanged(true) + self.internalHighligthedChanged(true) + } return super.beginTracking(touch, with: event) } open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { - self.highligthedChanged(false) - self.internalHighligthedChanged(false) + if self.internalHighlighted { + self.internalHighlighted = false + self.highligthedChanged(false) + self.internalHighligthedChanged(false) + } super.endTracking(touch, with: event) } open override func cancelTracking(with event: UIEvent?) { - self.highligthedChanged(false) - self.internalHighligthedChanged(false) + if self.internalHighlighted { + self.internalHighlighted = false + self.highligthedChanged(false) + self.internalHighligthedChanged(false) + } super.cancelTracking(with: event) } + + open override func touchesCancelled(_ touches: Set, with event: UIEvent?) { + if self.internalHighlighted { + self.internalHighlighted = false + self.highligthedChanged(false) + self.internalHighligthedChanged(false) + } + + super.touchesCancelled(touches, with: event) + } } diff --git a/Display/HighlightableButton.swift b/Display/HighlightableButton.swift index 60c844e3de..8d0d6b7908 100644 --- a/Display/HighlightableButton.swift +++ b/Display/HighlightableButton.swift @@ -27,25 +27,45 @@ open class HighlightableButton: HighlightTrackingButton { } open class HighlightTrackingButtonNode: ASButtonNode { + private var internalHighlighted = false + public var highligthedChanged: (Bool) -> Void = { _ in } open override func beginTracking(with touch: UITouch, with event: UIEvent?) -> Bool { - self.highligthedChanged(true) + if !self.internalHighlighted { + self.internalHighlighted = true + self.highligthedChanged(true) + } return super.beginTracking(with: touch, with: event) } open override func endTracking(with touch: UITouch?, with event: UIEvent?) { - self.highligthedChanged(false) + if self.internalHighlighted { + self.internalHighlighted = false + self.highligthedChanged(false) + } super.endTracking(with: touch, with: event) } open override func cancelTracking(with event: UIEvent?) { - self.highligthedChanged(false) + if self.internalHighlighted { + self.internalHighlighted = false + self.highligthedChanged(false) + } super.cancelTracking(with: event) } + + open override func touchesCancelled(_ touches: Set?, with event: UIEvent?) { + super.touchesCancelled(touches, with: event) + + if self.internalHighlighted { + self.internalHighlighted = false + self.highligthedChanged(false) + } + } } open class HighlightableButtonNode: HighlightTrackingButtonNode { diff --git a/Display/KeyboardManager.swift b/Display/KeyboardManager.swift index 6abee24933..3ffd75f8bc 100644 --- a/Display/KeyboardManager.swift +++ b/Display/KeyboardManager.swift @@ -98,7 +98,7 @@ class KeyboardManager { } var firstResponderView: UIView? - for surface in surfaces { + for surface in self.surfaces { if hasFirstResponder(surface.host) { firstResponderView = surface.host break diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index 2a3bd70b83..cf53e6d78b 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -33,9 +33,8 @@ open class LegacyPresentedController: ViewController { self.legacyController = legacyController self.presentation = presentation - super.init() + super.init(navigationBarTheme: nil) - self.navigationBar.isHidden = true /*legacyController.navigation_setDismiss { [weak self] in self?.dismiss() }*/ diff --git a/Display/LegacyPresentedControllerNode.swift b/Display/LegacyPresentedControllerNode.swift index f853701566..3a35e282fc 100644 --- a/Display/LegacyPresentedControllerNode.swift +++ b/Display/LegacyPresentedControllerNode.swift @@ -1,6 +1,5 @@ import Foundation import AsyncDisplayKit -import Display final class LegacyPresentedControllerNode: ASDisplayNode { private var containerLayout: ContainerViewLayout? diff --git a/Display/ListView.swift b/Display/ListView.swift index 12a64822e1..15c1045da7 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1982,7 +1982,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) - self.updateFloatingAccessoryNodes(animated: animated, currentTimestamp: timestamp) if let scrollToItem = scrollToItem , scrollToItem.animated { if self.itemNodes.count != 0 { @@ -2434,22 +2433,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateFloatingAccessoryNodes(animated: Bool, currentTimestamp: Double) { - var previousFloatingAccessoryItem: ListViewAccessoryItem? - for itemNode in self.itemNodes { - if let index = itemNode.index, let floatingAccessoryItem = self.items[index].floatingAccessoryItem { - if itemNode.floatingAccessoryItemNode == nil { - let floatingAccessoryItemNode = floatingAccessoryItem.node() - itemNode.floatingAccessoryItemNode = floatingAccessoryItemNode - itemNode.addSubnode(floatingAccessoryItemNode) - } - } else { - itemNode.floatingAccessoryItemNode?.removeFromSupernode() - itemNode.floatingAccessoryItemNode = nil - } - } - } - private func enqueueUpdateVisibleItems() { if !self.enqueuedUpdateVisibleItems { self.enqueuedUpdateVisibleItems = true @@ -2738,6 +2721,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public func forEachItemHeaderNode(_ f: (ListViewItemHeaderNode) -> Void) { + for (_, itemNode) in self.itemHeaderNodes { + f(itemNode) + } + } + public func ensureItemNodeVisible(_ node: ListViewItemNode) { if let index = node.index { if node.frame.minY < self.insets.top { diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 2d7d954a98..3555960169 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -67,9 +67,20 @@ private let springAnimationIn: CABasicAnimation = { return animation }() +private let springAnimationSolver: (CGFloat) -> CGFloat = { () -> (CGFloat) -> CGFloat in + if #available(iOS 9.0, *) { + return { t in + return springAnimationValueAt(springAnimationIn, t) + } + } else { + return { t in + return bezierPoint(0.23, 1.0, 0.32, 1.0, t) + } + } +}() + public let listViewAnimationCurveSystem: (CGFloat) -> CGFloat = { t in - //return bezierPoint(0.23, 1.0, 0.32, 1.0, t) - return springAnimationValueAt(springAnimationIn, t) + return springAnimationSolver(t) } public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 7f5db6fe76..5b0f1a2e58 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -34,7 +34,6 @@ public protocol ListViewItem { var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } - var floatingAccessoryItem: ListViewAccessoryItem? { get } var selectable: Bool { get } func selected(listView: ListView) @@ -49,10 +48,6 @@ public extension ListViewItem { return nil } - var floatingAccessoryItem: ListViewAccessoryItem? { - return nil - } - var selectable: Bool { return false } diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index 25e851f184..100df12c96 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -6,8 +6,10 @@ public enum ListViewItemHeaderStickDirection { case bottom } +public typealias ListViewItemHeaderId = Int64 + public protocol ListViewItemHeader: class { - var id: Int64 { get } + var id: ListViewItemHeaderId { get } var stickDirection: ListViewItemHeaderStickDirection { get } var height: CGFloat { get } @@ -51,14 +53,28 @@ open class ListViewItemHeaderNode: ASDisplayNode { open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { } - public init(dynamicBounce: Bool = false, isRotated: Bool = false) { + public init(layerBacked: Bool = false, dynamicBounce: Bool = false, isRotated: Bool = false, seeThrough: Bool = false) { self.wantsScrollDynamics = dynamicBounce self.isRotated = isRotated if dynamicBounce { self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) } - super.init() + if seeThrough { + if (layerBacked) { + super.init(layerBlock: { + return CASeeThroughTracingLayer() + }, didLoad: nil) + } else { + super.init(viewBlock: { + return CASeeThroughTracingView() + }, didLoad: nil) + } + } else { + super.init() + + self.isLayerBacked = layerBacked + } } open func updateStickDistanceFactor(_ factor: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 40bf158e44..cbe3e79bf5 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -28,9 +28,9 @@ struct ListViewItemSpring { } private class ListViewItemView: UIView { - override class var layerClass: AnyClass { + /*override class var layerClass: AnyClass { return ASTransformLayer.self - } + }*/ } public struct ListViewItemNodeLayout { @@ -78,8 +78,6 @@ open class ListViewItemNode: ASDisplayNode { } } - final var floatingAccessoryItemNode: ListViewAccessoryItemNode? - private final var spring: ListViewItemSpring? private final var animations: [(String, ListViewAnimation)] = [] @@ -157,7 +155,7 @@ open class ListViewItemNode: ASDisplayNode { return .complete() } - public init(layerBacked: Bool, dynamicBounce: Bool = true, rotated: Bool = false) { + public init(layerBacked: Bool, dynamicBounce: Bool = true, rotated: Bool = false, seeThrough: Bool = false) { if true { if dynamicBounce { self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) @@ -183,8 +181,21 @@ open class ListViewItemNode: ASDisplayNode { }, didLoad: nil) }*/ - super.init() - self.isLayerBacked = layerBacked + if seeThrough { + if (layerBacked) { + super.init(layerBlock: { + return CASeeThroughTracingLayer() + }, didLoad: nil) + } else { + super.init(viewBlock: { + return CASeeThroughTracingView() + }, didLoad: nil) + } + } else { + super.init() + + self.isLayerBacked = layerBacked + } } /*deinit { diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 22077acf36..566a499fe8 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -19,27 +19,27 @@ public class NavigationBackButtonNode: ASControlNode { private let arrowSpacing: CGFloat = 4.0 private var _text: String = "" - var text: String { + public var text: String { get { return self._text } set(value) { self._text = value - self.label.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.label.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) self.invalidateCalculatedLayout() } } - var color: UIColor = UIColor(0x007ee5) { + public var color: UIColor = UIColor(rgb: 0x007ee5) { didSet { - self.label.attributedString = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) + self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) } } private var touchCount = 0 var pressed: () -> () = {} - override init() { + override public init() { self.arrow = ASDisplayNode() self.label = ASTextNode() diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index b3dc8370c9..ed78bd041c 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -11,6 +11,20 @@ private func generateBackArrowImage(color: UIColor) -> UIImage? { private var backArrowImageCache: [Int32: UIImage] = [:] +public final class NavigationBarTheme { + public let buttonColor: UIColor + public let primaryTextColor: UIColor + public let backgroundColor: UIColor + public let separatorColor: UIColor + + public init(buttonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor) { + self.buttonColor = buttonColor + self.primaryTextColor = primaryTextColor + self.backgroundColor = backgroundColor + self.separatorColor = separatorColor + } +} + private func backArrowImage(color: UIColor) -> UIImage? { var red: CGFloat = 0.0 var green: CGFloat = 0.0 @@ -32,22 +46,7 @@ private func backArrowImage(color: UIColor) -> UIImage? { } open class NavigationBar: ASDisplayNode { - open var foregroundColor: UIColor = UIColor.black { - didSet { - if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor) - } - } - } - - open var accentColor: UIColor = UIColor(0x007ee5) { - didSet { - self.backButtonNode.color = self.accentColor - self.leftButtonNode.color = self.accentColor - self.rightButtonNode.color = self.accentColor - self.backButtonArrow.image = backArrowImage(color: self.accentColor) - } - } + private var theme: NavigationBarTheme var backPressed: () -> () = { } @@ -58,14 +57,10 @@ open class NavigationBar: ASDisplayNode { } private let stripeNode: ASDisplayNode - public var stripeColor: UIColor = UIColor(red: 0.6953125, green: 0.6953125, blue: 0.6953125, alpha: 1.0) { - didSet { - self.stripeNode.backgroundColor = self.stripeColor - } - } - private let clippingNode: ASDisplayNode + var contentNode: NavigationBarContentNode? + private var itemTitleListenerKey: Int? private var itemTitleViewListenerKey: Int? @@ -173,7 +168,7 @@ open class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -204,21 +199,44 @@ open class NavigationBar: ASDisplayNode { private let titleNode: ASTextNode var previousItemListenerKey: Int? + var previousItemBackListenerKey: Int? + var _previousItem: UINavigationItem? var previousItem: UINavigationItem? { get { return self._previousItem } set(value) { - if let previousValue = self._previousItem, let previousItemListenerKey = self.previousItemListenerKey { - previousValue.removeSetTitleListener(previousItemListenerKey) - self.previousItemListenerKey = nil + if let previousValue = self._previousItem { + if let previousItemListenerKey = self.previousItemListenerKey { + previousValue.removeSetTitleListener(previousItemListenerKey) + self.previousItemListenerKey = nil + } + if let previousItemBackListenerKey = self.previousItemBackListenerKey { + previousValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) + self.previousItemBackListenerKey = nil + } } self._previousItem = value - if let previousItem = value, previousItem.backBarButtonItem == nil { - self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] text in - if let strongSelf = self { - strongSelf.backButtonNode.text = text ?? "Back" + if let previousItem = value { + self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem { + if let backBarButtonItem = previousItem.backBarButtonItem { + strongSelf.backButtonNode.text = backBarButtonItem.title ?? "" + } else { + strongSelf.backButtonNode.text = previousItem.title ?? "" + } + strongSelf.invalidateCalculatedLayout() + } + } + + self.previousItemBackListenerKey = previousItem.addSetBackBarButtonItemListener { [weak self] _, _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem { + if let backBarButtonItem = previousItem.backBarButtonItem { + strongSelf.backButtonNode.text = backBarButtonItem.title ?? "" + } else { + strongSelf.backButtonNode.text = previousItem.title ?? "" + } strongSelf.invalidateCalculatedLayout() } } @@ -319,7 +337,7 @@ open class NavigationBar: ASDisplayNode { if let value = value { switch value.role { case .top: - if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.foregroundColor) { + if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.theme.primaryTextColor) { self.transitionTitleNode = transitionTitleNode if self.leftButtonNode.supernode != nil { self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) @@ -330,11 +348,11 @@ open class NavigationBar: ASDisplayNode { } } case .bottom: - if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.accentColor) { + if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.theme.buttonColor) { self.transitionBackButtonNode = transitionBackButtonNode self.clippingNode.addSubnode(transitionBackButtonNode) } - if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.accentColor) { + if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.theme.buttonColor) { self.transitionBackArrowNode = transitionBackArrowNode self.clippingNode.addSubnode(transitionBackArrowNode) } @@ -350,7 +368,8 @@ open class NavigationBar: ASDisplayNode { private var transitionBackButtonNode: NavigationButtonNode? private var transitionBackArrowNode: ASDisplayNode? - public override init() { + public init(theme: NavigationBarTheme) { + self.theme = theme self.stripeNode = ASDisplayNode() self.titleNode = ASTextNode() @@ -358,22 +377,29 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow = ASImageNode() self.backButtonArrow.displayWithoutProcessing = true self.backButtonArrow.displaysAsynchronously = false - self.backButtonArrow.image = backArrowImage(color: self.accentColor) self.leftButtonNode = NavigationButtonNode() self.rightButtonNode = NavigationButtonNode() self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true + self.backButtonNode.color = self.theme.buttonColor + self.leftButtonNode.color = self.theme.buttonColor + self.rightButtonNode.color = self.theme.buttonColor + self.backButtonArrow.image = backArrowImage(color: self.theme.buttonColor) + if let title = self.title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + } + self.stripeNode.backgroundColor = self.theme.separatorColor + super.init() self.addSubnode(self.clippingNode) - self.backgroundColor = UIColor(red: 0.968626451, green: 0.968626451, blue: 0.968626451, alpha: 1.0) + self.backgroundColor = self.theme.backgroundColor self.stripeNode.isLayerBacked = true self.stripeNode.displaysAsynchronously = false - self.stripeNode.backgroundColor = self.stripeColor self.addSubnode(self.stripeNode) self.titleNode.displaysAsynchronously = false @@ -405,18 +431,36 @@ open class NavigationBar: ASDisplayNode { } } + public func updateTheme(_ theme: NavigationBarTheme) { + if theme !== self.theme { + self.theme = theme + + self.backgroundColor = self.theme.backgroundColor + + self.backButtonNode.color = self.theme.buttonColor + self.leftButtonNode.color = self.theme.buttonColor + self.rightButtonNode.color = self.theme.buttonColor + self.backButtonArrow.image = backArrowImage(color: self.theme.buttonColor) + if let title = self.title { + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + } + self.stripeNode.backgroundColor = self.theme.separatorColor + } + } + open override func layout() { - var size = self.bounds.size + let size = self.bounds.size let leftButtonInset: CGFloat = 8.0 let backButtonInset: CGFloat = 27.0 self.clippingNode.frame = CGRect(origin: CGPoint(), size: size) + self.contentNode?.frame = CGRect(origin: CGPoint(), size: size) self.stripeNode.frame = CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel) - var nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 - var contentVerticalOrigin = size.height - nominalHeight + let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 + let contentVerticalOrigin = size.height - nominalHeight var leftTitleInset: CGFloat = 8.0 var rightTitleInset: CGFloat = 8.0 @@ -503,6 +547,11 @@ open class NavigationBar: ASDisplayNode { } } + leftTitleInset = floor(leftTitleInset) + if Int(leftTitleInset) % 2 != 0 { + leftTitleInset -= 1.0 + } + if self.titleNode.supernode != nil { let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) @@ -574,4 +623,48 @@ open class NavigationBar: ASDisplayNode { return nil } } + + public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { + if self.contentNode !== contentNode { + if let previous = self.contentNode { + if animated { + previous.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak self, weak previous] _ in + if let strongSelf = self, let previous = previous { + if previous !== strongSelf.contentNode { + previous.removeFromSupernode() + } + } + }) + } else { + previous.removeFromSupernode() + } + } + self.contentNode = contentNode + if let contentNode = contentNode { + contentNode.layer.removeAnimation(forKey: "opacity") + self.addSubnode(contentNode) + if animated { + contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + if !self.clippingNode.alpha.isZero { + self.clippingNode.alpha = 0.0 + if animated { + self.clippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) + } + } + + if !self.bounds.size.width.isZero { + self.layout() + } else { + self.setNeedsLayout() + } + } else if self.clippingNode.alpha.isZero { + self.clippingNode.alpha = 1.0 + if animated { + self.clippingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + } + } } diff --git a/Display/NavigationBarContentNode.swift b/Display/NavigationBarContentNode.swift new file mode 100644 index 0000000000..db12a52e5b --- /dev/null +++ b/Display/NavigationBarContentNode.swift @@ -0,0 +1,6 @@ +import Foundation +import AsyncDisplayKit + +open class NavigationBarContentNode: ASDisplayNode { + +} diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 10247d8e0f..e07a229e3f 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -66,7 +66,7 @@ public class NavigationButtonNode: ASTextNode { } } - public var color: UIColor = UIColor(0x007ee5) { + public var color: UIColor = UIColor(rgb: 0x007ee5) { didSet { if let text = self._text { self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index d03e82a29b..10cb7f9237 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -18,7 +18,7 @@ private class NavigationControllerView: UIView { } } -open class NavigationController: NavigationControllerProxy, ContainableController, UIGestureRecognizerDelegate { +open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { public private(set) weak var overlayPresentingController: ViewController? private var containerLayout = ContainerViewLayout() @@ -51,8 +51,8 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle return self._viewControllers.last } - public override init() { - super.init() + public init() { + super.init(nibName: nil, bundle: nil) } public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { @@ -75,7 +75,7 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle self.containerLayout = layout self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) - let containedLayout = ContainerViewLayout(size: layout.size, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) + let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) if let topViewController = self.topViewController { if let topViewController = topViewController as? ContainableController { @@ -316,7 +316,11 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let topViewController = viewControllers[viewControllers.count - 1] as UIViewController if let controller = topViewController as? ContainableController { - controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + var layoutToApply = self.containerLayout + if !self.viewControllers.contains(where: { $0 === controller }) { + layoutToApply = layoutToApply.withUpdatedInputHeight(nil) + } + controller.containerLayoutUpdated(layoutToApply, transition: .immediate) } else { topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) } @@ -329,9 +333,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle if let bottomController = bottomController as? ViewController { if viewControllers.count >= 2 { - bottomController.navigationBar.previousItem = viewControllers[viewControllers.count - 2].navigationItem + bottomController.navigationBar?.previousItem = viewControllers[viewControllers.count - 2].navigationItem } else { - bottomController.navigationBar.previousItem = nil + bottomController.navigationBar?.previousItem = nil } } @@ -366,7 +370,7 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle let bottomController = self.viewControllers.last! as UIViewController if let topController = topController as? ViewController { - topController.navigationBar.previousItem = bottomController.navigationItem + topController.navigationBar?.previousItem = bottomController.navigationItem } bottomController.viewWillDisappear(true) @@ -413,9 +417,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle if let topController = viewControllers.last { if let topController = topController as? ViewController { if viewControllers.count >= 2 { - topController.navigationBar.previousItem = viewControllers[viewControllers.count - 2].navigationItem + topController.navigationBar?.previousItem = viewControllers[viewControllers.count - 2].navigationItem } else { - topController.navigationBar.previousItem = nil + topController.navigationBar?.previousItem = nil } } diff --git a/Display/NavigationControllerProxy.m b/Display/NavigationControllerProxy.m index a6e77f8a55..6a86a5062f 100644 --- a/Display/NavigationControllerProxy.m +++ b/Display/NavigationControllerProxy.m @@ -7,8 +7,7 @@ - (instancetype)init { self = [super initWithNavigationBarClass:[NavigationBarProxy class] toolbarClass:[UIToolbar class]]; - if (self != nil) - { + if (self != nil) { } return self; } diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 104743d4bf..1aae757ce6 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -95,7 +95,7 @@ class NavigationTransitionCoordinator { var dimInset: CGFloat = 0.0 if let topNavigationBar = self.topNavigationBar , self.inlineNavigationBarTransition { - dimInset = topNavigationBar.frame.size.height + dimInset = topNavigationBar.frame.maxY } let containerSize = self.container.bounds.size diff --git a/Display/PresentableViewController.swift b/Display/PresentableViewController.swift deleted file mode 100644 index 2752c36d8f..0000000000 --- a/Display/PresentableViewController.swift +++ /dev/null @@ -1,6 +0,0 @@ -import UIKit -import AsyncDisplayKit - -public protocol PresentableViewController: class { - -} diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 6bc02aec96..4904eac7f2 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -24,7 +24,45 @@ public class StatusBarSurface { } } -public class StatusBar: ASDisplayNode { +private let inCallBackgroundColor = UIColor(rgb: 0x43d551) + +private func addInCallAnimation(_ layer: CALayer) { + let animation = CAKeyframeAnimation(keyPath: "opacity") + animation.keyTimes = [0.0 as NSNumber, 0.1 as NSNumber, 0.5 as NSNumber, 0.9 as NSNumber, 1.0 as NSNumber] + animation.values = [1.0 as NSNumber, 1.0 as NSNumber, 0.0 as NSNumber, 1.0 as NSNumber, 1.0 as NSNumber] + animation.duration = 1.8 + animation.autoreverses = true + animation.repeatCount = Float.infinity + animation.beginTime = 1.0 + layer.add(animation, forKey: "blink") +} + +private final class StatusBarLabelNode: ASTextNode { + override func willEnterHierarchy() { + super.willEnterHierarchy() + + addInCallAnimation(self.layer) + } + + override func didExitHierarchy() { + super.didExitHierarchy() + + self.layer.removeAnimation(forKey: "blink") + } +} + +private final class StatusBarView: UITracingLayerView { + weak var node: StatusBar? + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let node = self.node { + return node.hitTest(point, with: event) + } + return nil + } +} + +public final class StatusBar: ASDisplayNode { public var statusBarStyle: StatusBarStyle = .Black { didSet { if self.statusBarStyle != oldValue { @@ -32,43 +70,160 @@ public class StatusBar: ASDisplayNode { } } } + + public var ignoreInCall: Bool = false + + var inCallNavigate: (() -> Void)? + private var proxyNode: StatusBarProxyNode? private var removeProxyNodeScheduled = false + private let inCallBackgroundNode = ASDisplayNode() + private let inCallLabel: StatusBarLabelNode + + private var inCallText: String? = nil + public override init() { + self.inCallLabel = StatusBarLabelNode() + self.inCallLabel.isLayerBacked = true + + let labelSize = self.inCallLabel.measure(CGSize(width: 300.0, height: 300.0)) + self.inCallLabel.frame = CGRect(origin: CGPoint(x: 10.0, y: 20.0 + 4.0), size: labelSize) + super.init(viewBlock: { - return UITracingLayerView() + return StatusBarView() }, didLoad: nil) + (self.view as! StatusBarView).node = self + + self.addSubnode(self.inCallBackgroundNode) self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar)) self.clipsToBounds = true - self.isUserInteractionEnabled = false + + self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func removeProxyNode() { - self.removeProxyNodeScheduled = true + func updateState(statusBar: UIView?, inCallText: String?, animated: Bool) { + if let statusBar = statusBar { + self.removeProxyNodeScheduled = false + let resolvedStyle: StatusBarStyle + if inCallText != nil && !self.ignoreInCall { + resolvedStyle = .White + } else { + resolvedStyle = self.statusBarStyle + } + if let proxyNode = self.proxyNode { + proxyNode.statusBarStyle = resolvedStyle + } else { + self.proxyNode = StatusBarProxyNode(statusBarStyle: resolvedStyle, statusBar: statusBar) + self.proxyNode!.isHidden = false + self.addSubnode(self.proxyNode!) + } + } else { + self.removeProxyNodeScheduled = true + + DispatchQueue.main.async(execute: { [weak self] in + if let strongSelf = self { + if strongSelf.removeProxyNodeScheduled { + strongSelf.removeProxyNodeScheduled = false + strongSelf.proxyNode?.isHidden = true + strongSelf.proxyNode?.removeFromSupernode() + strongSelf.proxyNode = nil + } + } + }) + } - DispatchQueue.main.async(execute: { [weak self] in - if let strongSelf = self { - if strongSelf.removeProxyNodeScheduled { - strongSelf.removeProxyNodeScheduled = false - strongSelf.proxyNode?.isHidden = true - strongSelf.proxyNode?.removeFromSupernode() - strongSelf.proxyNode = nil + var ignoreInCall = self.ignoreInCall + switch self.statusBarStyle { + case .Black, .White: + break + default: + ignoreInCall = true + } + + var resolvedInCallText: String? = inCallText + if ignoreInCall { + resolvedInCallText = nil + } + + if (resolvedInCallText != nil) != (self.inCallText != nil) { + if let _ = resolvedInCallText { + self.addSubnode(self.inCallLabel) + addInCallAnimation(self.inCallLabel.layer) + + self.inCallBackgroundNode.layer.backgroundColor = inCallBackgroundColor.cgColor + if animated { + self.inCallBackgroundNode.layer.animate(from: UIColor.clear.cgColor, to: inCallBackgroundColor.cgColor, keyPath: "backgroundColor", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.3) + } + } else { + self.inCallLabel.removeFromSupernode() + + self.inCallBackgroundNode.layer.backgroundColor = UIColor.clear.cgColor + if animated { + self.inCallBackgroundNode.layer.animate(from: inCallBackgroundColor.cgColor, to: UIColor.clear.cgColor, keyPath: "backgroundColor", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: 0.3) } } - }) + } + + + if let resolvedInCallText = resolvedInCallText { + if self.inCallText != resolvedInCallText { + self.inCallLabel.attributedText = NSAttributedString(string: resolvedInCallText, font: Font.regular(14.0), textColor: .white) + } + + self.layoutInCallLabel() + } + + self.inCallText = resolvedInCallText } - func updateProxyNode(statusBar: UIView) { - self.removeProxyNodeScheduled = false - if let proxyNode = proxyNode { - proxyNode.statusBarStyle = self.statusBarStyle + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) && self.inCallText != nil { + return self.view } else { - self.proxyNode = StatusBarProxyNode(statusBarStyle: self.statusBarStyle, statusBar: statusBar) - self.proxyNode!.isHidden = false - self.addSubnode(self.proxyNode!) + return nil + } + } + + @objc func tapGesture(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state, self.inCallText != nil { + self.inCallNavigate?() + } + } + + override public func layout() { + super.layout() + + self.layoutInCallLabel() + } + + override public var frame: CGRect { + didSet { + if oldValue.size != self.frame.size { + let bounds = self.bounds + self.inCallBackgroundNode.frame = CGRect(origin: CGPoint(), size: bounds.size) + } + } + } + + override public var bounds: CGRect { + didSet { + if oldValue.size != self.bounds.size { + let bounds = self.bounds + self.inCallBackgroundNode.frame = CGRect(origin: CGPoint(), size: bounds.size) + } + } + } + + private func layoutInCallLabel() { + if self.inCallLabel.supernode != nil { + let size = self.bounds.size + if !size.width.isZero && !size.height.isZero { + let labelSize = self.inCallLabel.measure(size) + self.inCallLabel.frame = CGRect(origin: CGPoint(x: floor((size.width - labelSize.width) / 2.0), y: 20.0 + floor((20.0 - labelSize.height) / 2.0)), size: labelSize) + } } } } diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index f75b236c13..3141c9341e 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -12,25 +12,36 @@ private struct MappedStatusBarSurface { let surface: StatusBarSurface } -private func mapStatusBar(_ statusBar: StatusBar) -> MappedStatusBar { +private func mapStatusBar(_ statusBar: StatusBar, forceInCall: Bool) -> MappedStatusBar { let frame = CGRect(origin: statusBar.view.convert(CGPoint(), to: nil), size: statusBar.frame.size) - return MappedStatusBar(style: statusBar.statusBarStyle, frame: frame, statusBar: statusBar) + let resolvedStyle: StatusBarStyle + switch statusBar.statusBarStyle { + case .Black, .White: + if forceInCall { + resolvedStyle = .White + } else { + resolvedStyle = statusBar.statusBarStyle + } + default: + resolvedStyle = statusBar.statusBarStyle + } + return MappedStatusBar(style: resolvedStyle, frame: frame, statusBar: statusBar) } -private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurface { +private func mappedSurface(_ surface: StatusBarSurface, forceInCall: Bool) -> MappedStatusBarSurface { var statusBars: [MappedStatusBar] = [] for statusBar in surface.statusBars { if statusBar.statusBarStyle != .Ignore { - statusBars.append(mapStatusBar(statusBar)) + statusBars.append(mapStatusBar(statusBar, forceInCall: forceInCall)) } } return MappedStatusBarSurface(statusBars: statusBars, surface: surface) } -private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface) -> MappedStatusBarSurface { +private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface, forceInCall: Bool) -> MappedStatusBarSurface { if surface.statusBars.count > 1 { for i in 1 ..< surface.statusBars.count { - if surface.statusBars[i].style != surface.statusBars[i - 1].style || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat.ulpOfOne { + if (!forceInCall && surface.statusBars[i].style != surface.statusBars[i - 1].style) || abs(surface.statusBars[i].frame.origin.y - surface.statusBars[i - 1].frame.origin.y) > CGFloat.ulpOfOne { return surface } if let lhsStatusBar = surface.statusBars[i - 1].statusBar, let rhsStatusBar = surface.statusBars[i].statusBar , !lhsStatusBar.alpha.isEqual(to: rhsStatusBar.alpha) { @@ -38,7 +49,7 @@ private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusB } } let size = statusBarSize - return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) + return MappedStatusBarSurface(statusBars: [MappedStatusBar(style: forceInCall ? .White : surface.statusBars[0].style, frame: CGRect(origin: CGPoint(x: 0.0, y: surface.statusBars[0].frame.origin.y), size: size), statusBar: nil)], surface: surface.surface) } else { return surface } @@ -60,22 +71,30 @@ private func displayHiddenAnimation() -> CAAnimation { class StatusBarManager { private var host: StatusBarHost - var surfaces: [StatusBarSurface] = [] { - didSet { - self.updateSurfaces(oldValue) - } - } + private var surfaces: [StatusBarSurface] = [] + + var inCallNavigate: (() -> Void)? init(host: StatusBarHost) { self.host = host } - private func updateSurfaces(_ previousSurfaces: [StatusBarSurface]) { + func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, animated: Bool) { + let previousSurfaces = self.surfaces + self.surfaces = surfaces + self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, animated: animated) + } + + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, animated: Bool) { let statusBarFrame = self.host.statusBarFrame guard let statusBarView = self.host.statusBarView else { return } + if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) { + self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil) + } + var mappedSurfaces: [MappedStatusBarSurface] = [] var mapIndex = 0 var doNotOptimize = false @@ -87,12 +106,12 @@ class StatusBarManager { } } - let mapped = mappedSurface(surface) + let mapped = mappedSurface(surface, forceInCall: forceInCallStatusBarText != nil) if doNotOptimize { mappedSurfaces.append(mapped) } else { - mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped)) + mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped, forceInCall: forceInCallStatusBarText != nil)) } mapIndex += 1 } @@ -176,25 +195,31 @@ class StatusBarManager { for surface in previousSurfaces { for statusBar in surface.statusBars { if !visibleStatusBars.contains(where: {$0 === statusBar}) { - statusBar.removeProxyNode() + statusBar.updateState(statusBar: nil, inCallText: forceInCallStatusBarText, animated: animated) } } } for surface in self.surfaces { for statusBar in surface.statusBars { + statusBar.inCallNavigate = self.inCallNavigate if !visibleStatusBars.contains(where: {$0 === statusBar}) { - statusBar.removeProxyNode() + statusBar.updateState(statusBar: nil, inCallText: forceInCallStatusBarText, animated: animated) } } } for statusBar in visibleStatusBars { - statusBar.updateProxyNode(statusBar: statusBarView) + statusBar.updateState(statusBar: statusBarView, inCallText: forceInCallStatusBarText, animated: animated) } if let globalStatusBar = globalStatusBar { - let statusBarStyle: UIStatusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent + let statusBarStyle: UIStatusBarStyle + if forceInCallStatusBarText != nil { + statusBarStyle = .lightContent + } else { + statusBarStyle = globalStatusBar.0 == .Black ? .default : .lightContent + } if self.host.statusBarStyle != statusBarStyle { self.host.statusBarStyle = statusBarStyle } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index e77e3d58e2..296e7a2fac 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -31,7 +31,7 @@ private class StatusBarItemNode: ASDisplayNode { func update() { let context = DrawingContext(size: self.targetView.frame.size, clear: true) - if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as! CFTypeRef) == CGImage.typeID && false { + if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as CFTypeRef) == CGImage.typeID && false { let image = contents as! CGImage context.withFlippedContext { c in c.setAlpha(CGFloat(self.targetView.layer.opacity)) @@ -42,7 +42,7 @@ private class StatusBarItemNode: ASDisplayNode { if let sublayers = self.targetView.layer.sublayers { for sublayer in sublayers { let origin = sublayer.frame.origin - if let contents = sublayer.contents , CFGetTypeID(contents as! CFTypeRef) == CGImage.typeID { + if let contents = sublayer.contents , CFGetTypeID(contents as CFTypeRef) == CGImage.typeID { let image = contents as! CGImage context.withFlippedContext { c in c.translateBy(x: origin.x, y: origin.y) @@ -73,6 +73,8 @@ private class StatusBarItemNode: ASDisplayNode { self.contents = context.generateImage()?.cgImage self.frame = self.targetView.frame + + } } diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index 0a1a3c6797..9028bebd82 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -4,6 +4,28 @@ import AsyncDisplayKit open class SwitchNode: ASDisplayNode { public var valueUpdated: ((Bool) -> Void)? + public var frameColor = UIColor(rgb: 0xe0e0e0) { + didSet { + if self.isNodeLoaded { + (self.view as! UISwitch).tintColor = self.frameColor + } + } + } + public var handleColor = UIColor(rgb: 0xffffff) { + didSet { + if self.isNodeLoaded { + (self.view as! UISwitch).thumbTintColor = self.handleColor + } + } + } + public var contentColor = UIColor(rgb: 0x42d451) { + didSet { + if self.isNodeLoaded { + (self.view as! UISwitch).onTintColor = self.contentColor + } + } + } + private var _isOn: Bool = false public var isOn: Bool { get { @@ -27,7 +49,10 @@ open class SwitchNode: ASDisplayNode { override open func didLoad() { super.didLoad() - (self.view as! UISwitch).backgroundColor = .white + (self.view as! UISwitch).backgroundColor = self.backgroundColor + (self.view as! UISwitch).tintColor = self.frameColor + (self.view as! UISwitch).onTintColor = self.contentColor + (self.view as! UISwitch).onTintColor = self.contentColor (self.view as! UISwitch).setOn(self._isOn, animated: false) diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 7c8ff8792a..ee2c33e6e6 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -14,16 +14,24 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(itemSelected: @escaping (Int) -> Void) { - self.tabBarNode = TabBarNode(itemSelected: itemSelected) + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int) -> Void) { + self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) super.init(viewBlock: { return UITracingLayerView() }, didLoad: nil) + self.backgroundColor = theme.backgroundColor + self.addSubnode(self.tabBarNode) } + func updateTheme(_ theme: TabBarControllerTheme) { + self.backgroundColor = theme.backgroundColor + + self.tabBarNode.updateTheme(theme) + } + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets(options: []).bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0)) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 988b23678f..4434010aa0 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -3,6 +3,26 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +public final class TabBarControllerTheme { + public let backgroundColor: UIColor + public let tabBarBackgroundColor: UIColor + public let tabBarSeparatorColor: UIColor + public let tabBarTextColor: UIColor + public let tabBarSelectedTextColor: UIColor + public let tabBarBadgeBackgroundColor: UIColor + public let tabBarBadgeTextColor: UIColor + + public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeTextColor: UIColor) { + self.backgroundColor = backgroundColor + self.tabBarBackgroundColor = tabBarBackgroundColor + self.tabBarSeparatorColor = tabBarSeparatorColor + self.tabBarTextColor = tabBarTextColor + self.tabBarSelectedTextColor = tabBarSelectedTextColor + self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor + self.tabBarBadgeTextColor = tabBarBadgeTextColor + } +} + open class TabBarController: ViewController { private var containerLayout = ContainerViewLayout() @@ -22,7 +42,7 @@ open class TabBarController: ViewController { } } - private var _selectedIndex: Int = 1 + private var _selectedIndex: Int = 2 public var selectedIndex: Int { get { return _selectedIndex @@ -44,8 +64,12 @@ open class TabBarController: ViewController { private let pendingControllerDisposable = MetaDisposable() - override public init(navigationBar: NavigationBar = NavigationBar()) { - super.init(navigationBar: navigationBar) + private var theme: TabBarControllerTheme + + public init(navigationBarTheme: NavigationBarTheme, theme: TabBarControllerTheme) { + self.theme = theme + + super.init(navigationBarTheme: navigationBarTheme) } required public init(coder aDecoder: NSCoder) { @@ -56,8 +80,18 @@ open class TabBarController: ViewController { self.pendingControllerDisposable.dispose() } + public func updateTheme(navigationBarTheme: NavigationBarTheme, theme: TabBarControllerTheme) { + self.navigationBar?.updateTheme(navigationBarTheme) + if self.theme !== theme { + self.theme = theme + if self.isNodeLoaded { + self.tabBarControllerNode.updateTheme(theme) + } + } + } + override open func loadDisplayNode() { - self.displayNode = TabBarControllerNode(itemSelected: { [weak self] index in + self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index in if let strongSelf = self { strongSelf.controllers[index].containerLayoutUpdated(strongSelf.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in @@ -97,13 +131,14 @@ open class TabBarController: ViewController { currentController.willMove(toParentViewController: self) currentController.containerLayoutUpdated(self.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) self.tabBarControllerNode.currentControllerView = currentController.view - currentController.navigationBar.isHidden = true + currentController.navigationBar?.isHidden = true self.addChildViewController(currentController) currentController.didMove(toParentViewController: self) currentController.navigationItem.setTarget(self.navigationItem) displayNavigationBar = currentController.displayNavigationBar currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) + self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle } else { self.navigationItem.title = nil self.navigationItem.leftBarButtonItem = nil diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index dd7f0895df..2eba180f4e 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale -private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColor) -> UIImage { +private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor) -> UIImage? { let font = Font.regular(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil).size @@ -18,7 +18,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColo UIGraphicsBeginImageContextWithOptions(size, true, 0.0) if let context = UIGraphicsGetCurrentContext() { - context.setFillColor(UIColor(0xf7f7f7).cgColor) + context.setFillColor(backgroundColor.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) if let image = image { @@ -36,18 +36,20 @@ private func tabBarItemImage(_ image: UIImage?, title: String, tintColor: UIColo (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: tintColor]) - let image = UIGraphicsGetImageFromCurrentImageContext()! + let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return image } -private let badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: UIColor(0xff3b30), backgroundColor: nil) private let badgeFont = Font.regular(13.0) private final class TabBarNodeContainer { let item: UITabBarItem let updateBadgeListenerIndex: Int + let updateTitleListenerIndex: Int + let updateImageListenerIndex: Int + let updateSelectedImageListenerIndex: Int let imageNode: ASImageNode let badgeBackgroundNode: ASImageNode @@ -56,7 +58,16 @@ private final class TabBarNodeContainer { var badgeValue: String? var appliedBadgeValue: String? - init(item: UITabBarItem, imageNode: ASImageNode, updateBadge: @escaping (String) -> Void) { + var titleValue: String? + var appliedTitleValue: String? + + var imageValue: UIImage? + var appliedImageValue: UIImage? + + var selectedImageValue: UIImage? + var appliedSelectedImageValue: UIImage? + + init(item: UITabBarItem, imageNode: ASImageNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void) { self.item = item self.imageNode = imageNode @@ -65,7 +76,6 @@ private final class TabBarNodeContainer { self.badgeBackgroundNode.isLayerBacked = true self.badgeBackgroundNode.displayWithoutProcessing = true self.badgeBackgroundNode.displaysAsynchronously = false - self.badgeBackgroundNode.image = badgeImage self.badgeTextNode = ASTextNode() self.badgeTextNode.maximumNumberOfLines = 1 @@ -76,10 +86,28 @@ private final class TabBarNodeContainer { self.updateBadgeListenerIndex = UITabBarItem_addSetBadgeListener(item, { value in updateBadge(value ?? "") }) + + self.titleValue = item.title + self.updateTitleListenerIndex = item.addSetTitleListener { value in + updateTitle(value ?? "") + } + + self.imageValue = item.image + self.updateImageListenerIndex = item.addSetImageListener { value in + updateImage(value) + } + + self.selectedImageValue = item.selectedImage + self.updateSelectedImageListenerIndex = item.addSetSelectedImageListener { value in + updateSelectedImage(value) + } } deinit { item.removeSetBadgeListener(self.updateBadgeListenerIndex) + item.removeSetTitleListener(self.updateTitleListenerIndex) + item.removeSetImageListener(self.updateImageListenerIndex) + item.removeSetSelectedImageListener(self.updateSelectedImageListenerIndex) } } @@ -106,25 +134,49 @@ class TabBarNode: ASDisplayNode { private let itemSelected: (Int) -> Void + private var theme: TabBarControllerTheme + + private var badgeImage: UIImage + let separatorNode: ASDisplayNode private var tabBarNodeContainers: [TabBarNodeContainer] = [] - init(itemSelected: @escaping (Int) -> Void) { + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int) -> Void) { self.itemSelected = itemSelected + self.theme = theme self.separatorNode = ASDisplayNode() - self.separatorNode.backgroundColor = UIColor(0xb2b2b2) + self.separatorNode.backgroundColor = theme.tabBarSeparatorColor self.separatorNode.isOpaque = true self.separatorNode.isLayerBacked = true + self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, backgroundColor: nil)! + super.init() self.isOpaque = true - self.backgroundColor = UIColor(0xf7f7f7) + self.backgroundColor = theme.tabBarBackgroundColor self.addSubnode(self.separatorNode) } + func updateTheme(_ theme: TabBarControllerTheme) { + if self.theme !== theme { + self.theme = theme + + self.separatorNode.backgroundColor = theme.tabBarSeparatorColor + self.backgroundColor = theme.tabBarBackgroundColor + + self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, backgroundColor: nil)! + + for i in 0 ..< self.tabBarItems.count { + self.updateNodeImage(i) + + self.tabBarNodeContainers[i].badgeBackgroundNode.image = self.badgeImage + } + } + } + private func reloadTabBarItems() { for node in self.tabBarNodeContainers { node.imageNode.removeFromSupernode() @@ -139,14 +191,21 @@ class TabBarNode: ASDisplayNode { node.displaysAsynchronously = false node.displayWithoutProcessing = true node.isLayerBacked = true - if let selectedIndex = self.selectedIndex , selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x007ee5)) - } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) - } let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) + }, updateTitle: { [weak self] _ in + self?.updateNodeImage(i) + }, updateImage: { [weak self] _ in + self?.updateNodeImage(i) + }, updateSelectedImage: { [weak self] _ in + self?.updateNodeImage(i) }) + if let selectedIndex = self.selectedIndex, selectedIndex == i { + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor) + } else { + node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor) + } + container.badgeBackgroundNode.image = self.badgeImage tabBarNodeContainers.append(container) self.addSubnode(node) } @@ -166,10 +225,14 @@ class TabBarNode: ASDisplayNode { let node = self.tabBarNodeContainers[index].imageNode let item = self.tabBarItems[index] - if let selectedIndex = self.selectedIndex , selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", tintColor: UIColor(0x007ee5)) + let previousImage = node.image + if let selectedIndex = self.selectedIndex, selectedIndex == index { + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor) } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", tintColor: UIColor(0x929292)) + node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor) + } + if previousImage?.size != node.image?.size { + self.layout() } } } @@ -181,6 +244,13 @@ class TabBarNode: ASDisplayNode { } } + private func updateNodeTitle(_ index: Int, value: String) { + self.tabBarNodeContainers[index].titleValue = value + if self.tabBarNodeContainers[index].titleValue != self.tabBarNodeContainers[index].appliedTitleValue { + self.layout() + } + } + override func layout() { super.layout() @@ -197,10 +267,10 @@ class TabBarNode: ASDisplayNode { for i in 0 ..< self.tabBarNodeContainers.count { let container = self.tabBarNodeContainers[i] let node = container.imageNode - node.measure(CGSize(width: internalWidth, height: size.height)) + let nodeSize = node.image?.size ?? CGSize() - let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - node.calculatedSize.width / 2.0) - node.frame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: node.calculatedSize) + let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - nodeSize.width / 2.0) + node.frame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize) if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index dc833c1c35..1d1fd10c78 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -26,14 +26,14 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { init(action: TextAlertAction) { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true - self.backgroundNode.backgroundColor = UIColor(0xe0e5e6) + self.backgroundNode.backgroundColor = UIColor(rgb: 0xe0e5e6) self.backgroundNode.alpha = 0.0 self.action = action super.init() - self.setTitle(action.title, with: action.type == .defaultAction ? Font.medium(17.0) : Font.regular(17.0), with: UIColor(0x007ee5), for: []) + self.setTitle(action.title, with: action.type == .defaultAction ? Font.medium(17.0) : Font.regular(17.0), with: UIColor(rgb: 0x007ee5), for: []) self.highligthedChanged = { [weak self] value in if let strongSelf = self { @@ -96,7 +96,7 @@ final class TextAlertContentNode: AlertContentNode { self.actionNodesSeparator = ASDisplayNode() self.actionNodesSeparator.isLayerBacked = true - self.actionNodesSeparator.backgroundColor = UIColor(0xc9cdd7) + self.actionNodesSeparator.backgroundColor = UIColor(rgb: 0xc9cdd7) self.actionNodes = actions.map { action -> TextAlertContentActionNode in return TextAlertContentActionNode(action: action) @@ -107,7 +107,7 @@ final class TextAlertContentNode: AlertContentNode { for _ in 0 ..< actions.count - 1 { let separatorNode = ASDisplayNode() separatorNode.isLayerBacked = true - separatorNode.backgroundColor = UIColor(0xc9cdd7) + separatorNode.backgroundColor = UIColor(rgb: 0xc9cdd7) actionVerticalSeparators.append(separatorNode) } } diff --git a/Display/UIBarButtonItem+Proxy.h b/Display/UIBarButtonItem+Proxy.h index 13ada65244..62a3b1137a 100644 --- a/Display/UIBarButtonItem+Proxy.h +++ b/Display/UIBarButtonItem+Proxy.h @@ -7,8 +7,10 @@ typedef void (^UIBarButtonItemSetEnabledListener)(BOOL); @interface UIBarButtonItem (Proxy) @property (nonatomic, strong, readonly) ASDisplayNode *customDisplayNode; +@property (nonatomic, readonly) bool backButtonAppearance; - (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode; +- (instancetype)initWithBackButtonAppearanceWithTitle:(NSString *)title target:(id)target action:(SEL)action; - (void)performActionOnTarget; diff --git a/Display/UIBarButtonItem+Proxy.m b/Display/UIBarButtonItem+Proxy.m index 945c12411a..d9c85ae8f3 100644 --- a/Display/UIBarButtonItem+Proxy.m +++ b/Display/UIBarButtonItem+Proxy.m @@ -6,6 +6,7 @@ static const void *setEnabledListenerBagKey = &setEnabledListenerBagKey; static const void *setTitleListenerBagKey = &setTitleListenerBagKey; static const void *customDisplayNodeKey = &customDisplayNodeKey; +static const void *backButtonAppearanceKey = &backButtonAppearanceKey; @implementation UIBarButtonItem (Proxy) @@ -27,10 +28,22 @@ static const void *customDisplayNodeKey = &customDisplayNodeKey; return self; } +- (instancetype)initWithBackButtonAppearanceWithTitle:(NSString *)title target:(id)target action:(SEL)action { + self = [self initWithTitle:title style:UIBarButtonItemStylePlain target:target action:action]; + if (self != nil) { + [self setAssociatedObject:@true forKey:backButtonAppearanceKey]; + } + return self; +} + - (ASDisplayNode *)customDisplayNode { return [self associatedObjectForKey:customDisplayNodeKey]; } +- (bool)backButtonAppearance { + return [[self associatedObjectForKey:backButtonAppearanceKey] boolValue]; +} + - (void)_c1e56039_setEnabled:(BOOL)enabled { [self _c1e56039_setEnabled:enabled]; diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 195d4a7dcf..09e212bddd 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -54,6 +54,8 @@ CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPat if (canSetInitialVelocity) { springAnimation.initialVelocity = initialVelocity; springAnimation.duration = springAnimation.settlingDuration; + } else { + springAnimation.duration = 0.1; } springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 999fe5d312..bdf88d7b10 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -34,13 +34,27 @@ public func floorToScreenPixels(_ value: CGFloat) -> CGFloat { public let UIScreenPixel = 1.0 / UIScreenScale public extension UIColor { - convenience init(_ rgb: UInt32) { + convenience init(rgb: UInt32) { self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: 1.0) } - convenience init(_ rgb: UInt32, _ alpha: CGFloat) { + convenience init(rgb: UInt32, alpha: CGFloat) { self.init(red: CGFloat((rgb >> 16) & 0xff) / 255.0, green: CGFloat((rgb >> 8) & 0xff) / 255.0, blue: CGFloat(rgb & 0xff) / 255.0, alpha: alpha) } + + convenience init(argb: UInt32) { + self.init(red: CGFloat((argb >> 16) & 0xff) / 255.0, green: CGFloat((argb >> 8) & 0xff) / 255.0, blue: CGFloat(argb & 0xff) / 255.0, alpha: CGFloat((argb >> 24) & 0xff) / 255.0) + } + + var argb: UInt32 { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + self.getRed(&red, green: &green, blue: &blue, alpha: &alpha) + + return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) + } } public extension CGSize { @@ -151,3 +165,27 @@ public extension UIView { } } } + +public extension CGRect { + public var topLeft: CGPoint { + return self.origin + } + + public var topRight: CGPoint { + return CGPoint(x: self.maxX, y: self.minY) + } + + public var bottomLeft: CGPoint { + return CGPoint(x: self.minX, y: self.maxY) + } + + public var bottomRight: CGPoint { + return CGPoint(x: self.maxX, y: self.maxY) + } +} + +public extension CGPoint { + public func offsetBy(dx: CGFloat, dy: CGFloat) -> CGPoint { + return CGPoint(x: self.x + dx, y: self.y + dy) + } +} diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index a32d4b0199..959f98d181 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -2,6 +2,7 @@ typedef void (^UINavigationItemSetTitleListener)(NSString *); typedef void (^UINavigationItemSetTitleViewListener)(UIView *); +typedef void (^UINavigationItemSetImageListener)(UIImage *); typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, UIBarButtonItem *, BOOL); typedef void (^UITabBarItemSetBadgeListener)(NSString *); @@ -17,6 +18,8 @@ typedef void (^UITabBarItemSetBadgeListener)(NSString *); - (void)removeSetLeftBarButtonItemListener:(NSInteger)key; - (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; - (void)removeSetRightBarButtonItemListener:(NSInteger)key; +- (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (void)removeSetBackBarButtonItemListener:(NSInteger)key; @end @@ -26,4 +29,13 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa - (void)removeSetBadgeListener:(NSInteger)key; +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; +- (void)removeSetTitleListener:(NSInteger)key; + +- (NSInteger)addSetImageListener:(UINavigationItemSetImageListener)listener; +- (void)removeSetImageListener:(NSInteger)key; + +- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener)listener; +- (void)removeSetSelectedImageListener:(NSInteger)key; + @end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index daa72e99c5..e86bffb586 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -7,9 +7,12 @@ static const void *sourceItemKey = &sourceItemKey; static const void *targetItemKey = &targetItemKey; static const void *setTitleListenerBagKey = &setTitleListenerBagKey; +static const void *setImageListenerBagKey = &setImageListenerBagKey; +static const void *setSelectedImageListenerBagKey = &setSelectedImageListenerBagKey; static const void *setTitleViewListenerBagKey = &setTitleViewListenerBagKey; static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemListenerBagKey; static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; +static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey; static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; @implementation UINavigationItem (Proxy) @@ -25,6 +28,7 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setBackBarButtonItem:) newSelector:@selector(_ac91f40f_setBackBarButtonItem:)]; }); } @@ -96,6 +100,22 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; } } +- (void)_ac91f40f_setBackBarButtonItem:(UIBarButtonItem *)backBarButtonItem +{ + UIBarButtonItem *previousItem = self.rightBarButtonItem; + + [self _ac91f40f_setBackBarButtonItem:backBarButtonItem]; + + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setBackBarButtonItem:backBarButtonItem]; + } else { + [(NSBag *)[self associatedObjectForKey:setBackBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) { + listener(previousItem, backBarButtonItem, false); + }]; + } +} + - (void)setTargetItem:(UINavigationItem *)targetItem { NSWeakReference *previousSourceItem = [targetItem associatedObjectForKey:sourceItemKey]; [(UINavigationItem *)previousSourceItem.value setAssociatedObject:nil forKey:targetItemKey associationPolicy:NSObjectAssociationPolicyRetain]; @@ -184,6 +204,20 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] removeItem:key]; } +- (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener { + NSBag *bag = [self associatedObjectForKey:setBackBarButtonItemListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setBackBarButtonItemListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetBackBarButtonItemListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setBackBarButtonItemListenerBagKey] removeItem:key]; +} + @end @implementation UITabBarItem (Proxy) @@ -194,6 +228,9 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; dispatch_once(&onceToken, ^ { [RuntimeUtils swizzleInstanceMethodOfClass:[UITabBarItem class] currentSelector:@selector(setBadgeValue:) newSelector:@selector(_ac91f40f_setBadgeValue:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UITabBarItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_ac91f40f_setTitle:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UITabBarItem class] currentSelector:@selector(setImage:) newSelector:@selector(_ac91f40f_setImage:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UITabBarItem class] currentSelector:@selector(setSelectedImage:) newSelector:@selector(_ac91f40f_setSelectedImage:)]; }); } @@ -219,4 +256,70 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa }]; } +- (void)_ac91f40f_setTitle:(NSString *)value { + [self _ac91f40f_setTitle:value]; + + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { + listener(value); + }]; +} + +- (void)_ac91f40f_setImage:(UIImage *)value { + [self _ac91f40f_setImage:value]; + + [(NSBag *)[self associatedObjectForKey:setImageListenerBagKey] enumerateItems:^(UINavigationItemSetImageListener listener) { + listener(value); + }]; +} + +- (void)_ac91f40f_setSelectedImage:(UIImage *)value { + [self _ac91f40f_setSelectedImage:value]; + + [(NSBag *)[self associatedObjectForKey:setSelectedImageListenerBagKey] enumerateItems:^(UINavigationItemSetImageListener listener) { + listener(value); + }]; +} + +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener { + NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setTitleListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetTitleListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetImageListener:(UINavigationItemSetImageListener)listener { + NSBag *bag = [self associatedObjectForKey:setImageListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setImageListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetImageListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setImageListenerBagKey] removeItem:key]; +} + +- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener)listener { + NSBag *bag = [self associatedObjectForKey:setSelectedImageListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setSelectedImageListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetSelectedImageListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setSelectedImageListenerBagKey] removeItem:key]; +} + @end diff --git a/Display/UniversalMasterController.swift b/Display/UniversalMasterController.swift index 117e2b1ec1..e69de29bb2 100644 --- a/Display/UniversalMasterController.swift +++ b/Display/UniversalMasterController.swift @@ -1,22 +0,0 @@ -import Foundation -import UIKit -import AsyncDisplayKit -import SwiftSignalKit - -class UniversalMasterController: ViewController { - private var controllers: [ViewController] = [] - - public init() { - super.init(navigationBar: NavigationBar()) - } - - required public init(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - super.containerLayoutUpdated(layout, transition: transition) - } - - -} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 0dcfd25ff1..fad7bdc69e 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -64,7 +64,7 @@ open class ViewControllerPresentationArguments { } public let statusBar: StatusBar - public let navigationBar: NavigationBar + public let navigationBar: NavigationBar? public var displayNavigationBar = true @@ -72,7 +72,11 @@ open class ViewControllerPresentationArguments { private weak var activeInputView: UIResponder? open var navigationHeight: CGFloat { - return self.navigationBar.frame.maxY + if let navigationBar = self.navigationBar { + return navigationBar.frame.maxY + } else { + return 0.0 + } } private let _ready = Promise(true) @@ -107,17 +111,21 @@ open class ViewControllerPresentationArguments { } } - public init(navigationBar: NavigationBar = NavigationBar()) { + public init(navigationBarTheme: NavigationBarTheme?) { self.statusBar = StatusBar() - self.navigationBar = navigationBar + if let navigationBarTheme = navigationBarTheme { + self.navigationBar = NavigationBar(theme: navigationBarTheme) + } else { + self.navigationBar = nil + } self.presentationContext = PresentationContext() super.init(nibName: nil, bundle: nil) - self.navigationBar.backPressed = { [weak self] in + self.navigationBar?.backPressed = { [weak self] in self?.navigationController?.popViewController(animated: true) } - self.navigationBar.item = self.navigationItem + self.navigationBar?.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false } @@ -151,7 +159,9 @@ open class ViewControllerPresentationArguments { navigationBarFrame.origin.y = -navigationBarFrame.size.height } - transition.updateFrame(node: self.navigationBar, frame: navigationBarFrame) + if let navigationBar = self.navigationBar { + transition.updateFrame(node: navigationBar, frame: navigationBarFrame) + } self.presentationContext.containerLayoutUpdated(layout, transition: transition) @@ -162,7 +172,9 @@ open class ViewControllerPresentationArguments { open override func loadView() { self.view = self.displayNode.view - self.displayNode.addSubnode(self.navigationBar) + if let navigationBar = self.navigationBar { + self.displayNode.addSubnode(navigationBar) + } self.view.addSubview(self.statusBar.view) self.presentationContext.view = self.view } diff --git a/Display/ViewControllerTracingNode.swift b/Display/ViewControllerTracingNode.swift new file mode 100644 index 0000000000..6124180039 --- /dev/null +++ b/Display/ViewControllerTracingNode.swift @@ -0,0 +1,34 @@ +import Foundation +import AsyncDisplayKit + +private final class ViewControllerTracingNodeView: UITracingLayerView { + private var inHitTest = false + var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.inHitTest { + return super.hitTest(point, with: event) + } else { + self.inHitTest = true + let result = self.hitTestImpl?(point, event) + self.inHitTest = false + return result + } + } +} + +open class ViewControllerTracingNode: ASDisplayNode { + override public init() { + super.init(viewBlock: { + return ViewControllerTracingNodeView() + }, didLoad: nil) + } + + override open func didLoad() { + super.didLoad() + + (self.view as! ViewControllerTracingNodeView).hitTestImpl = { [weak self] point, event in + return self?.hitTest(point, with: event) + } + } +} diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 386d85fd3c..bcf7e87dc5 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -15,45 +15,51 @@ private class WindowRootViewController: UIViewController { private struct WindowLayout: Equatable { public let size: CGSize + public let metrics: LayoutMetrics public let statusBarHeight: CGFloat? + public let forceInCallStatusBarText: String? public let inputHeight: CGFloat? public let inputMinimized: Bool -} -private func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { - if !lhs.size.equalTo(rhs.size) { - return false - } - - if let lhsStatusBarHeight = lhs.statusBarHeight { - if let rhsStatusBarHeight = rhs.statusBarHeight { - if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { - return false - } - } else { + static func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { + if !lhs.size.equalTo(rhs.size) { return false } - } else if let _ = rhs.statusBarHeight { - return false - } - - if let lhsInputHeight = lhs.inputHeight { - if let rhsInputHeight = rhs.inputHeight { - if !lhsInputHeight.isEqual(to: rhsInputHeight) { + + if let lhsStatusBarHeight = lhs.statusBarHeight { + if let rhsStatusBarHeight = rhs.statusBarHeight { + if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { + return false + } + } else { return false } - } else { + } else if let _ = rhs.statusBarHeight { return false } - } else if let _ = rhs.inputHeight { - return false + + if lhs.forceInCallStatusBarText != rhs.forceInCallStatusBarText { + return false + } + + if let lhsInputHeight = lhs.inputHeight { + if let rhsInputHeight = rhs.inputHeight { + if !lhsInputHeight.isEqual(to: rhsInputHeight) { + return false + } + } else { + return false + } + } else if let _ = rhs.inputHeight { + return false + } + + if lhs.inputMinimized != rhs.inputMinimized { + return false + } + + return true } - - if lhs.inputMinimized != rhs.inputMinimized { - return false - } - - return true } private struct UpdatingLayout { @@ -72,28 +78,35 @@ private struct UpdatingLayout { } } - mutating func update(size: CGSize, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + mutating func update(size: CGSize, metrics: LayoutMetrics, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: size, statusBarHeight: self.layout.statusBarHeight, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) + } + + + mutating func update(forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) } mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, statusBarHeight: statusBarHeight, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) } mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, statusBarHeight: self.layout.statusBarHeight, inputHeight: inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, inputMinimized: self.layout.inputMinimized) } mutating func update(inputMinimized: Bool, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, statusBarHeight: self.layout.statusBarHeight, inputHeight: self.layout.inputHeight, inputMinimized: inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: inputMinimized) } } @@ -106,7 +119,18 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container inputHeight = floor(0.85 * inputHeightValue) } - return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: inputHeight) + let resolvedStatusBarHeight: CGFloat? + if let statusBarHeight = layout.statusBarHeight { + if layout.forceInCallStatusBarText != nil { + resolvedStatusBarHeight = 40.0 + } else { + resolvedStatusBarHeight = statusBarHeight + } + } else { + resolvedStatusBarHeight = nil + } + + return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(), statusBarHeight: resolvedStatusBarHeight, inputHeight: inputHeight) } public final class WindowHostView { @@ -138,6 +162,10 @@ public protocol WindowHost { func present(_ controller: ViewController) } +private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { + return LayoutMetrics(widthClass: .compact, heightClass: .compact) +} + public class Window1 { public let hostView: WindowHostView @@ -156,6 +184,13 @@ public class Window1 { private var statusBarHidden = false + public private(set) var forceInCallStatusBarText: String? = nil + public var inCallNavigate: (() -> Void)? { + didSet { + self.statusBarManager?.inCallNavigate = self.inCallNavigate + } + } + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -178,7 +213,7 @@ public class Window1 { minimized = false } - self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized) + self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, inputMinimized: minimized) self.presentationContext = PresentationContext() self.hostView.present = { [weak self] controller in @@ -260,6 +295,16 @@ public class Window1 { } } + public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) { + if self.forceInCallStatusBarText != forceInCallStatusBarText { + self.forceInCallStatusBarText = forceInCallStatusBarText + + self.updateLayout { $0.update(forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } + + self.invalidateTracingStatusBars() + } + } + private func invalidateTracingStatusBars() { self.tracingStatusBarsInvalidated = true self.hostView.view.setNeedsLayout() @@ -274,8 +319,10 @@ public class Window1 { } } - if let result = self._topLevelOverlayController?.view.hitTest(point, with: event) { - return result + for controller in self._topLevelOverlayControllers.reversed() { + if let result = controller.view.hitTest(point, with: event) { + return result + } } if let result = self.presentationContext.hitTest(point, with: event) { @@ -291,7 +338,7 @@ public class Window1 { } else { transition = .immediate } - self.updateLayout { $0.update(size: value, transition: transition, overrideTransition: true) } + self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } } private var _rootController: ContainableController? @@ -313,24 +360,24 @@ public class Window1 { } } - private var _topLevelOverlayController: ContainableController? - public var topLevelOverlayController: ContainableController? { + private var _topLevelOverlayControllers: [ContainableController] = [] + public var topLevelOverlayControllers: [ContainableController] { get { - return _topLevelOverlayController + return _topLevelOverlayControllers } set(value) { - if let topLevelOverlayController = self._topLevelOverlayController { - topLevelOverlayController.view.removeFromSuperview() + for controller in self._topLevelOverlayControllers { + controller.view.removeFromSuperview() } - self._topLevelOverlayController = value + self._topLevelOverlayControllers = value - if let topLevelOverlayController = self._topLevelOverlayController { - topLevelOverlayController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + for controller in self._topLevelOverlayControllers { + controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - self.hostView.view.addSubview(topLevelOverlayController.view) + self.hostView.view.addSubview(controller.view) } - self.presentationContext.topLevelSubview = self._topLevelOverlayController?.view + self.presentationContext.topLevelSubview = self._topLevelOverlayControllers.first?.view } } @@ -339,7 +386,7 @@ public class Window1 { self.tracingStatusBarsInvalidated = false if self.statusBarHidden { - statusBarManager.surfaces = [] + statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, animated: false) } else { var statusBarSurfaces: [StatusBarSurface] = [] for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { @@ -353,7 +400,13 @@ public class Window1 { statusBarSurfaces.append(surface) } self.hostView.view.layer.adjustTraceableLayerTransforms(CGSize()) - statusBarManager.surfaces = statusBarSurfaces + var animatedUpdate = false + if let updatingLayout = self.updatingLayout { + if case .animated = updatingLayout.transition { + animatedUpdate = true + } + } + statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, animated: animatedUpdate) } var keyboardSurfaces: [KeyboardSurface] = [] @@ -430,7 +483,7 @@ public class Window1 { self.tracingStatusBarsInvalidated = true self.hostView.view.setNeedsLayout() } - self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized) + self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized) self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) From 82499816e7c691756212c93b8c4f209420ce11ee Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 14 Jul 2017 15:31:53 +0300 Subject: [PATCH 041/245] no message --- Display.xcodeproj/project.pbxproj | 149 ++++++++++++++---- .../xcschemes/xcschememanagement.plist | 4 +- Display/GenerateImage.swift | 34 ++-- Display/NativeWindowHostView.swift | 25 ++- Display/NavigationBar.swift | 4 +- Display/PresentationContext.swift | 16 +- Display/SwitchNode.swift | 17 +- Display/TextAlertController.swift | 2 +- Display/ViewController.swift | 28 ++-- Display/WindowContent.swift | 25 ++- 10 files changed, 228 insertions(+), 76 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 634b1f8735..94fec9abf4 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -843,7 +843,7 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - D05CC2751B69316F00E235A3 /* Debug */ = { + D05CC2751B69316F00E235A3 /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -889,9 +889,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Debug; + name = "Debug Hockeyapp"; }; - D05CC2761B69316F00E235A3 /* Release */ = { + D05CC2761B69316F00E235A3 /* Release Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -930,9 +930,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Release; + name = "Release Hockeyapp"; }; - D05CC2781B69316F00E235A3 /* Debug */ = { + D05CC2781B69316F00E235A3 /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -956,9 +956,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 3.0; }; - name = Debug; + name = "Debug Hockeyapp"; }; - D05CC2791B69316F00E235A3 /* Release */ = { + D05CC2791B69316F00E235A3 /* Release Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -982,9 +982,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 3.0; }; - name = Release; + name = "Release Hockeyapp"; }; - D05CC27B1B69316F00E235A3 /* Debug */ = { + D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -993,9 +993,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = Debug; + name = "Debug Hockeyapp"; }; - D05CC27C1B69316F00E235A3 /* Release */ = { + D05CC27C1B69316F00E235A3 /* Release Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1005,9 +1005,94 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 3.0; }; - name = Release; + name = "Release Hockeyapp"; }; - D086A56E1CC0115D00F08284 /* Hockeyapp */ = { + D079FD091F06BD9C0038FADE /* Debug AppStore */ = { + 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug AppStore"; + }; + D079FD0A1F06BD9C0038FADE /* Debug AppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 3.0; + }; + name = "Debug AppStore"; + }; + D079FD0B1F06BD9C0038FADE /* Debug AppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = "Debug AppStore"; + }; + D086A56E1CC0115D00F08284 /* Release AppStore */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1046,9 +1131,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = Hockeyapp; + name = "Release AppStore"; }; - D086A56F1CC0115D00F08284 /* Hockeyapp */ = { + D086A56F1CC0115D00F08284 /* Release AppStore */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1068,12 +1153,13 @@ PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 3.0; }; - name = Hockeyapp; + name = "Release AppStore"; }; - D086A5701CC0115D00F08284 /* Hockeyapp */ = { + D086A5701CC0115D00F08284 /* Release AppStore */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1082,7 +1168,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = Hockeyapp; + name = "Release AppStore"; }; /* End XCBuildConfiguration section */ @@ -1090,32 +1176,35 @@ D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { isa = XCConfigurationList; buildConfigurations = ( - D05CC2751B69316F00E235A3 /* Debug */, - D05CC2761B69316F00E235A3 /* Release */, - D086A56E1CC0115D00F08284 /* Hockeyapp */, + D05CC2751B69316F00E235A3 /* Debug Hockeyapp */, + D079FD091F06BD9C0038FADE /* Debug AppStore */, + D05CC2761B69316F00E235A3 /* Release Hockeyapp */, + D086A56E1CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release Hockeyapp"; }; D05CC2771B69316F00E235A3 /* Build configuration list for PBXNativeTarget "Display" */ = { isa = XCConfigurationList; buildConfigurations = ( - D05CC2781B69316F00E235A3 /* Debug */, - D05CC2791B69316F00E235A3 /* Release */, - D086A56F1CC0115D00F08284 /* Hockeyapp */, + D05CC2781B69316F00E235A3 /* Debug Hockeyapp */, + D079FD0A1F06BD9C0038FADE /* Debug AppStore */, + D05CC2791B69316F00E235A3 /* Release Hockeyapp */, + D086A56F1CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release Hockeyapp"; }; D05CC27A1B69316F00E235A3 /* Build configuration list for PBXNativeTarget "DisplayTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - D05CC27B1B69316F00E235A3 /* Debug */, - D05CC27C1B69316F00E235A3 /* Release */, - D086A5701CC0115D00F08284 /* Hockeyapp */, + D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */, + D079FD0B1F06BD9C0038FADE /* Debug AppStore */, + D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, + D086A5701CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = Release; + defaultConfigurationName = "Release Hockeyapp"; }; /* End XCConfigurationList section */ }; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 8f0ec3539d..3cff77364e 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,12 +7,12 @@ Display.xcscheme orderHint - 23 + 24 DisplayTests.xcscheme orderHint - 24 + 25 SuppressBuildableAutocreation diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 3c3e82e34b..75bf8e1d94 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -96,7 +96,7 @@ public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? return UIImage(cgImage: image, scale: selectedScale, orientation: .up) } -public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { +public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, strokeColor: UIColor? = nil, strokeWidth: CGFloat? = nil, backgroundColor: UIColor? = nil) -> UIImage? { return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) if let backgroundColor = backgroundColor { @@ -104,17 +104,30 @@ public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, backgr context.fill(CGRect(origin: CGPoint(), size: size)) } - if let color = color { - context.setFillColor(color.cgColor) + if let strokeColor = strokeColor, let strokeWidth = strokeWidth { + context.setFillColor(strokeColor.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) + + if let color = color { + context.setFillColor(color.cgColor) + } else { + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + } + context.fillEllipse(in: CGRect(origin: CGPoint(x: strokeWidth, y: strokeWidth), size: CGSize(width: size.width - strokeWidth * 2.0, height: size.height - strokeWidth * 2.0))) } else { - context.setFillColor(UIColor.clear.cgColor) - context.setBlendMode(.copy) + if let color = color { + context.setFillColor(color.cgColor) + } else { + context.setFillColor(UIColor.clear.cgColor) + context.setBlendMode(.copy) + } + context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) } - context.fillEllipse(in: CGRect(origin: CGPoint(), size: size)) }) } -public func generateCircleImage(diameter: CGFloat, lineWidth: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { +public func generateCircleImage(diameter: CGFloat, lineWidth: CGFloat, color: UIColor?, strokeColor: UIColor? = nil, strokeWidth: CGFloat? = nil, backgroundColor: UIColor? = nil) -> UIImage? { return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) if let backgroundColor = backgroundColor { @@ -139,7 +152,7 @@ public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor return generateFilledCircleImage(diameter: radius * 2.0, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) } -public func generateStretchableFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { +public func generateStretchableFilledCircleImage(diameter: CGFloat, color: UIColor?, strokeColor: UIColor? = nil, strokeWidth: CGFloat? = nil, backgroundColor: UIColor? = nil) -> UIImage? { let intRadius = Int(diameter / 2.0) let intDiameter = Int(diameter) let cap: Int @@ -151,7 +164,7 @@ public func generateStretchableFilledCircleImage(diameter: CGFloat, color: UICol cap = intRadius } - return generateFilledCircleImage(diameter: diameter, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) + return generateFilledCircleImage(diameter: diameter, color: color, strokeColor: strokeColor, strokeWidth: strokeWidth, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: cap, topCapHeight: cap) } public func generateVerticallyStretchableFilledCircleImage(radius: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? { @@ -307,6 +320,9 @@ public class DrawingContext { var srcY = 0 let dstX = Int(at.x * self.scale) var dstY = Int(at.y * self.scale) + if dstX < 0 || dstY < 0 { + return + } let width = min(Int(self.size.width * self.scale) - dstX, Int(other.size.width * self.scale)) let height = min(Int(self.size.height * self.scale) - dstY, Int(other.size.height * self.scale)) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index d2d78ae42a..f43ec4a6f2 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -10,7 +10,7 @@ private let defaultOrientations: UIInterfaceOrientationMask = { }() private class WindowRootViewController: UIViewController { - var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? + var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)? var orientations: UIInterfaceOrientationMask = defaultOrientations { didSet { if oldValue != self.orientations { @@ -44,8 +44,9 @@ private final class NativeWindow: UIWindow, WindowHost { var layoutSubviewsEvent: (() -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateToInterfaceOrientation: (() -> Void)? - var presentController: ((ViewController) -> Void)? + var presentController: ((ViewController, PresentationSurfaceLevel) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? + var presentNativeImpl: ((UIViewController) -> Void)? override var frame: CGRect { get { @@ -88,8 +89,12 @@ private final class NativeWindow: UIWindow, WindowHost { self.updateToInterfaceOrientation?() } - func present(_ controller: ViewController) { - self.presentController?(controller) + func present(_ controller: ViewController, on level: PresentationSurfaceLevel) { + self.presentController?(controller, level) + } + + func presentNative(_ controller: UIViewController) { + self.presentNativeImpl?(controller) } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { @@ -128,17 +133,21 @@ public func nativeWindowHostView() -> WindowHostView { hostView?.updateToInterfaceOrientation?() } - window.presentController = { [weak hostView] controller in - hostView?.present?(controller) + window.presentController = { [weak hostView] controller, level in + hostView?.present?(controller, level) + } + + window.presentNativeImpl = { [weak hostView] controller in + hostView?.presentNative?(controller) } window.hitTestImpl = { [weak hostView] point, event in return hostView?.hitTest?(point, event) } - rootViewController.presentController = { [weak hostView] controller, animated, completion in + rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let strongSelf = hostView { - strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom)) + strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) if let completion = completion { completion() } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index ed78bd041c..0497aa89ee 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -413,9 +413,7 @@ open class NavigationBar: ASDisplayNode { } } self.backButtonNode.pressed = { [weak self] in - if let backPressed = self?.backPressed { - backPressed() - } + self?.backPressed() } self.leftButtonNode.pressed = { [weak self] in diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 5b94f1113e..7b6be08559 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -1,8 +1,16 @@ import SwiftSignalKit +public struct PresentationSurfaceLevel: RawRepresentable { + public var rawValue: Int32 + + public init(rawValue: Int32) { + self.rawValue = rawValue + } +} + public enum PresentationContextType { case current - case window + case window(PresentationSurfaceLevel) } final class PresentationContext { @@ -35,7 +43,7 @@ final class PresentationContext { var topLevelSubview: UIView? - public func present(_ controller: ViewController) { + public func present(_ controller: ViewController, on: PresentationSurfaceLevel) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -54,8 +62,8 @@ final class PresentationContext { strongSelf.controllers.append(controller) if let view = strongSelf.view, let layout = strongSelf.layout { - controller.navigation_setDismiss({ [weak strongSelf, weak controller] in - if let strongSelf = strongSelf, let controller = controller { + controller.navigation_setDismiss({ [weak controller] in + if let strongSelf = self, let controller = controller { strongSelf.dismiss(controller) } }, rootController: nil) diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index 9028bebd82..a50cdfbe06 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -1,6 +1,17 @@ import Foundation import AsyncDisplayKit +private final class SwitchNodeViewLayer: CALayer { + override func setNeedsDisplay() { + } +} + +private final class SwitchNodeView: UISwitch { + override class var layerClass: AnyClass { + return SwitchNodeViewLayer.self + } +} + open class SwitchNode: ASDisplayNode { public var valueUpdated: ((Bool) -> Void)? @@ -14,7 +25,7 @@ open class SwitchNode: ASDisplayNode { public var handleColor = UIColor(rgb: 0xffffff) { didSet { if self.isNodeLoaded { - (self.view as! UISwitch).thumbTintColor = self.handleColor + //(self.view as! UISwitch).thumbTintColor = self.handleColor } } } @@ -42,7 +53,7 @@ open class SwitchNode: ASDisplayNode { override public init() { super.init(viewBlock: { - return UISwitch() + return SwitchNodeView() }, didLoad: nil) } @@ -51,7 +62,7 @@ open class SwitchNode: ASDisplayNode { (self.view as! UISwitch).backgroundColor = self.backgroundColor (self.view as! UISwitch).tintColor = self.frameColor - (self.view as! UISwitch).onTintColor = self.contentColor + //(self.view as! UISwitch).thumbTintColor = self.handleColor (self.view as! UISwitch).onTintColor = self.contentColor (self.view as! UISwitch).setOn(self._isOn, animated: false) diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 1d1fd10c78..314b14833e 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -208,7 +208,7 @@ public func textAlertController(title: NSAttributedString?, text: NSAttributedSt public func standardTextAlertController(title: String?, text: String, actions: [TextAlertAction]) -> AlertController { var dismissImpl: (() -> Void)? - let controller = AlertController(contentNode: TextAlertContentNode(title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: .black, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.medium(17.0) : Font.regular(13.0), textColor: .black, paragraphAlignment: .center), actions: actions.map { action in + let controller = AlertController(contentNode: TextAlertContentNode(title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: .black, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.semibold(17.0) : Font.regular(13.0), textColor: .black, paragraphAlignment: .center), actions: actions.map { action in return TextAlertAction(type: action.type, title: action.title, action: { dismissImpl?() action.action() diff --git a/Display/ViewController.swift b/Display/ViewController.swift index fad7bdc69e..a6b4c0155f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -71,6 +71,18 @@ open class ViewControllerPresentationArguments { private weak var activeInputViewCandidate: UIResponder? private weak var activeInputView: UIResponder? + private var navigationBarOrigin: CGFloat = 0.0 + + public var navigationOffset: CGFloat = 0.0 { + didSet { + if let navigationBar = self.navigationBar { + var navigationBarFrame = navigationBar.frame + navigationBarFrame.origin.y = self.navigationBarOrigin + self.navigationOffset + navigationBar.frame = navigationBarFrame + } + } + } + open var navigationHeight: CGFloat { if let navigationBar = self.navigationBar { return navigationBar.frame.maxY @@ -151,7 +163,6 @@ open class ViewControllerPresentationArguments { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0)) if layout.statusBarHeight == nil { - //navigationBarFrame.origin.y -= 20.0 navigationBarFrame.size.height = 44.0 } @@ -159,6 +170,9 @@ open class ViewControllerPresentationArguments { navigationBarFrame.origin.y = -navigationBarFrame.size.height } + navigationBarOrigin = navigationBarFrame.origin.y + navigationBarFrame.origin.y += self.navigationOffset + if let navigationBar = self.navigationBar { transition.updateFrame(node: navigationBar, frame: navigationBarFrame) } @@ -214,12 +228,6 @@ open class ViewControllerPresentationArguments { override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { super.present(viewControllerToPresent, animated: flag, completion: completion) return - - if let controller = viewControllerToPresent as? ViewController { - self.present(controller, in: .window) - } else { - preconditionFailure("use present(_:in) for \(viewControllerToPresent)") - } } override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { @@ -247,9 +255,9 @@ open class ViewControllerPresentationArguments { controller.presentationArguments = arguments switch context { case .current: - self.presentationContext.present(controller) - case .window: - self.window?.present(controller) + self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0)) + case let .window(level): + self.window?.present(controller, on: level) } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index bcf7e87dc5..58afdfdbf5 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -139,7 +139,8 @@ public final class WindowHostView { let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void - var present: ((ViewController) -> Void)? + var present: ((ViewController, PresentationSurfaceLevel) -> Void)? + var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize) -> Void)? var layoutSubviews: (() -> Void)? var updateToInterfaceOrientation: (() -> Void)? @@ -159,7 +160,7 @@ public struct WindowTracingTags { } public protocol WindowHost { - func present(_ controller: ViewController) + func present(_ controller: ViewController, on level: PresentationSurfaceLevel) } private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { @@ -216,8 +217,12 @@ public class Window1 { self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, inputMinimized: minimized) self.presentationContext = PresentationContext() - self.hostView.present = { [weak self] controller in - self?.present(controller) + self.hostView.present = { [weak self] controller, level in + self?.present(controller, on: level) + } + + self.hostView.presentNative = { [weak self] controller in + self?.presentNative(controller) } self.hostView.updateSize = { [weak self] size in @@ -487,11 +492,19 @@ public class Window1 { self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) + + for controller in self.topLevelOverlayControllers { + controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) + } } } } - public func present(_ controller: ViewController) { - self.presentationContext.present(controller) + public func present(_ controller: ViewController, on level: PresentationSurfaceLevel) { + self.presentationContext.present(controller, on: level) + } + + public func presentNative(_ controller: UIViewController) { + } } From 4244aadacbe90f89b8aa10e1e5b89ccc764cc21c Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 15 Aug 2017 14:46:10 +0300 Subject: [PATCH 042/245] no message --- Display.xcodeproj/project.pbxproj | 335 ++++++++++++++++++ .../xcschemes/xcschememanagement.plist | 14 +- Display/ASTransformLayerNode.swift | 22 +- Display/AlertControllerNode.swift | 2 +- Display/ContainableController.swift | 47 +++ Display/ContextMenuAction.swift | 1 + Display/ContextMenuActionNode.swift | 45 ++- Display/GridNode.swift | 109 ++++-- Display/GridNodeScroller.swift | 6 +- Display/LegacyPresentedControllerNode.swift | 6 +- Display/ListView.swift | 68 +++- Display/ListViewItemHeader.swift | 11 +- Display/ListViewItemNode.swift | 19 +- Display/ListViewScrollerAppkit.swift | 5 + Display/NavigationBar.swift | 54 ++- .../NavigationBarTitleTransitionNode.swift | 6 + Display/StatusBar.swift | 20 +- Display/StatusBarManager.swift | 21 +- Display/StatusBarProxyNode.swift | 2 +- Display/SwitchNode.swift | 6 +- Display/TabBarContollerNode.swift | 6 +- Display/TextFieldNode.swift | 6 +- Display/UIKitUtils.swift | 4 + Display/ViewControllerTracingNode.swift | 6 +- DisplayMac/DisplayMac.h | 19 + DisplayMac/Info.plist | 26 ++ 26 files changed, 772 insertions(+), 94 deletions(-) create mode 100644 Display/ListViewScrollerAppkit.swift create mode 100644 Display/NavigationBarTitleTransitionNode.swift create mode 100644 DisplayMac/DisplayMac.h create mode 100644 DisplayMac/Info.plist diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 94fec9abf4..4b8df465e5 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -9,6 +9,8 @@ /* Begin PBXBuildFile section */ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; + D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D01159B91F40E96C0039383E /* DisplayMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; + D01159C21F40EA120039383E /* ListViewScrollerAppkit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */; }; D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; @@ -93,6 +95,7 @@ D0C0B59D1EE022CC000F4D2C /* NavigationBarContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */; }; D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; + D0C12A1A1F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C12A191F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift */; }; D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; }; D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */; }; D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBD1CC4431D0044FF83 /* Spring.swift */; }; @@ -137,6 +140,10 @@ /* Begin PBXFileReference section */ D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = ""; }; + D01159B71F40E96B0039383E /* DisplayMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DisplayMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D01159B91F40E96C0039383E /* DisplayMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DisplayMac.h; sourceTree = ""; }; + D01159BA1F40E96C0039383E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewScrollerAppkit.swift; sourceTree = ""; }; D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; @@ -224,6 +231,7 @@ D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarContentNode.swift; sourceTree = ""; }; D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; + D0C12A191F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTitleTransitionNode.swift; sourceTree = ""; }; D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTransformLayerNode.swift; sourceTree = ""; }; D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemNode.swift; sourceTree = ""; }; D0C2DFBD1CC4431D0044FF83 /* Spring.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Spring.swift; sourceTree = ""; }; @@ -257,6 +265,13 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ + D01159B31F40E96B0039383E /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D05CC25F1B69316F00E235A3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -277,6 +292,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D01159B81F40E96C0039383E /* DisplayMac */ = { + isa = PBXGroup; + children = ( + D01159B91F40E96C0039383E /* DisplayMac.h */, + D01159BA1F40E96C0039383E /* Info.plist */, + ); + path = DisplayMac; + sourceTree = ""; + }; D015F7501D1ADC6800E269B5 /* Window */ = { isa = PBXGroup; children = ( @@ -381,6 +405,7 @@ D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( + D01159B81F40E96C0039383E /* DisplayMac */, D05CC2A31B6932D500E235A3 /* Frameworks */, D05CC2651B69316F00E235A3 /* Display */, D05CC2711B69316F00E235A3 /* DisplayTests */, @@ -393,6 +418,7 @@ children = ( D05CC2631B69316F00E235A3 /* Display.framework */, D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */, + D01159B71F40E96B0039383E /* DisplayMac.framework */, ); name = Products; sourceTree = ""; @@ -493,6 +519,7 @@ D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */, D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */, D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */, + D0C12A191F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift */, D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */, D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */, @@ -561,6 +588,7 @@ D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */, D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */, D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, + D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */, D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */, D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */, ); @@ -605,6 +633,14 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ + D01159B41F40E96B0039383E /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D05CC2601B69316F00E235A3 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -630,6 +666,24 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ + D01159B61F40E96B0039383E /* DisplayMac */ = { + isa = PBXNativeTarget; + buildConfigurationList = D01159C01F40E96C0039383E /* Build configuration list for PBXNativeTarget "DisplayMac" */; + buildPhases = ( + D01159B21F40E96B0039383E /* Sources */, + D01159B31F40E96B0039383E /* Frameworks */, + D01159B41F40E96B0039383E /* Headers */, + D01159B51F40E96B0039383E /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = DisplayMac; + productName = DisplayMac; + productReference = D01159B71F40E96B0039383E /* DisplayMac.framework */; + productType = "com.apple.product-type.framework"; + }; D05CC2621B69316F00E235A3 /* Display */ = { isa = PBXNativeTarget; buildConfigurationList = D05CC2771B69316F00E235A3 /* Build configuration list for PBXNativeTarget "Display" */; @@ -676,6 +730,12 @@ LastUpgradeCheck = 0800; ORGANIZATIONNAME = Telegram; TargetAttributes = { + D01159B61F40E96B0039383E = { + CreatedOnToolsVersion = 8.3.2; + DevelopmentTeam = X834Q8SBVP; + LastSwiftMigration = 0830; + ProvisioningStyle = Automatic; + }; D05CC2621B69316F00E235A3 = { CreatedOnToolsVersion = 7.0; ProvisioningStyle = Manual; @@ -699,11 +759,19 @@ targets = ( D05CC2621B69316F00E235A3 /* Display */, D05CC26C1B69316F00E235A3 /* DisplayTests */, + D01159B61F40E96B0039383E /* DisplayMac */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ + D01159B51F40E96B0039383E /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; D05CC2611B69316F00E235A3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -723,6 +791,14 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ + D01159B21F40E96B0039383E /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + D01159C21F40EA120039383E /* ListViewScrollerAppkit.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; D05CC25E1B69316F00E235A3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -779,6 +855,7 @@ D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, + D0C12A1A1F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift in Sources */, D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, @@ -843,6 +920,254 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ + D01159BC1F40E96C0039383E /* Debug Hockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug Hockeyapp"; + }; + D01159BD1F40E96C0039383E /* Debug AppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug AppStore"; + }; + D01159BE1F40E96C0039383E /* Release Hockeyapp */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Hockeyapp"; + }; + D01159BF1F40E96C0039383E /* Release AppStore */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 3.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release AppStore"; + }; D05CC2751B69316F00E235A3 /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1173,6 +1498,16 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ + D01159C01F40E96C0039383E /* Build configuration list for PBXNativeTarget "DisplayMac" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + D01159BC1F40E96C0039383E /* Debug Hockeyapp */, + D01159BD1F40E96C0039383E /* Debug AppStore */, + D01159BE1F40E96C0039383E /* Release Hockeyapp */, + D01159BF1F40E96C0039383E /* Release AppStore */, + ); + defaultConfigurationIsVisible = 0; + }; D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 3cff77364e..7a4a41da85 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,16 +7,26 @@ Display.xcscheme orderHint - 24 + 22 - DisplayTests.xcscheme + DisplayMac.xcscheme orderHint 25 + DisplayTests.xcscheme + + orderHint + 23 + SuppressBuildableAutocreation + D01159B61F40E96B0039383E + + primary + + D05CC2621B69316F00E235A3 primary diff --git a/Display/ASTransformLayerNode.swift b/Display/ASTransformLayerNode.swift index 0e204e8074..6e8fa21298 100644 --- a/Display/ASTransformLayerNode.swift +++ b/Display/ASTransformLayerNode.swift @@ -33,30 +33,36 @@ class ASTransformView: UIView { open class ASTransformLayerNode: ASDisplayNode { public override init() { - super.init(layerBlock: { + super.init() + self.setLayerBlock({ return ASTransformLayer() - }, didLoad: nil) + }) } } open class ASTransformViewNode: ASDisplayNode { public override init() { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return ASTransformView() - }, didLoad: nil) + }) } } open class ASTransformNode: ASDisplayNode { public init(layerBacked: Bool = true) { if layerBacked { - super.init(layerBlock: { + super.init() + self.setLayerBlock({ return ASTransformLayer() - }, didLoad: nil) + }) } else { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return ASTransformView() - }, didLoad: nil) + }) } } } diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 02c9719d36..bab1916e40 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -21,7 +21,7 @@ final class AlertControllerNode: ASDisplayNode { self.effectNode = ASDisplayNode(viewBlock: { let view = UIView()//UIVisualEffectView(effect: UIBlurEffect(style: .light)) return view - }, didLoad: nil) + }) self.contentNode = contentNode diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index a5a02e8400..33c728f4d8 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -15,6 +15,15 @@ public extension ContainedViewLayoutTransitionCurve { return kCAMediaTimingFunctionSpring } } + + var viewAnimationOptions: UIViewAnimationOptions { + switch self { + case .easeInOut: + return [.curveEaseInOut] + case .spring: + return UIViewAnimationOptions(rawValue: 7 << 16) + } + } } public enum ContainedViewLayoutTransition { @@ -251,6 +260,44 @@ public extension ContainedViewLayoutTransition { } } + func updateTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } +} + +public extension ContainedViewLayoutTransition { + public func animateView(_ f: @escaping () -> Void) { + switch self { + case .immediate: + f() + case let .animated(duration, curve): + UIView.animate(withDuration: duration, delay: 0.0, options: curve.viewAnimationOptions, animations: { + f() + }, completion: nil) + } + } } public protocol ContainableController: class { diff --git a/Display/ContextMenuAction.swift b/Display/ContextMenuAction.swift index 898a8db7c1..1aa73ccc42 100644 --- a/Display/ContextMenuAction.swift +++ b/Display/ContextMenuAction.swift @@ -1,6 +1,7 @@ public enum ContextMenuActionContent { case text(String) + case icon(UIImage) } public struct ContextMenuAction { diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift index 8e7b91ce68..9325ce0db9 100644 --- a/Display/ContextMenuActionNode.swift +++ b/Display/ContextMenuActionNode.swift @@ -2,17 +2,31 @@ import Foundation import AsyncDisplayKit final class ContextMenuActionNode: ASDisplayNode { - private let textNode: ASTextNode + private let textNode: ASTextNode? + private let iconNode: ASImageNode? private let action: () -> Void private let button: HighlightTrackingButton var dismiss: (() -> Void)? init(action: ContextMenuAction) { - self.textNode = ASTextNode() switch action.content { case let .text(title): - self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white) + let textNode = ASTextNode() + textNode.isLayerBacked = true + textNode.displaysAsynchronously = false + textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white) + + self.textNode = textNode + self.iconNode = nil + case let .icon(image): + let iconNode = ASImageNode() + iconNode.displaysAsynchronously = false + iconNode.displayWithoutProcessing = true + iconNode.image = image + + self.iconNode = iconNode + self.textNode = nil } self.action = action.action @@ -21,7 +35,12 @@ final class ContextMenuActionNode: ASDisplayNode { super.init() self.backgroundColor = UIColor(white: 0.0, alpha: 0.8) - self.addSubnode(self.textNode) + if let textNode = self.textNode { + self.addSubnode(textNode) + } + if let iconNode = self.iconNode { + self.addSubnode(iconNode) + } self.button.highligthedChanged = { [weak self] highlighted in self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8) @@ -45,14 +64,26 @@ final class ContextMenuActionNode: ASDisplayNode { } override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - let textSize = self.textNode.measure(constrainedSize) - return CGSize(width: textSize.width + 36.0, height: 54.0) + if let textNode = self.textNode { + let textSize = textNode.measure(constrainedSize) + return CGSize(width: textSize.width + 36.0, height: 54.0) + } else if let iconNode = self.iconNode, let image = iconNode.image { + return CGSize(width: image.size.width + 36.0, height: 54.0) + } else { + return CGSize(width: 36.0, height: 54.0) + } } override func layout() { super.layout() self.button.frame = self.bounds - self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - self.textNode.calculatedSize.width) / 2.0), y: floor((self.bounds.size.height - self.textNode.calculatedSize.height) / 2.0)), size: self.textNode.calculatedSize) + if let textNode = self.textNode { + textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - textNode.calculatedSize.width) / 2.0), y: floor((self.bounds.size.height - textNode.calculatedSize.height) / 2.0)), size: textNode.calculatedSize) + } + if let iconNode = self.iconNode, let image = iconNode.image { + let iconSize = image.size + iconNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - iconSize.width) / 2.0), y: floor((self.bounds.size.height - iconSize.height) / 2.0)), size: iconSize) + } } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index e5f0697de3..af791c7628 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -15,10 +15,12 @@ public struct GridNodeInsertItem { public struct GridNodeUpdateItem { public let index: Int + public let previousIndex: Int public let item: GridItem - public init(index: Int, item: GridItem) { + public init(index: Int, previousIndex: Int, item: GridItem) { self.index = index + self.previousIndex = previousIndex self.item = item } } @@ -139,15 +141,17 @@ public struct GridNodeTransaction { public let updateItems: [GridNodeUpdateItem] public let scrollToItem: GridNodeScrollToItem? public let updateLayout: GridNodeUpdateLayout? + public let itemTransition: ContainedViewLayoutTransition public let stationaryItems: GridNodeStationaryItems public let updateFirstIndexInSectionOffset: Int? - public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?) { + public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?) { self.deleteItems = deleteItems self.insertItems = insertItems self.updateItems = updateItems self.scrollToItem = scrollToItem self.updateLayout = updateLayout + self.itemTransition = itemTransition self.stationaryItems = stationaryItems self.updateFirstIndexInSectionOffset = updateFirstIndexInSectionOffset } @@ -300,8 +304,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } for updatedItem in transaction.updateItems { - self.items[updatedItem.index] = updatedItem.item - if let itemNode = self.itemNodes[updatedItem.index] { + self.items[updatedItem.previousIndex] = updatedItem.item + if let itemNode = self.itemNodes[updatedItem.previousIndex] { updatedItem.item.update(node: itemNode) } } @@ -377,7 +381,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, completion: completion) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, itemTransition: transaction.itemTransition, completion: completion) } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { @@ -396,7 +400,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, completion: { _ in }) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, itemTransition: .immediate, completion: { _ in }) } } @@ -736,25 +740,33 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { return lowestHeaderNode } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, completion: (GridNodeDisplayedItemRange) -> Void) { - var previousItemFrames: ([WrappedGridItemNode: CGRect])? + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) { + var previousItemFrames: [WrappedGridItemNode: CGRect]? + var saveItemFrames = false switch presentationLayoutTransition.transition { case .animated: - var itemFrames: [WrappedGridItemNode: CGRect] = [:] - let contentOffset = self.scrollView.contentOffset - for (_, itemNode) in self.itemNodes { - itemFrames[WrappedGridItemNode(node: itemNode)] = itemNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) - } - for (_, sectionNode) in self.sectionNodes { - itemFrames[WrappedGridItemNode(node: sectionNode)] = sectionNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) - } - for itemNode in removedNodes { - itemFrames[WrappedGridItemNode(node: itemNode)] = itemNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) - } - previousItemFrames = itemFrames + saveItemFrames = true case .immediate: break } + if case .animated = itemTransition { + saveItemFrames = true + } + + if saveItemFrames { + var itemFrames: [WrappedGridItemNode: CGRect] = [:] + let contentOffset = self.scrollView.contentOffset + for (_, itemNode) in self.itemNodes { + itemFrames[WrappedGridItemNode(node: itemNode)] = itemNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + } + for (_, sectionNode) in self.sectionNodes { + itemFrames[WrappedGridItemNode(node: sectionNode)] = sectionNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + } + for itemNode in removedNodes { + itemFrames[WrappedGridItemNode(node: itemNode)] = itemNode.frame.offsetBy(dx: 0.0, dy: -contentOffset.y) + } + previousItemFrames = itemFrames + } applyingContentOffset = true @@ -941,6 +953,63 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { itemNode.removeFromSupernode() } } + } else if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = itemTransition { + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + + for index in self.itemNodes.keys { + let itemNode = self.itemNodes[index]! + if !existingItemIndices.contains(index) { + if let _ = previousItemFrames[WrappedGridItemNode(node: itemNode)] { + self.removeItemNodeWithIndex(index, removeNode: false) + itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false) + itemNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } else { + self.removeItemNodeWithIndex(index, removeNode: true) + } + } else if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { + itemNode.layer.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: itemNode.layer.position, duration: duration, timingFunction: timingFunction) + } else { + itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12, timingFunction: kCAMediaTimingFunctionEaseIn) + itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) + } + } + + for itemNode in removedNodes { + if let _ = previousItemFrames[WrappedGridItemNode(node: itemNode)] { + itemNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false) + itemNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.18, timingFunction: kCAMediaTimingFunctionEaseIn, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode?.removeFromSupernode() + }) + } else { + itemNode.removeFromSupernode() + } + } + + for wrappedSection in self.sectionNodes.keys { + let sectionNode = self.sectionNodes[wrappedSection]! + if !existingSections.contains(wrappedSection) { + if let _ = previousItemFrames[WrappedGridItemNode(node: sectionNode)] { + self.removeSectionNodeWithSection(wrappedSection, removeNode: false) + sectionNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak sectionNode] _ in + sectionNode?.removeFromSupernode() + }) + } else { + self.removeSectionNodeWithSection(wrappedSection, removeNode: true) + } + } else if let previousFrame = previousItemFrames[WrappedGridItemNode(node: sectionNode)] { + sectionNode.layer.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: sectionNode.layer.position, duration: duration, timingFunction: timingFunction) + } else { + sectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn) + } + } } else { for index in self.itemNodes.keys { if !existingItemIndices.contains(index) { diff --git a/Display/GridNodeScroller.swift b/Display/GridNodeScroller.swift index b4a8c5e6fb..e4fe809f60 100644 --- a/Display/GridNodeScroller.swift +++ b/Display/GridNodeScroller.swift @@ -25,9 +25,11 @@ open class GridNodeScroller: ASDisplayNode, UIGestureRecognizerDelegate { } override init() { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return GridNodeScrollerView() - }, didLoad: nil) + }) self.scrollView.scrollsToTop = false } diff --git a/Display/LegacyPresentedControllerNode.swift b/Display/LegacyPresentedControllerNode.swift index 3a35e282fc..129f33670c 100644 --- a/Display/LegacyPresentedControllerNode.swift +++ b/Display/LegacyPresentedControllerNode.swift @@ -13,9 +13,11 @@ final class LegacyPresentedControllerNode: ASDisplayNode { } override init() { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return UITracingLayerView() - }, didLoad: nil) + }) } func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { diff --git a/Display/ListView.swift b/Display/ListView.swift index 15c1045da7..2762a7de34 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -4,6 +4,7 @@ import SwiftSignalKit private let usePerformanceTracker = false private let useDynamicTuning = false +private let useBackgroundDeallocation = false private let infiniteScrollSize: CGFloat = 10000.0 private let insertionAnimationDuration: Double = 0.4 @@ -29,7 +30,13 @@ private final class ListViewBackingLayer: CALayer { } } -final class ListViewBackingView: UIView { +#if os(iOS) +typealias ListBaseView = UIView +#else +typealias ListBaseView = NSView +#endif + +final class ListViewBackingView: ListBaseView { weak var target: ListView? override class var layerClass: AnyClass { @@ -193,9 +200,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel performanceTrackerConfig.reportStackTraces = true self.performanceTracker = FBAnimationPerformanceTracker(config: performanceTrackerConfig)*/ - super.init(viewBlock: { Void -> UIView in + super.init() + + self.setViewBlock({ Void -> UIView in return ListViewBackingView() - }, didLoad: nil) + }) self.clipsToBounds = true @@ -261,11 +270,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.pauseAnimations() self.displayLink.invalidate() - for itemNode in self.itemNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) - } - for itemHeaderNode in self.itemHeaderNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemHeaderNode) + if useBackgroundDeallocation { + for itemNode in self.itemNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + } + for itemHeaderNode in self.itemHeaderNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemHeaderNode) + } + } else { + for itemNode in self.itemNodes { + ASPerformMainThreadDeallocation(itemNode) + } + for itemHeaderNode in self.itemHeaderNodes { + ASPerformMainThreadDeallocation(itemHeaderNode) + } } self.waitingForNodesDisposable.dispose() @@ -2089,17 +2107,31 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel animation.completion = { _ in for itemNode in temporaryPreviousNodes { itemNode.removeFromSupernode() - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + if useBackgroundDeallocation { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + } else { + ASPerformMainThreadDeallocation(itemNode) + } } for headerNode in temporaryHeaderNodes { headerNode.removeFromSupernode() - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) + if useBackgroundDeallocation { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) + } else { + ASPerformMainThreadDeallocation(headerNode) + } } } self.layer.add(animation, forKey: nil) } else { - for itemNode in temporaryPreviousNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + if useBackgroundDeallocation { + for itemNode in temporaryPreviousNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + } + } else { + for itemNode in temporaryPreviousNodes { + ASPerformMainThreadDeallocation(itemNode) + } } } } @@ -2135,7 +2167,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for (previousNode, _) in previousApparentFrames { if previousNode.supernode == nil { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: previousNode) + if useBackgroundDeallocation { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: previousNode) + } else { + ASPerformMainThreadDeallocation(previousNode) + } } } @@ -2469,7 +2505,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let node = self.itemNodes[i] if node.index == nil && node.apparentHeight <= CGFloat.ulpOfOne { self.removeItemNodeAtIndex(i) - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) + if useBackgroundDeallocation { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) + } else { + ASPerformMainThreadDeallocation(node) + } } else { i += 1 } diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index 100df12c96..01458552e7 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -62,13 +62,16 @@ open class ListViewItemHeaderNode: ASDisplayNode { if seeThrough { if (layerBacked) { - super.init(layerBlock: { + super.init() + self.setLayerBlock({ return CASeeThroughTracingLayer() - }, didLoad: nil) + }) } else { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return CASeeThroughTracingView() - }, didLoad: nil) + }) } } else { super.init() diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index cbe3e79bf5..ebc762c643 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -174,22 +174,27 @@ open class ListViewItemNode: ASDisplayNode { /*if layerBacked { super.init(layerBlock: { return ASTransformLayer() - }, didLoad: nil) + }) } else { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return ListViewItemView() - }, didLoad: nil) + }) }*/ if seeThrough { if (layerBacked) { - super.init(layerBlock: { + super.init() + self.setLayerBlock({ return CASeeThroughTracingLayer() - }, didLoad: nil) + }) } else { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return CASeeThroughTracingView() - }, didLoad: nil) + }) } } else { super.init() diff --git a/Display/ListViewScrollerAppkit.swift b/Display/ListViewScrollerAppkit.swift new file mode 100644 index 0000000000..95e31a3df5 --- /dev/null +++ b/Display/ListViewScrollerAppkit.swift @@ -0,0 +1,5 @@ +import Foundation +import AppKit + +class ListViewScroller: CALayer { +} diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 0497aa89ee..bf57a0e019 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -23,6 +23,10 @@ public final class NavigationBarTheme { self.backgroundColor = backgroundColor self.separatorColor = separatorColor } + + public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { + return NavigationBarTheme(buttonColor: self.buttonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color) + } } private func backArrowImage(color: UIColor) -> UIImage? { @@ -478,7 +482,7 @@ open class NavigationBar: ASDisplayNode { let finalX: CGFloat = floor((size.width - backButtonSize.width) / 2.0) - size.width self.backButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) - self.backButtonNode.alpha = 1.0 - progress + self.backButtonNode.alpha = (1.0 - progress) * (1.0 - progress) if let transitionTitleNode = self.transitionTitleNode { let transitionTitleSize = transitionTitleNode.measure(CGSize(width: size.width, height: nominalHeight)) @@ -487,7 +491,7 @@ open class NavigationBar: ASDisplayNode { let finalX: CGFloat = floor((size.width - transitionTitleSize.width) / 2.0) - size.width transitionTitleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - transitionTitleSize.height) / 2.0)), size: transitionTitleSize) - transitionTitleNode.alpha = progress + transitionTitleNode.alpha = progress * progress } self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) @@ -532,7 +536,7 @@ open class NavigationBar: ASDisplayNode { let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0) transitionBackButtonNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - transitionBackButtonSize.height) / 2.0)), size: transitionBackButtonSize) - transitionBackButtonNode.alpha = 1.0 - progress + transitionBackButtonNode.alpha = (1.0 - progress) * (1.0 - progress) } if let transitionBackArrowNode = self.transitionBackArrowNode { @@ -562,7 +566,7 @@ open class NavigationBar: ASDisplayNode { let finalX: CGFloat = leftButtonInset self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - self.titleNode.alpha = 1.0 - progress + self.titleNode.alpha = (1.0 - progress) * (1.0 - progress) case .bottom: var initialX: CGFloat = backButtonInset if otherNavigationBar.backButtonNode.supernode != nil { @@ -572,7 +576,7 @@ open class NavigationBar: ASDisplayNode { let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0) self.titleNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) - self.titleNode.alpha = progress + self.titleNode.alpha = progress * progress } } else { self.titleNode.alpha = 1.0 @@ -581,15 +585,45 @@ open class NavigationBar: ASDisplayNode { } if let titleView = self.titleView { - let titleViewSize = CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight) - titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleViewSize) + let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight) + titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) + + if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { + let progress = transitionState.progress + + switch transitionState.role { + case .top: + let initialX = floor((size.width - titleSize.width) / 2.0) + let finalX: CGFloat = leftButtonInset + + titleView.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + titleView.alpha = (1.0 - progress) * (1.0 - progress) + case .bottom: + var initialX: CGFloat = backButtonInset + if otherNavigationBar.backButtonNode.supernode != nil { + initialX += floor((otherNavigationBar.backButtonNode.frame.size.width - titleSize.width) / 2.0) + } + initialX += size.width * 0.3 + let finalX: CGFloat = floor((size.width - titleSize.width) / 2.0) + + titleView.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + titleView.alpha = progress * progress + } + } else { + titleView.alpha = 1.0 + titleView.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) + } } - - //self.effectView.frame = self.bounds } public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? { - if let title = self.title { + if let titleView = self.titleView { + if let transitionView = titleView as? NavigationBarTitleTransitionNode { + return transitionView.makeTransitionMirrorNode() + } else { + return nil + } + } else if let title = self.title { let node = ASTextNode() node.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: foregroundColor) return node diff --git a/Display/NavigationBarTitleTransitionNode.swift b/Display/NavigationBarTitleTransitionNode.swift new file mode 100644 index 0000000000..1121280999 --- /dev/null +++ b/Display/NavigationBarTitleTransitionNode.swift @@ -0,0 +1,6 @@ +import Foundation +import AsyncDisplayKit + +public protocol NavigationBarTitleTransitionNode { + func makeTransitionMirrorNode() -> ASDisplayNode +} diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 4904eac7f2..894114ab9d 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -78,23 +78,39 @@ public final class StatusBar: ASDisplayNode { private var proxyNode: StatusBarProxyNode? private var removeProxyNodeScheduled = false + let offsetNode = ASDisplayNode() private let inCallBackgroundNode = ASDisplayNode() private let inCallLabel: StatusBarLabelNode private var inCallText: String? = nil + public var verticalOffset: CGFloat = 0.0 { + didSet { + if !self.verticalOffset.isEqual(to: oldValue) { + self.offsetNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.verticalOffset), size: CGSize()) + self.layer.invalidateUpTheTree() + } + } + } + public override init() { self.inCallLabel = StatusBarLabelNode() self.inCallLabel.isLayerBacked = true + self.offsetNode.isLayerBacked = true + let labelSize = self.inCallLabel.measure(CGSize(width: 300.0, height: 300.0)) self.inCallLabel.frame = CGRect(origin: CGPoint(x: 10.0, y: 20.0 + 4.0), size: labelSize) - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return StatusBarView() - }, didLoad: nil) + }) + (self.view as! StatusBarView).node = self + self.addSubnode(self.offsetNode) self.addSubnode(self.inCallBackgroundNode) self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar)) diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 3141c9341e..b2cd7a8542 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -159,19 +159,21 @@ class StatusBarManager { var visibleStatusBars: [StatusBar] = [] - var globalStatusBar: (StatusBarStyle, CGFloat)? + var globalStatusBar: (StatusBarStyle, CGFloat, CGFloat)? var coveredIdentity = false var statusBarIndex = 0 for i in 0 ..< mappedSurfaces.count { for mappedStatusBar in mappedSurfaces[i].statusBars { if let statusBar = mappedStatusBar.statusBar { - if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() { + if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() && !statusBar.offsetNode.layer.hasPositionAnimations() { if !coveredIdentity { if statusBar.statusBarStyle != .Hide { - coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) + if statusBar.offsetNode.frame.origin.equalTo(CGPoint()) { + coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) + } if statusBarIndex == 0 && globalStatusBar == nil { - globalStatusBar = (mappedStatusBar.style, statusBar.alpha) + globalStatusBar = (mappedStatusBar.style, statusBar.alpha, statusBar.offsetNode.frame.origin.y) } else { visibleStatusBars.append(statusBar) } @@ -184,7 +186,7 @@ class StatusBarManager { if !coveredIdentity { coveredIdentity = true if statusBarIndex == 0 && globalStatusBar == nil { - globalStatusBar = (mappedStatusBar.style, 1.0) + globalStatusBar = (mappedStatusBar.style, 1.0, 0.0) } } } @@ -223,7 +225,14 @@ class StatusBarManager { if self.host.statusBarStyle != statusBarStyle { self.host.statusBarStyle = statusBarStyle } - self.host.statusBarWindow?.alpha = globalStatusBar.1 + if let statusBarWindow = self.host.statusBarWindow { + statusBarWindow.alpha = globalStatusBar.1 + var statusBarBounds = statusBarWindow.bounds + if !statusBarBounds.origin.y.isEqual(to: globalStatusBar.2) { + statusBarBounds.origin.y = globalStatusBar.2 + statusBarWindow.bounds = statusBarBounds + } + } } else { self.host.statusBarWindow?.alpha = 0.0 } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 296e7a2fac..e9c5177706 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -115,7 +115,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp targetX += 1 } - let batteryColor = (baseMidRow + baseX).pointee + let batteryColor = (baseMidRow + baseX + 2).pointee let batteryR = (batteryColor >> 16) & 0xff let batteryG = (batteryColor >> 8) & 0xff let batteryB = batteryColor & 0xff diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index a50cdfbe06..8d920ef87a 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -52,9 +52,11 @@ open class SwitchNode: ASDisplayNode { } override public init() { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return SwitchNodeView() - }, didLoad: nil) + }) } override open func didLoad() { diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index ee2c33e6e6..baf4d120db 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -17,9 +17,11 @@ final class TabBarControllerNode: ASDisplayNode { init(theme: TabBarControllerTheme, itemSelected: @escaping (Int) -> Void) { self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return UITracingLayerView() - }, didLoad: nil) + }) self.backgroundColor = theme.backgroundColor diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index 8522926cc3..4cd28d20b5 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -26,8 +26,10 @@ public class TextFieldNode: ASDisplayNode { } override public init() { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return TextFieldNodeView() - }, didLoad: nil) + }) } } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index bdf88d7b10..e979fd59c1 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -182,6 +182,10 @@ public extension CGRect { public var bottomRight: CGPoint { return CGPoint(x: self.maxX, y: self.maxY) } + + public var center: CGPoint { + return CGPoint(x: self.midX, y: self.midY) + } } public extension CGPoint { diff --git a/Display/ViewControllerTracingNode.swift b/Display/ViewControllerTracingNode.swift index 6124180039..1367ad831e 100644 --- a/Display/ViewControllerTracingNode.swift +++ b/Display/ViewControllerTracingNode.swift @@ -19,9 +19,11 @@ private final class ViewControllerTracingNodeView: UITracingLayerView { open class ViewControllerTracingNode: ASDisplayNode { override public init() { - super.init(viewBlock: { + super.init() + + self.setViewBlock({ return ViewControllerTracingNodeView() - }, didLoad: nil) + }) } override open func didLoad() { diff --git a/DisplayMac/DisplayMac.h b/DisplayMac/DisplayMac.h new file mode 100644 index 0000000000..09491004ba --- /dev/null +++ b/DisplayMac/DisplayMac.h @@ -0,0 +1,19 @@ +// +// DisplayMac.h +// DisplayMac +// +// Created by Peter on 8/13/17. +// Copyright © 2017 Telegram. All rights reserved. +// + +#import + +//! Project version number for DisplayMac. +FOUNDATION_EXPORT double DisplayMacVersionNumber; + +//! Project version string for DisplayMac. +FOUNDATION_EXPORT const unsigned char DisplayMacVersionString[]; + +// In this header, you should import all the public headers of your framework using statements like #import + + diff --git a/DisplayMac/Info.plist b/DisplayMac/Info.plist new file mode 100644 index 0000000000..d35efc62b4 --- /dev/null +++ b/DisplayMac/Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + en + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + FMWK + CFBundleShortVersionString + 1.0 + CFBundleVersion + $(CURRENT_PROJECT_VERSION) + NSHumanReadableCopyright + Copyright © 2017 Telegram. All rights reserved. + NSPrincipalClass + + + From 7ac26198ec2654d464a242d2e52a9a7126849156 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 11 Sep 2017 00:03:40 +0300 Subject: [PATCH 043/245] no message --- Display.xcodeproj/project.pbxproj | 29 +++++-- Display/ActionSheetControllerNode.swift | 4 + Display/ActionSheetItemGroupNode.swift | 3 + Display/DisplayLinkDispatcher.swift | 4 +- Display/Font.swift | 14 +-- Display/GenerateImage.swift | 21 ++++- Display/GridNodeScroller.swift | 14 ++- ...teractiveTransitionGestureRecognizer.swift | 14 ++- Display/ListView.swift | 53 +++++++----- Display/ListViewAnimation.swift | 6 +- Display/ListViewIntermediateState.swift | 83 +++++++++--------- Display/ListViewScroller.swift | 3 + Display/ListViewTransactionQueue.swift | 4 +- Display/NavigationBackButtonNode.swift | 6 +- Display/NavigationBar.swift | 86 +++++++++++++++++-- Display/NavigationBarBadge.swift | 55 ++++++++++++ Display/NavigationButtonNode.swift | 11 +-- Display/NavigationController.swift | 3 + Display/NavigationTitleNode.swift | 6 +- Display/PageControlNode.swift | 78 +++++++++++++++++ Display/ScrollToTopProxyView.swift | 3 + Display/StatusBarProxyNode.swift | 30 +++++-- Display/TabBarNode.swift | 4 +- Display/UIKitUtils.swift | 6 +- Display/UINavigationItem+Proxy.h | 4 + Display/UINavigationItem+Proxy.m | 27 ++++++ Display/WindowContent.swift | 4 +- 27 files changed, 450 insertions(+), 125 deletions(-) create mode 100644 Display/NavigationBarBadge.swift create mode 100644 Display/PageControlNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 4b8df465e5..95429a3ba8 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -36,6 +36,7 @@ D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DF71C96C5F200C07816 /* NSWeakReference.m */; }; D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */; }; D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */; }; + D04C468E1F4C97BE00D30FE1 /* PageControlNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */; }; D05174B31EAA833200A1BF36 /* CASeeThroughTracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */; }; D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -79,6 +80,7 @@ D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; + D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D077B8E81F4637040046D27A /* NavigationBarBadge.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; D08CAA7B1ED73C990000FDA8 /* ViewControllerTracingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */; }; D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; @@ -169,6 +171,7 @@ D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = ""; }; D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = ""; }; D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; + D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlNode.swift; sourceTree = ""; }; D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CASeeThroughTracingLayer.h; sourceTree = ""; }; D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CASeeThroughTracingLayer.m; sourceTree = ""; }; D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CATracingLayer.h; sourceTree = ""; }; @@ -215,6 +218,7 @@ D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; + D077B8E81F4637040046D27A /* NavigationBarBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarBadge.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerTracingNode.swift; sourceTree = ""; }; D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; @@ -364,6 +368,7 @@ D0E35A021DE473B900BC6096 /* HighlightableButton.swift */, D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */, D0A749941E3A9E7B00AD786E /* SwitchNode.swift */, + D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */, ); name = Nodes; sourceTree = ""; @@ -523,6 +528,7 @@ D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */, D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */, + D077B8E81F4637040046D27A /* NavigationBarBadge.swift */, ); name = Navigation; sourceTree = ""; @@ -848,6 +854,7 @@ D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, + D04C468E1F4C97BE00D30FE1 /* PageControlNode.swift in Sources */, D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */, D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, @@ -878,6 +885,7 @@ D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, + D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */, D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, @@ -980,7 +988,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1046,7 +1054,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1104,7 +1112,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1162,7 +1170,7 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = macosx; SKIP_INSTALL = YES; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -1273,13 +1281,14 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = none; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = "Debug Hockeyapp"; }; @@ -1299,13 +1308,14 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = "Release Hockeyapp"; }; @@ -1396,13 +1406,14 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = none; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = "Debug AppStore"; }; @@ -1474,13 +1485,14 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; - SWIFT_VERSION = 3.0; + SWIFT_VERSION = 4.0; }; name = "Release AppStore"; }; @@ -1507,6 +1519,7 @@ D01159BF1F40E96C0039383E /* Release AppStore */, ); defaultConfigurationIsVisible = 0; + defaultConfigurationName = "Release Hockeyapp"; }; D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { isa = XCConfigurationList; diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index f6d8914ec5..054884fcd5 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -27,6 +27,10 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { override init() { self.scrollView = ActionSheetControllerNodeScrollView() + + if #available(iOSApplicationExtension 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } self.scrollView.alwaysBounceVertical = true self.scrollView.delaysContentTouches = false self.scrollView.canCancelContentTouches = true diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift index 09d9ae277a..b340085195 100644 --- a/Display/ActionSheetItemGroupNode.swift +++ b/Display/ActionSheetItemGroupNode.swift @@ -44,6 +44,9 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) self.scrollView = ActionSheetItemGroupNodeScrollView() + if #available(iOSApplicationExtension 11.0, *) { + self.scrollView.contentInsetAdjustmentBehavior = .never + } self.scrollView.delaysContentTouches = false self.scrollView.canCancelContentTouches = true self.scrollView.showsVerticalScrollIndicator = false diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index 6890a87eee..dc23141aea 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -2,7 +2,7 @@ import Foundation public class DisplayLinkDispatcher: NSObject { private var displayLink: CADisplayLink! - private var blocksToDispatch: [(Void) -> Void] = [] + private var blocksToDispatch: [() -> Void] = [] private let limit: Int public init(limit: Int = 0) { @@ -19,7 +19,7 @@ public class DisplayLinkDispatcher: NSObject { } } - public func dispatch(f: @escaping (Void) -> Void) { + public func dispatch(f: @escaping () -> Void) { if self.displayLink == nil { if Thread.isMainThread { f() diff --git a/Display/Font.swift b/Display/Font.swift index 39bdf5b419..89461a969a 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -8,7 +8,7 @@ public struct Font { public static func medium(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: size, weight: UIFontWeightMedium) + return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.medium) } else { return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) } @@ -16,7 +16,7 @@ public struct Font { public static func semibold(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: size, weight: UIFontWeightSemibold) + return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.semibold) } else { return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) } @@ -32,7 +32,7 @@ public struct Font { public static func light(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { - return UIFont.systemFont(ofSize: size, weight: UIFontWeightLight) + return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light) } else { return CTFontCreateWithName("HelveticaNeue-Light" as CFString, size, nil) } @@ -45,15 +45,15 @@ public struct Font { public extension NSAttributedString { convenience init(string: String, font: UIFont? = nil, textColor: UIColor = UIColor.black, paragraphAlignment: NSTextAlignment? = nil) { - var attributes: [String: AnyObject] = [:] + var attributes: [NSAttributedStringKey: AnyObject] = [:] if let font = font { - attributes[NSFontAttributeName] = font + attributes[NSAttributedStringKey.font] = font } - attributes[NSForegroundColorAttributeName] = textColor + attributes[NSAttributedStringKey.foregroundColor] = textColor if let paragraphAlignment = paragraphAlignment { let paragraphStyle = NSMutableParagraphStyle() paragraphStyle.alignment = paragraphAlignment - attributes[NSParagraphStyleAttributeName] = paragraphStyle + attributes[NSAttributedStringKey.paragraphStyle] = paragraphStyle } self.init(string: string, attributes: attributes) } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 75bf8e1d94..b41b717307 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -274,10 +274,16 @@ public class DrawingContext { } } - public init(size: CGSize, scale: CGFloat = deviceScale, clear: Bool = false) { + public init(size: CGSize, scale: CGFloat = 0.0, clear: Bool = false) { + let actualScale: CGFloat + if scale.isZero { + actualScale = deviceScale + } else { + actualScale = scale + } self.size = size - self.scale = scale - self.scaledSize = CGSize(width: size.width * scale, height: size.height * scale) + self.scale = actualScale + self.scaledSize = CGSize(width: size.width * actualScale, height: size.height * actualScale) self.bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) self.length = bytesPerRow * Int(scaledSize.height) @@ -450,6 +456,15 @@ public func drawSvgPath(_ context: CGContext, path: StaticString, strokeOnMove: context.fillPath() //CGContextBeginPath(context) //print("Close") + } else if c == 83 { // S + if index != end && index.pointee != 32 { + throw ParsingError.Generic + } + + //CGContextClosePath(context) + context.strokePath() + //CGContextBeginPath(context) + //print("Close") } else if c == 32 { // space continue } else { diff --git a/Display/GridNodeScroller.swift b/Display/GridNodeScroller.swift index e4fe809f60..98c0c241b9 100644 --- a/Display/GridNodeScroller.swift +++ b/Display/GridNodeScroller.swift @@ -10,6 +10,18 @@ private class GridNodeScrollerView: UIScrollView { return GridNodeScrollerLayer.self } + override init(frame: CGRect) { + super.init(frame: frame) + + if #available(iOSApplicationExtension 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func touchesShouldCancel(in view: UIView) -> Bool { return true } @@ -28,7 +40,7 @@ open class GridNodeScroller: ASDisplayNode, UIGestureRecognizerDelegate { super.init() self.setViewBlock({ - return GridNodeScrollerView() + return GridNodeScrollerView(frame: CGRect()) }) self.scrollView.scrollsToTop = false diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index fdc25bc829..7b5e5601c3 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -8,8 +8,11 @@ private func hasHorizontalGestures(_ view: UIView) -> Bool { if let view = view as? ListViewBackingView { let transform = view.transform - let angle = Double(atan2f(Float(transform.b), Float(transform.a))) - if abs(angle - M_PI / 2.0) < 0.001 || abs(angle + M_PI / 2.0) < 0.001 || abs(angle - M_PI * 3.0 / 2.0) < 0.001 { + let angle: Double = Double(atan2f(Float(transform.b), Float(transform.a))) + let term1: Double = abs(angle - Double.pi / 2.0) + let term2: Double = abs(angle + Double.pi / 2.0) + let term3: Double = abs(angle - Double.pi * 3.0 / 2.0) + if term1 < 0.001 || term2 < 0.001 || term3 < 0.001 { return true } } @@ -54,14 +57,17 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { let location = touches.first!.location(in: self.view) let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y) + let absTranslationX: CGFloat = abs(translation.x) + let absTranslationY: CGFloat = abs(translation.y) + if !validatedGesture { if self.firstLocation.x < 16.0 { validatedGesture = true } else if translation.x < 0.0 { self.state = .failed - } else if abs(translation.y) > 2.0 && abs(translation.y) > abs(translation.x) * 2.0 { + } else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 { self.state = .failed - } else if abs(translation.x) > 2.0 && abs(translation.y) * 2.0 < abs(translation.x) { + } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX { validatedGesture = true } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 2762a7de34..f4cc1eab6d 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -202,7 +202,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel super.init() - self.setViewBlock({ Void -> UIView in + self.setViewBlock({ () -> UIView in return ListViewBackingView() }) @@ -381,10 +381,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if self.snapToBottomInsetUntilFirstInteraction { self.snapToBottomInsetUntilFirstInteraction = false } - - /*if usePerformanceTracker { - self.performanceTracker.start() - }*/ } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { @@ -682,7 +678,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return (snappedTopInset, offset) } - private func updateVisibleContentOffset() { + public func visibleContentOffset() -> ListViewVisibleContentOffset { var offset: ListViewVisibleContentOffset = .unknown var topItemIndexAndFrame: (Int, CGRect) = (-1, CGRect()) for itemNode in self.itemNodes { @@ -696,8 +692,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else if topItemIndexAndFrame.0 == -1 { offset = .none } - - self.visibleContentOffsetChanged(offset) + return offset + } + + private func updateVisibleContentOffset() { + self.visibleContentOffsetChanged(self.visibleContentOffset()) } private func stopScrolling() { @@ -710,7 +709,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func updateBottomItemOverscrollBackground() { if self.keepBottomItemOverscrollBackground { var bottomItemFound = false - if self.itemNodes[itemNodes.count - 1].index == self.items.count - 1 { + let lastItemNodeIndex: Int? = self.itemNodes[itemNodes.count - 1].index + if lastItemNodeIndex == self.items.count - 1 { bottomItemFound = true } @@ -746,8 +746,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return } - var topItemFound = false - var bottomItemFound = false + var topItemFound: Bool = false + var bottomItemFound: Bool = false var topItemEdge: CGFloat = 0.0 var bottomItemEdge: CGFloat = 0.0 @@ -769,7 +769,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var completeHeight = effectiveInsets.top + effectiveInsets.bottom - if itemNodes[itemNodes.count - 1].index == self.items.count - 1 { + if let index = itemNodes[itemNodes.count - 1].index, index == self.items.count - 1 { bottomItemFound = true bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY } @@ -906,7 +906,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping (Void) -> Void) { + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size @@ -1799,19 +1799,19 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let index = itemNode.index, index == scrollToItem.index { let offset: CGFloat switch scrollToItem.position { - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom - case .Top: - offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top - case let .Center(overflow): + case let .bottom(additionalOffset): + offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset + case let .top(additionalOffset): + offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + additionalOffset + case let .center(overflow): let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { offset = self.insets.top + floor(((self.visibleSize.height - self.insets.bottom - self.insets.top) - itemNode.frame.size.height) / 2.0) - itemNode.apparentFrame.minY } else { switch overflow { - case .Top: + case .top: offset = self.insets.top - itemNode.apparentFrame.minY - case .Bottom: + case .bottom: offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY } } @@ -2495,7 +2495,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateVisibleItemsTransaction(completion: @escaping (Void) -> Void) { + private func updateVisibleItemsTransaction(completion: @escaping () -> Void) { if self.items.count == 0 && self.itemNodes.count == 0 { completion() return @@ -2770,13 +2770,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func ensureItemNodeVisible(_ node: ListViewItemNode) { if let index = node.index { if node.frame.minY < self.insets.top { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Top, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.Bottom, animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } } + public func itemNodeRelativeOffset(_ node: ListViewItemNode) -> CGFloat? { + if let _ = node.index { + return node.frame.minY - self.insets.top + } + return nil + } + override open func touchesMoved(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { let location = touches.first!.location(in: self.view) diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 3555960169..0d1a84768b 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -27,7 +27,11 @@ private func floorToPixels(_ value: UIEdgeInsets) -> UIEdgeInsets { extension CGFloat: Interpolatable { public static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> Interpolatable { return { from, to, t -> Interpolatable in - return floorToPixels((to as! CGFloat) * t + (from as! CGFloat) * (1.0 - t)) + let fromValue: CGFloat = from as! CGFloat + let toValue: CGFloat = to as! CGFloat + let invT: CGFloat = 1.0 - t + let term: CGFloat = toValue * t + fromValue * invT + return floorToPixels(term) } } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index e59bf1dadf..47313cfed0 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -2,38 +2,35 @@ import Foundation import SwiftSignalKit public enum ListViewCenterScrollPositionOverflow { - case Top - case Bottom + case top + case bottom } public enum ListViewScrollPosition: Equatable { - case Top - case Bottom - case Center(ListViewCenterScrollPositionOverflow) -} + case top(CGFloat) + case bottom(CGFloat) + case center(ListViewCenterScrollPositionOverflow) -public func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { - switch lhs { - case .Top: - switch rhs { - case .Top: - return true - default: - return false - } - case .Bottom: - switch rhs { - case .Bottom: - return true - default: - return false - } - case let .Center(lhsOverflow): - switch rhs { - case let .Center(rhsOverflow) where lhsOverflow == rhsOverflow: - return true - default: - return false + public static func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { + switch lhs { + case let .top(offset): + if case .top(offset) = rhs { + return true + } else { + return false + } + case let .bottom(offset): + if case .bottom(offset) = rhs { + return true + } else { + return false + } + case let .center(overflow): + if case .center(overflow) = rhs { + return true + } else { + return false + } } } } @@ -306,22 +303,22 @@ struct ListViewState { if let index = node.index , index == fixedIndex { let offset: CGFloat switch fixedPosition { - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY - case .Top: - offset = self.insets.top - node.frame.minY - case let .Center(overflow): - let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top - if node.frame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { - offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY - } else { - switch overflow { - case .Top: - offset = self.insets.top - node.frame.minY - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + case let .bottom(additionalOffset): + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + additionalOffset + case let .top(additionalOffset): + offset = self.insets.top - node.frame.minY + additionalOffset + case let .center(overflow): + let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top + if node.frame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { + offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY + } else { + switch overflow { + case .top: + offset = self.insets.top - node.frame.minY + case .bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + } } - } } var minY: CGFloat = CGFloat.greatestFiniteMagnitude diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 935a6fa064..2d1292ff41 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -5,6 +5,9 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { super.init(frame: frame) self.scrollsToTop = false + if #available(iOSApplicationExtension 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } } required init?(coder aDecoder: NSCoder) { diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 39a510c9cb..a175edb4ec 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -1,11 +1,11 @@ import Foundation import SwiftSignalKit -public typealias ListViewTransaction = (@escaping (Void) -> Void) -> Void +public typealias ListViewTransaction = (@escaping () -> Void) -> Void public final class ListViewTransactionQueue { private var transactions: [ListViewTransaction] = [] - public final var transactionCompleted: (Void) -> Void = { } + public final var transactionCompleted: () -> Void = { } public init() { } diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 566a499fe8..8b81086c1a 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -6,10 +6,10 @@ public class NavigationBackButtonNode: ASControlNode { return UIFont.systemFont(ofSize: 17.0) } - private func attributesForCurrentState() -> [String : AnyObject] { + private func attributesForCurrentState() -> [NSAttributedStringKey : AnyObject] { return [ - NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray + NSAttributedStringKey.font: self.fontForCurrentState(), + NSAttributedStringKey.foregroundColor: self.isEnabled ? self.color : UIColor.gray ] } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index bf57a0e019..df35ba07d6 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1,17 +1,18 @@ import UIKit import AsyncDisplayKit -private func generateBackArrowImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 13.0, height: 22.0), contextGenerator: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setFillColor(color.cgColor) - let _ = try? drawSvgPath(context, path: "M10.6569398,0.0 L0.0,11 L10.6569398,22 L13,19.1782395 L5.07681762,11 L13,2.82176047 Z ") - }) -} - private var backArrowImageCache: [Int32: UIImage] = [:] public final class NavigationBarTheme { + public static func generateBackArrowImage(color: UIColor) -> UIImage? { + return generateImage(CGSize(width: 13.0, height: 22.0), contextGenerator: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + + let _ = try? drawSvgPath(context, path: "M10.3824541,0.421094342 L1.53851,8.52547877 L1.53851,8.52547877 C0.724154418,9.27173527 0.668949198,10.5368613 1.41520569,11.3512169 C1.45449493,11.3940915 1.49563546,11.435232 1.53851,11.4745212 L10.3824541,19.5789057 L10.3824541,19.5789057 C10.9981509,20.1431158 11.9429737,20.1431158 12.5586704,19.5789057 L12.5586704,19.5789057 L12.5586704,19.5789057 C13.1093629,19.0742639 13.1466944,18.2187464 12.6420526,17.6680539 C12.615484,17.6390608 12.5876635,17.6112403 12.5586704,17.5846717 L4.28186505,10 L12.5586704,2.41532829 L12.5586704,2.41532829 C13.1093629,1.91068651 13.1466944,1.05516904 12.6420526,0.50447654 C12.615484,0.475483443 12.5876635,0.447662941 12.5586704,0.421094342 L12.5586704,0.421094342 L12.5586704,0.421094342 C11.9429737,-0.143115824 10.9981509,-0.143115824 10.3824541,0.421094342 Z ") + }) + } + public let buttonColor: UIColor public let primaryTextColor: UIColor public let backgroundColor: UIColor @@ -40,7 +41,7 @@ private func backArrowImage(color: UIColor) -> UIImage? { if let image = backArrowImageCache[key] { return image } else { - if let image = generateBackArrowImage(color: color) { + if let image = NavigationBarTheme.generateBackArrowImage(color: color) { backArrowImageCache[key] = image return image } else { @@ -74,6 +75,8 @@ open class NavigationBar: ASDisplayNode { private var itemRightButtonListenerKey: Int? private var itemRightButtonSetEnabledListenerKey: Int? + private var itemBadgeListenerKey: Int? + private var _item: UINavigationItem? public var item: UINavigationItem? { get { @@ -104,6 +107,11 @@ open class NavigationBar: ASDisplayNode { previousValue.rightBarButtonItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey) self.itemRightButtonSetEnabledListenerKey = nil } + + if let itemBadgeListenerKey = self.itemBadgeListenerKey { + previousValue.removeSetBadgeListener(itemBadgeListenerKey) + self.itemBadgeListenerKey = nil + } } self._item = value @@ -159,6 +167,13 @@ open class NavigationBar: ASDisplayNode { } } + self.itemBadgeListenerKey = item.addSetBadgeListener { [weak self] text in + if let strongSelf = self { + strongSelf.updateBadgeText(text: text) + } + } + self.updateBadgeText(text: item.badge) + self.updateLeftButton() self.updateRightButton() } else { @@ -169,6 +184,7 @@ open class NavigationBar: ASDisplayNode { self.invalidateCalculatedLayout() } } + private var title: String? { didSet { if let title = self.title { @@ -251,11 +267,23 @@ open class NavigationBar: ASDisplayNode { } } + private func updateBadgeText(text: String?) { + let actualText = text ?? "" + if self.badgeNode.text != actualText { + self.badgeNode.text = actualText + self.badgeNode.isHidden = actualText.isEmpty + + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + private func updateLeftButton() { if let item = self.item { if let leftBarButtonItem = item.leftBarButtonItem { self.backButtonNode.removeFromSupernode() self.backButtonArrow.removeFromSupernode() + self.badgeNode.removeFromSupernode() self.leftButtonNode.text = leftBarButtonItem.title ?? "" self.leftButtonNode.bold = leftBarButtonItem.style == .done @@ -276,6 +304,7 @@ open class NavigationBar: ASDisplayNode { if self.backButtonNode.supernode == nil { self.clippingNode.addSubnode(self.backButtonNode) self.clippingNode.addSubnode(self.backButtonArrow) + self.clippingNode.addSubnode(self.badgeNode) } } else { self.backButtonNode.removeFromSupernode() @@ -286,6 +315,7 @@ open class NavigationBar: ASDisplayNode { self.leftButtonNode.removeFromSupernode() self.backButtonNode.removeFromSupernode() self.backButtonArrow.removeFromSupernode() + self.badgeNode.removeFromSupernode() } } @@ -309,6 +339,7 @@ open class NavigationBar: ASDisplayNode { } private let backButtonNode: NavigationButtonNode + private let badgeNode: NavigationBarBadgeNode private let backButtonArrow: ASImageNode private let leftButtonNode: NavigationButtonNode private let rightButtonNode: NavigationButtonNode @@ -337,6 +368,11 @@ open class NavigationBar: ASDisplayNode { transitionBackArrowNode.removeFromSupernode() self.transitionBackArrowNode = nil } + + if let transitionBadgeNode = self.transitionBadgeNode { + transitionBadgeNode.removeFromSupernode() + self.transitionBadgeNode = nil + } if let value = value { switch value.role { @@ -360,6 +396,10 @@ open class NavigationBar: ASDisplayNode { self.transitionBackArrowNode = transitionBackArrowNode self.clippingNode.addSubnode(transitionBackArrowNode) } + if let transitionBadgeNode = value.navigationBar?.makeTransitionBadgeNode() { + self.transitionBadgeNode = transitionBadgeNode + self.clippingNode.addSubnode(transitionBadgeNode) + } } } } @@ -371,6 +411,7 @@ open class NavigationBar: ASDisplayNode { private var transitionTitleNode: ASDisplayNode? private var transitionBackButtonNode: NavigationButtonNode? private var transitionBackArrowNode: ASDisplayNode? + private var transitionBadgeNode: ASDisplayNode? public init(theme: NavigationBarTheme) { self.theme = theme @@ -378,6 +419,9 @@ open class NavigationBar: ASDisplayNode { self.titleNode = ASTextNode() self.backButtonNode = NavigationButtonNode() + self.badgeNode = NavigationBarBadgeNode(fillColor: .red, textColor: .white) + self.badgeNode.isUserInteractionEnabled = false + self.badgeNode.isHidden = true self.backButtonArrow = ASImageNode() self.backButtonArrow.displayWithoutProcessing = true self.backButtonArrow.displaysAsynchronously = false @@ -496,17 +540,20 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.backButtonArrow.alpha = max(0.0, 1.0 - progress * 1.3) + self.badgeNode.alpha = max(0.0, 1.0 - progress * 1.3) case .bottom: self.backButtonNode.alpha = 1.0 self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) self.backButtonArrow.alpha = 1.0 self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.badgeNode.alpha = 1.0 } } else { self.backButtonNode.alpha = 1.0 self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) self.backButtonArrow.alpha = 1.0 self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.badgeNode.alpha = 1.0 } } else if self.leftButtonNode.supernode != nil { let leftButtonSize = self.leftButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) @@ -516,6 +563,10 @@ open class NavigationBar: ASDisplayNode { self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize) } + let badgeSize = self.badgeNode.measure(CGSize(width: 200.0, height: 100.0)) + let backButtonArrowFrame = self.backButtonArrow.frame + self.badgeNode.frame = CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize) + if self.rightButtonNode.supernode != nil { let rightButtonSize = self.rightButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) rightTitleInset += rightButtonSize.width + leftButtonInset + 8.0 + 8.0 @@ -545,6 +596,11 @@ open class NavigationBar: ASDisplayNode { transitionBackArrowNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) transitionBackArrowNode.alpha = max(0.0, 1.0 - progress * 1.3) + + if let transitionBadgeNode = self.transitionBadgeNode { + transitionBadgeNode.frame = CGRect(origin: transitionBackArrowNode.frame.origin.offsetBy(dx: 7.0, dy: -9.0), size: transitionBadgeNode.bounds.size) + transitionBadgeNode.alpha = transitionBackArrowNode.alpha + } } } } @@ -656,6 +712,18 @@ open class NavigationBar: ASDisplayNode { } } + private func makeTransitionBadgeNode() -> ASDisplayNode? { + if self.badgeNode.supernode != nil && !self.badgeNode.isHidden { + let node = NavigationBarBadgeNode(fillColor: .red, textColor: .white) + node.text = self.badgeNode.text + let nodeSize = node.measure(CGSize(width: 200.0, height: 100.0)) + node.frame = CGRect(origin: CGPoint(), size: nodeSize) + return node + } else { + return nil + } + } + public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { if self.contentNode !== contentNode { if let previous = self.contentNode { diff --git a/Display/NavigationBarBadge.swift b/Display/NavigationBarBadge.swift new file mode 100644 index 0000000000..f5864262ca --- /dev/null +++ b/Display/NavigationBarBadge.swift @@ -0,0 +1,55 @@ +import Foundation +import AsyncDisplayKit + +final class NavigationBarBadgeNode: ASDisplayNode { + private var fillColor: UIColor + private var textColor: UIColor + + private let textNode: ASTextNode2 + private let backgroundNode: ASImageNode + + private let font: UIFont = Font.regular(13.0) + + var text: String = "" { + didSet { + self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) + self.invalidateCalculatedLayout() + } + } + + init(fillColor: UIColor, textColor: UIColor) { + self.fillColor = fillColor + self.textColor = textColor + + self.textNode = ASTextNode2() + self.textNode.isLayerBacked = true + self.textNode.displaysAsynchronously = false + + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor) + + super.init() + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.textNode) + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let badgeSize = self.textNode.measure(constrainedSize) + let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) + let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) + self.backgroundNode.frame = backgroundFrame + let textOffset: CGFloat + if UIScreenPixel.isLessThanOrEqualTo(1.0 / 3.0) { + textOffset = UIScreenPixel * 2.0 + } else { + textOffset = UIScreenPixel + } + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0) + textOffset), size: badgeSize) + + return backgroundSize + } +} diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index e07a229e3f..633b1f11c3 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -6,10 +6,10 @@ public class NavigationButtonNode: ASTextNode { return self.bold ? UIFont.boldSystemFont(ofSize: 17.0) : UIFont.systemFont(ofSize: 17.0) } - private func attributesForCurrentState() -> [String : AnyObject] { + private func attributesForCurrentState() -> [NSAttributedStringKey : AnyObject] { return [ - NSFontAttributeName: self.fontForCurrentState(), - NSForegroundColorAttributeName: self.isEnabled ? self.color : UIColor.gray + NSAttributedStringKey.font: self.fontForCurrentState(), + NSAttributedStringKey.foregroundColor: self.isEnabled ? self.color : UIColor.gray ] } @@ -92,7 +92,7 @@ public class NavigationButtonNode: ASTextNode { public var pressed: () -> () = { } public var highlightChanged: (Bool) -> () = { _ in } - public override init() { + override public init() { super.init() self.isUserInteractionEnabled = true @@ -103,6 +103,7 @@ public class NavigationButtonNode: ASTextNode { override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let superSize = super.calculateSizeThatFits(constrainedSize) + if let node = self.node { let nodeSize = node.measure(constrainedSize) return CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) @@ -192,7 +193,7 @@ public class NavigationButtonNode: ASTextNode { if self.isEnabled != value { super.isEnabled = value - self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 10cb7f9237..5d4d6fab2b 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -102,6 +102,9 @@ open class NavigationController: UINavigationController, ContainableController, self.view = NavigationControllerView() self.view.clipsToBounds = true + if #available(iOSApplicationExtension 11.0, *) { + self.navigationBar.prefersLargeTitles = false + } self.navigationBar.removeFromSuperview() let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 4ada590a0b..4064f61498 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -39,9 +39,9 @@ public class NavigationTitleNode: ASDisplayNode { } private func setText(_ text: NSString) { - var titleAttributes = [String : AnyObject]() - titleAttributes[NSFontAttributeName] = UIFont.boldSystemFont(ofSize: 17.0) - titleAttributes[NSForegroundColorAttributeName] = self.color + var titleAttributes = [NSAttributedStringKey : AnyObject]() + titleAttributes[NSAttributedStringKey.font] = UIFont.boldSystemFont(ofSize: 17.0) + titleAttributes[NSAttributedStringKey.foregroundColor] = self.color let titleString = NSAttributedString(string: text as String, attributes: titleAttributes) self.label.attributedString = titleString self.invalidateCalculatedLayout() diff --git a/Display/PageControlNode.swift b/Display/PageControlNode.swift new file mode 100644 index 0000000000..22bddea020 --- /dev/null +++ b/Display/PageControlNode.swift @@ -0,0 +1,78 @@ +import Foundation +import AsyncDisplayKit + +public final class PageControlNode: ASDisplayNode { + private let dotSize: CGFloat + private let dotSpacing: CGFloat + private let dotColor: UIColor + private var dotNodes: [ASImageNode] = [] + + private let normalDotImage: UIImage + + public init(dotSize: CGFloat = 7.0, dotSpacing: CGFloat = 9.0, dotColor: UIColor) { + self.dotSize = dotSize + self.dotSpacing = dotSpacing + self.dotColor = dotColor + self.normalDotImage = generateFilledCircleImage(diameter: dotSize, color: dotColor)! + + super.init() + } + + public var pagesCount: Int = 0 { + didSet { + if self.pagesCount != oldValue { + while self.dotNodes.count > self.pagesCount { + self.dotNodes[self.dotNodes.count - 1].removeFromSupernode() + self.dotNodes.removeLast() + } + while self.dotNodes.count < self.pagesCount { + let dotNode = ASImageNode() + dotNode.image = self.normalDotImage + dotNode.displaysAsynchronously = false + dotNode.displayWithoutProcessing = true + dotNode.isLayerBacked = true + self.dotNodes.append(dotNode) + self.addSubnode(dotNode) + } + } + } + } + + public func setPage(_ pageValue: CGFloat) { + let page = max(0.0, min(CGFloat(self.pagesCount - 1), pageValue)) + + for i in 0 ..< self.dotNodes.count { + var alpha: CGFloat = 0.0 + let delta = abs(CGFloat(i) - page) + if delta >= 1.0 { + alpha = 0.5 + } else { + alpha = 1.0 - delta + alpha *= alpha * alpha + } + if alpha < 0.5 { + alpha = 0.5 + } + + self.dotNodes[i].alpha = alpha + } + } + + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: self.dotSize * CGFloat(self.pagesCount) + self.dotSpacing * max(CGFloat(self.pagesCount - 1), 0.0), height: self.dotSize) + } + + override public func layout() { + super.layout() + + let dotSize = CGSize(width: self.dotSize, height: self.dotSize) + + let nominalWidth = self.dotSize * CGFloat(self.pagesCount) + self.dotSpacing * max(CGFloat(self.pagesCount - 1), 0.0) + + let startX = floor((self.bounds.size.width - nominalWidth) / 2) + + for i in 0 ..< self.dotNodes.count { + self.dotNodes[i].frame = CGRect(origin: CGPoint(x: startX + CGFloat(i) * (dotSize.width + self.dotSpacing), y: 0.0), size: dotSize) + } + } +} diff --git a/Display/ScrollToTopProxyView.swift b/Display/ScrollToTopProxyView.swift index c4ad8aed44..3433b8b88f 100644 --- a/Display/ScrollToTopProxyView.swift +++ b/Display/ScrollToTopProxyView.swift @@ -8,6 +8,9 @@ class ScrollToTopView: UIScrollView, UIScrollViewDelegate { self.delegate = self self.scrollsToTop = true + if #available(iOSApplicationExtension 11.0, *) { + self.contentInsetAdjustmentBehavior = .never + } } required init?(coder aDecoder: NSCoder) { diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index e9c5177706..a7a0c3ec5b 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -67,14 +67,12 @@ private class StatusBarItemNode: ASDisplayNode { UIGraphicsPopContext() } } - + //print("\(self.targetView)") let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic tintStatusBarItem(context, type: type, style: statusBarStyle) self.contents = context.generateImage()?.cgImage self.frame = self.targetView.frame - - } } @@ -102,7 +100,23 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp baseX += 1 } - baseX += 2 + while baseX < maxX { + let pixel = baseMidRow + baseX + let alpha = pixel.pointee & 0xff000000 + if alpha == 0 { + break + } + baseX += 1 + } + + while baseX < maxX { + let pixel = baseMidRow + baseX + let alpha = pixel.pointee & 0xff000000 + if alpha != 0 { + break + } + baseX += 1 + } var targetX = baseX while targetX < maxX { @@ -221,7 +235,13 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp } } -private let batteryItemClass: AnyClass? = NSClassFromString("UIStatusBarBatteryItemView") +private let batteryItemClass: AnyClass? = { () -> AnyClass? in + var nameString = "StatusBarBattery" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "ItemView" + } + return NSClassFromString("UI" + nameString) +}() private class StatusBarProxyNodeTimerTarget: NSObject { let action: () -> Void diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 2eba180f4e..117fad383a 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -5,7 +5,7 @@ import AsyncDisplayKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor) -> UIImage? { let font = Font.regular(10.0) - let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSFontAttributeName: font], context: nil).size + let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size let imageSize: CGSize if let image = image { @@ -34,7 +34,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: } } - (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSFontAttributeName: font, NSForegroundColorAttributeName: tintColor]) + (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index e979fd59c1..669190bf5e 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -20,8 +20,10 @@ private func dumpLayers(_ layer: CALayer, indent: String = "") { print("\(indent)\(layer)(frame: \(layer.frame), bounds: \(layer.bounds))") if layer.sublayers != nil { let nextIndent = indent + ".." - for sublayer in layer.sublayers ?? [] { - dumpLayers(sublayer as CALayer, indent: nextIndent) + if let sublayers = layer.sublayers { + for sublayer in sublayers { + dumpLayers(sublayer as CALayer, indent: nextIndent) + } } } } diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 959f98d181..63267511ba 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -20,6 +20,10 @@ typedef void (^UITabBarItemSetBadgeListener)(NSString *); - (void)removeSetRightBarButtonItemListener:(NSInteger)key; - (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; - (void)removeSetBackBarButtonItemListener:(NSInteger)key; +- (NSInteger)addSetBadgeListener:(UITabBarItemSetBadgeListener)listener; +- (void)removeSetBadgeListener:(NSInteger)key; + +@property (nonatomic, strong) NSString *badge; @end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index e86bffb586..293f1cc572 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -14,6 +14,7 @@ static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemLis static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey; static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; +static const void *badgeKey = &badgeKey; @implementation UINavigationItem (Proxy) @@ -218,6 +219,32 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; [(NSBag *)[self associatedObjectForKey:setBackBarButtonItemListenerBagKey] removeItem:key]; } +- (NSInteger)addSetBadgeListener:(UITabBarItemSetBadgeListener)listener { + NSBag *bag = [self associatedObjectForKey:setBadgeListenerBagKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setBadgeListenerBagKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetBadgeListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setBadgeListenerBagKey] removeItem:key]; +} + +- (void)setBadge:(NSString *)badge { + [self setAssociatedObject:badge forKey:badgeKey]; + + [(NSBag *)[self associatedObjectForKey:setBadgeListenerBagKey] enumerateItems:^(UITabBarItemSetBadgeListener listener) { + listener(badge); + }]; +} + +- (NSString *)badge { + return [self associatedObjectForKey:badgeKey]; +} + @end @implementation UITabBarItem (Proxy) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 58afdfdbf5..18894024ca 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -445,7 +445,7 @@ public class Window1 { } } - var postUpdateToInterfaceOrientationBlocks: [(Void) -> Void] = [] + var postUpdateToInterfaceOrientationBlocks: [() -> Void] = [] private func updateToInterfaceOrientation() { let blocks = self.postUpdateToInterfaceOrientationBlocks @@ -455,7 +455,7 @@ public class Window1 { } } - public func addPostUpdateToInterfaceOrientationBlock(f: @escaping (Void) -> Void) { + public func addPostUpdateToInterfaceOrientationBlock(f: @escaping () -> Void) { postUpdateToInterfaceOrientationBlocks.append(f) } From f330a1dc07269b16add13e482f6284c256fd84cb Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 26 Sep 2017 02:59:04 +0300 Subject: [PATCH 044/245] no message --- Display.xcodeproj/project.pbxproj | 16 ++- Display/CAAnimationUtils.swift | 2 +- Display/ChildWindowHostView.swift | 77 ++++++++++++ Display/ContainableController.swift | 46 +++++++ Display/GridItem.swift | 5 + Display/GridNode.swift | 168 +++++++++++++++---------- Display/GridNodeScroller.swift | 2 +- Display/ListView.swift | 78 ++++++++++-- Display/ListViewItemHeader.swift | 6 + Display/ListViewTransactionQueue.swift | 4 +- Display/NativeWindowHostView.swift | 16 +++ Display/NavigationBar.swift | 134 +++++++++++++++++--- Display/NavigationBarTitleView.swift | 6 + Display/NavigationButtonNode.swift | 2 +- Display/PresentationContext.swift | 10 ++ Display/StatusBarManager.swift | 4 +- Display/StatusBarProxyNode.swift | 5 +- Display/TabBarNode.swift | 10 +- Display/TextFieldNode.swift | 10 +- Display/UINavigationItem+Proxy.h | 36 +++--- Display/UINavigationItem+Proxy.m | 17 ++- Display/ViewController.swift | 9 +- Display/WindowContent.swift | 12 +- 23 files changed, 541 insertions(+), 134 deletions(-) create mode 100644 Display/ChildWindowHostView.swift create mode 100644 Display/NavigationBarTitleView.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 95429a3ba8..8b141de67b 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -82,6 +82,7 @@ D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D077B8E81F4637040046D27A /* NavigationBarBadge.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; + D087BFB51F75181D003FD209 /* ChildWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */; }; D08CAA7B1ED73C990000FDA8 /* ViewControllerTracingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */; }; D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; @@ -113,6 +114,7 @@ D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; + D0CE8CE91F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */; }; D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; }; D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; }; D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */; }; @@ -220,6 +222,7 @@ D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D077B8E81F4637040046D27A /* NavigationBarBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarBadge.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; + D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildWindowHostView.swift; sourceTree = ""; }; D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerTracingNode.swift; sourceTree = ""; }; D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; @@ -251,6 +254,7 @@ D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; + D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarTitleView.swift; sourceTree = ""; }; D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = ""; }; D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertControllerNode.swift; sourceTree = ""; }; @@ -310,6 +314,7 @@ children = ( D05CC2A11B69326C00E235A3 /* WindowContent.swift */, D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */, + D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */, ); name = Window; sourceTree = ""; @@ -529,6 +534,7 @@ D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */, D077B8E81F4637040046D27A /* NavigationBarBadge.swift */, + D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */, ); name = Navigation; sourceTree = ""; @@ -810,6 +816,7 @@ buildActionMask = 2147483647; files = ( D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */, + D087BFB51F75181D003FD209 /* ChildWindowHostView.swift in Sources */, D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, @@ -862,6 +869,7 @@ D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, + D0CE8CE91F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift in Sources */, D0C12A1A1F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift in Sources */, D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, @@ -1281,7 +1289,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; @@ -1308,7 +1316,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; @@ -1406,7 +1414,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; @@ -1485,7 +1493,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; - OTHER_SWIFT_FLAGS = "-Xfrontend -debug-time-function-bodies"; + OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index b1cff306af..3937eace9d 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -90,7 +90,7 @@ public extension CALayer { public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) - self.add(animation, forKey: keyPath) + self.add(animation, forKey: additive ? nil : keyPath) } public func animateGroup(_ animations: [CAAnimation], key: String) { diff --git a/Display/ChildWindowHostView.swift b/Display/ChildWindowHostView.swift new file mode 100644 index 0000000000..d2faf8c6e0 --- /dev/null +++ b/Display/ChildWindowHostView.swift @@ -0,0 +1,77 @@ +import Foundation +import UIKit + +private final class ChildWindowHostView: UIView { + var updateSize: ((CGSize) -> Void)? + var layoutSubviewsEvent: (() -> Void)? + var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? + + override var frame: CGRect { + didSet { + if self.frame.size != oldValue.size { + self.updateSize?(self.frame.size) + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layoutSubviewsEvent?() + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.hitTestImpl?(point, event) + } +} + +public func childWindowHostView(parent: UIView) -> WindowHostView { + let view = ChildWindowHostView() + view.autoresizingMask = [.flexibleWidth, .flexibleHeight] + + let hostView = WindowHostView(view: view, isRotating: { + return false + }, updateSupportedInterfaceOrientations: { orientations in + //rootViewController.orientations = orientations + }, updateDeferScreenEdgeGestures: { edges in + }) + + view.updateSize = { [weak hostView] size in + hostView?.updateSize?(size) + } + + view.layoutSubviewsEvent = { [weak hostView] in + hostView?.layoutSubviews?() + } + + /*window.updateIsUpdatingOrientationLayout = { [weak hostView] value in + hostView?.isUpdatingOrientationLayout = value + } + + window.updateToInterfaceOrientation = { [weak hostView] in + hostView?.updateToInterfaceOrientation?() + } + + window.presentController = { [weak hostView] controller, level in + hostView?.present?(controller, level) + } + + window.presentNativeImpl = { [weak hostView] controller in + hostView?.presentNative?(controller) + }*/ + + view.hitTestImpl = { [weak hostView] point, event in + return hostView?.hitTest?(point, event) + } + + /*rootViewController.presentController = { [weak hostView] controller, level, animated, completion in + if let strongSelf = hostView { + strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) + if let completion = completion { + completion() + } + } + }*/ + + return hostView +} diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 33c728f4d8..4f6593b787 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -140,6 +140,21 @@ public extension ContainedViewLayoutTransition { } } + func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { switch self { case .immediate: @@ -153,7 +168,38 @@ public extension ContainedViewLayoutTransition { timingFunction = kCAMediaTimingFunctionSpring } node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + } + } + + func animateOffsetAdditive(layer: CALayer, offset: CGFloat) { + switch self { + case .immediate: break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) } } diff --git a/Display/GridItem.swift b/Display/GridItem.swift index d5707c3d86..0076ac16d1 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -13,10 +13,15 @@ public protocol GridItem { func node(layout: GridNodeLayout) -> GridItemNode func update(node: GridItemNode) var aspectRatio: CGFloat { get } + var fillsRowWithHeight: CGFloat? { get } } public extension GridItem { var aspectRatio: CGFloat { return 1.0 } + + var fillsRowWithHeight: CGFloat? { + return nil + } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index af791c7628..b87337fe5a 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -29,6 +29,7 @@ public enum GridNodeScrollToItemPosition { case top case bottom case center + case visible } public struct GridNodeScrollToItem { @@ -287,7 +288,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func transaction(_ transaction: GridNodeTransaction, completion: (GridNodeDisplayedItemRange) -> Void) { if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout && (transaction.updateFirstIndexInSectionOffset == nil || transaction.updateFirstIndexInSectionOffset == self.firstIndexInSectionOffset)) { if let presentationLayoutUpdated = self.presentationLayoutUpdated { - presentationLayoutUpdated(GridNodeCurrentPresentationLayout(layout: self.gridLayout, contentOffset: self.scrollView.contentOffset, contentSize: self.itemLayout.contentSize), .immediate) + presentationLayoutUpdated(GridNodeCurrentPresentationLayout(layout: self.gridLayout, contentOffset: self.scrollView.contentOffset, contentSize: self.itemLayout.contentSize), transaction.updateLayout?.transition ?? .immediate) } completion(self.displayedItemRange()) return @@ -424,24 +425,35 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } private func generateItemLayout() -> GridNodeItemLayout { - if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.items.isEmpty { + if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) { var contentSize = CGSize(width: gridLayout.size.width, height: 0.0) var items: [GridNodePresentationItem] = [] var sections: [GridNodePresentationSection] = [] switch gridLayout.type { - case let .fixed(itemSize, lineSpacing): - let itemsInRow = Int(gridLayout.size.width / itemSize.width) - let itemsInRowWidth = CGFloat(itemsInRow) * itemSize.width - let remainingWidth = max(0.0, gridLayout.size.width - itemsInRowWidth) + case let .fixed(defaultItemSize, lineSpacing): + let itemInsets = gridLayout.insets + + let effectiveWidth = gridLayout.size.width - itemInsets.left - itemInsets.right + + let itemsInRow = Int(effectiveWidth / defaultItemSize.width) + let itemsInRowWidth = CGFloat(itemsInRow) * defaultItemSize.width + let remainingWidth = max(0.0, effectiveWidth - itemsInRowWidth) let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) var incrementedCurrentRow = false - var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) + var nextItemOrigin = CGPoint(x: itemInsets.left + itemSpacing, y: 0.0) var index = 0 var previousSection: GridSection? for item in self.items { + var itemSize = defaultItemSize + if let height = item.fillsRowWithHeight { + nextItemOrigin.x = 0.0 + itemSize.width = gridLayout.size.width + itemSize.height = height + } + let section = item.section var keepSection = true if let previousSection = previousSection, let section = section { @@ -452,7 +464,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if !keepSection { if incrementedCurrentRow { - nextItemOrigin.x = itemSpacing + nextItemOrigin.x = itemSpacing + itemInsets.left nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false } @@ -471,7 +483,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } if index == 0 { - let itemsInRow = Int(gridLayout.size.width) / Int(itemSize.width) + let itemsInRow = max(1, Int(effectiveWidth) / Int(itemSize.width)) let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) } @@ -481,7 +493,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { nextItemOrigin.x += itemSize.width + itemSpacing if nextItemOrigin.x + itemSize.width > gridLayout.size.width { - nextItemOrigin.x = itemSpacing + nextItemOrigin.x = itemSpacing + itemInsets.left nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false } @@ -576,7 +588,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } private func generatePresentationLayoutTransition(stationaryItems: GridNodeStationaryItems = .none, layoutTransactionOffset: CGFloat, scrollToItem: GridNodeScrollToItem? = nil) -> GridNodePresentationLayoutTransition { - if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) && !self.itemLayout.items.isEmpty { + if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) { var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up var transition: ContainedViewLayoutTransition = .immediate let contentOffset: CGPoint @@ -587,61 +599,73 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { switch updatedStationaryItems { case .none: if let scrollToItem = scrollToItem { - let itemFrame = self.itemLayout.items[scrollToItem.index] - - var additionalOffset: CGFloat = 0.0 - if scrollToItem.adjustForSection { - var adjustForSection: GridSection? - if scrollToItem.index == 0 { - if let itemSection = self.items[scrollToItem.index].section { - adjustForSection = itemSection - } - } else { - let itemSection = self.items[scrollToItem.index].section - let previousSection = self.items[scrollToItem.index - 1].section - if let itemSection = itemSection, let previousSection = previousSection { - if !itemSection.isEqual(to: previousSection) { + if self.itemLayout.items.isEmpty { + transitionDirectionHint = scrollToItem.directionHint + transition = scrollToItem.transition + contentOffset = CGPoint(x: 0.0, y: -self.gridLayout.insets.top) + } else { + let itemFrame = self.itemLayout.items[scrollToItem.index] + + var additionalOffset: CGFloat = 0.0 + if scrollToItem.adjustForSection { + var adjustForSection: GridSection? + if scrollToItem.index == 0 { + if let itemSection = self.items[scrollToItem.index].section { + adjustForSection = itemSection + } + } else { + let itemSection = self.items[scrollToItem.index].section + let previousSection = self.items[scrollToItem.index - 1].section + if let itemSection = itemSection, let previousSection = previousSection { + if !itemSection.isEqual(to: previousSection) { + adjustForSection = itemSection + } + } else if let itemSection = itemSection { adjustForSection = itemSection } - } else if let itemSection = itemSection { - adjustForSection = itemSection } + + if let adjustForSection = adjustForSection { + additionalOffset = -adjustForSection.height + } + + if scrollToItem.adjustForTopInset { + additionalOffset += -gridLayout.insets.top + } + } else if scrollToItem.adjustForTopInset { + additionalOffset = -gridLayout.insets.top } - if let adjustForSection = adjustForSection { - additionalOffset = -adjustForSection.height + let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) + var verticalOffset: CGFloat = self.scrollView.contentOffset.y + + switch scrollToItem.position { + case .top: + verticalOffset = itemFrame.frame.minY + additionalOffset + case .center: + verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + additionalOffset + case .bottom: + verticalOffset = itemFrame.frame.maxY - displayHeight + additionalOffset + case .visible: + if verticalOffset + self.gridLayout.insets.top > itemFrame.frame.minY { + //verticalOffset = -self.gridLayout.insets.top + itemFrame.frame.minY + } else if verticalOffset + self.gridLayout.insets.top + displayHeight < itemFrame.frame.maxY { + verticalOffset = -self.gridLayout.insets.top - displayHeight + itemFrame.frame.maxY + } } - if scrollToItem.adjustForTopInset { - additionalOffset += -gridLayout.insets.top + if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { + verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height } - } else if scrollToItem.adjustForTopInset { - additionalOffset = -gridLayout.insets.top + if verticalOffset < -self.gridLayout.insets.top { + verticalOffset = -self.gridLayout.insets.top + } + + transitionDirectionHint = scrollToItem.directionHint + transition = scrollToItem.transition + + contentOffset = CGPoint(x: 0.0, y: verticalOffset) } - - let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) - var verticalOffset: CGFloat - - switch scrollToItem.position { - case .top: - verticalOffset = itemFrame.frame.minY + additionalOffset - case .center: - verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + additionalOffset - case .bottom: - verticalOffset = itemFrame.frame.maxY - displayHeight + additionalOffset - } - - if verticalOffset > self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height { - verticalOffset = self.itemLayout.contentSize.height + self.gridLayout.insets.bottom - self.gridLayout.size.height - } - if verticalOffset < -self.gridLayout.insets.top { - verticalOffset = -self.gridLayout.insets.top - } - - transitionDirectionHint = scrollToItem.directionHint - transition = scrollToItem.transition - - contentOffset = CGPoint(x: 0.0, y: verticalOffset) } else { if !layoutTransactionOffset.isZero { var verticalOffset = self.scrollView.contentOffset.y - layoutTransactionOffset @@ -686,17 +710,17 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - let lowerDisplayBound = contentOffset.y - self.gridLayout.preloadSize - let upperDisplayBound = contentOffset.y + self.gridLayout.size.height + self.gridLayout.preloadSize + let lowerDisplayBound = contentOffset.y - self.gridLayout.insets.top - self.gridLayout.preloadSize + let upperDisplayBound = contentOffset.y + self.gridLayout.insets.bottom + self.gridLayout.size.height + self.gridLayout.preloadSize var presentationItems: [GridNodePresentationItem] = [] var validSections = Set() for item in self.itemLayout.items { - if item.frame.origin.y < lowerDisplayBound { + if item.frame.maxY < lowerDisplayBound { continue } - if item.frame.origin.y + item.frame.size.height > upperDisplayBound { + if item.frame.minY > upperDisplayBound { break } presentationItems.append(item) @@ -741,6 +765,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) { + let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate + var previousItemFrames: [WrappedGridItemNode: CGRect]? var saveItemFrames = false switch presentationLayoutTransition.transition { @@ -770,16 +796,22 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { applyingContentOffset = true + let previousBounds = self.bounds self.scrollView.contentSize = presentationLayoutTransition.layout.contentSize - self.scrollView.contentInset = presentationLayoutTransition.layout.layout.insets + let layoutInsets = presentationLayoutTransition.layout.layout.insets + self.scrollView.contentInset = UIEdgeInsets(top: layoutInsets.top, left: 0.0, bottom: layoutInsets.bottom, right: 0.0) if let scrollIndicatorInsets = presentationLayoutTransition.layout.layout.scrollIndicatorInsets { self.scrollView.scrollIndicatorInsets = scrollIndicatorInsets } else { self.scrollView.scrollIndicatorInsets = presentationLayoutTransition.layout.layout.insets } + var boundsOffset: CGFloat = 0.0 if !self.scrollView.contentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) || self.bounds.size != presentationLayoutTransition.layout.layout.size { - //self.scrollView.contentOffset = presentationLayoutTransition.layout.contentOffset - self.bounds = CGRect(origin: presentationLayoutTransition.layout.contentOffset, size: presentationLayoutTransition.layout.layout.size) + let updatedBounds = CGRect(origin: presentationLayoutTransition.layout.contentOffset, size: presentationLayoutTransition.layout.layout.size) + boundsOffset = updatedBounds.origin.y - previousBounds.origin.y + self.bounds = updatedBounds + //boundsTransition.animateOffsetAdditive(layer: self.layer, offset: -boundsOffset - insetsOffset) + boundsTransition.animateBounds(layer: self.layer, from: previousBounds) } applyingContentOffset = false @@ -832,7 +864,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { for (index, itemNode) in self.itemNodes { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)], existingItemIndices.contains(index) { let currentFrame = itemNode.frame.offsetBy(dx: 0.0, dy: -presentationLayoutTransition.layout.contentOffset.y) - offset = previousFrame.origin.y - currentFrame.origin.y + offset = previousFrame.origin.y - currentFrame.origin.y - boundsOffset break } } @@ -962,6 +994,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { timingFunction = kCAMediaTimingFunctionSpring } + let contentOffset = self.scrollView.contentOffset + for index in self.itemNodes.keys { let itemNode = self.itemNodes[index]! if !existingItemIndices.contains(index) { @@ -975,7 +1009,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.removeItemNodeWithIndex(index, removeNode: true) } } else if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { - itemNode.layer.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: itemNode.layer.position, duration: duration, timingFunction: timingFunction) + itemNode.layer.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY + contentOffset.y), to: itemNode.layer.position, duration: duration, timingFunction: timingFunction) } else { itemNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12, timingFunction: kCAMediaTimingFunctionEaseIn) itemNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) @@ -1005,7 +1039,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.removeSectionNodeWithSection(wrappedSection, removeNode: true) } } else if let previousFrame = previousItemFrames[WrappedGridItemNode(node: sectionNode)] { - sectionNode.layer.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY), to: sectionNode.layer.position, duration: duration, timingFunction: timingFunction) + sectionNode.layer.animatePosition(from: CGPoint(x: previousFrame.midX, y: previousFrame.midY + contentOffset.y), to: sectionNode.layer.position, duration: duration, timingFunction: timingFunction) } else { sectionNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseIn) } diff --git a/Display/GridNodeScroller.swift b/Display/GridNodeScroller.swift index 98c0c241b9..9adaac922f 100644 --- a/Display/GridNodeScroller.swift +++ b/Display/GridNodeScroller.swift @@ -32,7 +32,7 @@ private class GridNodeScrollerView: UIScrollView { } open class GridNodeScroller: ASDisplayNode, UIGestureRecognizerDelegate { - var scrollView: UIScrollView { + public var scrollView: UIScrollView { return self.view as! UIScrollView } diff --git a/Display/ListView.swift b/Display/ListView.swift index f4cc1eab6d..464a291ca2 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -124,9 +124,25 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var stackFromBottom: Bool = false public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 public final var limitHitTestToNodes: Bool = false - public final var keepBottomItemOverscrollBackground: Bool = false + public final var keepTopItemOverscrollBackground: UIColor? { + didSet { + if let color = self.keepTopItemOverscrollBackground { + self.topItemOverscrollBackground?.backgroundColor = color + } + self.updateTopItemOverscrollBackground() + } + } + public final var keepBottomItemOverscrollBackground: UIColor? { + didSet { + if let color = self.keepBottomItemOverscrollBackground { + self.bottomItemOverscrollBackground?.backgroundColor = color + } + self.updateBottomItemOverscrollBackground() + } + } public final var snapToBottomInsetUntilFirstInteraction: Bool = false + private var topItemOverscrollBackground: ASDisplayNode? private var bottomItemOverscrollBackground: ASDisplayNode? private var touchesPosition = CGPoint() @@ -153,6 +169,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final var opaqueTransactionState: Any? public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } + public final var beganInteractiveDragging: () -> Void = { } private final var animations: [ListViewAnimation] = [] private final var actionsForVSync: [() -> ()] = [] @@ -381,6 +398,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if self.snapToBottomInsetUntilFirstInteraction { self.snapToBottomInsetUntilFirstInteraction = false } + + self.beganInteractiveDragging() } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { @@ -706,10 +725,53 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.ignoreScrollingEvents = wasIgnoringScrollingEvents } + private func updateTopItemOverscrollBackground() { + if let color = self.keepTopItemOverscrollBackground { + let topItemOverscrollBackground: ASDisplayNode + if let current = self.topItemOverscrollBackground { + topItemOverscrollBackground = current + } else { + topItemOverscrollBackground = ASDisplayNode() + topItemOverscrollBackground.isLayerBacked = true + topItemOverscrollBackground.backgroundColor = color + self.topItemOverscrollBackground = topItemOverscrollBackground + self.insertSubnode(topItemOverscrollBackground, at: 0) + } + var topItemFound = false + var topItemNodeIndex: Int? + if !self.itemNodes.isEmpty { + topItemNodeIndex = self.itemNodes[0].index + } + if topItemNodeIndex == 0 { + topItemFound = true + } + + if topItemFound { + let realTopItemEdge = itemNodes.first!.apparentFrame.origin.y + let realTopItemEdgeOffset = max(0.0, realTopItemEdge) + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: realTopItemEdgeOffset)) + if !backgroundFrame.equalTo(topItemOverscrollBackground.frame) { + topItemOverscrollBackground.frame = backgroundFrame + } + } else { + let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: 0.0)) + if !backgroundFrame.equalTo(topItemOverscrollBackground.frame) { + topItemOverscrollBackground.frame = backgroundFrame + } + } + } else if let topItemOverscrollBackground = self.topItemOverscrollBackground { + self.topItemOverscrollBackground = nil + topItemOverscrollBackground.removeFromSupernode() + } + } + private func updateBottomItemOverscrollBackground() { - if self.keepBottomItemOverscrollBackground { + if let color = self.keepBottomItemOverscrollBackground { var bottomItemFound = false - let lastItemNodeIndex: Int? = self.itemNodes[itemNodes.count - 1].index + var lastItemNodeIndex: Int? + if !itemNodes.isEmpty { + lastItemNodeIndex = self.itemNodes[itemNodes.count - 1].index + } if lastItemNodeIndex == self.items.count - 1 { bottomItemFound = true } @@ -719,7 +781,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel bottomItemOverscrollBackground = currentBottomItemOverscrollBackground } else { bottomItemOverscrollBackground = ASDisplayNode() - bottomItemOverscrollBackground.backgroundColor = .white + bottomItemOverscrollBackground.backgroundColor = color bottomItemOverscrollBackground.isLayerBacked = true self.insertSubnode(bottomItemOverscrollBackground, at: 0) self.bottomItemOverscrollBackground = bottomItemOverscrollBackground @@ -738,6 +800,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel bottomItemOverscrollBackground.frame = backgroundFrame } } + } else if let bottomItemOverscrollBackground = self.bottomItemOverscrollBackground { + self.bottomItemOverscrollBackground = nil + bottomItemOverscrollBackground.removeFromSupernode() } } @@ -791,6 +856,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.updateTopItemOverscrollBackground() self.updateBottomItemOverscrollBackground() let wasIgnoringScrollingEvents = self.ignoreScrollingEvents @@ -2264,10 +2330,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true) headerNode.internalStickLocationDistance = stickLocationDistance if !hasValidNodes && !headerNode.alpha.isZero { - headerNode.alpha = 0.0 if animateInsertion { - headerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) - headerNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.2) + headerNode.animateRemoved(duration: 0.2) } } else if hasValidNodes && headerNode.alpha.isZero { headerNode.alpha = 1.0 diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index 01458552e7..ec7e2d9419 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -133,4 +133,10 @@ open class ListViewItemHeaderNode: ASDisplayNode { return continueAnimations } + + open func animateRemoved(duration: Double) { + self.alpha = 0.0 + self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) + self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false) + } } diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index a175edb4ec..0513e7f1e1 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -34,7 +34,9 @@ public final class ListViewTransactionQueue { private func endTransaction() { Queue.mainQueue().async { self.transactionCompleted() - let _ = self.transactions.removeFirst() + if !self.transactions.isEmpty { + let _ = self.transactions.removeFirst() + } if let nextTransaction = self.transactions.first { nextTransaction({ [weak self] in diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index f43ec4a6f2..c502450939 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -26,6 +26,16 @@ private class WindowRootViewController: UIViewController { } } + var gestureEdges: UIRectEdge = [] { + didSet { + if oldValue != self.gestureEdges { + if #available(iOSApplicationExtension 11.0, *) { + self.setNeedsUpdateOfScreenEdgesDeferringSystemGestures() + } + } + } + } + override var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -37,6 +47,10 @@ private class WindowRootViewController: UIViewController { override var supportedInterfaceOrientations: UIInterfaceOrientationMask { return orientations } + + override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { + return self.gestureEdges + } } private final class NativeWindow: UIWindow, WindowHost { @@ -115,6 +129,8 @@ public func nativeWindowHostView() -> WindowHostView { return window.isRotating() }, updateSupportedInterfaceOrientations: { orientations in rootViewController.orientations = orientations + }, updateDeferScreenEdgeGestures: { edges in + rootViewController.gestureEdges = edges }) window.updateSize = { [weak hostView] size in diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index df35ba07d6..9e6818d00d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -5,11 +5,13 @@ private var backArrowImageCache: [Int32: UIImage] = [:] public final class NavigationBarTheme { public static func generateBackArrowImage(color: UIColor) -> UIImage? { - return generateImage(CGSize(width: 13.0, height: 22.0), contextGenerator: { size, context in + return generateImage(CGSize(width: 13.0, height: 22.0), rotatedContext: { size, context in context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(color.cgColor) - let _ = try? drawSvgPath(context, path: "M10.3824541,0.421094342 L1.53851,8.52547877 L1.53851,8.52547877 C0.724154418,9.27173527 0.668949198,10.5368613 1.41520569,11.3512169 C1.45449493,11.3940915 1.49563546,11.435232 1.53851,11.4745212 L10.3824541,19.5789057 L10.3824541,19.5789057 C10.9981509,20.1431158 11.9429737,20.1431158 12.5586704,19.5789057 L12.5586704,19.5789057 L12.5586704,19.5789057 C13.1093629,19.0742639 13.1466944,18.2187464 12.6420526,17.6680539 C12.615484,17.6390608 12.5876635,17.6112403 12.5586704,17.5846717 L4.28186505,10 L12.5586704,2.41532829 L12.5586704,2.41532829 C13.1093629,1.91068651 13.1466944,1.05516904 12.6420526,0.50447654 C12.615484,0.475483443 12.5876635,0.447662941 12.5586704,0.421094342 L12.5586704,0.421094342 L12.5586704,0.421094342 C11.9429737,-0.143115824 10.9981509,-0.143115824 10.3824541,0.421094342 Z ") + context.translateBy(x: 0.0, y: 2.0) + + let _ = try? drawSvgPath(context, path: "M8.16012402,0.373030797 L0.635333572,7.39652821 L0.635333572,7.39652821 C-0.172148528,8.15021677 -0.215756811,9.41579564 0.537931744,10.2232777 C0.56927099,10.2568538 0.601757528,10.2893403 0.635333572,10.3206796 L8.16012402,17.344177 L8.16012402,17.344177 C8.69299787,17.8415514 9.51995274,17.8415514 10.0528266,17.344177 L10.0528266,17.344177 L10.0528266,17.344177 C10.5406633,16.8888394 10.567009,16.1242457 10.1116715,15.636409 C10.092738,15.6161242 10.0731114,15.5964976 10.0528266,15.5775641 L2.85430928,8.85860389 L10.0528266,2.13964366 L10.0528266,2.13964366 C10.5406633,1.68430612 10.567009,0.919712345 10.1116715,0.431875673 C10.092738,0.411590857 10.0731114,0.391964261 10.0528266,0.373030797 L10.0528266,0.373030797 L10.0528266,0.373030797 C9.51995274,-0.124343599 8.69299787,-0.124343599 8.16012402,0.373030797 Z ") }) } @@ -77,6 +79,8 @@ open class NavigationBar: ASDisplayNode { private var itemBadgeListenerKey: Int? + private var hintAnimateTitleNodeOnNextLayout: Bool = false + private var _item: UINavigationItem? public var item: UINavigationItem? { get { @@ -120,9 +124,13 @@ open class NavigationBar: ASDisplayNode { if let item = value { self.title = item.title - self.itemTitleListenerKey = item.addSetTitleListener { [weak self] text in + self.itemTitleListenerKey = item.addSetTitleListener { [weak self] text, animated in if let strongSelf = self { + let animateIn = animated && (strongSelf.title?.isEmpty ?? true) strongSelf.title = text + if animateIn { + strongSelf.titleNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } } } @@ -133,20 +141,20 @@ open class NavigationBar: ASDisplayNode { } } - self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] previousItem, _, _ in + self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] previousItem, _, animated in if let strongSelf = self { if let itemLeftButtonSetEnabledListenerKey = strongSelf.itemLeftButtonSetEnabledListenerKey { previousItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey) strongSelf.itemLeftButtonSetEnabledListenerKey = nil } - strongSelf.updateLeftButton() + strongSelf.updateLeftButton(animated: animated) strongSelf.invalidateCalculatedLayout() strongSelf.setNeedsLayout() } } - self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, _ in + self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, animated in if let strongSelf = self { if let itemRightButtonSetEnabledListenerKey = strongSelf.itemRightButtonSetEnabledListenerKey { previousItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey) @@ -156,12 +164,12 @@ open class NavigationBar: ASDisplayNode { if let currentItem = currentItem { strongSelf.itemRightButtonSetEnabledListenerKey = currentItem.addSetEnabledListener { _ in if let strongSelf = self { - strongSelf.updateRightButton() + strongSelf.updateRightButton(animated: false) } } } - strongSelf.updateRightButton() + strongSelf.updateRightButton(animated: animated) strongSelf.invalidateCalculatedLayout() strongSelf.setNeedsLayout() } @@ -174,12 +182,12 @@ open class NavigationBar: ASDisplayNode { } self.updateBadgeText(text: item.badge) - self.updateLeftButton() - self.updateRightButton() + self.updateLeftButton(animated: false) + self.updateRightButton(animated: false) } else { self.title = nil - self.updateLeftButton() - self.updateRightButton() + self.updateLeftButton(animated: false) + self.updateRightButton(animated: false) } self.invalidateCalculatedLayout() } @@ -188,7 +196,7 @@ open class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.theme.primaryTextColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -239,7 +247,7 @@ open class NavigationBar: ASDisplayNode { self._previousItem = value if let previousItem = value { - self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] _ in + self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] _, _ in if let strongSelf = self, let previousItem = strongSelf.previousItem { if let backBarButtonItem = previousItem.backBarButtonItem { strongSelf.backButtonNode.text = backBarButtonItem.title ?? "" @@ -261,7 +269,7 @@ open class NavigationBar: ASDisplayNode { } } } - self.updateLeftButton() + self.updateLeftButton(animated: false) self.invalidateCalculatedLayout() } @@ -278,9 +286,51 @@ open class NavigationBar: ASDisplayNode { } } - private func updateLeftButton() { + private func updateLeftButton(animated: Bool) { if let item = self.item { if let leftBarButtonItem = item.leftBarButtonItem { + if animated { + if self.leftButtonNode.view.superview != nil { + if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { + snapshotView.frame = self.leftButtonNode.frame + self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + if self.backButtonNode.view.superview != nil { + if let snapshotView = self.backButtonNode.view.snapshotContentTree() { + snapshotView.frame = self.backButtonNode.frame + self.backButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.backButtonNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + if self.backButtonArrow.view.superview != nil { + if let snapshotView = self.backButtonArrow.view.snapshotContentTree() { + snapshotView.frame = self.backButtonArrow.frame + self.backButtonArrow.view.superview?.insertSubview(snapshotView, aboveSubview: self.backButtonArrow.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + if self.badgeNode.view.superview != nil { + if let snapshotView = self.badgeNode.view.snapshotContentTree() { + snapshotView.frame = self.badgeNode.frame + self.badgeNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.badgeNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + } + self.backButtonNode.removeFromSupernode() self.backButtonArrow.removeFromSupernode() self.badgeNode.removeFromSupernode() @@ -291,7 +341,22 @@ open class NavigationBar: ASDisplayNode { if self.leftButtonNode.supernode == nil { self.clippingNode.addSubnode(self.leftButtonNode) } + + if animated { + self.leftButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } } else { + if animated { + if self.leftButtonNode.view.superview != nil { + if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { + snapshotView.frame = self.leftButtonNode.frame + self.leftButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.leftButtonNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + } self.leftButtonNode.removeFromSupernode() if let previousItem = self.previousItem { @@ -306,9 +371,14 @@ open class NavigationBar: ASDisplayNode { self.clippingNode.addSubnode(self.backButtonArrow) self.clippingNode.addSubnode(self.badgeNode) } + + if animated { + self.backButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.backButtonArrow.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + self.badgeNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) + } } else { self.backButtonNode.removeFromSupernode() - } } } else { @@ -317,11 +387,24 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.removeFromSupernode() self.badgeNode.removeFromSupernode() } + + if animated { + self.hintAnimateTitleNodeOnNextLayout = true + } } - private func updateRightButton() { + private func updateRightButton(animated: Bool) { if let item = self.item { if let rightBarButtonItem = item.rightBarButtonItem { + if animated, self.rightButtonNode.view.superview != nil { + if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { + snapshotView.frame = self.rightButtonNode.frame + self.rightButtonNode.view.superview?.insertSubview(snapshotView, aboveSubview: self.rightButtonNode.view) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } self.rightButtonNode.text = rightBarButtonItem.title ?? "" self.rightButtonNode.image = rightBarButtonItem.image self.rightButtonNode.bold = rightBarButtonItem.style == .done @@ -330,12 +413,19 @@ open class NavigationBar: ASDisplayNode { if self.rightButtonNode.supernode == nil { self.clippingNode.addSubnode(self.rightButtonNode) } + if animated { + self.rightButtonNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } } else { self.rightButtonNode.removeFromSupernode() } } else { self.rightButtonNode.removeFromSupernode() } + + if animated { + self.hintAnimateTitleNodeOnNextLayout = true + } } private let backButtonNode: NavigationButtonNode @@ -497,7 +587,7 @@ open class NavigationBar: ASDisplayNode { open override func layout() { let size = self.bounds.size - let leftButtonInset: CGFloat = 8.0 + let leftButtonInset: CGFloat = 16.0 let backButtonInset: CGFloat = 27.0 self.clippingNode.frame = CGRect(origin: CGPoint(), size: size) @@ -666,6 +756,12 @@ open class NavigationBar: ASDisplayNode { titleView.alpha = progress * progress } } else { + if self.hintAnimateTitleNodeOnNextLayout { + self.hintAnimateTitleNodeOnNextLayout = false + if let titleView = titleView as? NavigationBarTitleView { + titleView.animateLayoutTransition() + } + } titleView.alpha = 1.0 titleView.frame = CGRect(origin: CGPoint(x: floor((size.width - titleSize.width) / 2.0), y: contentVerticalOrigin + floorToScreenPixels((nominalHeight - titleSize.height) / 2.0)), size: titleSize) } diff --git a/Display/NavigationBarTitleView.swift b/Display/NavigationBarTitleView.swift new file mode 100644 index 0000000000..630e23606e --- /dev/null +++ b/Display/NavigationBarTitleView.swift @@ -0,0 +1,6 @@ +import Foundation +import UIKit + +public protocol NavigationBarTitleView { + func animateLayoutTransition() +} diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 633b1f11c3..87afb05a70 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -110,7 +110,7 @@ public class NavigationButtonNode: ASTextNode { } else if let imageNode = self.imageNode { let nodeSize = imageNode.measure(constrainedSize) let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) - imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0), y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize) + imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0) + 5.0, y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize) return size } return superSize diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 7b6be08559..fd2cb9e088 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -173,4 +173,14 @@ final class PresentationContext { return mask } + + func combinedDeferScreenEdgeGestures() -> UIRectEdge { + var edges: UIRectEdge = [] + + for controller in self.controllers { + edges = edges.union(controller.deferScreenEdgeGestures) + } + + return edges + } } diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index b2cd7a8542..71e6d8f376 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -233,8 +233,8 @@ class StatusBarManager { statusBarWindow.bounds = statusBarBounds } } - } else { - self.host.statusBarWindow?.alpha = 0.0 + } else if let statusBarWindow = self.host.statusBarWindow { + statusBarWindow.alpha = 0.0 } } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index a7a0c3ec5b..83808959d6 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -67,7 +67,6 @@ private class StatusBarItemNode: ASDisplayNode { UIGraphicsPopContext() } } - //print("\(self.targetView)") let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic tintStatusBarItem(context, type: type, style: statusBarStyle) self.contents = context.generateImage()?.cgImage @@ -184,7 +183,9 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp pixel += 1 } - if batteryColor != 0xffffffff && batteryColor != 0xff000000 { + let whiteColor: UInt32 = 0xffffffff as UInt32 + let blackColor: UInt32 = 0xff000000 as UInt32 + if batteryColor != whiteColor && batteryColor != blackColor { var y = baseY + 2 while y < targetY { let baseRow = basePixel + pixelsPerRow * y diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 117fad383a..fbfc1169bb 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -67,7 +67,7 @@ private final class TabBarNodeContainer { var selectedImageValue: UIImage? var appliedSelectedImageValue: UIImage? - init(item: UITabBarItem, imageNode: ASImageNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void) { + init(item: UITabBarItem, imageNode: ASImageNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void) { self.item = item self.imageNode = imageNode @@ -88,8 +88,8 @@ private final class TabBarNodeContainer { }) self.titleValue = item.title - self.updateTitleListenerIndex = item.addSetTitleListener { value in - updateTitle(value ?? "") + self.updateTitleListenerIndex = item.addSetTitleListener { value, animated in + updateTitle(value ?? "", animated) } self.imageValue = item.image @@ -193,7 +193,7 @@ class TabBarNode: ASDisplayNode { node.isLayerBacked = true let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) - }, updateTitle: { [weak self] _ in + }, updateTitle: { [weak self] _, _ in self?.updateNodeImage(i) }, updateImage: { [weak self] _ in self?.updateNodeImage(i) @@ -287,7 +287,7 @@ class TabBarNode: ASDisplayNode { if !container.badgeBackgroundNode.isHidden { let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) - let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.calculatedSize.width / 2.0) - 5.0, y: 2.0), size: backgroundSize) + let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.calculatedSize.width / 2.0) - 3.0 + node.calculatedSize.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) container.badgeBackgroundNode.frame = backgroundFrame container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: 3.0), size: badgeSize) } diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index 4cd28d20b5..cdf2ebabc4 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -4,12 +4,14 @@ import AsyncDisplayKit public final class TextFieldNodeView: UITextField { public var didDeleteBackwardWhileEmpty: (() -> Void)? + var fixOffset: Bool = true + override public func editingRect(forBounds bounds: CGRect) -> CGRect { return bounds.offsetBy(dx: 0.0, dy: -UIScreenPixel) } override public func placeholderRect(forBounds bounds: CGRect) -> CGRect { - return self.editingRect(forBounds: bounds) + return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: self.fixOffset ? 0.0 : -UIScreenPixel)) } override public func deleteBackward() { @@ -25,6 +27,12 @@ public class TextFieldNode: ASDisplayNode { return self.view as! TextFieldNodeView } + public var fixOffset: Bool = true { + didSet { + self.textField.fixOffset = self.fixOffset + } + } + override public init() { super.init() diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 63267511ba..08a1ef6da6 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -1,45 +1,47 @@ #import -typedef void (^UINavigationItemSetTitleListener)(NSString *); -typedef void (^UINavigationItemSetTitleViewListener)(UIView *); -typedef void (^UINavigationItemSetImageListener)(UIImage *); -typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, UIBarButtonItem *, BOOL); -typedef void (^UITabBarItemSetBadgeListener)(NSString *); +typedef void (^UINavigationItemSetTitleListener)(NSString * _Nullable, bool); +typedef void (^UINavigationItemSetTitleViewListener)(UIView * _Nullable); +typedef void (^UINavigationItemSetImageListener)(UIImage * _Nullable); +typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem * _Nullable, UIBarButtonItem * _Nullable, BOOL); +typedef void (^UITabBarItemSetBadgeListener)(NSString * _Nullable); @interface UINavigationItem (Proxy) -- (void)setTargetItem:(UINavigationItem *)targetItem; +- (void)setTargetItem:(UINavigationItem * _Nullable)targetItem; -- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; +- (void)setTitle:(NSString * _Nullable)title animated:(bool)animated; + +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener _Nonnull)listener; - (void)removeSetTitleListener:(NSInteger)key; -- (NSInteger)addSetTitleViewListener:(UINavigationItemSetTitleViewListener)listener; +- (NSInteger)addSetTitleViewListener:(UINavigationItemSetTitleViewListener _Nonnull)listener; - (void)removeSetTitleViewListener:(NSInteger)key; -- (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener; - (void)removeSetLeftBarButtonItemListener:(NSInteger)key; -- (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener; - (void)removeSetRightBarButtonItemListener:(NSInteger)key; -- (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener; +- (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener; - (void)removeSetBackBarButtonItemListener:(NSInteger)key; -- (NSInteger)addSetBadgeListener:(UITabBarItemSetBadgeListener)listener; +- (NSInteger)addSetBadgeListener:(UITabBarItemSetBadgeListener _Nonnull)listener; - (void)removeSetBadgeListener:(NSInteger)key; -@property (nonatomic, strong) NSString *badge; +@property (nonatomic, strong) NSString * _Nullable badge; @end -NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBadgeListener listener); +NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem * _Nonnull item, UITabBarItemSetBadgeListener _Nonnull listener); @interface UITabBarItem (Proxy) - (void)removeSetBadgeListener:(NSInteger)key; -- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener; +- (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener _Nonnull)listener; - (void)removeSetTitleListener:(NSInteger)key; -- (NSInteger)addSetImageListener:(UINavigationItemSetImageListener)listener; +- (NSInteger)addSetImageListener:(UINavigationItemSetImageListener _Nonnull)listener; - (void)removeSetImageListener:(NSInteger)key; -- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener)listener; +- (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener _Nonnull)listener; - (void)removeSetSelectedImageListener:(NSInteger)key; @end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index 293f1cc572..98871e7042 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -42,7 +42,20 @@ static const void *badgeKey = &badgeKey; [targetItem setTitle:title]; } else { [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { - listener(title); + listener(title, false); + }]; + } +} + +- (void)setTitle:(NSString * _Nullable)title animated:(bool)animated { + [self _ac91f40f_setTitle:title]; + + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setTitle:title]; + } else { + [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { + listener(title, animated); }]; } } @@ -287,7 +300,7 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa [self _ac91f40f_setTitle:value]; [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UINavigationItemSetTitleListener listener) { - listener(value); + listener(value, false); }]; } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index a6b4c0155f..d5205cd68f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -38,6 +38,11 @@ open class ViewControllerPresentationArguments { return self.supportedOrientations } + public final var deferScreenEdgeGestures: UIRectEdge = [] + override open func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { + return .bottom + } + public private(set) var presentationArguments: Any? private var _displayNode: ASDisplayNode? @@ -187,7 +192,9 @@ open class ViewControllerPresentationArguments { open override func loadView() { self.view = self.displayNode.view if let navigationBar = self.navigationBar { - self.displayNode.addSubnode(navigationBar) + if navigationBar.supernode == nil { + self.displayNode.addSubnode(navigationBar) + } } self.view.addSubview(self.statusBar.view) self.presentationContext.view = self.view diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 18894024ca..a515a931d7 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -138,6 +138,7 @@ public final class WindowHostView { public let isRotating: () -> Bool let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void + let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void var present: ((ViewController, PresentationSurfaceLevel) -> Void)? var presentNative: ((UIViewController) -> Void)? @@ -147,10 +148,11 @@ public final class WindowHostView { var isUpdatingOrientationLayout = false var hitTest: ((CGPoint, UIEvent?) -> UIView?)? - init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void) { + init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void) { self.view = view self.isRotating = isRotating self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations + self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures } } @@ -358,7 +360,9 @@ public class Window1 { self._rootController = value if let rootController = self._rootController { - rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero { + rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + } self.hostView.view.addSubview(rootController.view) } @@ -423,7 +427,9 @@ public class Window1 { } } keyboardManager.surfaces = keyboardSurfaces - self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) + self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) + + self.hostView.updateDeferScreenEdgeGestures(self.presentationContext.combinedDeferScreenEdgeGestures()) } if !UIWindow.isDeviceRotating() { From dc9cec5a0eb008241a9d7456cd27ed2db93b2b6f Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 14 Nov 2017 20:39:42 +0300 Subject: [PATCH 045/245] no message --- Display.xcodeproj/project.pbxproj | 12 + Display/ActionSheetButtonItem.swift | 22 +- Display/ActionSheetCheckboxItem.swift | 40 +- Display/ActionSheetController.swift | 8 +- Display/ActionSheetControllerNode.swift | 30 +- Display/ActionSheetItem.swift | 2 +- Display/ActionSheetItemGroupNode.swift | 18 +- .../ActionSheetItemGroupsContainerNode.swift | 12 +- Display/ActionSheetItemNode.swift | 11 +- Display/ActionSheetTextItem.swift | 16 +- Display/ActionSheetTheme.swift | 33 ++ Display/CAAnimationUtils.swift | 20 +- Display/ContainableController.swift | 79 +++- Display/ContainerViewLayout.swift | 90 +++-- Display/ContextMenuNode.swift | 150 ++++++- Display/GenerateImage.swift | 3 + Display/GridNode.swift | 30 +- Display/KeyboardManager.swift | 109 ++---- Display/ListView.swift | 63 ++- Display/ListViewIntermediateState.swift | 1 + Display/NativeWindowHostView.swift | 39 +- Display/NavigationBar.swift | 20 +- Display/NavigationBarBadge.swift | 14 +- Display/NavigationController.swift | 21 +- Display/NotificationCenterUtils.m | 1 + Display/PresentationContext.swift | 2 +- Display/StatusBarManager.swift | 8 +- Display/StatusBarProxyNode.swift | 23 ++ Display/TabBarContollerNode.swift | 9 +- Display/TabBarController.swift | 46 ++- Display/TabBarNode.swift | 19 +- Display/TextAlertController.swift | 3 +- Display/UIViewController+Navigation.h | 5 + Display/UIViewController+Navigation.m | 37 ++ Display/ViewController.swift | 24 +- Display/WindowContent.swift | 366 ++++++++++++++++-- .../WindowInputAccessoryHeightProvider.swift | 6 + Display/WindowPanRecognizer.swift | 80 ++++ 38 files changed, 1159 insertions(+), 313 deletions(-) create mode 100644 Display/ActionSheetTheme.swift create mode 100644 Display/WindowInputAccessoryHeightProvider.swift create mode 100644 Display/WindowPanRecognizer.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 8b141de67b..e9eca87f96 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -93,6 +93,7 @@ D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; + D0BB4EBA1F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */; }; D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */; }; D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */; }; D0C0B59D1EE022CC000F4D2C /* NavigationBarContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */; }; @@ -113,7 +114,9 @@ D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; }; D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; + D0CB78901F9822F8004AB79B /* WindowPanRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; + D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */; }; D0CE8CE91F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */; }; D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; }; D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; }; @@ -233,6 +236,7 @@ D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; + D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowInputAccessoryHeightProvider.swift; sourceTree = ""; }; D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWindowHostView.swift; sourceTree = ""; }; D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetTextItem.swift; sourceTree = ""; }; D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarContentNode.swift; sourceTree = ""; }; @@ -253,7 +257,9 @@ D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = ""; }; D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; + D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowPanRecognizer.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; + D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetTheme.swift; sourceTree = ""; }; D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarTitleView.swift; sourceTree = ""; }; D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = ""; }; D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; @@ -315,6 +321,8 @@ D05CC2A11B69326C00E235A3 /* WindowContent.swift */, D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */, D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */, + D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */, + D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */, ); name = Window; sourceTree = ""; @@ -342,6 +350,7 @@ D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */, D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */, D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */, + D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */, ); name = "Action Sheet"; sourceTree = ""; @@ -822,6 +831,7 @@ D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, + D0BB4EBA1F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */, @@ -894,10 +904,12 @@ D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */, + D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */, D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */, + D0CB78901F9822F8004AB79B /* WindowPanRecognizer.swift in Sources */, D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */, diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index ede39695f3..ac5e3b9cbe 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -20,8 +20,8 @@ public class ActionSheetButtonItem: ActionSheetItem { self.action = action } - public func node() -> ActionSheetItemNode { - let node = ActionSheetButtonNode() + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = ActionSheetButtonNode(theme: theme) node.setItem(self) return node } @@ -37,6 +37,8 @@ public class ActionSheetButtonItem: ActionSheetItem { } public class ActionSheetButtonNode: ActionSheetItemNode { + private let theme: ActionSheetControllerTheme + public static let defaultFont: UIFont = Font.regular(20.0) private var item: ActionSheetButtonItem? @@ -44,7 +46,9 @@ public class ActionSheetButtonNode: ActionSheetItemNode { private let button: HighlightTrackingButton private let label: ASTextNode - override public init() { + override public init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.button = HighlightTrackingButton() self.label = ASTextNode() @@ -52,7 +56,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false - super.init() + super.init(theme: theme) self.view.addSubview(self.button) @@ -62,10 +66,10 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor } else { UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor }) } } @@ -80,11 +84,11 @@ public class ActionSheetButtonNode: ActionSheetItemNode { let textColor: UIColor switch item.color { case .accent: - textColor = UIColor(rgb: 0x007ee5) + textColor = self.theme.standardActionTextColor case .destructive: - textColor = .red + textColor = self.theme.destructiveActionTextColor case .disabled: - textColor = .gray + textColor = self.theme.disabledActionTextColor } self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: textColor) diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift index a946c34625..26a3cfe2d9 100644 --- a/Display/ActionSheetCheckboxItem.swift +++ b/Display/ActionSheetCheckboxItem.swift @@ -1,16 +1,6 @@ import Foundation import AsyncDisplayKit -private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in - context.clear(CGRect(origin: CGPoint(), size: size)) - context.setStrokeColor(UIColor(rgb: 0x007ee5).cgColor) - context.setLineWidth(2.0) - context.move(to: CGPoint(x: 12.0, y: 1.0)) - context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) - context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) - context.strokePath() -}) - public class ActionSheetCheckboxItem: ActionSheetItem { public let title: String public let label: String @@ -24,8 +14,8 @@ public class ActionSheetCheckboxItem: ActionSheetItem { self.action = action } - public func node() -> ActionSheetItemNode { - let node = ActionSheetCheckboxItemNode() + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = ActionSheetCheckboxItemNode(theme: theme) node.setItem(self) return node } @@ -43,6 +33,8 @@ public class ActionSheetCheckboxItem: ActionSheetItem { public class ActionSheetCheckboxItemNode: ActionSheetItemNode { public static let defaultFont: UIFont = Font.regular(20.0) + private let theme: ActionSheetControllerTheme + private var item: ActionSheetCheckboxItem? private let button: HighlightTrackingButton @@ -50,7 +42,9 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { private let labelNode: ASTextNode private let checkNode: ASImageNode - public override init() { + override public init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.button = HighlightTrackingButton() self.titleNode = ASTextNode() @@ -67,9 +61,17 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.checkNode.isUserInteractionEnabled = false self.checkNode.displayWithoutProcessing = true self.checkNode.displaysAsynchronously = false - self.checkNode.image = checkIcon + self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(theme.controlAccentColor.cgColor) + context.setLineWidth(2.0) + context.move(to: CGPoint(x: 12.0, y: 1.0)) + context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) + context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) + context.strokePath() + }) - super.init() + super.init(theme: theme) self.view.addSubview(self.button) self.addSubnode(self.titleNode) @@ -79,10 +81,10 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.button.highligthedChanged = { [weak self] highlighted in if let strongSelf = self { if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor } else { UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor }) } } @@ -94,8 +96,8 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { func setItem(_ item: ActionSheetCheckboxItem) { self.item = item - self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: .black) - self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: UIColor(rgb: 0x8e8e93)) + self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.primaryTextColor) + self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.secondaryTextColor) self.checkNode.isHidden = !item.value self.setNeedsLayout() diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 5ac67b2859..b3643e8b07 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -5,9 +5,13 @@ open class ActionSheetController: ViewController { return self.displayNode as! ActionSheetControllerNode } + private let theme: ActionSheetControllerTheme + private var groups: [ActionSheetItemGroup] = [] - public init() { + public init(theme: ActionSheetControllerTheme) { + self.theme = theme + super.init(navigationBarTheme: nil) } @@ -20,7 +24,7 @@ open class ActionSheetController: ViewController { } open override func loadDisplayNode() { - self.displayNode = ActionSheetControllerNode() + self.displayNode = ActionSheetControllerNode(theme: self.theme) self.displayNodeDidLoad() self.actionSheetNode.dismiss = { [weak self] in diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 054884fcd5..7f9fb5e152 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -1,7 +1,7 @@ import UIKit import AsyncDisplayKit -private let containerInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 8.0, right: 16.0) +private let containerInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0) private class ActionSheetControllerNodeScrollView: UIScrollView { override func touchesShouldCancel(in view: UIView) -> Bool { @@ -10,7 +10,7 @@ private class ActionSheetControllerNodeScrollView: UIScrollView { } final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { - static let dimColor: UIColor = UIColor(white: 0.0, alpha: 0.4) + private let theme: ActionSheetControllerTheme private let dismissTapView: UIView @@ -25,7 +25,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { var dismiss: () -> Void = { } - override init() { + init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.scrollView = ActionSheetControllerNodeScrollView() if #available(iOSApplicationExtension 11.0, *) { @@ -38,22 +40,22 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.dismissTapView = UIView() self.leftDimView = UIView() - self.leftDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.leftDimView.backgroundColor = self.theme.dimColor self.leftDimView.isUserInteractionEnabled = false self.rightDimView = UIView() - self.rightDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.rightDimView.backgroundColor = self.theme.dimColor self.rightDimView.isUserInteractionEnabled = false self.topDimView = UIView() - self.topDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.topDimView.backgroundColor = self.theme.dimColor self.topDimView.isUserInteractionEnabled = false self.bottomDimView = UIView() - self.bottomDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.bottomDimView.backgroundColor = self.theme.dimColor self.bottomDimView.isUserInteractionEnabled = false - self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode() + self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode(theme: self.theme) super.init() @@ -74,7 +76,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - var insets = layout.insets(options: [.statusBar]) + let insets = layout.insets(options: [.statusBar]) self.scrollView.frame = CGRect(origin: CGPoint(), size: layout.size) self.dismissTapView.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -88,7 +90,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func animateIn() { let tempDimView = UIView() - tempDimView.backgroundColor = ActionSheetControllerNode.dimColor + tempDimView.backgroundColor = self.theme.dimColor tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height) self.view.addSubview(tempDimView) @@ -105,7 +107,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func animateOut() { let tempDimView = UIView() - tempDimView.backgroundColor = ActionSheetControllerNode.dimColor + tempDimView.backgroundColor = self.theme.dimColor tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height) self.view.addSubview(tempDimView) @@ -138,7 +140,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { let contentOffset = self.scrollView.contentOffset - var additionalTopHeight = max(0.0, -contentOffset.y) + let additionalTopHeight = max(0.0, -contentOffset.y) if additionalTopHeight >= 30.0 { self.animateOut() @@ -146,8 +148,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func updateScrollDimViews(size: CGSize) { - var additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) - var additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) + let additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) + let additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) diff --git a/Display/ActionSheetItem.swift b/Display/ActionSheetItem.swift index fdac4f2386..291b3478a6 100644 --- a/Display/ActionSheetItem.swift +++ b/Display/ActionSheetItem.swift @@ -1,6 +1,6 @@ import Foundation public protocol ActionSheetItem { - func node() -> ActionSheetItemNode + func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode func updateNode(_ node: ActionSheetItemNode) -> Void } diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift index b340085195..771f541177 100644 --- a/Display/ActionSheetItemGroupNode.swift +++ b/Display/ActionSheetItemGroupNode.swift @@ -8,6 +8,8 @@ private class ActionSheetItemGroupNodeScrollView: UIScrollView { } final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { + private let theme: ActionSheetControllerTheme + private let centerDimView: UIImageView private let topDimView: UIView private let bottomDimView: UIView @@ -22,26 +24,28 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { var respectInputHeight = true - override init() { + init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.centerDimView = UIImageView() - self.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: ActionSheetControllerNode.dimColor) + self.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: self.theme.dimColor) self.topDimView = UIView() - self.topDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.topDimView.backgroundColor = self.theme.dimColor self.topDimView.isUserInteractionEnabled = false self.bottomDimView = UIView() - self.bottomDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.bottomDimView.backgroundColor = self.theme.dimColor self.bottomDimView.isUserInteractionEnabled = false self.trailingDimView = UIView() - self.trailingDimView.backgroundColor = ActionSheetControllerNode.dimColor + self.trailingDimView.backgroundColor = self.theme.dimColor self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true self.clippingNode.cornerRadius = 16.0 - self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: self.theme.backgroundType == .light ? .light : .dark)) self.scrollView = ActionSheetItemGroupNodeScrollView() if #available(iOSApplicationExtension 11.0, *) { @@ -107,7 +111,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { i += 1 } - return CGSize(width: constrainedSize.width, height: min(itemNodesHeight, constrainedSize.height)) + return CGSize(width: constrainedSize.width, height: min(floorToScreenPixels(itemNodesHeight), constrainedSize.height)) } override func layout() { diff --git a/Display/ActionSheetItemGroupsContainerNode.swift b/Display/ActionSheetItemGroupsContainerNode.swift index 21805872d0..080024b0f9 100644 --- a/Display/ActionSheetItemGroupsContainerNode.swift +++ b/Display/ActionSheetItemGroupsContainerNode.swift @@ -1,13 +1,17 @@ import UIKit import AsyncDisplayKit -private let groupSpacing: CGFloat = 16.0 +private let groupSpacing: CGFloat = 8.0 final class ActionSheetItemGroupsContainerNode: ASDisplayNode { + private let theme: ActionSheetControllerTheme + private var groups: [ActionSheetItemGroup] = [] private var groupNodes: [ActionSheetItemGroupNode] = [] - override init() { + init(theme: ActionSheetControllerTheme) { + self.theme = theme + super.init() } @@ -20,8 +24,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { self.groupNodes.removeAll() for group in groups { - let groupNode = ActionSheetItemGroupNode() - groupNode.updateItemNodes(group.items.map({ $0.node() }), leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0) + let groupNode = ActionSheetItemGroupNode(theme: self.theme) + groupNode.updateItemNodes(group.items.map({ $0.node(theme: self.theme) }), leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0) self.groupNodes.append(groupNode) self.addSubnode(groupNode) } diff --git a/Display/ActionSheetItemNode.swift b/Display/ActionSheetItemNode.swift index 22214130a3..178bfc0f05 100644 --- a/Display/ActionSheetItemNode.swift +++ b/Display/ActionSheetItemNode.swift @@ -2,18 +2,19 @@ import UIKit import AsyncDisplayKit open class ActionSheetItemNode: ASDisplayNode { - public static let defaultBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.8) - public static let highlightedBackgroundColor: UIColor = UIColor(white: 0.9, alpha: 0.7) + private let theme: ActionSheetControllerTheme public let backgroundNode: ASDisplayNode private let overflowSeparatorNode: ASDisplayNode - public override init() { + public init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.backgroundNode = ASDisplayNode() - self.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + self.backgroundNode.backgroundColor = self.theme.itemBackgroundColor self.overflowSeparatorNode = ASDisplayNode() - self.overflowSeparatorNode.backgroundColor = UIColor(white: 0.5, alpha: 0.3) + self.overflowSeparatorNode.backgroundColor = self.theme.itemHighlightedBackgroundColor super.init() diff --git a/Display/ActionSheetTextItem.swift b/Display/ActionSheetTextItem.swift index 49dfda6f6c..6155bb1072 100644 --- a/Display/ActionSheetTextItem.swift +++ b/Display/ActionSheetTextItem.swift @@ -8,8 +8,8 @@ public class ActionSheetTextItem: ActionSheetItem { self.title = title } - public func node() -> ActionSheetItemNode { - let node = ActionSheetTextNode() + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = ActionSheetTextNode(theme: theme) node.setItem(self) return node } @@ -27,18 +27,22 @@ public class ActionSheetTextItem: ActionSheetItem { public class ActionSheetTextNode: ActionSheetItemNode { public static let defaultFont: UIFont = Font.regular(13.0) + private let theme: ActionSheetControllerTheme + private var item: ActionSheetTextItem? private let label: ASTextNode - override public init() { + override public init(theme: ActionSheetControllerTheme) { + self.theme = theme + self.label = ASTextNode() self.label.isLayerBacked = true self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false self.label.truncationMode = .byTruncatingTail - super.init() + super.init(theme: theme) self.label.isUserInteractionEnabled = false self.addSubnode(self.label) @@ -47,9 +51,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { func setItem(_ item: ActionSheetTextItem) { self.item = item - let textColor = UIColor(rgb: 0x7c7c7c) - - self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: textColor) + self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor) self.setNeedsLayout() } diff --git a/Display/ActionSheetTheme.swift b/Display/ActionSheetTheme.swift new file mode 100644 index 0000000000..934485d135 --- /dev/null +++ b/Display/ActionSheetTheme.swift @@ -0,0 +1,33 @@ +import Foundation +import UIKit + +public enum ActionSheetControllerThemeBackgroundType { + case light + case dark +} + +public final class ActionSheetControllerTheme { + public let dimColor: UIColor + public let backgroundType: ActionSheetControllerThemeBackgroundType + public let itemBackgroundColor: UIColor + public let itemHighlightedBackgroundColor: UIColor + public let standardActionTextColor: UIColor + public let destructiveActionTextColor: UIColor + public let disabledActionTextColor: UIColor + public let primaryTextColor: UIColor + public let secondaryTextColor: UIColor + public let controlAccentColor: UIColor + + public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor) { + self.dimColor = dimColor + self.backgroundType = backgroundType + self.itemBackgroundColor = itemBackgroundColor + self.itemHighlightedBackgroundColor = itemHighlightedBackgroundColor + self.standardActionTextColor = standardActionTextColor + self.destructiveActionTextColor = destructiveActionTextColor + self.disabledActionTextColor = disabledActionTextColor + self.primaryTextColor = primaryTextColor + self.secondaryTextColor = secondaryTextColor + self.controlAccentColor = controlAccentColor + } +} diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 3937eace9d..de7cf0a653 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -39,7 +39,7 @@ public extension CAAnimation { } public extension CALayer { - public func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation { + public func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation { if timingFunction == kCAMediaTimingFunctionSpring { let animation = makeSpringAnimation(keyPath) animation.fromValue = from @@ -59,6 +59,11 @@ public extension CALayer { animation.speed = speed * Float(animation.duration / duration) animation.isAdditive = additive + if !delay.isZero { + animation.beginTime = CACurrentMediaTime() + delay + animation.fillMode = kCAFillModeBoth + } + return animation } else { let k = Float(UIView.animationDurationFactor()) @@ -84,12 +89,17 @@ public extension CALayer { animation.delegate = CALayerAnimationDelegate(completion: completion) } + if !delay.isZero { + animation.beginTime = CACurrentMediaTime() + delay + animation.fillMode = kCAFillModeBoth + } + return animation } } - public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) self.add(animation, forKey: additive ? nil : keyPath) } @@ -184,8 +194,8 @@ public extension CALayer { self.add(animation, forKey: key) } - public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) } public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 4f6593b787..f77ca45763 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -62,8 +62,8 @@ public extension ContainedViewLayoutTransition { } } - func updateBounds(node: ASDisplayNode, bounds: CGRect, completion: ((Bool) -> Void)? = nil) { - if node.bounds.equalTo(bounds) { + func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.bounds.equalTo(bounds) && !force { completion?(true) } else { switch self { @@ -75,7 +75,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousBounds = node.bounds node.bounds = bounds - node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in if let completion = completion { completion(result) } @@ -140,6 +140,21 @@ public extension ContainedViewLayoutTransition { } } + func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { switch self { case .immediate: @@ -171,10 +186,10 @@ public extension ContainedViewLayoutTransition { } } - func animateOffsetAdditive(layer: CALayer, offset: CGFloat) { + func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { switch self { case .immediate: - break + completion?() case let .animated(duration, curve): let timingFunction: String switch curve { @@ -183,7 +198,9 @@ public extension ContainedViewLayoutTransition { case .spring: timingFunction = kCAMediaTimingFunctionSpring } - layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in + completion?() + }) } } @@ -203,6 +220,28 @@ public extension ContainedViewLayoutTransition { } } + func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if view.frame.equalTo(frame) && !force { + completion?(true) + } else { + switch self { + case .immediate: + view.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = view.frame + view.frame = frame + view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { if layer.frame.equalTo(frame) { completion?(true) @@ -331,6 +370,34 @@ public extension ContainedViewLayoutTransition { }) } } + + func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) { + print("update to \(offset) animated: \(self.isAnimated)") + let t = layer.transform + let currentOffset = CGPoint(x: t.m41, y: t.m42) + if currentOffset == offset { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) + layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } } public extension ContainedViewLayoutTransition { diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index d1c9b3d372..4680a6c64a 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -42,23 +42,29 @@ public struct ContainerViewLayout: Equatable { public let size: CGSize public let metrics: LayoutMetrics public let intrinsicInsets: UIEdgeInsets + public let safeInsets: UIEdgeInsets public let statusBarHeight: CGFloat? public let inputHeight: CGFloat? + public let inputHeightIsInteractivellyChanging: Bool public init() { self.size = CGSize() self.metrics = LayoutMetrics() self.intrinsicInsets = UIEdgeInsets() + self.safeInsets = UIEdgeInsets() self.statusBarHeight = nil self.inputHeight = nil + self.inputHeightIsInteractivellyChanging = false } - public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?) { + public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, inputHeightIsInteractivellyChanging: Bool) { self.size = size self.metrics = metrics self.intrinsicInsets = intrinsicInsets + self.safeInsets = safeInsets self.statusBarHeight = statusBarHeight self.inputHeight = inputHeight + self.inputHeightIsInteractivellyChanging = inputHeightIsInteractivellyChanging } public func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets { @@ -73,54 +79,62 @@ public struct ContainerViewLayout: Equatable { } public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight) + return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } -} -public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { - if !lhs.size.equalTo(rhs.size) { - return false - } - - if lhs.metrics != rhs.metrics { - return false - } - - if lhs.intrinsicInsets != rhs.intrinsicInsets { - return false - } - - if let lhsStatusBarHeight = lhs.statusBarHeight { - if let rhsStatusBarHeight = rhs.statusBarHeight { - if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { - return false - } - } else { + public static func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { + if !lhs.size.equalTo(rhs.size) { return false } - } else if let _ = rhs.statusBarHeight { - return false - } - - if let lhsInputHeight = lhs.inputHeight { - if let rhsInputHeight = rhs.inputHeight { - if !lhsInputHeight.isEqual(to: rhsInputHeight) { - return false - } - } else { + + if lhs.metrics != rhs.metrics { return false } - } else if let _ = rhs.inputHeight { - return false + + if lhs.intrinsicInsets != rhs.intrinsicInsets { + return false + } + + if lhs.safeInsets != rhs.safeInsets { + return false + } + + if let lhsStatusBarHeight = lhs.statusBarHeight { + if let rhsStatusBarHeight = rhs.statusBarHeight { + if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { + return false + } + } else { + return false + } + } else if let _ = rhs.statusBarHeight { + return false + } + + if let lhsInputHeight = lhs.inputHeight { + if let rhsInputHeight = rhs.inputHeight { + if !lhsInputHeight.isEqual(to: rhsInputHeight) { + return false + } + } else { + return false + } + } else if let _ = rhs.inputHeight { + return false + } + + if lhs.inputHeightIsInteractivellyChanging != rhs.inputHeightIsInteractivellyChanging { + return false + } + + return true } - - return true } diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index 06e8df983a..21f7fadd53 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -2,11 +2,139 @@ import Foundation import UIKit import AsyncDisplayKit +private func generateShadowImage() -> UIImage? { + return generateImage(CGSize(width: 30.0, height: 1.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(white: 0.18, alpha: 1.0).cgColor) + context.setFillColor(UIColor(white: 0.18, alpha: 1.0).cgColor) + context.fill(CGRect(origin: CGPoint(x: -15.0, y: 0.0), size: CGSize(width: 30.0, height: 1.0))) + }) +} + +private final class ContextMenuContentScrollNode: ASDisplayNode { + var contentWidth: CGFloat = 0.0 + + private var initialOffset: CGFloat = 0.0 + + private let leftShadow: ASImageNode + private let rightShadow: ASImageNode + private let leftOverscrollNode: ASDisplayNode + private let rightOverscrollNode: ASDisplayNode + let contentNode: ASDisplayNode + + override init() { + self.contentNode = ASDisplayNode() + + let shadowImage = generateShadowImage() + + self.leftShadow = ASImageNode() + self.leftShadow.displayWithoutProcessing = true + self.leftShadow.displaysAsynchronously = false + self.leftShadow.image = shadowImage + self.rightShadow = ASImageNode() + self.rightShadow.displayWithoutProcessing = true + self.rightShadow.displaysAsynchronously = false + self.rightShadow.image = shadowImage + self.rightShadow.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0) + + self.leftOverscrollNode = ASDisplayNode() + self.leftOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + self.rightOverscrollNode = ASDisplayNode() + self.rightOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + + super.init() + + self.contentNode.addSubnode(self.leftOverscrollNode) + self.contentNode.addSubnode(self.rightOverscrollNode) + self.addSubnode(self.contentNode) + + self.addSubnode(self.leftShadow) + self.addSubnode(self.rightShadow) + } + + override func didLoad() { + super.didLoad() + + let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) + self.view.addGestureRecognizer(panRecognizer) + } + + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + self.initialOffset = self.contentNode.bounds.origin.x + case .changed: + var bounds = self.contentNode.bounds + bounds.origin.x = self.initialOffset - recognizer.translation(in: self.view).x + if bounds.origin.x > self.contentWidth - bounds.size.width { + let delta = bounds.origin.x - (self.contentWidth - bounds.size.width) + bounds.origin.x = self.contentWidth - bounds.size.width + ((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0) + } + if bounds.origin.x < 0.0 { + let delta = -bounds.origin.x + bounds.origin.x = -((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0) + } + self.contentNode.bounds = bounds + self.updateShadows(.immediate) + case .ended, .cancelled: + var bounds = self.contentNode.bounds + bounds.origin.x = self.initialOffset - recognizer.translation(in: self.view).x + + var duration = 0.4 + + if abs(bounds.origin.x - self.initialOffset) > 10.0 || abs(recognizer.velocity(in: self.view).x) > 100.0 { + duration = 0.2 + if bounds.origin.x < self.initialOffset { + bounds.origin.x = 0.0 + } else { + bounds.origin.x = self.contentWidth - bounds.size.width + } + } else { + bounds.origin.x = self.initialOffset + } + + if bounds.origin.x > self.contentWidth - bounds.size.width { + bounds.origin.x = self.contentWidth - bounds.size.width + } + if bounds.origin.x < 0.0 { + bounds.origin.x = 0.0 + } + let previousBounds = self.contentNode.bounds + self.contentNode.bounds = bounds + self.contentNode.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring) + self.updateShadows(.animated(duration: duration, curve: .spring)) + default: + break + } + } + + override func layout() { + let bounds = self.bounds + self.contentNode.frame = bounds + self.leftShadow.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: bounds.height)) + self.rightShadow.frame = CGRect(origin: CGPoint(x: bounds.size.width - 30.0, y: 0.0), size: CGSize(width: 30.0, height: bounds.height)) + self.leftOverscrollNode.frame = bounds.offsetBy(dx: -bounds.width, dy: 0.0) + self.rightOverscrollNode.frame = bounds.offsetBy(dx: self.contentWidth, dy: 0.0) + self.updateShadows(.immediate) + } + + private func updateShadows(_ transition: ContainedViewLayoutTransition) { + let bounds = self.contentNode.bounds + + let leftAlpha = max(0.0, min(1.0, bounds.minX / 20.0)) + transition.updateAlpha(node: self.leftShadow, alpha: leftAlpha) + + let rightAlpha = max(0.0, min(1.0, (self.contentWidth - bounds.maxX) / 20.0)) + transition.updateAlpha(node: self.rightShadow, alpha: rightAlpha) + } +} + final class ContextMenuNode: ASDisplayNode { private let actions: [ContextMenuAction] private let dismiss: () -> Void private let containerNode: ContextMenuContainerNode + private let scrollNode: ContextMenuContentScrollNode private let actionNodes: [ContextMenuActionNode] var sourceRect: CGRect? @@ -19,6 +147,7 @@ final class ContextMenuNode: ASDisplayNode { self.dismiss = dismiss self.containerNode = ContextMenuContainerNode() + self.scrollNode = ContextMenuContentScrollNode() self.actionNodes = actions.map { action in return ContextMenuActionNode(action: action) @@ -26,28 +155,33 @@ final class ContextMenuNode: ASDisplayNode { super.init() + self.containerNode.addSubnode(self.scrollNode) + self.addSubnode(self.containerNode) let dismissNode = { dismiss() } for actionNode in self.actionNodes { actionNode.dismiss = dismissNode - self.containerNode.addSubnode(actionNode) + self.scrollNode.contentNode.addSubnode(actionNode) } } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - var actionsWidth: CGFloat = 0.0 + var unboundActionsWidth: CGFloat = 0.0 let actionSeparatorWidth: CGFloat = UIScreenPixel for actionNode in self.actionNodes { - if !actionsWidth.isZero { - actionsWidth += actionSeparatorWidth + if !unboundActionsWidth.isZero { + unboundActionsWidth += actionSeparatorWidth } let actionSize = actionNode.measure(CGSize(width: layout.size.width, height: 54.0)) - actionNode.frame = CGRect(origin: CGPoint(x: actionsWidth, y: 0.0), size: actionSize) - actionsWidth += actionSize.width + actionNode.frame = CGRect(origin: CGPoint(x: unboundActionsWidth, y: 0.0), size: actionSize) + unboundActionsWidth += actionSize.width } + let maxActionsWidth = layout.size.width - 20.0 + let actionsWidth = min(unboundActionsWidth, maxActionsWidth) + let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize()) let insets = layout.insets(options: [.statusBar, .input]) @@ -67,7 +201,11 @@ final class ContextMenuNode: ASDisplayNode { self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0)) self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) + self.scrollNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: actionsWidth, height: 54.0)) + self.scrollNode.contentWidth = unboundActionsWidth + self.containerNode.layout() + self.scrollNode.layout() } func animateIn() { diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index b41b717307..833a594ac0 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -300,6 +300,9 @@ public class DrawingContext { } public func generateImage() -> UIImage? { + if self.scaledSize.width.isZero || self.scaledSize.height.isZero { + return nil + } if let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) { return UIImage(cgImage: image, scale: scale, orientation: .up) } else { diff --git a/Display/GridNode.swift b/Display/GridNode.swift index b87337fe5a..4d36d1e715 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -373,16 +373,21 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.itemLayout = self.generateItemLayout() + var updateLayoutTransition = transaction.updateLayout?.transition + let generatedScrollToItem: GridNodeScrollToItem? if let scrollToItem = transaction.scrollToItem { generatedScrollToItem = scrollToItem + if updateLayoutTransition == nil { + updateLayoutTransition = scrollToItem.transition + } } else if previousLayoutWasEmpty { generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .immediate, directionHint: .up, adjustForSection: true, adjustForTopInset: true) } else { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, itemTransition: transaction.itemTransition, completion: completion) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, completion: completion) } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { @@ -401,7 +406,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, itemTransition: .immediate, completion: { _ in }) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, completion: { _ in }) } } @@ -764,7 +769,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { return lowestHeaderNode } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) { + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) { let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate var previousItemFrames: [WrappedGridItemNode: CGRect]? @@ -806,14 +811,14 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.scrollView.scrollIndicatorInsets = presentationLayoutTransition.layout.layout.insets } var boundsOffset: CGFloat = 0.0 + var shouldAnimateBounds = false if !self.scrollView.contentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) || self.bounds.size != presentationLayoutTransition.layout.layout.size { let updatedBounds = CGRect(origin: presentationLayoutTransition.layout.contentOffset, size: presentationLayoutTransition.layout.layout.size) boundsOffset = updatedBounds.origin.y - previousBounds.origin.y self.bounds = updatedBounds - //boundsTransition.animateOffsetAdditive(layer: self.layer, offset: -boundsOffset - insetsOffset) - boundsTransition.animateBounds(layer: self.layer, from: previousBounds) + shouldAnimateBounds = true } - applyingContentOffset = false + self.applyingContentOffset = false let lowestSectionNode: ASDisplayNode? = self.lowestSectionNode() @@ -860,6 +865,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = presentationLayoutTransition.transition { let contentOffset = presentationLayoutTransition.layout.contentOffset + boundsOffset = 0.0 + shouldAnimateBounds = false + var offset: CGFloat? for (index, itemNode) in self.itemNodes { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)], existingItemIndices.contains(index) { @@ -934,7 +942,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { self.removeItemNodeWithIndex(index, removeNode: false) let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) - itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in itemNode?.removeFromSupernode() }) } else { @@ -946,7 +954,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { for itemNode in removedNodes { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) - itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak itemNode] _ in + itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in itemNode?.removeFromSupernode() }) } else { @@ -960,7 +968,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: sectionNode)] { self.removeSectionNodeWithSection(wrappedSection, removeNode: false) let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) - sectionNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak sectionNode] _ in + sectionNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak sectionNode] _ in sectionNode?.removeFromSupernode() }) } else { @@ -1062,6 +1070,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + if shouldAnimateBounds { + boundsTransition.animateBounds(layer: self.layer, from: previousBounds) + } + completion(self.displayedItemRange()) self.updateItemNodeVisibilititesAndScrolling() diff --git a/Display/KeyboardManager.swift b/Display/KeyboardManager.swift index 3ffd75f8bc..dfca12345d 100644 --- a/Display/KeyboardManager.swift +++ b/Display/KeyboardManager.swift @@ -5,43 +5,25 @@ struct KeyboardSurface { let host: UIView } -private func hasFirstResponder(_ view: UIView) -> Bool { +private func getFirstResponder(_ view: UIView) -> UIView? { if view.isFirstResponder { - return true + return view } else { for subview in view.subviews { - if hasFirstResponder(subview) { - return true + if let result = getFirstResponder(subview) { + return result } } - return false + return nil } } -private func findKeyboardBackdrop(_ view: UIView) -> UIView? { - if NSStringFromClass(type(of: view)) == "UIKBInputBackdropView" { - return view - } - for subview in view.subviews { - if let result = findKeyboardBackdrop(subview) { - return result - } - } - return nil -} - class KeyboardManager { private let host: StatusBarHost private weak var previousPositionAnimationMirrorSource: CATracingLayer? private weak var previousFirstResponderView: UIView? - - var gestureRecognizer: MinimizeKeyboardGestureRecognizer? = nil - - var minimized: Bool = false - var minimizedUpdated: (() -> Void)? - - var updatedMinimizedBackdrop = false + private var interactiveInputOffset: CGFloat = 0.0 var surfaces: [KeyboardSurface] = [] { didSet { @@ -53,63 +35,49 @@ class KeyboardManager { self.host = host } + func updateInteractiveInputOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { + guard let keyboardView = self.host.keyboardView else { + return + } + + self.interactiveInputOffset = offset + + let previousBounds = keyboardView.bounds + let updatedBounds = CGRect(origin: CGPoint(x: 0.0, y: -offset), size: previousBounds.size) + keyboardView.layer.bounds = updatedBounds + if transition.isAnimated { + transition.animateOffsetAdditive(layer: keyboardView.layer, offset: previousBounds.minY - updatedBounds.minY, completion: completion) + } else { + completion() + } + + //transition.updateSublayerTransformOffset(layer: keyboardView.layer, offset: CGPoint(x: 0.0, y: offset)) + } + private func updateSurfaces(_ previousSurfaces: [KeyboardSurface]) { guard let keyboardWindow = self.host.keyboardWindow else { return } - if let keyboardView = self.host.keyboardView { - if self.minimized { - let normalizedHeight = floor(0.85 * keyboardView.frame.size.height) - let factor = normalizedHeight / keyboardView.frame.size.height - let scaleTransform = CATransform3DMakeScale(factor, factor, 1.0) - let horizontalOffset = (keyboardView.frame.size.width - keyboardView.frame.size.width * factor) / 2.0 - let verticalOffset = (keyboardView.frame.size.height - keyboardView.frame.size.height * factor) / 2.0 - let translate = CATransform3DMakeTranslation(horizontalOffset, verticalOffset, 0.0) - keyboardView.layer.sublayerTransform = CATransform3DConcat(scaleTransform, translate) - - self.updatedMinimizedBackdrop = false - - if let backdrop = findKeyboardBackdrop(keyboardView) { - let scale = CATransform3DMakeScale(1.0 / factor, 1.0, 0.0) - let translate = CATransform3DMakeTranslation(-horizontalOffset * (1.0 / factor), 0.0, 0.0) - backdrop.layer.sublayerTransform = CATransform3DConcat(scale, translate) - } - } else { - keyboardView.layer.sublayerTransform = CATransform3DIdentity - if !self.updatedMinimizedBackdrop { - if let backdrop = findKeyboardBackdrop(keyboardView) { - backdrop.layer.sublayerTransform = CATransform3DIdentity - } - - self.updatedMinimizedBackdrop = true - } - } - } - - if let gestureRecognizer = self.gestureRecognizer { - if keyboardWindow.gestureRecognizers == nil || !keyboardWindow.gestureRecognizers!.contains(gestureRecognizer) { - keyboardWindow.addGestureRecognizer(gestureRecognizer) - } - } else { - let gestureRecognizer = MinimizeKeyboardGestureRecognizer(target: self, action: #selector(self.minimizeGesture(_:))) - self.gestureRecognizer = gestureRecognizer - keyboardWindow.addGestureRecognizer(gestureRecognizer) - } - var firstResponderView: UIView? + var firstResponderDisablesAutomaticKeyboardHandling = false for surface in self.surfaces { - if hasFirstResponder(surface.host) { + if let view = getFirstResponder(surface.host) { firstResponderView = surface.host + firstResponderDisablesAutomaticKeyboardHandling = view.disablesAutomaticKeyboardHandling break } } if let firstResponderView = firstResponderView { let containerOrigin = firstResponderView.convert(CGPoint(), to: nil) - let horizontalTranslation = CATransform3DMakeTranslation(containerOrigin.x, 0.0, 0.0) - keyboardWindow.layer.sublayerTransform = horizontalTranslation - if let tracingLayer = firstResponderView.layer as? CATracingLayer { + let horizontalTranslation = CATransform3DMakeTranslation(firstResponderDisablesAutomaticKeyboardHandling ? 0.0 : containerOrigin.x, 0.0, 0.0) + let currentTransform = keyboardWindow.layer.sublayerTransform + if !CATransform3DEqualToTransform(horizontalTranslation, currentTransform) { + //print("set to \(CGPoint(x: containerOrigin.x, y: self.interactiveInputOffset))") + keyboardWindow.layer.sublayerTransform = horizontalTranslation + } + if let tracingLayer = firstResponderView.layer as? CATracingLayer, !firstResponderDisablesAutomaticKeyboardHandling { if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer { previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) } @@ -137,11 +105,4 @@ class KeyboardManager { self.previousFirstResponderView = firstResponderView } - - @objc func minimizeGesture(_ recognizer: UISwipeGestureRecognizer) { - if case .ended = recognizer.state { - self.minimized = !self.minimized - self.minimizedUpdated?() - } - } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 464a291ca2..c978a5f14a 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -146,8 +146,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var bottomItemOverscrollBackground: ASDisplayNode? private var touchesPosition = CGPoint() - private var isTracking = false - private var isDeceleratingAfterTracking = false + public private(set) var isTracking = false + public private(set) var trackingOffset: CGFloat = 0.0 + public private(set) var beganTrackingAtTopOrigin = false + public private(set) var isDeceleratingAfterTracking = false private final var transactionQueue: ListViewTransactionQueue private final var transactionOffset: CGFloat = 0.0 @@ -447,6 +449,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.transactionOffset += -deltaY + if self.isTracking { + self.trackingOffset += -deltaY + } + self.enqueueUpdateVisibleItems() var useScrollDynamics = false @@ -612,7 +618,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var transition: ContainedViewLayoutTransition = .immediate if let updateSizeAndInsets = updateSizeAndInsets { - if updateSizeAndInsets.duration > DBL_EPSILON { + if !updateSizeAndInsets.duration.isZero { switch updateSizeAndInsets.curve { case let .Spring(duration): transition = .animated(duration: duration, curve: .spring) @@ -954,8 +960,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom) } - public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { - if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil { + public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { + if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero { completion(self.immediateDisplayedItemRange()) return } @@ -963,7 +969,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.transactionQueue.addTransaction({ [weak self] transactionCompletion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, completion: { [weak strongSelf] in + strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, completion: { [weak strongSelf] in completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil)) transactionCompletion() @@ -972,7 +978,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel }) } - private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { + private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size @@ -1249,7 +1255,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let beginReplay = { [weak self] in if let strongSelf = self { - strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { + strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { if options.contains(.PreferSynchronousDrawing) { let startTime = CACurrentMediaTime() self?.recursivelyEnsureDisplaySynchronously(true) @@ -1656,7 +1662,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func replayOperations(animated: Bool, animateAlpha: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { let timestamp = CACurrentMediaTime() if let updateOpaqueState = updateOpaqueState { @@ -1666,10 +1672,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var previousTopItemVerticalOrigin: CGFloat? var previousBottomItemMaxY: CGFloat? var snapshotView: UIView? + if animateCrossfade { + snapshotView = self.view.snapshotView(afterScreenUpdates: false) + } if animateTopItemVerticalOrigin { previousTopItemVerticalOrigin = self.topItemVerticalOrigin() previousBottomItemMaxY = self.bottomItemMaxY() - snapshotView = self.view.snapshotView(afterScreenUpdates: false) } var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] @@ -1913,6 +1921,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel break } } + } else if !additionalScrollDistance.isZero { + self.stopScrolling() + /*for itemNode in self.itemNodes { + var frame = itemNode.frame + frame.origin.y += additionalScrollDistance + itemNode.frame = frame + }*/ } self.insertNodesInBatches(nodes: [], completion: { @@ -1928,12 +1943,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.visibleSize = updateSizeAndInsets.size var offsetFix: CGFloat - if self.snapToBottomInsetUntilFirstInteraction { + if self.isTracking { + offsetFix = 0.0 + } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom } else { offsetFix = updateSizeAndInsets.insets.top - self.insets.top } + offsetFix += additionalScrollDistance + self.insets = updateSizeAndInsets.insets self.visibleSize = updateSizeAndInsets.size @@ -1962,7 +1981,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel sizeAndInsetsOffset = offsetFix completeOffset += snapToBoundsOffset - if updateSizeAndInsets.duration > DBL_EPSILON { + if !updateSizeAndInsets.duration.isZero { let animation: CABasicAnimation switch updateSizeAndInsets.curve { case let .Spring(duration): @@ -2063,6 +2082,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if !snapToBoundsOffset.isZero { self.updateVisibleContentOffset() } + + if let snapshotView = snapshotView { + snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: snapshotView.frame.size) + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } } self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) @@ -2586,7 +2613,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) self.dispatchOnVSync { - self.replayOperations(animated: false, animateAlpha: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) } } } @@ -2759,6 +2786,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } override open func touchesBegan(_ touches: Set, with event: UIEvent?) { + let offset = self.visibleContentOffset() + switch offset { + case let .known(value) where value <= 10.0: + self.beganTrackingAtTopOrigin = true + default: + self.beganTrackingAtTopOrigin = false + } + self.touchesPosition = touches.first!.location(in: self.view) self.selectionTouchLocation = touches.first!.location(in: self.view) @@ -2915,7 +2950,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel switch recognizer.state { case .began: self.isTracking = true - break + self.trackingOffset = 0.0 case .changed: self.touchesPosition = recognizer.location(in: self.view) case .ended, .cancelled: diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 47313cfed0..cf1fc977dc 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -121,6 +121,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public static let AnimateTopItemPosition = ListViewDeleteAndInsertOptions(rawValue: 32) public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64) public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128) + public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256) } public struct ListViewUpdateSizeAndInsets { diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index c502450939..dd1bd8009b 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -61,13 +61,22 @@ private final class NativeWindow: UIWindow, WindowHost { var presentController: ((ViewController, PresentationSurfaceLevel) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? + var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? + + private var frameTransition: ContainedViewLayoutTransition? override var frame: CGRect { get { return super.frame } set(value) { let sizeUpdated = super.frame.size != value.size - super.frame = value + if sizeUpdated, let transition = self.frameTransition, case let .animated(duration, curve) = transition { + let previousFrame = super.frame + super.frame = value + self.layer.animateFrame(from: previousFrame, to: value, duration: duration, timingFunction: curve.timingFunction) + } else { + super.frame = value + } if sizeUpdated { self.updateSize?(value.size) @@ -97,7 +106,11 @@ private final class NativeWindow: UIWindow, WindowHost { override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { self.updateIsUpdatingOrientationLayout?(true) + if !arg2.isZero { + self.frameTransition = .animated(duration: arg2, curve: .easeInOut) + } super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) + self.frameTransition = nil self.updateIsUpdatingOrientationLayout?(false) self.updateToInterfaceOrientation?() @@ -114,6 +127,26 @@ private final class NativeWindow: UIWindow, WindowHost { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return self.hitTestImpl?(point, event) } + + override func insertSubview(_ view: UIView, at index: Int) { + super.insertSubview(view, at: index) + } + + override func addSubview(_ view: UIView) { + super.addSubview(view) + } + + override func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView) { + if let transitionClass = NSClassFromString("UITransitionView"), view.isKind(of: transitionClass) { + super.insertSubview(view, aboveSubview: self.subviews.last!) + } else { + super.insertSubview(view, aboveSubview: siblingSubview) + } + } + + func invalidateDeferScreenEdgeGestures() { + self.invalidateDeferScreenEdgeGestureImpl?() + } } public func nativeWindowHostView() -> WindowHostView { @@ -161,6 +194,10 @@ public func nativeWindowHostView() -> WindowHostView { return hostView?.hitTest?(point, event) } + window.invalidateDeferScreenEdgeGestureImpl = { [weak hostView] in + return hostView?.invalidateDeferScreenEdgeGesture?() + } + rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let strongSelf = hostView { strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 9e6818d00d..6249b98b6c 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -9,9 +9,9 @@ public final class NavigationBarTheme { context.clear(CGRect(origin: CGPoint(), size: size)) context.setFillColor(color.cgColor) - context.translateBy(x: 0.0, y: 2.0) + context.translateBy(x: 0.0, y: -UIScreenPixel) - let _ = try? drawSvgPath(context, path: "M8.16012402,0.373030797 L0.635333572,7.39652821 L0.635333572,7.39652821 C-0.172148528,8.15021677 -0.215756811,9.41579564 0.537931744,10.2232777 C0.56927099,10.2568538 0.601757528,10.2893403 0.635333572,10.3206796 L8.16012402,17.344177 L8.16012402,17.344177 C8.69299787,17.8415514 9.51995274,17.8415514 10.0528266,17.344177 L10.0528266,17.344177 L10.0528266,17.344177 C10.5406633,16.8888394 10.567009,16.1242457 10.1116715,15.636409 C10.092738,15.6161242 10.0731114,15.5964976 10.0528266,15.5775641 L2.85430928,8.85860389 L10.0528266,2.13964366 L10.0528266,2.13964366 C10.5406633,1.68430612 10.567009,0.919712345 10.1116715,0.431875673 C10.092738,0.411590857 10.0731114,0.391964261 10.0528266,0.373030797 L10.0528266,0.373030797 L10.0528266,0.373030797 C9.51995274,-0.124343599 8.69299787,-0.124343599 8.16012402,0.373030797 Z ") + let _ = try? drawSvgPath(context, path: "M3.60751322,11.5 L11.5468531,3.56066017 C12.1326395,2.97487373 12.1326395,2.02512627 11.5468531,1.43933983 C10.9610666,0.853553391 10.0113191,0.853553391 9.42553271,1.43933983 L0.449102936,10.4157696 C-0.149700979,11.0145735 -0.149700979,11.9854265 0.449102936,12.5842304 L9.42553271,21.5606602 C10.0113191,22.1464466 10.9610666,22.1464466 11.5468531,21.5606602 C12.1326395,20.9748737 12.1326395,20.0251263 11.5468531,19.4393398 L3.60751322,11.5 Z ") }) } @@ -19,16 +19,22 @@ public final class NavigationBarTheme { public let primaryTextColor: UIColor public let backgroundColor: UIColor public let separatorColor: UIColor + public let badgeBackgroundColor: UIColor + public let badgeStrokeColor: UIColor + public let badgeTextColor: UIColor - public init(buttonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor) { + public init(buttonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor) { self.buttonColor = buttonColor self.primaryTextColor = primaryTextColor self.backgroundColor = backgroundColor self.separatorColor = separatorColor + self.badgeBackgroundColor = badgeBackgroundColor + self.badgeStrokeColor = badgeStrokeColor + self.badgeTextColor = badgeTextColor } public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { - return NavigationBarTheme(buttonColor: self.buttonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color) + return NavigationBarTheme(buttonColor: self.buttonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) } } @@ -509,7 +515,7 @@ open class NavigationBar: ASDisplayNode { self.titleNode = ASTextNode() self.backButtonNode = NavigationButtonNode() - self.badgeNode = NavigationBarBadgeNode(fillColor: .red, textColor: .white) + self.badgeNode = NavigationBarBadgeNode(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor) self.badgeNode.isUserInteractionEnabled = false self.badgeNode.isHidden = true self.backButtonArrow = ASImageNode() @@ -581,6 +587,8 @@ open class NavigationBar: ASDisplayNode { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) } self.stripeNode.backgroundColor = self.theme.separatorColor + + self.badgeNode.updateTheme(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor) } } @@ -810,7 +818,7 @@ open class NavigationBar: ASDisplayNode { private func makeTransitionBadgeNode() -> ASDisplayNode? { if self.badgeNode.supernode != nil && !self.badgeNode.isHidden { - let node = NavigationBarBadgeNode(fillColor: .red, textColor: .white) + let node = NavigationBarBadgeNode(fillColor: self.theme.badgeBackgroundColor, strokeColor: self.theme.badgeStrokeColor, textColor: self.theme.badgeTextColor) node.text = self.badgeNode.text let nodeSize = node.measure(CGSize(width: 200.0, height: 100.0)) node.frame = CGRect(origin: CGPoint(), size: nodeSize) diff --git a/Display/NavigationBarBadge.swift b/Display/NavigationBarBadge.swift index f5864262ca..e52e565149 100644 --- a/Display/NavigationBarBadge.swift +++ b/Display/NavigationBarBadge.swift @@ -3,6 +3,7 @@ import AsyncDisplayKit final class NavigationBarBadgeNode: ASDisplayNode { private var fillColor: UIColor + private var strokeColor: UIColor private var textColor: UIColor private let textNode: ASTextNode2 @@ -17,8 +18,9 @@ final class NavigationBarBadgeNode: ASDisplayNode { } } - init(fillColor: UIColor, textColor: UIColor) { + init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { self.fillColor = fillColor + self.strokeColor = strokeColor self.textColor = textColor self.textNode = ASTextNode2() @@ -29,7 +31,7 @@ final class NavigationBarBadgeNode: ASDisplayNode { self.backgroundNode.isLayerBacked = true self.backgroundNode.displayWithoutProcessing = true self.backgroundNode.displaysAsynchronously = false - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) super.init() @@ -37,6 +39,14 @@ final class NavigationBarBadgeNode: ASDisplayNode { self.addSubnode(self.textNode) } + func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) { + self.fillColor = fillColor + self.strokeColor = strokeColor + self.textColor = textColor + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0) + self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor) + } + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { let badgeSize = self.textNode.measure(constrainedSize) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 5d4d6fab2b..e0fedbdb29 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -75,7 +75,7 @@ open class NavigationController: UINavigationController, ContainableController, self.containerLayout = layout self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) - let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight) + let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) if let topViewController = self.topViewController { if let topViewController = topViewController as? ContainableController { @@ -220,13 +220,16 @@ open class NavigationController: UINavigationController, ContainableController, } public func pushViewController(_ controller: ViewController) { - self.view.endEditing(true) - let appliedLayout = self.containerLayout.withUpdatedInputHeight(nil) + if !controller.hasActiveInput { + self.view.endEditing(true) + } + let appliedLayout = self.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? self.containerLayout.inputHeight : nil) controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in + self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { - if strongSelf.containerLayout.withUpdatedInputHeight(nil) != appliedLayout { - controller.containerLayoutUpdated(strongSelf.containerLayout.withUpdatedInputHeight(nil), transition: .immediate) + let containerLayout = strongSelf.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? strongSelf.containerLayout.inputHeight : nil) + if containerLayout != appliedLayout { + controller.containerLayoutUpdated(containerLayout, transition: .immediate) } strongSelf.pushViewController(controller, animated: true) } @@ -320,7 +323,11 @@ open class NavigationController: UINavigationController, ContainableController, if let controller = topViewController as? ContainableController { var layoutToApply = self.containerLayout - if !self.viewControllers.contains(where: { $0 === controller }) { + var hasActiveInput = false + if let controller = controller as? ViewController { + hasActiveInput = controller.hasActiveInput + } + if !hasActiveInput { layoutToApply = layoutToApply.withUpdatedInputHeight(nil) } controller.containerLayoutUpdated(layoutToApply, transition: .immediate) diff --git a/Display/NotificationCenterUtils.m b/Display/NotificationCenterUtils.m index d1b1aa9bff..a99f1b4b15 100644 --- a/Display/NotificationCenterUtils.m +++ b/Display/NotificationCenterUtils.m @@ -29,6 +29,7 @@ static NSMutableArray *notificationHandlers() { } } + //printf("***** %s\n", [aName cStringUsingEncoding:NSUTF8StringEncoding]); [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index fd2cb9e088..bf91c03433 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -37,7 +37,7 @@ final class PresentationContext { return self.view != nil && self.layout != nil } - private var controllers: [ViewController] = [] + private(set) var controllers: [ViewController] = [] private var presentationDisposables = DisposableSet() diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 71e6d8f376..6f12a61d1e 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -79,13 +79,13 @@ class StatusBarManager { self.host = host } - func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, animated: Bool) { + func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let previousSurfaces = self.surfaces self.surfaces = surfaces - self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, animated: animated) + self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated) } - private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, animated: Bool) { + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let statusBarFrame = self.host.statusBarFrame guard let statusBarView = self.host.statusBarView else { return @@ -215,7 +215,7 @@ class StatusBarManager { statusBar.updateState(statusBar: statusBarView, inCallText: forceInCallStatusBarText, animated: animated) } - if let globalStatusBar = globalStatusBar { + if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { let statusBarStyle: UIStatusBarStyle if forceInCallStatusBarText != nil { statusBarStyle = .lightContent diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 83808959d6..2786088fe0 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum StatusBarStyle { @@ -6,6 +7,28 @@ public enum StatusBarStyle { case White case Ignore case Hide + + public init(systemStyle: UIStatusBarStyle) { + switch systemStyle { + case .default: + self = .Black + case .lightContent: + self = .White + case .blackOpaque: + self = .Black + } + } + + public var systemStyle: UIStatusBarStyle { + switch self { + case .Black: + return .default + case .White: + return .lightContent + default: + return .default + } + } } private enum StatusBarItemType { diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index baf4d120db..0b7bed48e5 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -36,14 +36,17 @@ final class TabBarControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { - self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets(options: []).bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0)) - self.tabBarNode.layout() + let tabBarHeight = 49.0 + layout.insets(options: []).bottom + self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) + if self.tabBarNode.isNodeLoaded { + self.tabBarNode.layout() + } } switch transition { case .immediate: update() - case let .animated(duration, curve): + case .animated: update() } } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 4434010aa0..daf9c043ee 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -10,15 +10,17 @@ public final class TabBarControllerTheme { public let tabBarTextColor: UIColor public let tabBarSelectedTextColor: UIColor public let tabBarBadgeBackgroundColor: UIColor + public let tabBarBadgeStrokeColor: UIColor public let tabBarBadgeTextColor: UIColor - public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeTextColor: UIColor) { + public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor) { self.backgroundColor = backgroundColor self.tabBarBackgroundColor = tabBarBackgroundColor self.tabBarSeparatorColor = tabBarSeparatorColor self.tabBarTextColor = tabBarTextColor self.tabBarSelectedTextColor = tabBarSelectedTextColor self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor + self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor self.tabBarBadgeTextColor = tabBarBadgeTextColor } } @@ -32,20 +34,16 @@ open class TabBarController: ViewController { } } - public var controllers: [ViewController] = [] { - didSet { - self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem }) - - if oldValue.count == 0 && self.controllers.count != 0 { - self.updateSelectedIndex() - } - } - } + private var controllers: [ViewController] = [] - private var _selectedIndex: Int = 2 + private var _selectedIndex: Int? public var selectedIndex: Int { get { - return _selectedIndex + if let _selectedIndex = self._selectedIndex { + return _selectedIndex + } else { + return 0 + } } set(value) { let index = max(0, min(self.controllers.count - 1, value)) if _selectedIndex != index { @@ -122,8 +120,8 @@ open class TabBarController: ViewController { self.currentController = nil } - if self._selectedIndex < self.controllers.count { - self.currentController = self.controllers[self._selectedIndex] + if let _selectedIndex = self._selectedIndex, _selectedIndex < self.controllers.count { + self.currentController = self.controllers[_selectedIndex] } var displayNavigationBar = false @@ -150,6 +148,8 @@ open class TabBarController: ViewController { if self.displayNavigationBar != displayNavigationBar { self.setDisplayNavigationBar(displayNavigationBar) } + + self.tabBarControllerNode.containerLayoutUpdated(self.containerLayout, transition: .immediate) } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { @@ -177,4 +177,22 @@ open class TabBarController: ViewController { currentController.viewDidDisappear(animated) } } + + public func setControllers(_ controllers: [ViewController], selectedIndex: Int?) { + var updatedSelectedIndex: Int? = selectedIndex + if updatedSelectedIndex == nil, let selectedIndex = self._selectedIndex, selectedIndex < self.controllers.count { + if let index = controllers.index(where: { $0 === self.controllers[selectedIndex] }) { + updatedSelectedIndex = index + } else { + updatedSelectedIndex = 0 + } + } + self.controllers = controllers + self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem }) + + if let updatedSelectedIndex = updatedSelectedIndex { + self.selectedIndex = updatedSelectedIndex + self.updateSelectedIndex() + } + } } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index fbfc1169bb..087e614ce7 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor) -> UIImage? { - let font = Font.regular(10.0) + let font = Font.medium(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size let imageSize: CGSize @@ -22,7 +22,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: context.fill(CGRect(origin: CGPoint(), size: size)) if let image = image { - let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize) + let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize) context.saveGState() context.translateBy(x: imageRect.midX, y: imageRect.midY) context.scaleBy(x: 1.0, y: -1.0) @@ -34,7 +34,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: } } - (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -150,7 +150,7 @@ class TabBarNode: ASDisplayNode { self.separatorNode.isOpaque = true self.separatorNode.isLayerBacked = true - self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, backgroundColor: nil)! + self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)! super.init() @@ -167,7 +167,12 @@ class TabBarNode: ASDisplayNode { self.separatorNode.backgroundColor = theme.tabBarSeparatorColor self.backgroundColor = theme.tabBarBackgroundColor - self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, backgroundColor: nil)! + self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)! + for container in self.tabBarNodeContainers { + if let attributedText = container.badgeTextNode.attributedText, !attributedText.string.isEmpty { + container.badgeTextNode.attributedText = NSAttributedString(string: attributedText.string, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor) + } + } for i in 0 ..< self.tabBarItems.count { self.updateNodeImage(i) @@ -275,7 +280,7 @@ class TabBarNode: ASDisplayNode { if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue if let badgeValue = container.badgeValue, !badgeValue.isEmpty { - container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: .white) + container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor) container.badgeBackgroundNode.isHidden = false container.badgeTextNode.isHidden = false } else { @@ -287,7 +292,7 @@ class TabBarNode: ASDisplayNode { if !container.badgeBackgroundNode.isHidden { let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) - let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.calculatedSize.width / 2.0) - 3.0 + node.calculatedSize.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) + let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) container.badgeBackgroundNode.frame = backgroundFrame container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: 3.0), size: badgeSize) } diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 314b14833e..8bf98938ca 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -33,7 +33,8 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { super.init() - self.setTitle(action.title, with: action.type == .defaultAction ? Font.medium(17.0) : Font.regular(17.0), with: UIColor(rgb: 0x007ee5), for: []) + self.titleNode.maximumNumberOfLines = 2 + self.setAttributedTitle(NSAttributedString(string: action.title, font: Font.regular(17.0), textColor: UIColor(rgb: 0x007ee5), paragraphAlignment: .center), for: []) self.highligthedChanged = { [weak self] value in if let strongSelf = self { diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 1cbc548127..62e2c3d9b9 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -7,12 +7,17 @@ - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; - (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; - (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:( UIViewController * _Nullable )rootController; +- (void)state_setNeedsStatusBarAppearanceUpdate:(void (^_Nullable)())block; @end @interface UIView (Navigation) @property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer; +@property (nonatomic) bool disablesAutomaticKeyboardHandling; + +- (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block; +- (CGFloat)input_getInputAccessoryHeight; @end diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index dade219ffc..77c3643a11 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -35,6 +35,9 @@ static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNa static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey; static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey; static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey; +static const void *disablesAutomaticKeyboardHandlingKey = &disablesAutomaticKeyboardHandlingKey; +static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey; +static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey; static bool notyfyingShiftState = false; @@ -96,6 +99,7 @@ static bool notyfyingShiftState = false; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(navigationController) newSelector:@selector(_65087dc8_navigationController)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)]; //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)]; //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)]; @@ -189,6 +193,19 @@ static bool notyfyingShiftState = false; [self _65087dc8_presentViewController:viewControllerToPresent animated:flag completion:completion]; } +- (void)_65087dc8_setNeedsStatusBarAppearanceUpdate { + [self _65087dc8_setNeedsStatusBarAppearanceUpdate]; + + void (^block)() = [self associatedObjectForKey:setNeedsStatusBarAppearanceUpdateKey]; + if (block) { + block(); + } +} + +- (void)state_setNeedsStatusBarAppearanceUpdate:(void (^_Nullable)())block { + [self setAssociatedObject:[block copy] forKey:setNeedsStatusBarAppearanceUpdateKey]; +} + @end @implementation UIView (Navigation) @@ -201,6 +218,26 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey]; } +- (bool)disablesAutomaticKeyboardHandling { + return [[self associatedObjectForKey:disablesAutomaticKeyboardHandlingKey] boolValue]; +} + +- (void)setDisablesAutomaticKeyboardHandling:(bool)disablesAutomaticKeyboardHandling { + [self setAssociatedObject:@(disablesAutomaticKeyboardHandling) forKey:disablesAutomaticKeyboardHandlingKey]; +} + +- (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block { + [self setAssociatedObject:[block copy] forKey:inputAccessoryHeightProviderKey]; +} + +- (CGFloat)input_getInputAccessoryHeight { + CGFloat (^block)() = [self associatedObjectForKey:inputAccessoryHeightProviderKey]; + if (block) { + return block(); + } + return 0.0f; +} + @end static NSString *TGEncodeText(NSString *string, int key) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index d5205cd68f..f4f88ae472 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -38,7 +38,14 @@ open class ViewControllerPresentationArguments { return self.supportedOrientations } - public final var deferScreenEdgeGestures: UIRectEdge = [] + public final var deferScreenEdgeGestures: UIRectEdge = [] { + didSet { + if self.deferScreenEdgeGestures != oldValue { + self.window?.invalidateDeferScreenEdgeGestures() + } + } + } + override open func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { return .bottom } @@ -76,6 +83,8 @@ open class ViewControllerPresentationArguments { private weak var activeInputViewCandidate: UIResponder? private weak var activeInputView: UIResponder? + open var hasActiveInput: Bool = false + private var navigationBarOrigin: CGFloat = 0.0 public var navigationOffset: CGFloat = 0.0 { @@ -160,15 +169,22 @@ open class ViewControllerPresentationArguments { if !self.isViewLoaded { self.loadView() } - self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) + transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) if let _ = layout.statusBarHeight { self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) } let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 - var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0)) + let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + 44.0 + let navigationBarOffset: CGFloat + if statusBarHeight.isZero { + navigationBarOffset = -20.0 + } else { + navigationBarOffset = 0.0 + } + var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight)) if layout.statusBarHeight == nil { - navigationBarFrame.size.height = 44.0 + navigationBarFrame.size.height = 64.0 } if !self.displayNavigationBar { diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index a515a931d7..8525275dc0 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -14,12 +14,14 @@ private class WindowRootViewController: UIViewController { } private struct WindowLayout: Equatable { - public let size: CGSize - public let metrics: LayoutMetrics - public let statusBarHeight: CGFloat? - public let forceInCallStatusBarText: String? - public let inputHeight: CGFloat? - public let inputMinimized: Bool + let size: CGSize + let metrics: LayoutMetrics + let statusBarHeight: CGFloat? + let forceInCallStatusBarText: String? + let inputHeight: CGFloat? + let safeInsets: UIEdgeInsets + let onScreenNavigationHeight: CGFloat? + let upperKeyboardInputPositionBound: CGFloat? static func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { if !lhs.size.equalTo(rhs.size) { @@ -54,7 +56,15 @@ private struct WindowLayout: Equatable { return false } - if lhs.inputMinimized != rhs.inputMinimized { + if lhs.safeInsets != rhs.safeInsets { + return false + } + + if lhs.onScreenNavigationHeight != rhs.onScreenNavigationHeight { + return false + } + + if lhs.upperKeyboardInputPositionBound != rhs.upperKeyboardInputPositionBound { return false } @@ -81,44 +91,58 @@ private struct UpdatingLayout { mutating func update(size: CGSize, metrics: LayoutMetrics, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) } mutating func update(forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) } mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) } mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, inputMinimized: self.layout.inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) } - mutating func update(inputMinimized: Bool, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + mutating func update(safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: inputMinimized) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + } + + mutating func update(onScreenNavigationHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + } + + mutating func update(upperKeyboardInputPositionBound: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + self.update(transition: transition, override: overrideTransition) + + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: upperKeyboardInputPositionBound) } } private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3 private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone -private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout { - var inputHeight: CGFloat? = layout.inputHeight - if let inputHeightValue = inputHeight, layout.inputMinimized { - inputHeight = floor(0.85 * inputHeightValue) +private func inputHeightOffsetForLayout(_ layout: WindowLayout) -> CGFloat { + if let inputHeight = layout.inputHeight, let upperBound = layout.upperKeyboardInputPositionBound { + return max(0.0, upperBound - (layout.size.height - inputHeight)) } - + return 0.0 +} + +private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout { let resolvedStatusBarHeight: CGFloat? if let statusBarHeight = layout.statusBarHeight { if layout.forceInCallStatusBarText != nil { @@ -130,7 +154,94 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container resolvedStatusBarHeight = nil } - return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(), statusBarHeight: resolvedStatusBarHeight, inputHeight: inputHeight) + var updatedInputHeight = layout.inputHeight + if let inputHeight = updatedInputHeight, let _ = layout.upperKeyboardInputPositionBound { + updatedInputHeight = inputHeight - inputHeightOffsetForLayout(layout) + } + + var resolvedSafeInsets = layout.safeInsets + if layout.size.height.isEqual(to: 375.0) && layout.size.width.isEqual(to: 812.0) { + resolvedSafeInsets.left = 44.0 + resolvedSafeInsets.right = 44.0 + } + + return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil) +} + +private func encodeText(_ string: String, _ key: Int) -> String { + var result = "" + for c in string.unicodeScalars { + result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!)) + } + return result +} + +private func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UIView) -> Bool { + if view.disablesInteractiveTransitionGestureRecognizer { + return true + } + if let superview = view.superview { + return doesViewTreeDisableInteractiveTransitionGestureRecognizer(superview) + } + return false +} + +private let transitionClass: AnyClass? = NSClassFromString(encodeText("VJUsbotjujpoWjfx", -1)) +private let previewingClass: AnyClass? = NSClassFromString("UIVisualEffectView") +private let previewingActionGroupClass: AnyClass? = NSClassFromString("UIInterfaceActionGroupView") +private func checkIsPreviewingView(_ view: UIView) -> Bool { + if let transitionClass = transitionClass, view.isKind(of: transitionClass) { + for subview in view.subviews { + if let previewingClass = previewingClass, subview.isKind(of: previewingClass) { + return true + } + } + } + return false +} + +private func applyThemeToPreviewingView(_ view: UIView, accentColor: UIColor, darkBlur: Bool) { + if let previewingActionGroupClass = previewingActionGroupClass, view.isKind(of: previewingActionGroupClass) { + view.tintColor = accentColor + if darkBlur { + applyThemeToPreviewingEffectView(view) + } + return + } + + for subview in view.subviews { + applyThemeToPreviewingView(subview, accentColor: accentColor, darkBlur: darkBlur) + } +} + +private func applyThemeToPreviewingEffectView(_ view: UIView) { + if let previewingClass = previewingClass, view.isKind(of: previewingClass) { + if let view = view as? UIVisualEffectView { + view.effect = UIBlurEffect(style: .dark) + } + } + + for subview in view.subviews { + applyThemeToPreviewingEffectView(subview) + } +} + +private func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeight: CGFloat? = nil) -> (UIView?, CGFloat?) { + if view.isFirstResponder { + return (view, accessoryHeight) + } else { + var updatedAccessoryHeight = accessoryHeight + if let view = view as? WindowInputAccessoryHeightProvider { + updatedAccessoryHeight = view.getWindowInputAccessoryHeight() + } + for subview in view.subviews { + let (result, resultHeight) = getFirstResponderAndAccessoryHeight(subview, updatedAccessoryHeight) + if let result = result { + return (result, resultHeight) + } + } + return (nil, nil) + } } public final class WindowHostView { @@ -147,6 +258,7 @@ public final class WindowHostView { var updateToInterfaceOrientation: (() -> Void)? var isUpdatingOrientationLayout = false var hitTest: ((CGPoint, UIEvent?) -> UIView?)? + var invalidateDeferScreenEdgeGesture: (() -> Void)? init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void) { self.view = view @@ -163,12 +275,23 @@ public struct WindowTracingTags { public protocol WindowHost { func present(_ controller: ViewController, on level: PresentationSurfaceLevel) + func invalidateDeferScreenEdgeGestures() } private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { return LayoutMetrics(widthClass: .compact, heightClass: .compact) } +private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return true + } + + func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool { + return false + } +} + public class Window1 { public let hostView: WindowHostView @@ -180,13 +303,21 @@ public class Window1 { private var windowLayout: WindowLayout private var updatingLayout: UpdatingLayout? + private var updatedContainerLayout: ContainerViewLayout? + private var upperKeyboardInputPositionBound: CGFloat? + private var cachedWindowSubviewCount: Int = 0 + private var cachedHasPreview: Bool = false private let presentationContext: PresentationContext private var tracingStatusBarsInvalidated = false + private var shouldUpdateDeferScreenEdgeGestures = false private var statusBarHidden = false + public var previewThemeAccentColor: UIColor = .blue + public var previewThemeDarkBlur: Bool = false + public private(set) var forceInCallStatusBarText: String? = nil public var inCallNavigate: (() -> Void)? { didSet { @@ -194,6 +325,10 @@ public class Window1 { } } + private let keyboardGestureRecognizerDelegate = KeyboardGestureRecognizerDelegate() + private var keyboardGestureBeginLocation: CGPoint? + private var keyboardGestureAccessoryHeight: CGFloat? + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -209,14 +344,16 @@ public class Window1 { statusBarHeight = 20.0 } - let minimized: Bool - if let keyboardManager = self.keyboardManager { - minimized = keyboardManager.minimized - } else { - minimized = false + let boundsSize = self.hostView.view.bounds.size + + var onScreenNavigationHeight: CGFloat? + if (boundsSize.width.isEqual(to: 375.0) && boundsSize.height.isEqual(to: 812.0)) || boundsSize.height.isEqual(to: 375.0) && boundsSize.width.isEqual(to: 812.0) { + onScreenNavigationHeight = 20.0 } - self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, inputMinimized: minimized) + let safeInsets = UIEdgeInsets() + + self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) self.presentationContext = PresentationContext() self.hostView.present = { [weak self] controller, level in @@ -247,12 +384,8 @@ public class Window1 { return self?.hitTest(point, with: event) } - self.keyboardManager?.minimizedUpdated = { [weak self] in - if let strongSelf = self { - strongSelf.updateLayout { current in - current.update(inputMinimized: strongSelf.keyboardManager!.minimized, transition: .immediate, overrideTransition: false) - } - } + self.hostView.invalidateDeferScreenEdgeGesture = { [weak self] in + self?.invalidateDeferScreenEdgeGestures() } self.presentationContext.view = self.hostView.view @@ -287,6 +420,22 @@ public class Window1 { strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) } } }) + + let recognizer = WindowPanRecognizer(target: self, action: #selector(self.panGesture(_:))) + recognizer.cancelsTouchesInView = false + recognizer.delaysTouchesBegan = false + recognizer.delaysTouchesEnded = false + recognizer.delegate = self.keyboardGestureRecognizerDelegate + recognizer.began = { [weak self] point in + self?.panGestureBegan(location: point) + } + recognizer.moved = { [weak self] point in + self?.panGestureMoved(location: point) + } + recognizer.ended = { [weak self] point, velocity in + self?.panGestureEnded(location: point, velocity: velocity) + } + self.hostView.view.addGestureRecognizer(recognizer) } public required init(coder aDecoder: NSCoder) { @@ -317,6 +466,11 @@ public class Window1 { self.hostView.view.setNeedsLayout() } + public func invalidateDeferScreenEdgeGestures() { + self.shouldUpdateDeferScreenEdgeGestures = true + self.hostView.view.setNeedsLayout() + } + public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for view in self.hostView.view.subviews.reversed() { if NSStringFromClass(type(of: view)) == "UITransitionView" { @@ -391,11 +545,25 @@ public class Window1 { } private func layoutSubviews() { - if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager { + var hasPreview = false + var updatedHasPreview = false + for subview in self.hostView.view.subviews { + if checkIsPreviewingView(subview) { + applyThemeToPreviewingView(subview, accentColor: self.previewThemeAccentColor, darkBlur: self.previewThemeDarkBlur) + hasPreview = true + break + } + } + if hasPreview != self.cachedHasPreview { + self.cachedHasPreview = hasPreview + updatedHasPreview = true + } + + if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager { self.tracingStatusBarsInvalidated = false if self.statusBarHidden { - statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, animated: false) + statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false) } else { var statusBarSurfaces: [StatusBarSurface] = [] for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { @@ -415,7 +583,8 @@ public class Window1 { animatedUpdate = true } } - statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, animated: animatedUpdate) + self.cachedWindowSubviewCount = self.hostView.view.window?.subviews.count ?? 0 + statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate) } var keyboardSurfaces: [KeyboardSurface] = [] @@ -429,7 +598,13 @@ public class Window1 { keyboardManager.surfaces = keyboardSurfaces self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) - self.hostView.updateDeferScreenEdgeGestures(self.presentationContext.combinedDeferScreenEdgeGestures()) + self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) + + self.shouldUpdateDeferScreenEdgeGestures = false + } else if self.shouldUpdateDeferScreenEdgeGestures { + self.shouldUpdateDeferScreenEdgeGestures = false + + self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) } if !UIWindow.isDeviceRotating() { @@ -467,10 +642,16 @@ public class Window1 { private func updateLayout(_ update: (inout UpdatingLayout) -> ()) { if self.updatingLayout == nil { - self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) + var updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) + update(&updatingLayout) + if updatingLayout.layout != self.windowLayout { + self.updatingLayout = updatingLayout + self.hostView.view.setNeedsLayout() + } + } else { + update(&self.updatingLayout!) + self.hostView.view.setNeedsLayout() } - update(&self.updatingLayout!) - self.hostView.view.setNeedsLayout() } private func commitUpdatingLayout() { @@ -494,13 +675,33 @@ public class Window1 { self.tracingStatusBarsInvalidated = true self.hostView.view.setNeedsLayout() } - self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized) + let previousInputOffset = inputHeightOffsetForLayout(self.windowLayout) + self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: updatingLayout.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound) - self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) - self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) + let childLayout = containedLayoutForWindowLayout(self.windowLayout) + let childLayoutUpdated = self.updatedContainerLayout != childLayout + self.updatedContainerLayout = childLayout - for controller in self.topLevelOverlayControllers { - controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) + if childLayoutUpdated { + self._rootController?.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) + self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) + + for controller in self.topLevelOverlayControllers { + controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) + } + } + + let updatedInputOffset = inputHeightOffsetForLayout(self.windowLayout) + if !previousInputOffset.isEqual(to: updatedInputOffset) { + let hide = updatingLayout.transition.isAnimated && updatingLayout.layout.upperKeyboardInputPositionBound == updatingLayout.layout.size.height + self.keyboardManager?.updateInteractiveInputOffset(updatedInputOffset, transition: updatingLayout.transition, completion: { [weak self] in + if let strongSelf = self, hide { + strongSelf.updateLayout { + $0.update(upperKeyboardInputPositionBound: nil, transition: .immediate, overrideTransition: false) + } + strongSelf.hostView.view.endEditing(true) + } + }) } } } @@ -513,4 +714,83 @@ public class Window1 { public func presentNative(_ controller: UIViewController) { } + + private func panGestureBegan(location: CGPoint) { + let keyboardGestureBeginLocation = location + let view = self.hostView.view + let (firstResponder, accessoryHeight) = getFirstResponderAndAccessoryHeight(view) + if let inputHeight = self.windowLayout.inputHeight, !inputHeight.isZero, keyboardGestureBeginLocation.y < self.windowLayout.size.height - inputHeight - (accessoryHeight ?? 0.0) { + var enableGesture = true + if let view = self.hostView.view.hitTest(location, with: nil) { + if doesViewTreeDisableInteractiveTransitionGestureRecognizer(view) { + enableGesture = false + } + } + if enableGesture, let _ = firstResponder { + self.keyboardGestureBeginLocation = keyboardGestureBeginLocation + self.keyboardGestureAccessoryHeight = accessoryHeight + } + } + } + + private func panGestureMoved(location: CGPoint) { + if let keyboardGestureBeginLocation = self.keyboardGestureBeginLocation { + let currentLocation = location + let deltaY = keyboardGestureBeginLocation.y - location.y + if deltaY * deltaY >= 3.0 * 3.0 || self.windowLayout.upperKeyboardInputPositionBound != nil { + self.updateLayout { + $0.update(upperKeyboardInputPositionBound: currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0), transition: .immediate, overrideTransition: false) + } + } + } + } + + private func panGestureEnded(location: CGPoint, velocity: CGPoint?) { + self.keyboardGestureBeginLocation = nil + let currentLocation = location + if let velocity = velocity, let inputHeight = self.windowLayout.inputHeight, velocity.y > 100.0 && currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight { + self.updateLayout { + $0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) + } + } else { + self.updateLayout { + $0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) + } + } + } + + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .began: + self.panGestureBegan(location: recognizer.location(in: recognizer.view)) + case .changed: + self.panGestureMoved(location: recognizer.location(in: recognizer.view)) + case .ended: + self.panGestureEnded(location: recognizer.location(in: recognizer.view), velocity: recognizer.velocity(in: recognizer.view)) + case .cancelled: + self.panGestureEnded(location: recognizer.location(in: recognizer.view), velocity: nil) + default: + break + } + } + + private func collectScreenEdgeGestures() -> UIRectEdge { + var edges = self.presentationContext.combinedDeferScreenEdgeGestures() + + for controller in self.topLevelOverlayControllers { + if let controller = controller as? ViewController { + edges = edges.union(controller.deferScreenEdgeGestures) + } + } + + return edges + } + + public func forEachViewController(_ f: (ViewController) -> Bool) { + for controller in self.presentationContext.controllers { + if !f(controller) { + break + } + } + } } diff --git a/Display/WindowInputAccessoryHeightProvider.swift b/Display/WindowInputAccessoryHeightProvider.swift new file mode 100644 index 0000000000..ef51090cbc --- /dev/null +++ b/Display/WindowInputAccessoryHeightProvider.swift @@ -0,0 +1,6 @@ +import Foundation +import UIKit + +public protocol WindowInputAccessoryHeightProvider: class { + func getWindowInputAccessoryHeight() -> CGFloat +} diff --git a/Display/WindowPanRecognizer.swift b/Display/WindowPanRecognizer.swift new file mode 100644 index 0000000000..85efc21149 --- /dev/null +++ b/Display/WindowPanRecognizer.swift @@ -0,0 +1,80 @@ +import Foundation + +final class WindowPanRecognizer: UIGestureRecognizer { + var began: ((CGPoint) -> Void)? + var moved: ((CGPoint) -> Void)? + var ended: ((CGPoint, CGPoint?) -> Void)? + + private var previousPoints: [(CGPoint, Double)] = [] + + override func reset() { + super.reset() + + self.previousPoints.removeAll() + } + + private func addPoint(_ point: CGPoint) { + self.previousPoints.append((point, CACurrentMediaTime())) + if self.previousPoints.count > 6 { + self.previousPoints.removeFirst() + } + } + + private func estimateVerticalVelocity() -> CGFloat { + let timestamp = CACurrentMediaTime() + var sum: CGFloat = 0.0 + var count = 0 + if self.previousPoints.count > 1 { + for i in 1 ..< self.previousPoints.count { + if self.previousPoints[i].1 >= timestamp - 0.1 { + sum += (self.previousPoints[i].0.y - self.previousPoints[i - 1].0.y) / CGFloat(self.previousPoints[i].1 - self.previousPoints[i - 1].1) + count += 1 + } + } + } + + if count != 0 { + return sum / CGFloat(count * 5) + } else { + return 0.0 + } + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if let touch = touches.first { + let location = touch.location(in: self.view) + self.addPoint(location) + self.began?(location) + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if let touch = touches.first { + let location = touch.location(in: self.view) + self.addPoint(location) + self.moved?(location) + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + if let touch = touches.first { + let location = touch.location(in: self.view) + self.addPoint(location) + self.ended?(location, CGPoint(x: 0.0, y: self.estimateVerticalVelocity())) + } + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + if let touch = touches.first { + self.ended?(touch.location(in: self.view), nil) + } + } +} From 147dfe39cacbd5a305dd9367888c1c115d70d4e1 Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 9 Jan 2018 13:17:48 +0400 Subject: [PATCH 046/245] no message --- Display.xcodeproj/project.pbxproj | 230 ++++++- .../xcschemes/xcschememanagement.plist | 6 +- Display/ActionSheetButtonItem.swift | 3 +- Display/ActionSheetControllerNode.swift | 20 +- Display/CAAnimationUtils.swift | 6 +- Display/ChildWindowHostView.swift | 2 +- Display/ContainableController.swift | 412 ------------- Display/ContainedViewLayoutTransition.swift | 560 ++++++++++++++++++ Display/GenerateImage.swift | 3 + Display/KeyboardManager.swift | 22 +- Display/ListView.swift | 404 ++++++++++--- Display/ListViewAccessoryItemNode.swift | 14 +- Display/ListViewAnimation.swift | 2 + Display/ListViewFloatingHeaderNode.swift | 8 + Display/ListViewIntermediateState.swift | 4 + Display/ListViewItem.swift | 8 +- Display/ListViewItemHeader.swift | 37 +- Display/ListViewItemNode.swift | 109 ++-- .../ListViewOverscrollBackgroundNode.swift | 31 + Display/ListViewScroller.swift | 3 + Display/ListViewScrollerAppkit.swift | 5 - Display/MergedLayoutEvents.swift | 9 - Display/NativeWindowHostView.swift | 48 ++ Display/NavigationBar.swift | 93 ++- Display/NavigationController.swift | 11 +- Display/StatusBar.swift | 6 +- Display/StatusBarManager.swift | 12 +- Display/StatusBarProxyNode.swift | 2 +- ...ainedControllerTransitionCoordinator.swift | 73 --- Display/TabBarContollerNode.swift | 13 +- Display/TabBarNode.swift | 149 +++-- Display/TextAlertController.swift | 21 +- Display/UIKitUtils.m | 31 +- Display/UIKitUtils.swift | 38 ++ Display/UIViewController+Navigation.h | 7 +- Display/UIViewController+Navigation.m | 10 +- Display/ViewController.swift | 13 +- Display/WindowContent.swift | 128 +++- DisplayMac/ASDisplayNode.swift | 84 +++ DisplayMac/NSValueAdditions.swift | 12 + DisplayMac/UIGestureRecognizer.swift | 47 ++ DisplayMac/UIKit.swift | 71 +++ DisplayMac/UIScrollView.swift | 24 + DisplayMac/UISlider.swift | 5 + DisplayMac/UITouch.swift | 5 + DisplayMac/UIView.swift | 49 ++ 46 files changed, 2042 insertions(+), 808 deletions(-) create mode 100644 Display/ContainedViewLayoutTransition.swift create mode 100644 Display/ListViewFloatingHeaderNode.swift create mode 100644 Display/ListViewOverscrollBackgroundNode.swift delete mode 100644 Display/ListViewScrollerAppkit.swift delete mode 100644 Display/MergedLayoutEvents.swift delete mode 100644 Display/SystemContainedControllerTransitionCoordinator.swift create mode 100644 DisplayMac/ASDisplayNode.swift create mode 100644 DisplayMac/NSValueAdditions.swift create mode 100644 DisplayMac/UIGestureRecognizer.swift create mode 100644 DisplayMac/UIKit.swift create mode 100644 DisplayMac/UIScrollView.swift create mode 100644 DisplayMac/UISlider.swift create mode 100644 DisplayMac/UITouch.swift create mode 100644 DisplayMac/UIView.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index e9eca87f96..6f0aefa93f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -10,11 +10,32 @@ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D01159B91F40E96C0039383E /* DisplayMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; - D01159C21F40EA120039383E /* ListViewScrollerAppkit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */; }; D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; - D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; + D01847611FFA703B00075256 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847601FFA703B00075256 /* UIKit.swift */; }; + D01847631FFA70FC00075256 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847621FFA70FC00075256 /* UIView.swift */; }; + D01847641FFA723600075256 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; + D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */; }; + D01847671FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */; }; + D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */; }; + D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */; }; + D018476A1FFA75EE00075256 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBD1CC4431D0044FF83 /* Spring.swift */; }; + D018476D1FFA765D00075256 /* NSValueAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018476B1FFA765D00075256 /* NSValueAdditions.swift */; }; + D018476E1FFA76DC00075256 /* ListViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */; }; + D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */; }; + D01847701FFA773100075256 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; + D01847711FFA778100075256 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; + D01847741FFA780400075256 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847721FFA780400075256 /* UIScrollView.swift */; }; + D01847751FFA78B200075256 /* ListViewScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */; }; + D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847761FFA78C100075256 /* UIGestureRecognizer.swift */; }; + D01847791FFA7A4E00075256 /* UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847781FFA7A4E00075256 /* UITouch.swift */; }; + D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */; }; + D018477C1FFA7ABF00075256 /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018477B1FFA7ABF00075256 /* UISlider.swift */; }; + D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C06C11FC254F8001561AB /* ASDisplayNode.swift */; }; + D01C06C31FC2552C001561AB /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; + D01C06C41FC25561001561AB /* ListViewItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */; }; + D01C06C51FC2558F001561AB /* SwiftSignalKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */; }; D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDD1D9049620066BF65 /* GridNode.swift */; }; D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; @@ -42,7 +63,6 @@ D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */; }; D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */; }; - D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; @@ -90,6 +110,8 @@ D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */; }; D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; }; + D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */; }; + D0AA84101FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; @@ -150,11 +172,19 @@ D01159B71F40E96B0039383E /* DisplayMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DisplayMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D01159B91F40E96C0039383E /* DisplayMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DisplayMac.h; sourceTree = ""; }; D01159BA1F40E96C0039383E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewScrollerAppkit.swift; sourceTree = ""; }; D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; - D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; + D01847601FFA703B00075256 /* UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = ""; }; + D01847621FFA70FC00075256 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; + D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainedViewLayoutTransition.swift; sourceTree = ""; }; + D018476B1FFA765D00075256 /* NSValueAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSValueAdditions.swift; sourceTree = ""; }; + D01847721FFA780400075256 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; + D01847761FFA78C100075256 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; + D01847781FFA7A4E00075256 /* UITouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouch.swift; sourceTree = ""; }; + D018477B1FFA7ABF00075256 /* UISlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISlider.swift; sourceTree = ""; }; + D01C06C11FC254F8001561AB /* ASDisplayNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASDisplayNode.swift; sourceTree = ""; }; + D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D01E2BDD1D9049620066BF65 /* GridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNode.swift; sourceTree = ""; }; D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; @@ -182,7 +212,6 @@ D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CATracingLayer.h; sourceTree = ""; }; D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CATracingLayer.m; sourceTree = ""; }; D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = ""; }; - D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MergedLayoutEvents.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; D05CC2681B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -233,6 +262,8 @@ D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetCheckboxItem.swift; sourceTree = ""; }; D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = ""; }; + D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewFloatingHeaderNode.swift; sourceTree = ""; }; + D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewOverscrollBackgroundNode.swift; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; @@ -283,6 +314,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + D01C06C51FC2558F001561AB /* SwiftSignalKitMac.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -311,6 +343,14 @@ children = ( D01159B91F40E96C0039383E /* DisplayMac.h */, D01159BA1F40E96C0039383E /* Info.plist */, + D01C06C11FC254F8001561AB /* ASDisplayNode.swift */, + D01847601FFA703B00075256 /* UIKit.swift */, + D01847621FFA70FC00075256 /* UIView.swift */, + D01847721FFA780400075256 /* UIScrollView.swift */, + D018476B1FFA765D00075256 /* NSValueAdditions.swift */, + D01847761FFA78C100075256 /* UIGestureRecognizer.swift */, + D01847781FFA7A4E00075256 /* UITouch.swift */, + D018477B1FFA7ABF00075256 /* UISlider.swift */, ); path = DisplayMac; sourceTree = ""; @@ -379,6 +419,7 @@ isa = PBXGroup; children = ( D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */, + D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */, D0E35A021DE473B900BC6096 /* HighlightableButton.swift */, D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */, D0A749941E3A9E7B00AD786E /* SwitchNode.swift */, @@ -410,13 +451,11 @@ D05BE4AC1D217F33002BD72C /* Utils */ = { isa = PBXGroup; children = ( - D05BE4AD1D217F6B002BD72C /* MergedLayoutEvents.swift */, - D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */, D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */, D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */, - D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */, D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */, D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */, + D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */, ); name = Utils; sourceTree = ""; @@ -472,6 +511,7 @@ D05CC2A31B6932D500E235A3 /* Frameworks */ = { isa = PBXGroup; children = ( + D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */, D0C2DFFB1CC528B70044FF83 /* SwiftSignalKit.framework */, D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */, D0E1D6351CBC159C00B04029 /* AVFoundation.framework */, @@ -609,9 +649,10 @@ D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */, D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */, D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */, - D01159C11F40EA120039383E /* ListViewScrollerAppkit.swift */, D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */, D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */, + D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */, + D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */, ); name = "List Node"; sourceTree = ""; @@ -816,7 +857,27 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - D01159C21F40EA120039383E /* ListViewScrollerAppkit.swift in Sources */, + D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */, + D01847611FFA703B00075256 /* UIKit.swift in Sources */, + D01847701FFA773100075256 /* ListView.swift in Sources */, + D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */, + D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */, + D01847631FFA70FC00075256 /* UIView.swift in Sources */, + D01847791FFA7A4E00075256 /* UITouch.swift in Sources */, + D01C06C41FC25561001561AB /* ListViewItemNode.swift in Sources */, + D018476D1FFA765D00075256 /* NSValueAdditions.swift in Sources */, + D01847641FFA723600075256 /* CAAnimationUtils.swift in Sources */, + D018476A1FFA75EE00075256 /* Spring.swift in Sources */, + D01C06C31FC2552C001561AB /* ListViewItemHeader.swift in Sources */, + D018477C1FFA7ABF00075256 /* UISlider.swift in Sources */, + D01847671FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, + D018476E1FFA76DC00075256 /* ListViewItem.swift in Sources */, + D01847751FFA78B200075256 /* ListViewScroller.swift in Sources */, + D01847711FFA778100075256 /* ListViewIntermediateState.swift in Sources */, + D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */, + D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */, + D01847741FFA780400075256 /* UIScrollView.swift in Sources */, + D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -885,6 +946,7 @@ D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, + D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */, @@ -898,14 +960,15 @@ D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, + D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, + D0AA84101FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift in Sources */, D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */, D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */, - D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */, @@ -919,7 +982,6 @@ D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */, - D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, @@ -1527,6 +1589,144 @@ }; name = "Release AppStore"; }; + D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */ = { + 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_VERSION = 3.0; + }; + name = "Release Hockeyapp Internal"; + }; + D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release Hockeyapp Internal"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1536,6 +1736,7 @@ D01159BC1F40E96C0039383E /* Debug Hockeyapp */, D01159BD1F40E96C0039383E /* Debug AppStore */, D01159BE1F40E96C0039383E /* Release Hockeyapp */, + D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */, D01159BF1F40E96C0039383E /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -1547,6 +1748,7 @@ D05CC2751B69316F00E235A3 /* Debug Hockeyapp */, D079FD091F06BD9C0038FADE /* Debug AppStore */, D05CC2761B69316F00E235A3 /* Release Hockeyapp */, + D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56E1CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -1558,6 +1760,7 @@ D05CC2781B69316F00E235A3 /* Debug Hockeyapp */, D079FD0A1F06BD9C0038FADE /* Debug AppStore */, D05CC2791B69316F00E235A3 /* Release Hockeyapp */, + D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56F1CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; @@ -1569,6 +1772,7 @@ D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */, D079FD0B1F06BD9C0038FADE /* Debug AppStore */, D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, + D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */, D086A5701CC0115D00F08284 /* Release AppStore */, ); defaultConfigurationIsVisible = 0; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 7a4a41da85..704fdb6c69 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,17 +7,17 @@ Display.xcscheme orderHint - 22 + 23 DisplayMac.xcscheme orderHint - 25 + 26 DisplayTests.xcscheme orderHint - 23 + 24 SuppressBuildableAutocreation diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index ac5e3b9cbe..e34e4ec1f2 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -55,6 +55,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.label.isLayerBacked = true self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false + self.label.truncationMode = .byTruncatingTail super.init(theme: theme) @@ -108,7 +109,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button.frame = CGRect(origin: CGPoint(), size: size) - let labelSize = self.label.measure(size) + let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 10.0), height: size.height)) self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) } diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 7f9fb5e152..90830cbe5a 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -25,6 +25,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { var dismiss: () -> Void = { } + private var validLayout: ContainerViewLayout? + init(theme: ActionSheetControllerTheme) { self.theme = theme @@ -76,7 +78,11 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - let insets = layout.insets(options: [.statusBar]) + var insets = layout.insets(options: [.statusBar]) + insets.left += layout.safeInsets.left + insets.right += layout.safeInsets.right + + self.validLayout = layout self.scrollView.frame = CGRect(origin: CGPoint(), size: layout.size) self.dismissTapView.frame = CGRect(origin: CGPoint(), size: layout.size) @@ -85,7 +91,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.itemGroupsContainerNode.frame = CGRect(origin: CGPoint(x: insets.left + containerInsets.left, y: layout.size.height - insets.bottom - containerInsets.bottom - self.itemGroupsContainerNode.calculatedSize.height), size: self.itemGroupsContainerNode.calculatedSize) self.itemGroupsContainerNode.layout() - self.updateScrollDimViews(size: layout.size) + self.updateScrollDimViews(size: layout.size, safeInsets: layout.safeInsets) } func animateIn() { @@ -135,7 +141,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func scrollViewDidScroll(_ scrollView: UIScrollView) { - self.updateScrollDimViews(size: self.scrollView.frame.size) + if let validLayout = self.validLayout { + self.updateScrollDimViews(size: validLayout.size, safeInsets: validLayout.safeInsets) + } } func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { @@ -147,15 +155,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateScrollDimViews(size: CGSize) { + func updateScrollDimViews(size: CGSize, safeInsets: UIEdgeInsets) { let additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) let additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) - self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) - self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) + self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left + safeInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) + self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right + safeInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) } func setGroups(_ groups: [ActionSheetItemGroup]) { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index de7cf0a653..140dc121e6 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,4 +1,8 @@ -import UIKit +#if os(macOS) + import Cocoa +#else + import UIKit +#endif @objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { var completion: ((Bool) -> Void)? diff --git a/Display/ChildWindowHostView.swift b/Display/ChildWindowHostView.swift index d2faf8c6e0..071334be6a 100644 --- a/Display/ChildWindowHostView.swift +++ b/Display/ChildWindowHostView.swift @@ -32,8 +32,8 @@ public func childWindowHostView(parent: UIView) -> WindowHostView { let hostView = WindowHostView(view: view, isRotating: { return false }, updateSupportedInterfaceOrientations: { orientations in - //rootViewController.orientations = orientations }, updateDeferScreenEdgeGestures: { edges in + }, updatePreferNavigationUIHidden: { value in }) view.updateSize = { [weak hostView] size in diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index f77ca45763..fd0aba9305 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -1,418 +1,6 @@ import UIKit import AsyncDisplayKit -public enum ContainedViewLayoutTransitionCurve { - case easeInOut - case spring -} - -public extension ContainedViewLayoutTransitionCurve { - var timingFunction: String { - switch self { - case .easeInOut: - return kCAMediaTimingFunctionEaseInEaseOut - case .spring: - return kCAMediaTimingFunctionSpring - } - } - - var viewAnimationOptions: UIViewAnimationOptions { - switch self { - case .easeInOut: - return [.curveEaseInOut] - case .spring: - return UIViewAnimationOptions(rawValue: 7 << 16) - } - } -} - -public enum ContainedViewLayoutTransition { - case immediate - case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve) - - public var isAnimated: Bool { - if case .immediate = self { - return false - } else { - return true - } - } -} - -public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { - if node.frame.equalTo(frame) && !force { - completion?(true) - } else { - switch self { - case .immediate: - node.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = node.frame - node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { - if node.bounds.equalTo(bounds) && !force { - completion?(true) - } else { - switch self { - case .immediate: - node.bounds = bounds - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousBounds = node.bounds - node.bounds = bounds - node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { - if node.position.equalTo(position) { - completion?(true) - } else { - switch self { - case .immediate: - node.position = position - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousPosition = node.position - node.position = position - node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - if node.position.equalTo(position) { - completion?(true) - } else { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { - switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) - } - } - - func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { - switch self { - case .immediate: - completion?() - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in - completion?() - }) - } - } - - func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { - switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) - } - } - - func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { - if view.frame.equalTo(frame) && !force { - completion?(true) - } else { - switch self { - case .immediate: - view.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = view.frame - view.frame = frame - view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { - if layer.frame.equalTo(frame) { - completion?(true) - } else { - switch self { - case .immediate: - layer.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = layer.frame - layer.frame = frame - layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - } - - func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { - if node.alpha.isEqual(to: alpha) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - node.alpha = alpha - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousAlpha = node.alpha - node.alpha = alpha - node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { - if layer.opacity.isEqual(to: Float(alpha)) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - layer.opacity = Float(alpha) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousAlpha = layer.opacity - layer.opacity = Float(alpha) - layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) { - if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - node.backgroundColor = color - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - if let nodeColor = node.backgroundColor { - node.backgroundColor = color - node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in - if let completion = completion { - completion(result) - } - }) - } else { - node.backgroundColor = color - if let completion = completion { - completion(true) - } - } - } - } - - func updateTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { - let t = node.layer.transform - let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) - if currentScale.isEqual(to: scale) { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) - node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in - if let completion = completion { - completion(result) - } - }) - } - } - - func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) { - print("update to \(offset) animated: \(self.isAnimated)") - let t = layer.transform - let currentOffset = CGPoint(x: t.m41, y: t.m42) - if currentOffset == offset { - if let completion = completion { - completion(true) - } - return - } - - switch self { - case .immediate: - layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) - layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { - result in - if let completion = completion { - completion(result) - } - }) - } - } -} - -public extension ContainedViewLayoutTransition { - public func animateView(_ f: @escaping () -> Void) { - switch self { - case .immediate: - f() - case let .animated(duration, curve): - UIView.animate(withDuration: duration, delay: 0.0, options: curve.viewAnimationOptions, animations: { - f() - }, completion: nil) - } - } -} - public protocol ContainableController: class { var view: UIView! { get } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift new file mode 100644 index 0000000000..0a8f51cd1c --- /dev/null +++ b/Display/ContainedViewLayoutTransition.swift @@ -0,0 +1,560 @@ +import Foundation + +#if os(macOS) + import QuartzCore +#else + import AsyncDisplayKit +#endif + +public enum ContainedViewLayoutTransitionCurve { + case easeInOut + case spring +} + +public extension ContainedViewLayoutTransitionCurve { + var timingFunction: String { + switch self { + case .easeInOut: + return kCAMediaTimingFunctionEaseInEaseOut + case .spring: + return kCAMediaTimingFunctionSpring + } + } + + #if os(iOS) + var viewAnimationOptions: UIViewAnimationOptions { + switch self { + case .easeInOut: + return [.curveEaseInOut] + case .spring: + return UIViewAnimationOptions(rawValue: 7 << 16) + } + } + #endif +} + +public enum ContainedViewLayoutTransition { + case immediate + case animated(duration: Double, curve: ContainedViewLayoutTransitionCurve) + + public var isAnimated: Bool { + if case .immediate = self { + return false + } else { + return true + } + } +} + +public extension ContainedViewLayoutTransition { + func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.frame.equalTo(frame) && !force { + completion?(true) + } else { + switch self { + case .immediate: + node.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = node.frame + node.frame = frame + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.bounds.equalTo(bounds) && !force { + completion?(true) + } else { + switch self { + case .immediate: + node.bounds = bounds + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousBounds = node.bounds + node.bounds = bounds + node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + if node.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + node.position = position + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousPosition = node.position + node.position = position + node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updatePosition(layer: CALayer, position: CGPoint, completion: ((Bool) -> Void)? = nil) { + if layer.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + layer.position = position + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousPosition = layer.position + layer.position = position + layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animatePosition(node: ASDisplayNode, to position: CGPoint, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + if node.position.equalTo(position) { + completion?(true) + } else { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + } + } + + func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { + switch self { + case .immediate: + completion?() + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in + completion?() + }) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, completion: (() -> Void)? = nil) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in + completion?() + }) + } + } + + func animatePositionAdditive(layer: CALayer, offset: CGPoint, completion: (() -> Void)? = nil) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in + completion?() + }) + } + } + + func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if view.frame.equalTo(frame) && !force { + completion?(true) + } else { + switch self { + case .immediate: + view.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = view.frame + view.frame = frame + view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { + if layer.frame.equalTo(frame) { + completion?(true) + } else { + switch self { + case .immediate: + layer.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = layer.frame + layer.frame = frame + layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + + func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + if node.alpha.isEqual(to: alpha) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.alpha = alpha + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = node.alpha + node.alpha = alpha + node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateAlpha(layer: CALayer, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + if layer.opacity.isEqual(to: Float(alpha)) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.opacity = Float(alpha) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = layer.opacity + layer.opacity = Float(alpha) + layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) { + if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.backgroundColor = color + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + if let nodeColor = node.backgroundColor { + node.backgroundColor = color + node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in + if let completion = completion { + completion(result) + } + }) + } else { + node.backgroundColor = color + if let completion = completion { + completion(true) + } + } + } + } + + func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: fromScale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateTransformScale(layer: CALayer, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = layer.transform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.transform = CATransform3DMakeScale(scale, scale, 1.0) + layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + let t = node.layer.sublayerTransform + let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + if currentScale.isEqual(to: scale) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) { + print("update to \(offset) animated: \(self.isAnimated)") + let t = layer.transform + let currentOffset = CGPoint(x: t.m41, y: t.m42) + if currentOffset == offset { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) + layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } +} + +#if os(iOS) + +public extension ContainedViewLayoutTransition { + public func animateView(_ f: @escaping () -> Void) { + switch self { + case .immediate: + f() + case let .animated(duration, curve): + UIView.animate(withDuration: duration, delay: 0.0, options: curve.viewAnimationOptions, animations: { + f() + }, completion: nil) + } + } +} + +#endif diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 833a594ac0..0115279078 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -297,6 +297,9 @@ public class DrawingContext { self.provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in free(bytes) }) + + assert(self.bytesPerRow % 16 == 0) + assert(unsafeBitCast(self.bytes, to: Int64.self) % 16 == 0) } public func generateImage() -> UIImage? { diff --git a/Display/KeyboardManager.swift b/Display/KeyboardManager.swift index dfca12345d..d77b09d346 100644 --- a/Display/KeyboardManager.swift +++ b/Display/KeyboardManager.swift @@ -35,6 +35,13 @@ class KeyboardManager { self.host = host } + func getCurrentKeyboardHeight() -> CGFloat { + guard let keyboardView = self.host.keyboardView else { + return 0.0 + } + return keyboardView.bounds.height + } + func updateInteractiveInputOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) { guard let keyboardView = self.host.keyboardView else { return @@ -60,24 +67,31 @@ class KeyboardManager { } var firstResponderView: UIView? - var firstResponderDisablesAutomaticKeyboardHandling = false + var firstResponderDisableAutomaticKeyboardHandling: UIResponderDisableAutomaticKeyboardHandling = [] for surface in self.surfaces { if let view = getFirstResponder(surface.host) { firstResponderView = surface.host - firstResponderDisablesAutomaticKeyboardHandling = view.disablesAutomaticKeyboardHandling + firstResponderDisableAutomaticKeyboardHandling = view.disableAutomaticKeyboardHandling break } } if let firstResponderView = firstResponderView { let containerOrigin = firstResponderView.convert(CGPoint(), to: nil) - let horizontalTranslation = CATransform3DMakeTranslation(firstResponderDisablesAutomaticKeyboardHandling ? 0.0 : containerOrigin.x, 0.0, 0.0) + var filteredTranslation = containerOrigin.x + if firstResponderDisableAutomaticKeyboardHandling.contains(.forward) { + filteredTranslation = max(0.0, filteredTranslation) + } + if firstResponderDisableAutomaticKeyboardHandling.contains(.backward) { + filteredTranslation = min(0.0, filteredTranslation) + } + let horizontalTranslation = CATransform3DMakeTranslation(filteredTranslation, 0.0, 0.0) let currentTransform = keyboardWindow.layer.sublayerTransform if !CATransform3DEqualToTransform(horizontalTranslation, currentTransform) { //print("set to \(CGPoint(x: containerOrigin.x, y: self.interactiveInputOffset))") keyboardWindow.layer.sublayerTransform = horizontalTranslation } - if let tracingLayer = firstResponderView.layer as? CATracingLayer, !firstResponderDisablesAutomaticKeyboardHandling { + if let tracingLayer = firstResponderView.layer as? CATracingLayer, firstResponderDisableAutomaticKeyboardHandling.isEmpty { if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer { previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil) } diff --git a/Display/ListView.swift b/Display/ListView.swift index c978a5f14a..8896f99983 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1,6 +1,10 @@ +#if os(macOS) +import SwiftSignalKitMac +#else import UIKit import AsyncDisplayKit import SwiftSignalKit +#endif private let usePerformanceTracker = false private let useDynamicTuning = false @@ -97,10 +101,30 @@ public enum ListViewVisibleContentOffset { case none } +public struct ListViewKeepTopItemOverscrollBackground { + public let color: UIColor + public let direction: Bool + + public init(color: UIColor, direction: Bool) { + self.color = color + self.direction = direction + } + + fileprivate func isEqual(to: ListViewKeepTopItemOverscrollBackground) -> Bool { + if !self.color.isEqual(to.color) { + return false + } + if self.direction != to.direction { + return false + } + return true + } +} + open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() - private final var insets = UIEdgeInsets() + public private(set) final var insets = UIEdgeInsets() private final var lastContentOffset: CGPoint = CGPoint() private final var lastContentOffsetTimestamp: CFAbsoluteTime = 0.0 private final var ignoreScrollingEvents: Bool = false @@ -124,12 +148,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var stackFromBottom: Bool = false public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 public final var limitHitTestToNodes: Bool = false - public final var keepTopItemOverscrollBackground: UIColor? { + public final var keepTopItemOverscrollBackground: ListViewKeepTopItemOverscrollBackground? { didSet { - if let color = self.keepTopItemOverscrollBackground { - self.topItemOverscrollBackground?.backgroundColor = color + if let value = self.keepTopItemOverscrollBackground { + self.topItemOverscrollBackground?.color = value.color } - self.updateTopItemOverscrollBackground() + self.updateTopItemOverscrollBackground(transition: .immediate) } } public final var keepBottomItemOverscrollBackground: UIColor? { @@ -142,7 +166,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } public final var snapToBottomInsetUntilFirstInteraction: Bool = false - private var topItemOverscrollBackground: ASDisplayNode? + public final var updateFloatingHeaderOffset: ((CGFloat, ContainedViewLayoutTransition) -> Void)? { + didSet { + + } + } + + private var topItemOverscrollBackground: ListViewOverscrollBackgroundNode? private var bottomItemOverscrollBackground: ASDisplayNode? private var touchesPosition = CGPoint() @@ -186,6 +216,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var selectionTouchLocation: CGPoint? private var selectionTouchDelayTimer: Foundation.Timer? + private var selectionLongTapDelayTimer: Foundation.Timer? private var flashNodesDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? @@ -290,18 +321,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.displayLink.invalidate() if useBackgroundDeallocation { - for itemNode in self.itemNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + assertionFailure() + /*for itemNode in self.itemNodes { + ASDeallocQueue.sharedDeallocation.releaseObject(inBackground: UnsafeMutablePointer(itemNode)) } for itemHeaderNode in self.itemHeaderNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemHeaderNode) - } + ASDeallocQueue.sharedDeallocatio.releaseObject(inBackground: itemHeaderNode) + }*/ } else { - for itemNode in self.itemNodes { - ASPerformMainThreadDeallocation(itemNode) + for i in (0 ..< self.itemNodes.count).reversed() { + var itemNode: AnyObject? = self.itemNodes[i] + self.itemNodes.remove(at: i) + ASPerformMainThreadDeallocation(&itemNode) } - for itemHeaderNode in self.itemHeaderNodes { - ASPerformMainThreadDeallocation(itemHeaderNode) + for key in self.itemHeaderNodes.keys { + var itemHeaderNode: AnyObject? = self.itemHeaderNodes[key] + self.itemHeaderNodes.removeValue(forKey: key) + ASPerformMainThreadDeallocation(&itemHeaderNode) } } @@ -443,7 +479,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let deltaY = scrollView.contentOffset.y - self.lastContentOffset.y self.lastContentOffset = scrollView.contentOffset - if self.lastContentOffsetTimestamp > DBL_EPSILON { + if !self.lastContentOffsetTimestamp.isZero { self.lastContentOffsetTimestamp = CACurrentMediaTime() } @@ -494,14 +530,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero { self.updateVisibleContentOffset() } - self.updateScroller() + self.updateScroller(transition: .immediate) - self.updateItemHeaders() + self.updateItemHeaders(leftInset: self.insets.left, rightInset: self.insets.right) for (_, headerNode) in self.itemHeaderNodes { - //let position = headerNode.position - //headerNode.position = CGPoint(x: position.x, y: position.y - deltaY) - if headerNode.wantsScrollDynamics { useScrollDynamics = true @@ -692,6 +725,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = itemNode.frame frame.origin.y += offset itemNode.frame = frame + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: self.insets.left, rightInset: self.insets.right) + } } } @@ -731,15 +767,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.ignoreScrollingEvents = wasIgnoringScrollingEvents } - private func updateTopItemOverscrollBackground() { - if let color = self.keepTopItemOverscrollBackground { - let topItemOverscrollBackground: ASDisplayNode + private func updateTopItemOverscrollBackground(transition: ContainedViewLayoutTransition) { + if let value = self.keepTopItemOverscrollBackground { + var applyTransition = transition + + let topItemOverscrollBackground: ListViewOverscrollBackgroundNode if let current = self.topItemOverscrollBackground { topItemOverscrollBackground = current } else { - topItemOverscrollBackground = ASDisplayNode() + applyTransition = .immediate + topItemOverscrollBackground = ListViewOverscrollBackgroundNode(color: value.color) topItemOverscrollBackground.isLayerBacked = true - topItemOverscrollBackground.backgroundColor = color self.topItemOverscrollBackground = topItemOverscrollBackground self.insertSubnode(topItemOverscrollBackground, at: 0) } @@ -752,25 +790,85 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel topItemFound = true } + var backgroundFrame: CGRect + if topItemFound { let realTopItemEdge = itemNodes.first!.apparentFrame.origin.y let realTopItemEdgeOffset = max(0.0, realTopItemEdge) - let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: realTopItemEdgeOffset)) - if !backgroundFrame.equalTo(topItemOverscrollBackground.frame) { - topItemOverscrollBackground.frame = backgroundFrame + backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: realTopItemEdgeOffset)) + if value.direction { + backgroundFrame.origin.y = 0.0 + backgroundFrame.size.height = realTopItemEdgeOffset + } else { + backgroundFrame.origin.y = min(self.insets.top, realTopItemEdgeOffset) + backgroundFrame.size.height = max(0.0, self.visibleSize.height - backgroundFrame.origin.y) + 400.0 } } else { - let backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: 0.0)) - if !backgroundFrame.equalTo(topItemOverscrollBackground.frame) { - topItemOverscrollBackground.frame = backgroundFrame + backgroundFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: self.visibleSize.width, height: 0.0)) + if value.direction { + backgroundFrame.origin.y = 0.0 + } else { + backgroundFrame.origin.y = 0.0 + backgroundFrame.size.height = self.visibleSize.height } } + + let previousFrame = topItemOverscrollBackground.frame + if !previousFrame.equalTo(backgroundFrame) { + topItemOverscrollBackground.frame = backgroundFrame + + let positionDelta = CGPoint(x: backgroundFrame.minX - previousFrame.minX, y: backgroundFrame.minY - previousFrame.minY) + + applyTransition.animateOffsetAdditive(node: topItemOverscrollBackground, offset: positionDelta.y) + } + + topItemOverscrollBackground.updateLayout(size: backgroundFrame.size, transition: applyTransition) } else if let topItemOverscrollBackground = self.topItemOverscrollBackground { self.topItemOverscrollBackground = nil topItemOverscrollBackground.removeFromSupernode() } } + private func updateFloatingHeaderNode(transition: ContainedViewLayoutTransition) { + guard let updateFloatingHeaderOffset = self.updateFloatingHeaderOffset else { + return + } + + var topItemFound = false + var topItemNodeIndex: Int? + if !self.itemNodes.isEmpty { + topItemNodeIndex = self.itemNodes[0].index + } + if topItemNodeIndex == 0 { + topItemFound = true + } + + var topOffset: CGFloat + + if topItemFound { + let realTopItemEdge = itemNodes.first!.apparentFrame.origin.y + let realTopItemEdgeOffset = max(0.0, realTopItemEdge) + + topOffset = realTopItemEdgeOffset + } else { + if !self.itemNodes.isEmpty { + if self.stackFromBottom { + topOffset = 0.0 + } else { + topOffset = self.visibleSize.height + } + } else { + if self.stackFromBottom { + topOffset = self.visibleSize.height + } else { + topOffset = 0.0 + } + } + } + + updateFloatingHeaderOffset(topOffset, transition) + } + private func updateBottomItemOverscrollBackground() { if let color = self.keepBottomItemOverscrollBackground { var bottomItemFound = false @@ -812,7 +910,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateScroller() { + private func updateScroller(transition: ContainedViewLayoutTransition) { if itemNodes.count == 0 { return } @@ -862,8 +960,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.updateTopItemOverscrollBackground() + self.updateTopItemOverscrollBackground(transition: transition) self.updateBottomItemOverscrollBackground() + self.updateFloatingHeaderNode(transition: transition) let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true @@ -897,7 +996,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, width: CGFloat, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -905,7 +1004,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: previousNode, width: width, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in + }, node: previousNode, params: params, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in if Thread.isMainThread { if synchronous { completion(previousNode, layout, { @@ -934,13 +1033,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } }) } else { - item.nodeConfiguredForWidth(async: { f in + item.nodeConfiguredForParams(async: { f in if synchronous { f() } else { self.async(f) } - }, width: width, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in + }, params: params, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in itemNode.index = index completion(itemNode, ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) }) @@ -1000,7 +1099,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.scroller.contentOffset = self.lastContentOffset self.ignoreScrollingEvents = wasIgnoringScrollingEvents - self.updateScroller() + self.updateScroller(transition: .immediate) completion() return @@ -1330,7 +1429,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in + }, node: referenceNode, params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in var updatedState = state var updatedOperations = operations @@ -1398,14 +1497,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if self.debugInfo { - print("insertionItemIndexAndDirection \(insertionItemIndexAndDirection)") + print("insertionItemIndexAndDirection \(String(describing: insertionItemIndexAndDirection))") } if let insertionItemIndexAndDirection = insertionItemIndexAndDirection { let index = insertionItemIndexAndDirection.0 let threadId = pthread_self() var tailRecurse = false - self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], width: state.visibleSize.width, updateAnimation: updateAnimation, completion: { (node, layout, apply) in + self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: updateAnimation, completion: { (node, layout, apply) in if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { tailRecurse = true @@ -1441,7 +1540,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { let updateItem = updateIndicesAndItems[0] if let previousNode = previousNodes[updateItem.index] { - self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in + self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations) updateIndicesAndItems.remove(at: 0) @@ -1471,7 +1570,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void), timestamp: Double) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void), timestamp: Double, listInsets: UIEdgeInsets) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -1491,6 +1590,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.insets = layout.insets node.apparentHeight = animated ? 0.0 : layout.size.height node.frame = nodeFrame + if let accessoryItemNode = node.accessoryItemNode { + node.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } apply().1() self.itemNodes.insert(node, at: nodeIndex) @@ -1505,7 +1607,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let _ = previousFrame , animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { let nextNode = self.itemNodes[nodeIndex + 1] - if nextNode.index == nil { + if nextNode.index == nil && nextNode.subnodes.isEmpty { let nextHeight = nextNode.apparentHeight if abs(nextHeight - previousApparentHeight) < CGFloat.ulpOfOne { if let animation = nextNode.animationForKey("apparentHeight") { @@ -1539,10 +1641,29 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if node.index == nil { + node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } else if animated { - if !takenAnimation { + if takenAnimation { + if let previousFrame = previousFrame { + if self.debugInfo { + assert(true) + } + + let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y + if node.rotated { + node.transitionOffset -= transitionOffsetDelta - previousApparentHeight + layout.size.height + } else { + node.transitionOffset += transitionOffsetDelta + } + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + if previousInsets != layout.insets { + node.insets = previousInsets + node.addInsetsAnimationToValue(layout.insets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + } + } else { if !nodeFrame.size.height.isEqual(to: node.apparentHeight) { node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in if let node = node { @@ -1596,6 +1717,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y -= offsetHeight self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } i -= 1 } case .Down: @@ -1604,6 +1728,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y += offsetHeight self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } i += 1 } } @@ -1665,6 +1792,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { let timestamp = CACurrentMediaTime() + let listInsets = updateSizeAndInsets?.insets ?? self.insets + if let updateOpaqueState = updateOpaqueState { self.opaqueTransactionState = updateOpaqueState } @@ -1713,7 +1842,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel updatedPreviousFrame = nil } - self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp) + self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets) if let _ = updatedPreviousFrame { if let lowestHeaderNode = lowestHeaderNode { self.insertSubnode(node, belowSubnode: lowestHeaderNode) @@ -1722,7 +1851,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } else { if animated { - self.insertSubnode(node, at: 0) + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + self.insertSubnode(node, aboveSubnode: topItemOverscrollBackground) + } else { + self.insertSubnode(node, at: 0) + } } else { if let lowestHeaderNode = lowestHeaderNode { self.insertSubnode(node, belowSubnode: lowestHeaderNode) @@ -1745,10 +1878,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) } else { referenceNode.index = nil - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) self.addSubnode(referenceNode) } } else { @@ -1772,6 +1905,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y -= height self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } } case .Down: @@ -1780,6 +1916,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = self.itemNodes[i].frame frame.origin.y += height self.itemNodes[i].frame = frame + if let accessoryItemNode = self.itemNodes[i].accessoryItemNode { + self.itemNodes[i].layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } } } @@ -1815,8 +1954,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } }) - let insetPart: CGFloat = previousInsets.top - layout.insets.top if node.rotated { + let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom node.transitionOffset += previousApparentHeight - layout.size.height - insetPart node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } @@ -1844,6 +1983,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + if let accessoryItemNode = node.accessoryItemNode { + node.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } + var index = 0 for itemNode in self.itemNodes { let offset = offsetRanges.offsetForIndex(index) @@ -1895,6 +2038,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = itemNode.frame frame.origin.y += offset itemNode.frame = frame + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } break @@ -1912,6 +2058,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var frame = itemNode.frame frame.origin.y += offset itemNode.frame = frame + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) + } } } @@ -1923,11 +2072,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } else if !additionalScrollDistance.isZero { self.stopScrolling() - /*for itemNode in self.itemNodes { - var frame = itemNode.frame - frame.origin.y += additionalScrollDistance - itemNode.frame = frame - }*/ } self.insertNodesInBatches(nodes: [], completion: { @@ -2092,9 +2236,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) + self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) - if let scrollToItem = scrollToItem , scrollToItem.animated { + if let scrollToItem = scrollToItem, scrollToItem.animated { if self.itemNodes.count != 0 { var offset: CGFloat? @@ -2146,7 +2290,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel previousItemHeaderNodes.append(headerNode) } - self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) if let offset = offset , abs(offset) > CGFloat.ulpOfOne { let lowestHeaderNode = self.lowestHeaderNode() @@ -2201,29 +2345,32 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for itemNode in temporaryPreviousNodes { itemNode.removeFromSupernode() if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) } else { - ASPerformMainThreadDeallocation(itemNode) + //ASPerformMainThreadDeallocation(itemNode) } } for headerNode in temporaryHeaderNodes { headerNode.removeFromSupernode() if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) } else { - ASPerformMainThreadDeallocation(headerNode) + //ASPerformMainThreadDeallocation(headerNode) } } } self.layer.add(animation, forKey: nil) } else { if useBackgroundDeallocation { - for itemNode in temporaryPreviousNodes { + assertionFailure() + /*for itemNode in temporaryPreviousNodes { ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) - } + }*/ } else { for itemNode in temporaryPreviousNodes { - ASPerformMainThreadDeallocation(itemNode) + //ASPerformMainThreadDeallocation(itemNode) } } } @@ -2231,39 +2378,50 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemNodesVisibilities() - self.updateScroller() + self.updateScroller(transition: headerNodesTransition.0) + + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + } + self.setNeedsAnimations() self.updateVisibleContentOffset() if self.debugInfo { - let delta = CACurrentMediaTime() - timestamp + //let delta = CACurrentMediaTime() - timestamp //print("replayOperations \(delta * 1000.0) ms") } completion() } else { - self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) self.updateItemNodesVisibilities() if animated { self.setNeedsAnimations() } - self.updateScroller() + self.updateScroller(transition: headerNodesTransition.0) + + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + } + self.updateVisibleContentOffset() if self.debugInfo { - let delta = CACurrentMediaTime() - timestamp + //let delta = CACurrentMediaTime() - timestamp //print("replayOperations \(delta * 1000.0) ms") } for (previousNode, _) in previousApparentFrames { if previousNode.supernode == nil { if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: previousNode) + assertionFailure() + //ASDeallocQueue.sharedDeallocatio.releaseObject(inBackground: previousNode) } else { - ASPerformMainThreadDeallocation(previousNode) + //ASPerformMainThreadDeallocation(previousNode) } } } @@ -2303,12 +2461,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.removeFromSupernode() node.accessoryItemNode?.removeFromSupernode() - node.accessoryItemNode = nil + node.setAccessoryItemNode(nil, leftInset: self.insets.left, rightInset: self.insets.right) node.headerAccessoryItemNode?.removeFromSupernode() node.headerAccessoryItemNode = nil } - private func updateItemHeaders(_ transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { + private func updateItemHeaders(leftInset: CGFloat, rightInset: CGFloat, transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { let upperDisplayBound = self.insets.top let lowerDisplayBound = self.visibleSize.height - self.insets.bottom var visibleHeaderNodes = Set() @@ -2354,6 +2512,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } } + + headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: true) headerNode.internalStickLocationDistance = stickLocationDistance if !hasValidNodes && !headerNode.alpha.isZero { @@ -2372,6 +2532,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let headerNode = item.node() headerNode.updateFlashingOnScrolling(flashing, animated: false) headerNode.frame = headerFrame + headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false) self.itemHeaderNodes[id] = headerNode self.addSubnode(headerNode) @@ -2432,7 +2593,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { + private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { var index = -1 let count = self.itemNodes.count for itemNode in self.itemNodes { @@ -2468,18 +2629,21 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel nextAccessoryItemNode.removeFromSupernode() itemNode.addSubnode(nextAccessoryItemNode) - itemNode.accessoryItemNode = nextAccessoryItemNode - self.itemNodes[i].accessoryItemNode = nil + + itemNode.setAccessoryItemNode(nextAccessoryItemNode, leftInset: leftInset, rightInset: rightInset) + self.itemNodes[i].setAccessoryItemNode(nil, leftInset: leftInset, rightInset: rightInset) var updatedAccessoryItemNodeOrigin = nextAccessoryItemNode.frame.origin - let updatedParentOrigin = itemNode.frame.origin + let updatedParentOrigin = itemNode.apparentFrame.origin updatedAccessoryItemNodeOrigin.x += updatedParentOrigin.x updatedAccessoryItemNodeOrigin.y += updatedParentOrigin.y updatedAccessoryItemNodeOrigin.y -= itemNode.bounds.origin.y + //updatedAccessoryItemNodeOrigin.y += itemNode.transitionOffset - let deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height - + var deltaHeight = itemNode.frame.size.height - nextItemNode.frame.size.height + //deltaHeight = 0.0 nextAccessoryItemNode.animateTransitionOffset(CGPoint(x: 0.0, y: updatedAccessoryItemNodeOrigin.y - previousAccessoryItemNodeOrigin.y - deltaHeight), beginAt: currentTimestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), curve: listViewAnimationCurveSystem) + } } else { break @@ -2491,12 +2655,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if !didStealAccessoryNode { let accessoryNode = accessoryItem.node() itemNode.addSubnode(accessoryNode) - itemNode.accessoryItemNode = accessoryNode + itemNode.setAccessoryItemNode(accessoryNode, leftInset: leftInset, rightInset: rightInset) } } } else { itemNode.accessoryItemNode?.removeFromSupernode() - itemNode.accessoryItemNode = nil + itemNode.setAccessoryItemNode(nil, leftInset: leftInset, rightInset: rightInset) } } @@ -2597,9 +2761,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if node.index == nil && node.apparentHeight <= CGFloat.ulpOfOne { self.removeItemNodeAtIndex(i) if useBackgroundDeallocation { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: node) } else { - ASPerformMainThreadDeallocation(node) + //ASPerformMainThreadDeallocation(node) } } else { i += 1 @@ -2740,6 +2905,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta) } + + if let accessoryItemNode = itemNode.accessoryItemNode { + itemNode.layoutAccessoryItemNode(accessoryItemNode, leftInset: self.insets.left, rightInset: self.insets.right) + } } if itemNode.index == nil && updatedApparentHeight <= CGFloat.ulpOfOne { @@ -2786,6 +2955,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } override open func touchesBegan(_ touches: Set, with event: UIEvent?) { + let touchesPosition = touches.first!.location(in: self.view) + + if let index = self.itemIndexAtPoint(touchesPosition) { + for i in 0 ..< self.itemNodes.count { + if self.itemNodes[i].preventsTouchesToOtherItems { + if index != self.itemNodes[i].index { + self.itemNodes[i].touchesToOtherItemsPrevented() + return + } + break + } + } + } + let offset = self.visibleContentOffset() switch offset { case let .known(value) where value <= 10.0: @@ -2794,27 +2977,57 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.beganTrackingAtTopOrigin = false } - self.touchesPosition = touches.first!.location(in: self.view) + self.touchesPosition = touchesPosition self.selectionTouchLocation = touches.first!.location(in: self.view) self.selectionTouchDelayTimer?.invalidate() + self.selectionLongTapDelayTimer?.invalidate() + self.selectionLongTapDelayTimer = nil let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in - if let strongSelf = self , strongSelf.selectionTouchLocation != nil { + if let strongSelf = self, strongSelf.selectionTouchLocation != nil { strongSelf.clearHighlightAnimated(false) if let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) { - if strongSelf.items[index].selectable { + var canBeSelectedOrLongTapped = false + for itemNode in strongSelf.itemNodes { + if itemNode.index == index && (strongSelf.items[index].selectable && itemNode.canBeSelected) || itemNode.canBeLongTapped { + canBeSelectedOrLongTapped = true + } + } + + if canBeSelectedOrLongTapped { strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { if itemNode.index == index && itemNode.canBeSelected { - if true { //!(itemNode.hitTest(CGPoint(x: strongSelf.touchesPosition.x - itemNode.frame.minX, y: strongSelf.touchesPosition.y - itemNode.frame.minY), with: event) is UIControl) { + if true { if !itemNode.isLayerBacked { strongSelf.view.bringSubview(toFront: itemNode.view) for (_, headerNode) in strongSelf.itemHeaderNodes { strongSelf.view.bringSubview(toFront: headerNode.view) } } - itemNode.setHighlighted(true, animated: false) + let itemNodeFrame = itemNode.frame + let itemNodeBounds = itemNode.bounds + if strongSelf.items[index].selectable { + itemNode.setHighlighted(true, at: strongSelf.touchesPosition.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY), animated: false) + } + + if itemNode.canBeLongTapped { + let timer = Timer(timeInterval: 0.3, target: ListViewTimerProxy { + if let strongSelf = self, strongSelf.highlightedItemIndex == index { + for itemNode in strongSelf.itemNodes { + if itemNode.index == index && itemNode.canBeLongTapped { + itemNode.longTapped() + strongSelf.clearHighlightAnimated(true) + strongSelf.selectionTouchLocation = nil + break + } + } + } + }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) + strongSelf.selectionLongTapDelayTimer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + } } break } @@ -2828,14 +3041,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel super.touchesBegan(touches, with: event) - self.updateScroller() + self.updateScroller(transition: .immediate) } public func clearHighlightAnimated(_ animated: Bool) { if let highlightedItemIndex = self.highlightedItemIndex { for itemNode in self.itemNodes { if itemNode.index == highlightedItemIndex { - itemNode.setHighlighted(false, animated: animated) + itemNode.setHighlighted(false, at: CGPoint(), animated: animated) break } } @@ -2845,7 +3058,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func itemIndexAtPoint(_ point: CGPoint) -> Int? { for itemNode in self.itemNodes { - if itemNode.apparentFrame.contains(point) { + if itemNode.apparentContentFrame.contains(point) { return itemNode.index } } @@ -2891,7 +3104,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if distance.x * distance.x + distance.y * distance.y > maxMovementDistance * maxMovementDistance { self.selectionTouchLocation = nil self.selectionTouchDelayTimer?.invalidate() + self.selectionLongTapDelayTimer?.invalidate() self.selectionTouchDelayTimer = nil + self.selectionLongTapDelayTimer = nil self.clearHighlightAnimated(false) } } @@ -2918,7 +3133,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.view.bringSubview(toFront: headerNode.view) } } - itemNode.setHighlighted(true, animated: false) + let itemNodeFrame = itemNode.frame + itemNode.setHighlighted(true, at: selectionTouchLocation.offsetBy(dx: -itemNodeFrame.minX, dy: -itemNodeFrame.minY), animated: false) } else { self.highlightedItemIndex = nil } @@ -2941,6 +3157,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.selectionTouchLocation = nil self.selectionTouchDelayTimer?.invalidate() self.selectionTouchDelayTimer = nil + self.selectionLongTapDelayTimer?.invalidate() + self.selectionLongTapDelayTimer = nil self.clearHighlightAnimated(false) super.touchesCancelled(touches, with: event) diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift index baeccbac18..1ac1354c09 100644 --- a/Display/ListViewAccessoryItemNode.swift +++ b/Display/ListViewAccessoryItemNode.swift @@ -1,5 +1,8 @@ import Foundation -import AsyncDisplayKit +#if os(macOS) +#else + import AsyncDisplayKit +#endif open class ListViewAccessoryItemNode: ASDisplayNode { var transitionOffset: CGPoint = CGPoint() { @@ -37,4 +40,13 @@ open class ListViewAccessoryItemNode: ASDisplayNode { return false } + + override open func layout() { + super.layout() + + self.updateLayout(size: self.bounds.size, leftInset: 0.0, rightInset: 0.0) + } + + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + } } diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index 0d1a84768b..cf0ea9c09e 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -91,6 +91,7 @@ public let listViewAnimationCurveLinear: (CGFloat) -> CGFloat = { t in return t } +#if os(iOS) public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewAnimationOptions) -> (CGFloat) -> CGFloat { if animationOptions.rawValue == UInt(7 << 16) { return listViewAnimationCurveSystem @@ -98,6 +99,7 @@ public func listViewAnimationCurveFromAnimationOptions(animationOptions: UIViewA return listViewAnimationCurveLinear } } +#endif public final class ListViewAnimation { let from: Interpolatable diff --git a/Display/ListViewFloatingHeaderNode.swift b/Display/ListViewFloatingHeaderNode.swift new file mode 100644 index 0000000000..ad2e7bdb5c --- /dev/null +++ b/Display/ListViewFloatingHeaderNode.swift @@ -0,0 +1,8 @@ +import Foundation +import AsyncDisplayKit + +open class ListViewFloatingHeaderNode: ASDisplayNode { + open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + return 0.0 + } +} diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index cf1fc977dc..8cf6609a95 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -1,5 +1,9 @@ import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else import SwiftSignalKit +#endif public enum ListViewCenterScrollPositionOverflow { case top diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 5b0f1a2e58..3e83961725 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -1,5 +1,9 @@ import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else import SwiftSignalKit +#endif public enum ListViewItemUpdateAnimation { case None @@ -29,8 +33,8 @@ public struct ListViewItemConfigureNodeFlags: OptionSet { } public protocol ListViewItem { - func nodeConfiguredForWidth(async: @escaping (@escaping () -> Void) -> Void, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, width: CGFloat, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index ec7e2d9419..db5a74e35f 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -1,5 +1,7 @@ import Foundation +#if !os(macOS) import AsyncDisplayKit +#endif public enum ListViewItemHeaderStickDirection { case top @@ -33,21 +35,6 @@ open class ListViewItemHeaderNode: ASDisplayNode { self.isFlashingOnScrolling = isFlashingOnScrolling self.updateFlashingOnScrolling(isFlashingOnScrolling, animated: animated) } - /*if self.isFlashing { - if self.alpha.isZero { - self.alpha = 1.0 - if animated { - self.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) - } - } - } else { - if !self.alpha.isZero { - self.alpha = 0.0 - if animated { - self.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) - } - } - }*/ } open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) { @@ -139,4 +126,24 @@ open class ListViewItemHeaderNode: ASDisplayNode { self.layer.animateAlpha(from: 1.0, to: 0.0, duration: duration, removeOnCompletion: false) self.layer.animateScale(from: 1.0, to: 0.2, duration: duration, removeOnCompletion: false) } + + private var cachedLayout: (CGSize, CGFloat, CGFloat)? + + func updateLayoutInternal(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + var update = false + if let cachedLayout = self.cachedLayout { + if cachedLayout.0 != size || cachedLayout.1 != leftInset || cachedLayout.2 != rightInset { + update = true + } + } else { + update = true + } + if update { + self.cachedLayout = (size, leftInset, rightInset) + self.updateLayout(size: size, leftInset: leftInset, rightInset: rightInset) + } + } + + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) { + } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index ebc762c643..e353a36b26 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -1,6 +1,10 @@ import Foundation +#if os(macOS) +import SwiftSignalKitMac +#else import AsyncDisplayKit import SwiftSignalKit +#endif var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) var testSpringFriction: CGFloat = 31.8211269378662 @@ -27,12 +31,6 @@ struct ListViewItemSpring { } } -private class ListViewItemView: UIView { - /*override class var layerClass: AnyClass { - return ASTransformLayer.self - }*/ -} - public struct ListViewItemNodeLayout { public let contentSize: CGSize public let insets: UIEdgeInsets @@ -58,15 +56,28 @@ public enum ListViewItemNodeVisibility { case visible } +public struct ListViewItemLayoutParams { + public let width: CGFloat + public let leftInset: CGFloat + public let rightInset: CGFloat + + public init(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat) { + self.width = width + self.leftInset = leftInset + self.rightInset = rightInset + } +} + open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? - public final var accessoryItemNode: ListViewAccessoryItemNode? { - didSet { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } + public private(set) var accessoryItemNode: ListViewAccessoryItemNode? + + func setAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode?, leftInset: CGFloat, rightInset: CGFloat) { + self.accessoryItemNode = accessoryItemNode + if let accessoryItemNode = accessoryItemNode { + self.layoutAccessoryItemNode(accessoryItemNode, leftInset: leftInset, rightInset: rightInset) } } @@ -95,18 +106,27 @@ open class ListViewItemNode: ASDisplayNode { return true } + open var canBeLongTapped: Bool { + return false + } + + open var preventsTouchesToOtherItems: Bool { + return false + } + + open func touchesToOtherItemsPrevented() { + + } + + open func longTapped() { + } + public final var insets: UIEdgeInsets = UIEdgeInsets() { didSet { let effectiveInsets = self.insets self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: self.contentSize.width, height: self.contentSize.height + effectiveInsets.top + effectiveInsets.bottom)) let bounds = self.bounds self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size) - - if oldValue != self.insets { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } - } } } @@ -167,22 +187,6 @@ open class ListViewItemNode: ASDisplayNode { self.rotated = rotated - //super.init() - - //self.layerBacked = layerBacked - - /*if layerBacked { - super.init(layerBlock: { - return ASTransformLayer() - }) - } else { - super.init() - - self.setViewBlock({ - return ListViewItemView() - }) - }*/ - if seeThrough { if (layerBacked) { super.init() @@ -203,12 +207,6 @@ open class ListViewItemNode: ASDisplayNode { } } - /*deinit { - if Thread.isMainThread { - print("deallocating on main thread") - } - }*/ - var apparentHeight: CGFloat = 0.0 private var _bounds: CGRect = CGRect() private var _position: CGPoint = CGPoint() @@ -226,9 +224,6 @@ open class ListViewItemNode: ASDisplayNode { self._contentSize = CGSize(width: value.size.width, height: value.size.height - effectiveInsets.top - effectiveInsets.bottom) if previousSize != value.size { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } if let headerAccessoryItemNode = self.headerAccessoryItemNode { self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) } @@ -248,9 +243,6 @@ open class ListViewItemNode: ASDisplayNode { self._contentSize = CGSize(width: value.size.width, height: value.size.height - effectiveInsets.top - effectiveInsets.bottom) if previousSize != value.size { - if let accessoryItemNode = self.accessoryItemNode { - self.layoutAccessoryItemNode(accessoryItemNode) - } if let headerAccessoryItemNode = self.headerAccessoryItemNode { self.layoutHeaderAccessoryItemNode(headerAccessoryItemNode) } @@ -279,13 +271,21 @@ open class ListViewItemNode: ASDisplayNode { return frame } + public final var apparentContentFrame: CGRect { + var frame = self.frame + let insets = self.insets + frame.origin.y += insets.top + frame.size.height = self.apparentHeight - insets.top - insets.bottom + return frame + } + public final var apparentBounds: CGRect { var bounds = self.bounds bounds.size.height = self.apparentHeight return bounds } - open func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { + open func layoutAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode, leftInset: CGFloat, rightInset: CGFloat) { } open func layoutHeaderAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode) { @@ -371,7 +371,7 @@ open class ListViewItemNode: ASDisplayNode { return continueAnimations } - open func layoutForWidth(_ width: CGFloat, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { + open func layoutForParams(_ params: ListViewItemLayoutParams, item: ListViewItem, previousItem: ListViewItem?, nextItem: ListViewItem?) { } public func animationForKey(_ key: String) -> ListViewAnimation? { @@ -417,6 +417,19 @@ open class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("insets", animation: animation) } + public func addHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { + let animation = ListViewAnimation(from: self.bounds.height, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in + if let strongSelf = self { + let frame = strongSelf.frame + strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue)) + if let update = update { + update(progress, currentValue) + } + } + }) + self.setAnimationForKey("height", animation: animation) + } + public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { @@ -468,7 +481,7 @@ open class ListViewItemNode: ASDisplayNode { open func animateRemoved(_ currentTimestamp: Double, duration: Double) { } - open func setHighlighted(_ highlighted: Bool, animated: Bool) { + open func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { } open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { diff --git a/Display/ListViewOverscrollBackgroundNode.swift b/Display/ListViewOverscrollBackgroundNode.swift new file mode 100644 index 0000000000..e44586eb3d --- /dev/null +++ b/Display/ListViewOverscrollBackgroundNode.swift @@ -0,0 +1,31 @@ +import Foundation +#if os(macOS) +#else +import AsyncDisplayKit +#endif + +final class ListViewOverscrollBackgroundNode: ASDisplayNode { + private let backgroundNode: ASDisplayNode + + var color: UIColor { + didSet { + self.backgroundNode.backgroundColor = color + } + } + + init(color: UIColor) { + self.color = color + + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = color + self.backgroundNode.isLayerBacked = true + + super.init() + + self.addSubnode(self.backgroundNode) + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size)) + } +} diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 2d1292ff41..c853d1fd0c 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -1,4 +1,7 @@ +#if os(macOS) +#else import UIKit +#endif class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { override init(frame: CGRect) { diff --git a/Display/ListViewScrollerAppkit.swift b/Display/ListViewScrollerAppkit.swift deleted file mode 100644 index 95e31a3df5..0000000000 --- a/Display/ListViewScrollerAppkit.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation -import AppKit - -class ListViewScroller: CALayer { -} diff --git a/Display/MergedLayoutEvents.swift b/Display/MergedLayoutEvents.swift deleted file mode 100644 index 575f4cea6a..0000000000 --- a/Display/MergedLayoutEvents.swift +++ /dev/null @@ -1,9 +0,0 @@ -import UIKit - -protocol MergeableLayoutEvent { - -} - -final class MergedLayoutEvents { - -} diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index dd1bd8009b..cd0288008b 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -36,6 +36,16 @@ private class WindowRootViewController: UIViewController { } } + var preferNavigationUIHidden: Bool = false { + didSet { + if oldValue != self.preferNavigationUIHidden { + if #available(iOSApplicationExtension 11.0, *) { + self.setNeedsUpdateOfHomeIndicatorAutoHidden() + } + } + } + } + override var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -51,6 +61,10 @@ private class WindowRootViewController: UIViewController { override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { return self.gestureEdges } + + override func prefersHomeIndicatorAutoHidden() -> Bool { + return self.preferNavigationUIHidden + } } private final class NativeWindow: UIWindow, WindowHost { @@ -62,6 +76,8 @@ private final class NativeWindow: UIWindow, WindowHost { var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? + var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? + var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? private var frameTransition: ContainedViewLayoutTransition? @@ -98,6 +114,20 @@ private final class NativeWindow: UIWindow, WindowHost { } } + override init(frame: CGRect) { + super.init(frame: frame) + + if let gestureRecognizers = self.gestureRecognizers { + for recognizer in gestureRecognizers { + recognizer.delaysTouchesBegan = false + } + } + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func layoutSubviews() { super.layoutSubviews() @@ -147,6 +177,14 @@ private final class NativeWindow: UIWindow, WindowHost { func invalidateDeferScreenEdgeGestures() { self.invalidateDeferScreenEdgeGestureImpl?() } + + func invalidatePreferNavigationUIHidden() { + self.invalidatePreferNavigationUIHiddenImpl?() + } + + func cancelInteractiveKeyboardGestures() { + self.cancelInteractiveKeyboardGesturesImpl?() + } } public func nativeWindowHostView() -> WindowHostView { @@ -164,6 +202,8 @@ public func nativeWindowHostView() -> WindowHostView { rootViewController.orientations = orientations }, updateDeferScreenEdgeGestures: { edges in rootViewController.gestureEdges = edges + }, updatePreferNavigationUIHidden: { value in + rootViewController.preferNavigationUIHidden = value }) window.updateSize = { [weak hostView] size in @@ -198,6 +238,14 @@ public func nativeWindowHostView() -> WindowHostView { return hostView?.invalidateDeferScreenEdgeGesture?() } + window.invalidatePreferNavigationUIHiddenImpl = { [weak hostView] in + return hostView?.invalidatePreferNavigationUIHidden?() + } + + window.cancelInteractiveKeyboardGesturesImpl = { [weak hostView] in + hostView?.cancelInteractiveKeyboardGestures?() + } + rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let strongSelf = hostView { strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 6249b98b6c..cdb421e869 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -61,6 +61,9 @@ private func backArrowImage(color: UIColor) -> UIImage? { open class NavigationBar: ASDisplayNode { private var theme: NavigationBarTheme + private var validLayout: (CGSize, CGFloat, CGFloat)? + private var requestedLayout: Bool = false + var backPressed: () -> () = { } private var collapsed: Bool { @@ -156,7 +159,7 @@ open class NavigationBar: ASDisplayNode { strongSelf.updateLeftButton(animated: animated) strongSelf.invalidateCalculatedLayout() - strongSelf.setNeedsLayout() + strongSelf.requestLayout() } } @@ -177,7 +180,7 @@ open class NavigationBar: ASDisplayNode { strongSelf.updateRightButton(animated: animated) strongSelf.invalidateCalculatedLayout() - strongSelf.setNeedsLayout() + strongSelf.requestLayout() } } @@ -196,6 +199,7 @@ open class NavigationBar: ASDisplayNode { self.updateRightButton(animated: false) } self.invalidateCalculatedLayout() + self.requestLayout() } } @@ -211,7 +215,7 @@ open class NavigationBar: ASDisplayNode { } self.invalidateCalculatedLayout() - self.setNeedsLayout() + self.requestLayout() } } @@ -226,7 +230,7 @@ open class NavigationBar: ASDisplayNode { } self.invalidateCalculatedLayout() - self.setNeedsLayout() + self.requestLayout() } } @@ -261,6 +265,7 @@ open class NavigationBar: ASDisplayNode { strongSelf.backButtonNode.text = previousItem.title ?? "" } strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() } } @@ -272,12 +277,14 @@ open class NavigationBar: ASDisplayNode { strongSelf.backButtonNode.text = previousItem.title ?? "" } strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() } } } self.updateLeftButton(animated: false) self.invalidateCalculatedLayout() + self.requestLayout() } } @@ -288,13 +295,13 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.isHidden = actualText.isEmpty self.invalidateCalculatedLayout() - self.setNeedsLayout() + self.requestLayout() } } private func updateLeftButton(animated: Bool) { if let item = self.item { - if let leftBarButtonItem = item.leftBarButtonItem { + if let leftBarButtonItem = item.leftBarButtonItem, !leftBarButtonItem.backButtonAppearance { if animated { if self.leftButtonNode.view.superview != nil { if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { @@ -365,13 +372,19 @@ open class NavigationBar: ASDisplayNode { } self.leftButtonNode.removeFromSupernode() - if let previousItem = self.previousItem { + var backTitle: String? + if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { + backTitle = leftBarButtonItem.title + } else if let previousItem = self.previousItem { if let backBarButtonItem = previousItem.backBarButtonItem { - self.backButtonNode.text = backBarButtonItem.title ?? "Back" + backTitle = backBarButtonItem.title ?? "Back" } else { - self.backButtonNode.text = previousItem.title ?? "Back" + backTitle = previousItem.title ?? "Back" } - + } + + if let backTitle = backTitle { + self.backButtonNode.text = backTitle if self.backButtonNode.supernode == nil { self.clippingNode.addSubnode(self.backButtonNode) self.clippingNode.addSubnode(self.backButtonArrow) @@ -500,6 +513,7 @@ open class NavigationBar: ASDisplayNode { } } + self.requestedLayout = true self.layout() } } @@ -557,7 +571,13 @@ open class NavigationBar: ASDisplayNode { } } self.backButtonNode.pressed = { [weak self] in - self?.backPressed() + if let strongSelf = self { + if let leftBarButtonItem = strongSelf.item?.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { + leftBarButtonItem.performActionOnTarget() + } else { + strongSelf.backPressed() + } + } } self.leftButtonNode.pressed = { [weak self] in @@ -592,25 +612,41 @@ open class NavigationBar: ASDisplayNode { } } - open override func layout() { - let size = self.bounds.size + private func requestLayout() { + self.requestedLayout = true + self.setNeedsLayout() + } + + override open func layout() { + super.layout() - let leftButtonInset: CGFloat = 16.0 - let backButtonInset: CGFloat = 27.0 + if let validLayout = self.validLayout, self.requestedLayout { + self.requestedLayout = false + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, transition: .immediate) + } + } + + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset) - self.clippingNode.frame = CGRect(origin: CGPoint(), size: size) - self.contentNode?.frame = CGRect(origin: CGPoint(), size: size) + let leftButtonInset: CGFloat = leftInset + 16.0 + let backButtonInset: CGFloat = leftInset + 27.0 - self.stripeNode.frame = CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel) + transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) + if let contentNode = self.contentNode { + transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height))) + } + + transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel)) let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 let contentVerticalOrigin = size.height - nominalHeight - var leftTitleInset: CGFloat = 8.0 - var rightTitleInset: CGFloat = 8.0 + var leftTitleInset: CGFloat = leftInset + 4.0 + var rightTitleInset: CGFloat = rightInset + 4.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) - leftTitleInset += backButtonSize.width + backButtonInset + 8.0 + 8.0 + leftTitleInset += backButtonSize.width + backButtonInset + 4.0 + 4.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 self.backButtonNode.hitTestSlop = UIEdgeInsetsMake(-topHitTestSlop, -27.0, -topHitTestSlop, -8.0) @@ -636,21 +672,21 @@ open class NavigationBar: ASDisplayNode { transitionTitleNode.alpha = progress * progress } - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0 - progress * size.width, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.backButtonArrow.alpha = max(0.0, 1.0 - progress * 1.3) self.badgeNode.alpha = max(0.0, 1.0 - progress * 1.3) case .bottom: self.backButtonNode.alpha = 1.0 self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) self.backButtonArrow.alpha = 1.0 - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.badgeNode.alpha = 1.0 } } else { self.backButtonNode.alpha = 1.0 self.backButtonNode.frame = CGRect(origin: CGPoint(x: backButtonInset, y: contentVerticalOrigin + floor((nominalHeight - backButtonSize.height) / 2.0)), size: backButtonSize) self.backButtonArrow.alpha = 1.0 - self.backButtonArrow.frame = CGRect(origin: CGPoint(x: 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) + self.backButtonArrow.frame = CGRect(origin: CGPoint(x: leftInset + 8.0, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) self.badgeNode.alpha = 1.0 } } else if self.leftButtonNode.supernode != nil { @@ -689,8 +725,8 @@ open class NavigationBar: ASDisplayNode { } if let transitionBackArrowNode = self.transitionBackArrowNode { - let initialX: CGFloat = 8.0 + size.width * 0.3 - let finalX: CGFloat = 8.0 + let initialX: CGFloat = leftInset + 8.0 + size.width * 0.3 + let finalX: CGFloat = leftInset + 8.0 transitionBackArrowNode.frame = CGRect(origin: CGPoint(x: initialX * (1.0 - progress) + finalX * progress, y: contentVerticalOrigin + floor((nominalHeight - 22.0) / 2.0)), size: CGSize(width: 13.0, height: 22.0)) transitionBackArrowNode.alpha = max(0.0, 1.0 - progress * 1.3) @@ -739,7 +775,7 @@ open class NavigationBar: ASDisplayNode { } if let titleView = self.titleView { - let titleSize = CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight) + let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { @@ -859,9 +895,10 @@ open class NavigationBar: ASDisplayNode { } if !self.bounds.size.width.isZero { + self.requestedLayout = true self.layout() } else { - self.setNeedsLayout() + self.requestLayout() } } else if self.clippingNode.alpha.isZero { self.clippingNode.alpha = 1.0 diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index e0fedbdb29..531ccdc445 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -73,7 +73,7 @@ open class NavigationController: UINavigationController, ContainableController, self.loadView() } self.containerLayout = layout - self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size) + transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) @@ -81,7 +81,7 @@ open class NavigationController: UINavigationController, ContainableController, if let topViewController = topViewController as? ContainableController { topViewController.containerLayoutUpdated(containedLayout, transition: transition) } else { - topViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + transition.updateFrame(view: topViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) } } @@ -89,7 +89,7 @@ open class NavigationController: UINavigationController, ContainableController, if let presentedViewController = presentedViewController as? ContainableController { presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) } else { - presentedViewController.view.frame = CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height) + transition.updateFrame(view: presentedViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) } } @@ -109,6 +109,7 @@ open class NavigationController: UINavigationController, ContainableController, let panRecognizer = InteractiveTransitionGestureRecognizer(target: self, action: #selector(self.panGesture(_:))) panRecognizer.delegate = self + panRecognizer.delaysTouchesBegan = false panRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(panRecognizer) @@ -349,9 +350,9 @@ open class NavigationController: UINavigationController, ContainableController, } } - bottomController.viewWillDisappear(true) + bottomController.viewWillAppear(true) let bottomView = bottomController.view! - topController.viewWillAppear(true) + topController.viewWillDisappear(true) let topView = topController.view! let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 894114ab9d..0cb39d4d89 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -120,7 +120,7 @@ public final class StatusBar: ASDisplayNode { self.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.tapGesture(_:)))) } - func updateState(statusBar: UIView?, inCallText: String?, animated: Bool) { + func updateState(statusBar: UIView?, withSafeInsets: Bool, inCallText: String?, animated: Bool) { if let statusBar = statusBar { self.removeProxyNodeScheduled = false let resolvedStyle: StatusBarStyle @@ -166,7 +166,9 @@ public final class StatusBar: ASDisplayNode { if (resolvedInCallText != nil) != (self.inCallText != nil) { if let _ = resolvedInCallText { - self.addSubnode(self.inCallLabel) + if !withSafeInsets { + self.addSubnode(self.inCallLabel) + } addInCallAnimation(self.inCallLabel.layer) self.inCallBackgroundNode.layer.backgroundColor = inCallBackgroundColor.cgColor diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 6f12a61d1e..4ab5f9483e 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -79,13 +79,13 @@ class StatusBarManager { self.host = host } - func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { + func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let previousSurfaces = self.surfaces self.surfaces = surfaces - self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated) + self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated) } - private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let statusBarFrame = self.host.statusBarFrame guard let statusBarView = self.host.statusBarView else { return @@ -197,7 +197,7 @@ class StatusBarManager { for surface in previousSurfaces { for statusBar in surface.statusBars { if !visibleStatusBars.contains(where: {$0 === statusBar}) { - statusBar.updateState(statusBar: nil, inCallText: forceInCallStatusBarText, animated: animated) + statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } } } @@ -206,13 +206,13 @@ class StatusBarManager { for statusBar in surface.statusBars { statusBar.inCallNavigate = self.inCallNavigate if !visibleStatusBars.contains(where: {$0 === statusBar}) { - statusBar.updateState(statusBar: nil, inCallText: forceInCallStatusBarText, animated: animated) + statusBar.updateState(statusBar: nil, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } } } for statusBar in visibleStatusBars { - statusBar.updateState(statusBar: statusBarView, inCallText: forceInCallStatusBarText, animated: animated) + statusBar.updateState(statusBar: statusBarView, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 2786088fe0..b6750b07fb 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -306,7 +306,7 @@ class StatusBarProxyNode: ASDisplayNode { self.updateItems() self.timer = Timer(timeInterval: 5.0, target: StatusBarProxyNodeTimerTarget { [weak self] in self?.updateItems() - }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) + }, selector: #selector(StatusBarProxyNodeTimerTarget.tick), userInfo: nil, repeats: true) RunLoop.main.add(self.timer!, forMode: .commonModes) } else { self.timer?.invalidate() diff --git a/Display/SystemContainedControllerTransitionCoordinator.swift b/Display/SystemContainedControllerTransitionCoordinator.swift deleted file mode 100644 index b861536e24..0000000000 --- a/Display/SystemContainedControllerTransitionCoordinator.swift +++ /dev/null @@ -1,73 +0,0 @@ -import UIKit - -final class SystemContainedControllerTransitionCoordinator: NSObject, UIViewControllerTransitionCoordinator { - public var isAnimated: Bool { - return false - } - - public var presentationStyle: UIModalPresentationStyle { - return .fullScreen - } - - public var initiallyInteractive: Bool { - return false - } - - public let isInterruptible: Bool = false - - public var isInteractive: Bool { - return false - } - - public var isCancelled: Bool { - return false - } - - public var transitionDuration: TimeInterval { - return 0.6 - } - - public var percentComplete: CGFloat { - return 0.0 - } - - public var completionVelocity: CGFloat { - return 0.0 - } - - public var completionCurve: UIViewAnimationCurve { - return .easeInOut - } - - public func viewController(forKey key: UITransitionContextViewControllerKey) -> UIViewController? { - return nil - } - - public func view(forKey key: UITransitionContextViewKey) -> UIView? { - return nil - } - - public var containerView: UIView { - return UIView() - } - - public var targetTransform: CGAffineTransform { - return CGAffineTransform.identity - } - - public func animate(alongsideTransition animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { - return false - } - - public func animateAlongsideTransition(in view: UIView?, animation: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)?, completion: ((UIViewControllerTransitionCoordinatorContext) -> Swift.Void)? = nil) -> Bool { - return false - } - - public func notifyWhenInteractionEnds(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> ()) { - - } - - public func notifyWhenInteractionChanges(_ handler: @escaping (UIViewControllerTransitionCoordinatorContext) -> ()) { - - } -} diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 0b7bed48e5..f4e5d3b420 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -36,11 +36,16 @@ final class TabBarControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { - let tabBarHeight = 49.0 + layout.insets(options: []).bottom - self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) - if self.tabBarNode.isNodeLoaded { - self.tabBarNode.layout() + let tabBarHeight: CGFloat + let bottomInset: CGFloat = layout.insets(options: []).bottom + if !layout.safeInsets.left.isZero { + tabBarHeight = 34.0 + bottomInset + } else { + tabBarHeight = 49.0 + bottomInset } + + transition.updateFrame(node: self.tabBarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))) + self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition) } switch transition { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 087e614ce7..1b19c3152a 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -3,18 +3,30 @@ import UIKit import AsyncDisplayKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale -private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor) -> UIImage? { - let font = Font.medium(10.0) +private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool) -> UIImage? { + let font = horizontal ? Font.regular(13.0) : Font.medium(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size let imageSize: CGSize if let image = image { - imageSize = image.size + if horizontal { + let factor: CGFloat = 0.8 + imageSize = CGSize(width: floor(image.size.width * factor), height: floor(image.size.height * factor)) + } else { + imageSize = image.size + } } else { imageSize = CGSize() } - let size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + let horizontalSpacing: CGFloat = 4.0 + + let size: CGSize + if horizontal { + size = CGSize(width: ceil(titleSize.width) + horizontalSpacing + imageSize.width, height: 34.0) + } else { + size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + } UIGraphicsBeginImageContextWithOptions(size, true, 0.0) if let context = UIGraphicsGetCurrentContext() { @@ -22,19 +34,35 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: context.fill(CGRect(origin: CGPoint(), size: size)) if let image = image { - let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize) - context.saveGState() - context.translateBy(x: imageRect.midX, y: imageRect.midY) - context.scaleBy(x: 1.0, y: -1.0) - context.translateBy(x: -imageRect.midX, y: -imageRect.midY) - context.clip(to: imageRect, mask: image.cgImage!) - context.setFillColor(tintColor.cgColor) - context.fill(imageRect) - context.restoreGState() + if horizontal { + let imageRect = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize) + context.saveGState() + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + context.restoreGState() + } else { + let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize) + context.saveGState() + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + context.restoreGState() + } } } - (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + if horizontal { + (title as NSString).draw(at: CGPoint(x: imageSize.width + horizontalSpacing, y: floor((size.height - titleSize.height) / 2.0) - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + } else { + (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + } let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() @@ -52,6 +80,7 @@ private final class TabBarNodeContainer { let updateSelectedImageListenerIndex: Int let imageNode: ASImageNode + let badgeContainerNode: ASDisplayNode let badgeBackgroundNode: ASImageNode let badgeTextNode: ASTextNode @@ -72,6 +101,9 @@ private final class TabBarNodeContainer { self.imageNode = imageNode + self.badgeContainerNode = ASDisplayNode() + self.badgeContainerNode.isLayerBacked = true + self.badgeBackgroundNode = ASImageNode() self.badgeBackgroundNode.isLayerBacked = true self.badgeBackgroundNode.displayWithoutProcessing = true @@ -82,6 +114,9 @@ private final class TabBarNodeContainer { self.badgeTextNode.isLayerBacked = true self.badgeTextNode.displaysAsynchronously = false + self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) + self.badgeContainerNode.addSubnode(self.badgeTextNode) + self.badgeValue = item.badgeValue ?? "" self.updateBadgeListenerIndex = UITabBarItem_addSetBadgeListener(item, { value in updateBadge(value ?? "") @@ -122,11 +157,11 @@ class TabBarNode: ASDisplayNode { didSet { if self.selectedIndex != oldValue { if let oldValue = oldValue { - self.updateNodeImage(oldValue) + self.updateNodeImage(oldValue, layout: true) } if let selectedIndex = self.selectedIndex { - self.updateNodeImage(selectedIndex) + self.updateNodeImage(selectedIndex, layout: true) } } } @@ -135,6 +170,8 @@ class TabBarNode: ASDisplayNode { private let itemSelected: (Int) -> Void private var theme: TabBarControllerTheme + private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? + private var horizontal: Bool = false private var badgeImage: UIImage @@ -175,7 +212,7 @@ class TabBarNode: ASDisplayNode { } for i in 0 ..< self.tabBarItems.count { - self.updateNodeImage(i) + self.updateNodeImage(i, layout: false) self.tabBarNodeContainers[i].badgeBackgroundNode.image = self.badgeImage } @@ -185,8 +222,7 @@ class TabBarNode: ASDisplayNode { private func reloadTabBarItems() { for node in self.tabBarNodeContainers { node.imageNode.removeFromSupernode() - node.badgeBackgroundNode.removeFromSupernode() - node.badgeTextNode.removeFromSupernode() + node.badgeContainerNode.removeFromSupernode() } var tabBarNodeContainers: [TabBarNodeContainer] = [] @@ -199,16 +235,16 @@ class TabBarNode: ASDisplayNode { let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) }, updateTitle: { [weak self] _, _ in - self?.updateNodeImage(i) + self?.updateNodeImage(i, layout: true) }, updateImage: { [weak self] _ in - self?.updateNodeImage(i) + self?.updateNodeImage(i, layout: true) }, updateSelectedImage: { [weak self] _ in - self?.updateNodeImage(i) + self?.updateNodeImage(i, layout: true) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor) + node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) } container.badgeBackgroundNode.image = self.badgeImage tabBarNodeContainers.append(container) @@ -216,8 +252,7 @@ class TabBarNode: ASDisplayNode { } for container in tabBarNodeContainers { - self.addSubnode(container.badgeBackgroundNode) - self.addSubnode(container.badgeTextNode) + self.addSubnode(container.badgeContainerNode) } self.tabBarNodeContainers = tabBarNodeContainers @@ -225,19 +260,21 @@ class TabBarNode: ASDisplayNode { self.setNeedsLayout() } - private func updateNodeImage(_ index: Int) { + private func updateNodeImage(_ index: Int, layout: Bool) { if index < self.tabBarNodeContainers.count && index < self.tabBarItems.count { let node = self.tabBarNodeContainers[index].imageNode let item = self.tabBarItems[index] let previousImage = node.image if let selectedIndex = self.selectedIndex, selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor) + node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor) + node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) } if previousImage?.size != node.image?.size { - self.layout() + if let validLayout = self.validLayout, layout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } } @@ -245,23 +282,33 @@ class TabBarNode: ASDisplayNode { private func updateNodeBadge(_ index: Int, value: String) { self.tabBarNodeContainers[index].badgeValue = value if self.tabBarNodeContainers[index].badgeValue != self.tabBarNodeContainers[index].appliedBadgeValue { - self.layout() + if let validLayout = self.validLayout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } private func updateNodeTitle(_ index: Int, value: String) { self.tabBarNodeContainers[index].titleValue = value if self.tabBarNodeContainers[index].titleValue != self.tabBarNodeContainers[index].appliedTitleValue { - self.layout() + if let validLayout = self.validLayout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } - override func layout() { - super.layout() + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, transition: ContainedViewLayoutTransition) { + self.validLayout = (size, leftInset, rightInset, bottomInset) - let size = self.bounds.size + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight), size: CGSize(width: size.width, height: separatorHeight))) - self.separatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: -separatorHeight), size: CGSize(width: size.width, height: separatorHeight)) + let horizontal = !leftInset.isZero + if self.horizontal != horizontal { + self.horizontal = horizontal + for i in 0 ..< self.tabBarItems.count { + self.updateNodeImage(i, layout: false) + } + } if self.tabBarNodeContainers.count != 0 { let distanceBetweenNodes = size.width / CGFloat(self.tabBarNodeContainers.count) @@ -275,26 +322,33 @@ class TabBarNode: ASDisplayNode { let nodeSize = node.image?.size ?? CGSize() let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - nodeSize.width / 2.0) - node.frame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize) + transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize)) if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue if let badgeValue = container.badgeValue, !badgeValue.isEmpty { container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor) - container.badgeBackgroundNode.isHidden = false - container.badgeTextNode.isHidden = false + container.badgeContainerNode.isHidden = false } else { - container.badgeBackgroundNode.isHidden = true - container.badgeTextNode.isHidden = true + container.badgeContainerNode.isHidden = true } } - if !container.badgeBackgroundNode.isHidden { + if !container.badgeContainerNode.isHidden { let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) - let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) - container.badgeBackgroundNode.frame = backgroundFrame - container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: 3.0), size: badgeSize) + let backgroundFrame: CGRect + if horizontal { + backgroundFrame = CGRect(origin: CGPoint(x: originX, y: 2.0), size: backgroundSize) + } else { + backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) + } + transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame) + container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) + let scaleFactor: CGFloat = horizontal ? 0.8 : 1.0 + container.badgeContainerNode.subnodeTransform = CATransform3DMakeScale(scaleFactor, scaleFactor, 1.0) + + container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((backgroundFrame.size.width - badgeSize.width) / 2.0), y: 1.0), size: badgeSize) } } } @@ -303,8 +357,11 @@ class TabBarNode: ASDisplayNode { override func touchesBegan(_ touches: Set, with event: UIEvent?) { super.touchesBegan(touches, with: event) - if let touch = touches.first { + if let touch = touches.first, let bottomInset = self.validLayout?.3 { let location = touch.location(in: self.view) + if location.y > self.bounds.size.height - bottomInset { + return + } var closestNode: (Int, CGFloat)? for i in 0 ..< self.tabBarNodeContainers.count { diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 8bf98938ca..fce5275127 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit public enum TextAlertActionType { case genericAction case defaultAction + case destructiveAction } public struct TextAlertAction { @@ -34,7 +35,15 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { super.init() self.titleNode.maximumNumberOfLines = 2 - self.setAttributedTitle(NSAttributedString(string: action.title, font: Font.regular(17.0), textColor: UIColor(rgb: 0x007ee5), paragraphAlignment: .center), for: []) + let font = Font.regular(17.0) + var color = UIColor(rgb: 0x007ee5) + switch action.type { + case .defaultAction, .genericAction: + break + case .destructiveAction: + color = UIColor(rgb: 0xff3b30) + } + self.setAttributedTitle(NSAttributedString(string: action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) self.highligthedChanged = { [weak self] value in if let strongSelf = self { @@ -154,7 +163,8 @@ final class TextAlertContentNode: AlertContentNode { let resultSize: CGSize if let titleNode = titleNode, let titleSize = titleSize { - let contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) + var contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) + contentWidth = max(contentWidth, 150.0) let spacing: CGFloat = 6.0 let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize) @@ -165,10 +175,13 @@ final class TextAlertContentNode: AlertContentNode { resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom) } else { - let textFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize) + var contentWidth = max(textSize.width, minActionsWidth) + contentWidth = max(contentWidth, 150.0) + + let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: insets.top), size: textSize) transition.updateFrame(node: self.textNode, frame: textFrame) - resultSize = CGSize(width: textSize.width + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom) + resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom) } self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel)) diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 09e212bddd..1041ec2a67 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -1,5 +1,7 @@ #import "UIKitUtils.h" +#import + #if TARGET_IPHONE_SIMULATOR UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient, use judiciously #endif @@ -19,14 +21,35 @@ UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient @interface CASpringAnimation () -- (float)_solveForInput:(float)arg1; - @end @implementation CASpringAnimation (AnimationUtils) - (CGFloat)valueAt:(CGFloat)t { - return [self _solveForInput:t]; + static dispatch_once_t onceToken; + static float (*impl)(id, float) = NULL; + static double (*dimpl)(id, double) = NULL; + dispatch_once(&onceToken, ^{ + Method method = class_getInstanceMethod([CASpringAnimation class], NSSelectorFromString([@"_" stringByAppendingString:@"solveForInput:"])); + if (method) { + const char *encoding = method_getTypeEncoding(method); + NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding]; + const char *argType = [signature getArgumentTypeAtIndex:2]; + if (strncmp(argType, "f", 1) == 0) { + impl = (float (*)(id, float))method_getImplementation(method); + } else if (strncmp(argType, "d", 1) == 0) { + dimpl = (double (*)(id, double))method_getImplementation(method); + } + } + }); + if (impl) { + float result = impl(self, (float)t); + return (CGFloat)result; + } else if (dimpl) { + double result = dimpl(self, (double)t); + return (CGFloat)result; + } + return t; } @end @@ -62,5 +85,5 @@ CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPat } CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t) { - return [(CASpringAnimation *)animation _solveForInput:t]; + return [(CASpringAnimation *)animation valueAt:t]; } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 669190bf5e..1fb1150a23 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -148,6 +148,7 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { let subtree = makeSubtreeSnapshot(layer: sublayer) if let subtree = subtree { subtree.frame = sublayer.frame + subtree.bounds = sublayer.bounds view.addSubview(subtree) } else { return nil @@ -157,6 +158,32 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { return view } +private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { + let view = CALayer() + //view.layer.isHidden = layer.isHidden + view.opacity = layer.opacity + view.contents = layer.contents + view.contentsRect = layer.contentsRect + view.contentsScale = layer.contentsScale + view.contentsCenter = layer.contentsCenter + view.contentsGravity = layer.contentsGravity + view.masksToBounds = layer.masksToBounds + view.cornerRadius = layer.cornerRadius + if let sublayers = layer.sublayers { + for sublayer in sublayers { + let subtree = makeLayerSubtreeSnapshot(layer: sublayer) + if let subtree = subtree { + subtree.frame = sublayer.frame + subtree.bounds = sublayer.bounds + layer.addSublayer(subtree) + } else { + return nil + } + } + } + return view +} + public extension UIView { public func snapshotContentTree() -> UIView? { if let snapshot = makeSubtreeSnapshot(layer: self.layer) { @@ -168,6 +195,17 @@ public extension UIView { } } +public extension CALayer { + public func snapshotContentTree() -> CALayer? { + if let snapshot = makeLayerSubtreeSnapshot(layer: self) { + snapshot.frame = self.frame + return snapshot + } else { + return nil + } + } +} + public extension CGRect { public var topLeft: CGPoint { return self.origin diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 62e2c3d9b9..316fc4bca2 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -1,5 +1,10 @@ #import +typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { + UIResponderDisableAutomaticKeyboardHandlingForward = 1 << 0, + UIResponderDisableAutomaticKeyboardHandlingBackward = 1 << 1 +}; + @interface UIViewController (Navigation) - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; @@ -14,7 +19,7 @@ @interface UIView (Navigation) @property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer; -@property (nonatomic) bool disablesAutomaticKeyboardHandling; +@property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling; - (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block; - (CGFloat)input_getInputAccessoryHeight; diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 77c3643a11..085c7be66d 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -35,7 +35,7 @@ static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNa static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey; static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey; static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey; -static const void *disablesAutomaticKeyboardHandlingKey = &disablesAutomaticKeyboardHandlingKey; +static const void *disableAutomaticKeyboardHandlingKey = &disableAutomaticKeyboardHandlingKey; static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey; static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey; @@ -218,12 +218,12 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey]; } -- (bool)disablesAutomaticKeyboardHandling { - return [[self associatedObjectForKey:disablesAutomaticKeyboardHandlingKey] boolValue]; +- (UIResponderDisableAutomaticKeyboardHandling)disableAutomaticKeyboardHandling { + return (UIResponderDisableAutomaticKeyboardHandling)[[self associatedObjectForKey:disableAutomaticKeyboardHandlingKey] unsignedIntegerValue]; } -- (void)setDisablesAutomaticKeyboardHandling:(bool)disablesAutomaticKeyboardHandling { - [self setAssociatedObject:@(disablesAutomaticKeyboardHandling) forKey:disablesAutomaticKeyboardHandlingKey]; +- (void)setDisableAutomaticKeyboardHandling:(UIResponderDisableAutomaticKeyboardHandling)disableAutomaticKeyboardHandling { + [self setAssociatedObject:@(disableAutomaticKeyboardHandling) forKey:disableAutomaticKeyboardHandlingKey]; } - (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index f4f88ae472..c00c6f1ba1 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -46,8 +46,16 @@ open class ViewControllerPresentationArguments { } } - override open func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { - return .bottom + public final var preferNavigationUIHidden: Bool = false { + didSet { + if self.preferNavigationUIHidden != oldValue { + self.window?.invalidatePreferNavigationUIHidden() + } + } + } + + override open func prefersHomeIndicatorAutoHidden() -> Bool { + return self.preferNavigationUIHidden } public private(set) var presentationArguments: Any? @@ -196,6 +204,7 @@ open class ViewControllerPresentationArguments { if let navigationBar = self.navigationBar { transition.updateFrame(node: navigationBar, frame: navigationBarFrame) + navigationBar.updateLayout(size: navigationBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) } self.presentationContext.containerLayoutUpdated(layout, transition: transition) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 8525275dc0..d74be8d3a8 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -1,5 +1,6 @@ import Foundation import AsyncDisplayKit +import SwiftSignalKit private class WindowRootViewController: UIViewController { var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? @@ -88,10 +89,10 @@ private struct UpdatingLayout { } } - mutating func update(size: CGSize, metrics: LayoutMetrics, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { + mutating func update(size: CGSize, metrics: LayoutMetrics, safeInsets: UIEdgeInsets, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) } @@ -146,7 +147,7 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container let resolvedStatusBarHeight: CGFloat? if let statusBarHeight = layout.statusBarHeight { if layout.forceInCallStatusBarText != nil { - resolvedStatusBarHeight = 40.0 + resolvedStatusBarHeight = max(40.0, layout.safeInsets.top) } else { resolvedStatusBarHeight = statusBarHeight } @@ -250,6 +251,7 @@ public final class WindowHostView { let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void + let updatePreferNavigationUIHidden: (Bool) -> Void var present: ((ViewController, PresentationSurfaceLevel) -> Void)? var presentNative: ((UIViewController) -> Void)? @@ -259,12 +261,15 @@ public final class WindowHostView { var isUpdatingOrientationLayout = false var hitTest: ((CGPoint, UIEvent?) -> UIView?)? var invalidateDeferScreenEdgeGesture: (() -> Void)? + var invalidatePreferNavigationUIHidden: (() -> Void)? + var cancelInteractiveKeyboardGestures: (() -> Void)? - init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void) { + init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { self.view = view self.isRotating = isRotating self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures + self.updatePreferNavigationUIHidden = updatePreferNavigationUIHidden } } @@ -276,12 +281,25 @@ public struct WindowTracingTags { public protocol WindowHost { func present(_ controller: ViewController, on level: PresentationSurfaceLevel) func invalidateDeferScreenEdgeGestures() + func invalidatePreferNavigationUIHidden() + func cancelInteractiveKeyboardGestures() } private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { return LayoutMetrics(widthClass: .compact, heightClass: .compact) } +private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { + if (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) { + if size.width.isEqual(to: 375.0) { + return UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) + } else { + return UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) + } + } + return UIEdgeInsets() +} + private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -300,6 +318,7 @@ public class Window1 { private let keyboardManager: KeyboardManager? private var statusBarChangeObserver: AnyObject? private var keyboardFrameChangeObserver: AnyObject? + private var keyboardTypeChangeObserver: AnyObject? private var windowLayout: WindowLayout private var updatingLayout: UpdatingLayout? @@ -312,6 +331,7 @@ public class Window1 { private var tracingStatusBarsInvalidated = false private var shouldUpdateDeferScreenEdgeGestures = false + private var shouldInvalidatePreferNavigationUIHidden = false private var statusBarHidden = false @@ -329,6 +349,8 @@ public class Window1 { private var keyboardGestureBeginLocation: CGPoint? private var keyboardGestureAccessoryHeight: CGFloat? + private var keyboardTypeChangeTimer: SwiftSignalKit.Timer? + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -348,12 +370,10 @@ public class Window1 { var onScreenNavigationHeight: CGFloat? if (boundsSize.width.isEqual(to: 375.0) && boundsSize.height.isEqual(to: 812.0)) || boundsSize.height.isEqual(to: 375.0) && boundsSize.width.isEqual(to: 812.0) { - onScreenNavigationHeight = 20.0 + onScreenNavigationHeight = 34.0 } - let safeInsets = UIEdgeInsets() - - self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) + self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) self.presentationContext = PresentationContext() self.hostView.present = { [weak self] controller, level in @@ -388,6 +408,14 @@ public class Window1 { self?.invalidateDeferScreenEdgeGestures() } + self.hostView.invalidatePreferNavigationUIHidden = { [weak self] in + self?.invalidatePreferNavigationUIHidden() + } + + self.hostView.cancelInteractiveKeyboardGestures = { [weak self] in + self?.cancelInteractiveKeyboardGestures() + } + self.presentationContext.view = self.hostView.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) @@ -421,6 +449,34 @@ public class Window1 { } }) + if #available(iOSApplicationExtension 11.0, *) { + self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: nil, using: { [weak self] notification in + if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if firstResponder.textInputMode?.primaryLanguage != nil { + return + } + + strongSelf.keyboardTypeChangeTimer?.invalidate() + let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: { + if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if firstResponder.textInputMode?.primaryLanguage != nil { + return + } + + if let keyboardManager = strongSelf.keyboardManager { + let updatedKeyboardHeight = keyboardManager.getCurrentKeyboardHeight() + if !updatedKeyboardHeight.isEqual(to: initialInputHeight) { + strongSelf.updateLayout({ $0.update(inputHeight: updatedKeyboardHeight, transition: .immediate, overrideTransition: false) }) + } + } + } + }, queue: Queue.mainQueue()) + strongSelf.keyboardTypeChangeTimer = timer + timer.start() + } + }) + } + let recognizer = WindowPanRecognizer(target: self, action: #selector(self.panGesture(_:))) recognizer.cancelsTouchesInView = false recognizer.delaysTouchesBegan = false @@ -449,6 +505,9 @@ public class Window1 { if let keyboardFrameChangeObserver = self.keyboardFrameChangeObserver { NotificationCenter.default.removeObserver(keyboardFrameChangeObserver) } + if let keyboardTypeChangeObserver = self.keyboardTypeChangeObserver { + NotificationCenter.default.removeObserver(keyboardTypeChangeObserver) + } } public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) { @@ -471,6 +530,23 @@ public class Window1 { self.hostView.view.setNeedsLayout() } + public func invalidatePreferNavigationUIHidden() { + self.shouldInvalidatePreferNavigationUIHidden = true + self.hostView.view.setNeedsLayout() + } + + public func cancelInteractiveKeyboardGestures() { + if self.windowLayout.upperKeyboardInputPositionBound != nil { + self.updateLayout { + $0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) + } + } + + if self.keyboardGestureBeginLocation != nil { + self.keyboardGestureBeginLocation = nil + } + } + public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for view in self.hostView.view.subviews.reversed() { if NSStringFromClass(type(of: view)) == "UITransitionView" { @@ -499,7 +575,7 @@ public class Window1 { } else { transition = .immediate } - self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } + self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), safeInsets: safeInsetsForScreenSize(value), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } } private var _rootController: ContainableController? @@ -563,7 +639,7 @@ public class Window1 { self.tracingStatusBarsInvalidated = false if self.statusBarHidden { - statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false) + statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false) } else { var statusBarSurfaces: [StatusBarSurface] = [] for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { @@ -584,7 +660,7 @@ public class Window1 { } } self.cachedWindowSubviewCount = self.hostView.view.window?.subviews.count ?? 0 - statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate) + statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate) } var keyboardSurfaces: [KeyboardSurface] = [] @@ -599,12 +675,16 @@ public class Window1 { self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) + self.hostView.updatePreferNavigationUIHidden(self.collectPreferNavigationUIHidden()) self.shouldUpdateDeferScreenEdgeGestures = false - } else if self.shouldUpdateDeferScreenEdgeGestures { + self.shouldInvalidatePreferNavigationUIHidden = false + } else if self.shouldUpdateDeferScreenEdgeGestures || self.shouldInvalidatePreferNavigationUIHidden { self.shouldUpdateDeferScreenEdgeGestures = false + self.shouldInvalidatePreferNavigationUIHidden = false self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) + self.hostView.updatePreferNavigationUIHidden(self.collectPreferNavigationUIHidden()) } if !UIWindow.isDeviceRotating() { @@ -716,6 +796,10 @@ public class Window1 { } private func panGestureBegan(location: CGPoint) { + if self.windowLayout.upperKeyboardInputPositionBound != nil { + return + } + let keyboardGestureBeginLocation = location let view = self.hostView.view let (firstResponder, accessoryHeight) = getFirstResponderAndAccessoryHeight(view) @@ -746,9 +830,23 @@ public class Window1 { } private func panGestureEnded(location: CGPoint, velocity: CGPoint?) { + if self.keyboardGestureBeginLocation == nil { + return + } + self.keyboardGestureBeginLocation = nil let currentLocation = location - if let velocity = velocity, let inputHeight = self.windowLayout.inputHeight, velocity.y > 100.0 && currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight { + + let accessoryHeight = (self.keyboardGestureAccessoryHeight ?? 0.0) + + var canDismiss = false + if let upperKeyboardInputPositionBound = self.windowLayout.upperKeyboardInputPositionBound, upperKeyboardInputPositionBound >= self.windowLayout.size.height - accessoryHeight { + canDismiss = true + } else if let velocity = velocity, velocity.y > 100.0 { + canDismiss = true + } + + if canDismiss, let inputHeight = self.windowLayout.inputHeight, currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight { self.updateLayout { $0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) } @@ -786,6 +884,10 @@ public class Window1 { return edges } + private func collectPreferNavigationUIHidden() -> Bool { + return false + } + public func forEachViewController(_ f: (ViewController) -> Bool) { for controller in self.presentationContext.controllers { if !f(controller) { diff --git a/DisplayMac/ASDisplayNode.swift b/DisplayMac/ASDisplayNode.swift new file mode 100644 index 0000000000..ed43abb43e --- /dev/null +++ b/DisplayMac/ASDisplayNode.swift @@ -0,0 +1,84 @@ +import Foundation + +open class ASDisplayNode: NSObject { + var layer: CALayer { + preconditionFailure() + } + + var view: UIView { + preconditionFailure() + } + + open var frame: CGRect { + get { + return self.layer.frame + } set(value) { + self.layer.frame = value + } + } + + open var bounds: CGRect { + get { + return self.layer.bounds + } set(value) { + self.layer.bounds = value + } + } + + open var position: CGPoint { + get { + return self.layer.position + } set(value) { + self.layer.position = value + } + } + + var alpha: CGFloat { + get { + return CGFloat(self.layer.opacity) + } set(value) { + self.layer.opacity = Float(value) + } + } + + var backgroundColor: UIColor? { + get { + if let backgroundColor = self.layer.backgroundColor { + return UIColor(cgColor: backgroundColor) + } else { + return nil + } + } set(value) { + self.layer.backgroundColor = value?.cgColor + } + } + + var isLayerBacked: Bool = false + + override init() { + super.init() + } + + func setLayerBlock(_ f: @escaping () -> CALayer) { + + } + + func setViewBlock(_ f: @escaping () -> UIView) { + + } + + open func layout() { + } + + open func addSubnode(_ subnode: ASDisplayNode) { + + } + + open func insertSubnode(belowSubnode: ASDisplayNode) { + + } + + open func insertSubnode(aboveSubnode: ASDisplayNode) { + + } +} diff --git a/DisplayMac/NSValueAdditions.swift b/DisplayMac/NSValueAdditions.swift new file mode 100644 index 0000000000..7f3047d0c8 --- /dev/null +++ b/DisplayMac/NSValueAdditions.swift @@ -0,0 +1,12 @@ +import Foundation +import Cocoa + +extension NSValue { + convenience init(cgRect: CGRect) { + self.init(rect: NSRect(origin: cgRect.origin, size: cgRect.size)) + } + + convenience init(cgPoint: CGPoint) { + self.init(point: NSPoint(x: cgPoint.x, y: cgPoint.y)) + } +} diff --git a/DisplayMac/UIGestureRecognizer.swift b/DisplayMac/UIGestureRecognizer.swift new file mode 100644 index 0000000000..b209e97153 --- /dev/null +++ b/DisplayMac/UIGestureRecognizer.swift @@ -0,0 +1,47 @@ +import Foundation + +public enum UIGestureRecognizerState : Int { + case possible + case began + case changed + case ended + case cancelled + case failed +} +open class UIGestureRecognizer: NSObject { + public init(target: Any?, action: Selector?) { + super.init() + } + + open var state: UIGestureRecognizerState = .possible { + didSet { + + } + } + + weak open var delegate: UIGestureRecognizerDelegate? + + open var isEnabled: Bool = true + + open var view: UIView? { + return nil + } + + open var cancelsTouchesInView: Bool = true + open var delaysTouchesBegan: Bool = false + open var delaysTouchesEnded: Bool = true + + open func location(in view: UIView?) -> CGPoint { + return CGPoint() + } + + open var numberOfTouches: Int { + return 0 + } +} + +@objc public protocol UIGestureRecognizerDelegate : NSObjectProtocol { + @objc optional func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool + @objc optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool + @objc optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool +} diff --git a/DisplayMac/UIKit.swift b/DisplayMac/UIKit.swift new file mode 100644 index 0000000000..54c06ae71c --- /dev/null +++ b/DisplayMac/UIKit.swift @@ -0,0 +1,71 @@ +import Foundation +import QuartzCore + +public struct UIEdgeInsets: Equatable { + public let top: CGFloat + public let left: CGFloat + public let bottom: CGFloat + public let right: CGFloat + + public init() { + self.top = 0.0 + self.left = 0.0 + self.bottom = 0.0 + self.right = 0.0 + } + + public init(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) { + self.top = top + self.left = left + self.bottom = bottom + self.right = right + } + + public static func ==(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> Bool { + if !lhs.top.isEqual(to: rhs.top) { + return false + } + if !lhs.left.isEqual(to: rhs.left) { + return false + } + if !lhs.bottom.isEqual(to: rhs.bottom) { + return false + } + if !lhs.right.isEqual(to: rhs.right) { + return false + } + return true + } +} + +public final class UIColor: NSObject { + let cgColor: CGColor + + init(rgb: Int32) { + preconditionFailure() + } + + init(cgColor: CGColor) { + self.cgColor = cgColor + } +} + +open class CASeeThroughTracingLayer: CALayer { + +} + +open class CASeeThroughTracingView: UIView { + +} + +func makeSpringAnimation(_ keyPath: String) -> CABasicAnimation { + return CABasicAnimation(keyPath: keyPath) +} + +func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CABasicAnimation { + return CABasicAnimation(keyPath: keyPath) +} + +func springAnimationValueAt(_ animation: CABasicAnimation, _ t: CGFloat) -> CGFloat { + return t +} diff --git a/DisplayMac/UIScrollView.swift b/DisplayMac/UIScrollView.swift new file mode 100644 index 0000000000..d3c4276004 --- /dev/null +++ b/DisplayMac/UIScrollView.swift @@ -0,0 +1,24 @@ +import Foundation +import QuartzCore + +public protocol UIScrollViewDelegate { +} + +open class UIScrollView: UIView { + public var contentOffset: CGPoint { + get { + return self.bounds.origin + } set(value) { + self.bounds.origin = value + } + } + + public var contentSize: CGSize = CGSize() { + didSet { + + } + } + + public var alwaysBoundsVertical: Bool = false + public var alwaysBoundsHorizontal: Bool = false +} diff --git a/DisplayMac/UISlider.swift b/DisplayMac/UISlider.swift new file mode 100644 index 0000000000..622661f29b --- /dev/null +++ b/DisplayMac/UISlider.swift @@ -0,0 +1,5 @@ +import Foundation + +final class UISlider: UIView { + +} diff --git a/DisplayMac/UITouch.swift b/DisplayMac/UITouch.swift new file mode 100644 index 0000000000..21e0edace9 --- /dev/null +++ b/DisplayMac/UITouch.swift @@ -0,0 +1,5 @@ +import Foundation + +public final class UITouch: NSObject { + +} diff --git a/DisplayMac/UIView.swift b/DisplayMac/UIView.swift new file mode 100644 index 0000000000..5fa17be3da --- /dev/null +++ b/DisplayMac/UIView.swift @@ -0,0 +1,49 @@ +import Foundation +import QuartzCore + +open class UIView: NSObject { + public let layer: CALayer + + open var frame: CGRect { + get { + return self.layer.frame + } set(value) { + self.layer.frame = value + } + } + + open var bounds: CGRect { + get { + return self.layer.bounds + } set(value) { + self.layer.bounds = value + } + } + + open var center: CGPoint { + get { + return self.layer.position + } set(value) { + self.layer.position = value + } + } + + init(frame: CGRect) { + self.layer = CALayer() + self.layer.frame = frame + + super.init() + } + + convenience override init() { + self.init(frame: CGRect()) + } + + static func animationDurationFactor() -> Double { + return 1.0 + } + + public func bringSubview(toFront: UIView) { + + } +} From 8131db136dddefbfa0ba418d0a47a03d8ededc28 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 12 Jan 2018 12:44:41 +0400 Subject: [PATCH 047/245] no message --- .../peter.xcuserdatad/xcschemes/xcschememanagement.plist | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 704fdb6c69..1a8a7d206a 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 23 + 11 DisplayMac.xcscheme @@ -17,7 +17,7 @@ DisplayTests.xcscheme orderHint - 24 + 12 SuppressBuildableAutocreation From 2d79df7e751ffde0b0a59ef9dd9bdc038790ac47 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Thu, 8 Feb 2018 14:21:11 +0400 Subject: [PATCH 048/245] no message --- Display.xcodeproj/project.pbxproj | 34 +++- .../xcschemes/DisplayMac.xcscheme | 82 +++++++++ .../xcschemes/xcschememanagement.plist | 2 +- Display/ActionSheetCheckboxItem.swift | 1 + Display/AlertController.swift | 28 ++- Display/AlertControllerNode.swift | 6 +- Display/BarButtonItemWrapper.swift | 49 ------ Display/CASeeThroughTracingLayer.h | 2 - Display/CASeeThroughTracingLayer.m | 4 - Display/ContextMenuContainerNode.swift | 11 +- Display/Font.swift | 24 +++ Display/ListView.swift | 97 ++--------- Display/ListViewScroller.swift | 4 + Display/ListViewTransactionQueue.swift | 4 + Display/NavigationBar.swift | 101 ++++++----- Display/NavigationButtonNode.swift | 161 +++++++++++++++++- Display/NavigationTransitionCoordinator.swift | 2 +- Display/TextAlertController.swift | 24 +-- Display/TooltipController.swift | 114 +++++++++++++ Display/TooltipControllerNode.swift | 118 +++++++++++++ Display/UINavigationItem+Proxy.h | 3 + Display/UINavigationItem+Proxy.m | 35 ++++ Display/WindowContent.swift | 62 +++++-- Display/WindowCoveringView.swift | 7 + DisplayMac/ASDisplayNode.swift | 31 +++- DisplayMac/CADisplayLink.swift | 89 ++++++++++ DisplayMac/UIKit.swift | 8 +- DisplayMac/UIScrollView.swift | 8 +- DisplayMac/UIView.swift | 35 +++- 29 files changed, 904 insertions(+), 242 deletions(-) create mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme delete mode 100644 Display/BarButtonItemWrapper.swift create mode 100644 Display/TooltipController.swift create mode 100644 Display/TooltipControllerNode.swift create mode 100644 Display/WindowCoveringView.swift create mode 100644 DisplayMac/CADisplayLink.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 6f0aefa93f..aeccfbe154 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701972029CAD6006B9E34 /* TooltipController.swift */; }; + D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */; }; D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D01159B91F40E96C0039383E /* DisplayMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -62,6 +64,7 @@ D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */; }; D053CB601D22B4F200DD41DF /* CATracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */; }; + D053DAD12018ECF900993D32 /* WindowCoveringView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D053DAD02018ECF900993D32 /* WindowCoveringView.swift */; }; D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */; }; D05CC2671B69316F00E235A3 /* Display.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2661B69316F00E235A3 /* Display.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; @@ -90,7 +93,6 @@ D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */; }; D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */; }; D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */; }; - D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */; }; D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */; }; D05CC31E1B695A9600E235A3 /* UIBarButtonItem+Proxy.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */; }; @@ -125,7 +127,6 @@ D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; }; D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */; }; D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBD1CC4431D0044FF83 /* Spring.swift */; }; - D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */; }; D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */; }; D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */; }; @@ -151,8 +152,11 @@ D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A021DE473B900BC6096 /* HighlightableButton.swift */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; + D0E8175E2014ED7D00B82BBB /* CADisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */; }; + D0E8175F2014F18F00B82BBB /* ListViewTransactionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; + D0F8C3932014FB7C00236FC5 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; D0FF9B301E7196F6000C66DB /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */; }; /* End PBXBuildFile section */ @@ -167,6 +171,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + D00701972029CAD6006B9E34 /* TooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; + D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipControllerNode.swift; sourceTree = ""; }; D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = ""; }; D01159B71F40E96B0039383E /* DisplayMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DisplayMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -211,6 +217,7 @@ D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CASeeThroughTracingLayer.m; sourceTree = ""; }; D053CB5E1D22B4F200DD41DF /* CATracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CATracingLayer.h; sourceTree = ""; }; D053CB5F1D22B4F200DD41DF /* CATracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CATracingLayer.m; sourceTree = ""; }; + D053DAD02018ECF900993D32 /* WindowCoveringView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowCoveringView.swift; sourceTree = ""; }; D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentationContext.swift; sourceTree = ""; }; D05CC2631B69316F00E235A3 /* Display.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Display.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D05CC2661B69316F00E235A3 /* Display.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Display.h; sourceTree = ""; }; @@ -242,7 +249,6 @@ D05CC30D1B695A9500E235A3 /* NavigationBackButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBackButtonNode.swift; sourceTree = ""; }; D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationButtonNode.swift; sourceTree = ""; }; D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationTitleNode.swift; sourceTree = ""; }; - D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BarButtonItemWrapper.swift; sourceTree = ""; }; D05CC3111B695A9600E235A3 /* UIBarButtonItem+Proxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "UIBarButtonItem+Proxy.m"; sourceTree = ""; }; D05CC3121B695A9600E235A3 /* UIBarButtonItem+Proxy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIBarButtonItem+Proxy.h"; sourceTree = ""; }; D05CC3131B695A9600E235A3 /* NavigationControllerProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationControllerProxy.m; sourceTree = ""; }; @@ -304,6 +310,7 @@ D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E35A021DE473B900BC6096 /* HighlightableButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableButton.swift; sourceTree = ""; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; + D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CADisplayLink.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; @@ -338,6 +345,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + D00701962029CAC4006B9E34 /* Tooltip */ = { + isa = PBXGroup; + children = ( + D00701972029CAD6006B9E34 /* TooltipController.swift */, + D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */, + ); + name = Tooltip; + sourceTree = ""; + }; D01159B81F40E96C0039383E /* DisplayMac */ = { isa = PBXGroup; children = ( @@ -351,6 +367,7 @@ D01847761FFA78C100075256 /* UIGestureRecognizer.swift */, D01847781FFA7A4E00075256 /* UITouch.swift */, D018477B1FFA7ABF00075256 /* UISlider.swift */, + D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */, ); path = DisplayMac; sourceTree = ""; @@ -363,6 +380,7 @@ D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */, D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */, D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */, + D053DAD02018ECF900993D32 /* WindowCoveringView.swift */, ); name = Window; sourceTree = ""; @@ -579,7 +597,6 @@ D05CC30E1B695A9500E235A3 /* NavigationButtonNode.swift */, D05CC30F1B695A9500E235A3 /* NavigationTitleNode.swift */, D0C12A191F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift */, - D05CC3101B695A9600E235A3 /* BarButtonItemWrapper.swift */, D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */, D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */, D077B8E81F4637040046D27A /* NavigationBarBadge.swift */, @@ -631,6 +648,7 @@ D015F7551D1B142300E269B5 /* Tab Bar */, D015F7561D1B465600E269B5 /* Action Sheet */, D03725BF1D6DF57B007FC290 /* Context Menu */, + D00701962029CAC4006B9E34 /* Tooltip */, D0DA444A1E4DCA36005FDCA7 /* Alert */, ); name = Controllers; @@ -860,6 +878,7 @@ D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */, D01847611FFA703B00075256 /* UIKit.swift in Sources */, D01847701FFA773100075256 /* ListView.swift in Sources */, + D0E8175F2014F18F00B82BBB /* ListViewTransactionQueue.swift in Sources */, D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */, D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */, D01847631FFA70FC00075256 /* UIView.swift in Sources */, @@ -877,6 +896,7 @@ D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */, D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */, D01847741FFA780400075256 /* UIScrollView.swift in Sources */, + D0E8175E2014ED7D00B82BBB /* CADisplayLink.swift in Sources */, D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -891,6 +911,7 @@ D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */, + D053DAD12018ECF900993D32 /* WindowCoveringView.swift in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, D0BB4EBA1F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, @@ -905,6 +926,7 @@ D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */, + D0F8C3932014FB7C00236FC5 /* ListView.swift in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, D05CC2F71B6955D000E235A3 /* UIKitUtils.swift in Sources */, @@ -916,9 +938,9 @@ D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, + D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */, D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */, D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */, - D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, @@ -933,7 +955,6 @@ D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, D04C468E1F4C97BE00D30FE1 /* PageControlNode.swift in Sources */, - D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */, D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */, D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, @@ -966,6 +987,7 @@ D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, + D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */, D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme new file mode 100644 index 0000000000..4c933753f7 --- /dev/null +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 1a8a7d206a..4b2e9b0571 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayMac.xcscheme orderHint - 26 + 24 DisplayTests.xcscheme diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift index 26a3cfe2d9..36a5b2e340 100644 --- a/Display/ActionSheetCheckboxItem.swift +++ b/Display/ActionSheetCheckboxItem.swift @@ -54,6 +54,7 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.labelNode = ASTextNode() self.labelNode.maximumNumberOfLines = 1 + self.labelNode.truncationMode = .byTruncatingTail self.labelNode.isUserInteractionEnabled = false self.labelNode.displaysAsynchronously = false diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 37bcb5f2d3..c8a1db2987 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -1,17 +1,41 @@ import Foundation import AsyncDisplayKit +public final class AlertControllerTheme { + public let backgroundColor: UIColor + public let separatorColor: UIColor + public let highlightedItemColor: UIColor + public let primaryColor: UIColor + public let secondaryColor: UIColor + public let accentColor: UIColor + public let destructiveColor: UIColor + + public init(backgroundColor: UIColor, separatorColor: UIColor, highlightedItemColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, accentColor: UIColor, destructiveColor: UIColor) { + self.backgroundColor = backgroundColor + self.separatorColor = separatorColor + self.highlightedItemColor = highlightedItemColor + self.primaryColor = primaryColor + self.secondaryColor = secondaryColor + self.accentColor = accentColor + self.destructiveColor = destructiveColor + } +} + open class AlertController: ViewController { private var controllerNode: AlertControllerNode { return self.displayNode as! AlertControllerNode } + private let theme: AlertControllerTheme private let contentNode: AlertContentNode - public init(contentNode: AlertContentNode) { + public init(theme: AlertControllerTheme, contentNode: AlertContentNode) { + self.theme = theme self.contentNode = contentNode super.init(navigationBarTheme: nil) + + self.statusBar.statusBarStyle = .Ignore } required public init(coder aDecoder: NSCoder) { @@ -19,7 +43,7 @@ open class AlertController: ViewController { } override open func loadDisplayNode() { - self.displayNode = AlertControllerNode(contentNode: self.contentNode) + self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme) self.displayNodeDidLoad() self.controllerNode.dismiss = { [weak self] in diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index bab1916e40..65ca70aacc 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -9,17 +9,17 @@ final class AlertControllerNode: ASDisplayNode { var dismiss: (() -> Void)? - init(contentNode: AlertContentNode) { + init(contentNode: AlertContentNode, theme: AlertControllerTheme) { self.dimmingNode = ASDisplayNode() self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) self.containerNode = ASDisplayNode() - self.containerNode.backgroundColor = .white + self.containerNode.backgroundColor = theme.backgroundColor self.containerNode.layer.cornerRadius = 14.0 self.containerNode.layer.masksToBounds = true self.effectNode = ASDisplayNode(viewBlock: { - let view = UIView()//UIVisualEffectView(effect: UIBlurEffect(style: .light)) + let view = UIView() return view }) diff --git a/Display/BarButtonItemWrapper.swift b/Display/BarButtonItemWrapper.swift deleted file mode 100644 index 3e6f1b5695..0000000000 --- a/Display/BarButtonItemWrapper.swift +++ /dev/null @@ -1,49 +0,0 @@ -import UIKit -import AsyncDisplayKit - -internal class BarButtonItemWrapper { - let parentNode: ASDisplayNode - let barButtonItem: UIBarButtonItem - let layoutNeeded: () -> () - - let buttonNode: NavigationButtonNode - - private var setEnabledListenerKey: Int! - private var setTitleListenerKey: Int! - - init(parentNode: ASDisplayNode, barButtonItem: UIBarButtonItem, layoutNeeded: @escaping () -> ()) { - self.parentNode = parentNode - self.barButtonItem = barButtonItem - self.layoutNeeded = layoutNeeded - - self.buttonNode = NavigationButtonNode() - self.buttonNode.pressed = { [weak self] in - self?.barButtonItem.performActionOnTarget() - return - } - self.parentNode.addSubnode(self.buttonNode) - - self.setEnabledListenerKey = barButtonItem.addSetEnabledListener({ [weak self] enabled in - self?.buttonNode.isEnabled = enabled - return - }) - - self.setTitleListenerKey = barButtonItem.addSetTitleListener({ [weak self] title in - self?.buttonNode.text = title ?? "" - if let layoutNeeded = self?.layoutNeeded { - layoutNeeded() - } - return - }) - - self.buttonNode.text = barButtonItem.title ?? "" - self.buttonNode.isEnabled = barButtonItem.isEnabled ?? true - self.buttonNode.bold = (barButtonItem.style ?? UIBarButtonItemStyle.plain) == UIBarButtonItemStyle.done - } - - deinit { - self.barButtonItem.removeSetTitleListener(self.setTitleListenerKey) - self.barButtonItem.removeSetEnabledListener(self.setEnabledListenerKey) - self.buttonNode.removeFromSupernode() - } -} diff --git a/Display/CASeeThroughTracingLayer.h b/Display/CASeeThroughTracingLayer.h index 231170d8b5..8c6ff3de8e 100644 --- a/Display/CASeeThroughTracingLayer.h +++ b/Display/CASeeThroughTracingLayer.h @@ -2,8 +2,6 @@ @interface CASeeThroughTracingLayer : CALayer -@property (nonatomic, copy) void (^updateRelativePosition)(CGPoint); - @end @interface CASeeThroughTracingView : UIView diff --git a/Display/CASeeThroughTracingLayer.m b/Display/CASeeThroughTracingLayer.m index b3c01da78b..79ff8d3631 100644 --- a/Display/CASeeThroughTracingLayer.m +++ b/Display/CASeeThroughTracingLayer.m @@ -44,10 +44,6 @@ [(CASeeThroughTracingLayer *)sublayer _mirrorTransformToSublayers]; } } - - if (_updateRelativePosition) { - _updateRelativePosition(sublayerParentOffset); - } } @end diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift index 02c6b9f7b9..74d7b9e28f 100644 --- a/Display/ContextMenuContainerNode.swift +++ b/Display/ContextMenuContainerNode.swift @@ -45,6 +45,10 @@ final class ContextMenuContainerNode: ASDisplayNode { override func layout() { super.layout() + self.updateLayout(transition: .immediate) + } + + func updateLayout(transition: ContainedViewLayoutTransition) { //self.effectView.frame = self.bounds let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true) @@ -78,7 +82,12 @@ final class ContextMenuContainerNode: ASDisplayNode { path.close() self.cachedMaskParams = maskParams - (self.maskView.layer as? CAShapeLayer)?.path = path.cgPath + if let layer = self.maskView.layer as? CAShapeLayer { + if case let .animated(duration, curve) = transition, let previousPath = layer.path { + layer.animate(from: previousPath, to: path.cgPath, keyPath: "path", timingFunction: curve.timingFunction, duration: duration) + } + layer.path = path.cgPath + } } } } diff --git a/Display/Font.swift b/Display/Font.swift index 89461a969a..ed0a02001d 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -38,6 +38,30 @@ public struct Font { } } + public static func semiboldItalic(_ size: CGFloat) -> UIFont { + if let descriptor = UIFont.systemFont(ofSize: size).fontDescriptor.withSymbolicTraits([.traitBold, .traitItalic]) { + return UIFont(descriptor: descriptor, size: size) + } else { + return UIFont.italicSystemFont(ofSize: size) + } + } + + public static func monospace(_ size: CGFloat) -> UIFont { + return UIFont(name: "Menlo-Regular", size: size - 1.0) ?? UIFont.systemFont(ofSize: size) + } + + public static func semiboldMonospace(_ size: CGFloat) -> UIFont { + return UIFont(name: "Menlo-Bold", size: size - 1.0) ?? UIFont.systemFont(ofSize: size) + } + + public static func italicMonospace(_ size: CGFloat) -> UIFont { + return UIFont(name: "Menlo-Italic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size) + } + + public static func semiboldItalicMonospace(_ size: CGFloat) -> UIFont { + return UIFont(name: "Menlo-BoldItalic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size) + } + public static func italic(_ size: CGFloat) -> UIFont { return UIFont.italicSystemFont(ofSize: size) } diff --git a/Display/ListView.swift b/Display/ListView.swift index 8896f99983..5d12a1c7bf 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -6,8 +6,6 @@ import AsyncDisplayKit import SwiftSignalKit #endif -private let usePerformanceTracker = false -private let useDynamicTuning = false private let useBackgroundDeallocation = false private let infiniteScrollSize: CGFloat = 10000.0 @@ -34,13 +32,7 @@ private final class ListViewBackingLayer: CALayer { } } -#if os(iOS) -typealias ListBaseView = UIView -#else -typealias ListBaseView = NSView -#endif - -final class ListViewBackingView: ListBaseView { +final class ListViewBackingView: UIView { weak var target: ListView? override class var layerClass: AnyClass { @@ -56,6 +48,7 @@ final class ListViewBackingView: ListBaseView { override func setNeedsDisplay() { } + #if os(iOS) override func touchesBegan(_ touches: Set, with event: UIEvent?) { self.target?.touchesBegan(touches, with: event) } @@ -80,6 +73,7 @@ final class ListViewBackingView: ListBaseView { } return super.hitTest(point, with: event) } + #endif } private final class ListViewTimerProxy: NSObject { @@ -212,8 +206,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private let freeResistanceSlider = UISlider() private let scrollingResistanceSlider = UISlider() - //let performanceTracker: FBAnimationPerformanceTracker - private var selectionTouchLocation: CGPoint? private var selectionTouchDelayTimer: Foundation.Timer? private var selectionLongTapDelayTimer: Foundation.Timer? @@ -246,10 +238,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.scroller = ListViewScroller() - /*var performanceTrackerConfig = FBAnimationPerformanceTracker.standardConfig() - performanceTrackerConfig.reportStackTraces = true - self.performanceTracker = FBAnimationPerformanceTracker(config: performanceTrackerConfig)*/ - super.init() self.setViewBlock({ () -> UIView in @@ -266,8 +254,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - //self.performanceTracker.delegate = self - self.scroller.alwaysBounceVertical = true self.scroller.contentSize = CGSize(width: 0.0, height: infiniteScrollSize * 2.0) self.scroller.isHidden = true @@ -282,38 +268,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) + #if os(iOS) if #available(iOS 10.0, *) { self.displayLink.preferredFramesPerSecond = 60 } + #endif self.displayLink.isPaused = true - - if useDynamicTuning { - self.frictionSlider.addTarget(self, action: #selector(self.frictionSliderChanged(_:)), for: .valueChanged) - self.springSlider.addTarget(self, action: #selector(self.springSliderChanged(_:)), for: .valueChanged) - self.freeResistanceSlider.addTarget(self, action: #selector(self.freeResistanceSliderChanged(_:)), for: .valueChanged) - self.scrollingResistanceSlider.addTarget(self, action: #selector(self.scrollingResistanceSliderChanged(_:)), for: .valueChanged) - - self.frictionSlider.minimumValue = Float(testSpringFrictionLimits.0) - self.frictionSlider.maximumValue = Float(testSpringFrictionLimits.1) - self.frictionSlider.value = Float(testSpringFriction) - - self.springSlider.minimumValue = Float(testSpringConstantLimits.0) - self.springSlider.maximumValue = Float(testSpringConstantLimits.1) - self.springSlider.value = Float(testSpringConstant) - - self.freeResistanceSlider.minimumValue = Float(testSpringResistanceFreeLimits.0) - self.freeResistanceSlider.maximumValue = Float(testSpringResistanceFreeLimits.1) - self.freeResistanceSlider.value = Float(testSpringFreeResistance) - - self.scrollingResistanceSlider.minimumValue = Float(testSpringResistanceScrollingLimits.0) - self.scrollingResistanceSlider.maximumValue = Float(testSpringResistanceScrollingLimits.1) - self.scrollingResistanceSlider.value = Float(testSpringScrollingResistance) - - self.view.addSubview(self.frictionSlider) - self.view.addSubview(self.springSlider) - self.view.addSubview(self.freeResistanceSlider) - self.view.addSubview(self.scrollingResistanceSlider) - } } deinit { @@ -344,26 +304,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.waitingForNodesDisposable.dispose() } - @objc func frictionSliderChanged(_ slider: UISlider) { - testSpringFriction = CGFloat(slider.value) - print("friction: \(testSpringFriction)") - } - - @objc func springSliderChanged(_ slider: UISlider) { - testSpringConstant = CGFloat(slider.value) - print("spring: \(testSpringConstant)") - } - - @objc func freeResistanceSliderChanged(_ slider: UISlider) { - testSpringFreeResistance = CGFloat(slider.value) - print("free resistance: \(testSpringFreeResistance)") - } - - @objc func scrollingResistanceSliderChanged(_ slider: UISlider) { - testSpringScrollingResistance = CGFloat(slider.value) - print("free resistance: \(testSpringScrollingResistance)") - } - private func displayLinkEvent() { self.updateAnimations() } @@ -451,9 +391,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateHeaderItemsFlashing(animated: true) self.lastContentOffsetTimestamp = 0.0 - /*if usePerformanceTracker { - self.performanceTracker.stop() - }*/ } } @@ -462,10 +399,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.isDeceleratingAfterTracking = false self.resetHeaderItemsFlashTimer(start: true) self.updateHeaderItemsFlashing(animated: true) - - /*if usePerformanceTracker { - self.performanceTracker.stop() - }*/ } public func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -1083,14 +1016,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets - if useDynamicTuning { - let size = updateSizeAndInsets.size - self.frictionSlider.frame = CGRect(x: 10.0, y: size.height - insets.bottom - 10.0 - self.frictionSlider.bounds.height, width: size.width - 20.0, height: self.frictionSlider.bounds.height) - self.springSlider.frame = CGRect(x: 10.0, y: self.frictionSlider.frame.minY - self.springSlider.bounds.height, width: size.width - 20.0, height: self.springSlider.bounds.height) - self.freeResistanceSlider.frame = CGRect(x: 10.0, y: self.springSlider.frame.minY - self.freeResistanceSlider.bounds.height, width: size.width - 20.0, height: self.freeResistanceSlider.bounds.height) - self.scrollingResistanceSlider.frame = CGRect(x: 10.0, y: self.freeResistanceSlider.frame.minY - self.scrollingResistanceSlider.bounds.height, width: size.width - 20.0, height: self.scrollingResistanceSlider.bounds.height) - } - let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size) @@ -1596,12 +1521,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel apply().1() self.itemNodes.insert(node, at: nodeIndex) - if useDynamicTuning { - self.insertSubnode(node, at: 0) - } else { - //self.addSubnode(node) - } - var offsetHeight = node.apparentHeight var takenAnimation = false @@ -2954,6 +2873,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + #if os(iOS) override open func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchesPosition = touches.first!.location(in: self.view) @@ -3043,6 +2963,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateScroller(transition: .immediate) } + #endif public func clearHighlightAnimated(_ animated: Bool) { if let highlightedItemIndex = self.highlightedItemIndex { @@ -3096,6 +3017,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return nil } + #if os(iOS) override open func touchesMoved(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { let location = touches.first!.location(in: self.view) @@ -3177,6 +3099,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel break } } + #endif public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -3189,6 +3112,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + #if os(iOS) fileprivate func internalHitTest(_ point: CGPoint, with event: UIEvent?) -> Bool { if self.limitHitTestToNodes { var foundHit = false @@ -3204,4 +3128,5 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } return true } + #endif } diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index c853d1fd0c..55ac809eea 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -7,10 +7,12 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { override init(frame: CGRect) { super.init(frame: frame) + #if os(iOS) self.scrollsToTop = false if #available(iOSApplicationExtension 11.0, *) { self.contentInsetAdjustmentBehavior = .never } + #endif } required init?(coder aDecoder: NSCoder) { @@ -21,7 +23,9 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { return false } + #if os(iOS) override func touchesShouldCancel(in view: UIView) -> Bool { return true } + #endif } diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 0513e7f1e1..689c8dbc81 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -1,5 +1,9 @@ import Foundation +#if os(iOS) import SwiftSignalKit +#else +import SwiftSignalKitMac +#endif public typealias ListViewTransaction = (@escaping () -> Void) -> Void diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index cdb421e869..1fd46152f3 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -75,7 +75,7 @@ open class NavigationBar: ASDisplayNode { private let stripeNode: ASDisplayNode private let clippingNode: ASDisplayNode - var contentNode: NavigationBarContentNode? + public private(set) var contentNode: NavigationBarContentNode? private var itemTitleListenerKey: Int? private var itemTitleViewListenerKey: Int? @@ -84,7 +84,7 @@ open class NavigationBar: ASDisplayNode { private var itemLeftButtonSetEnabledListenerKey: Int? private var itemRightButtonListenerKey: Int? - private var itemRightButtonSetEnabledListenerKey: Int? + private var itemRightButtonsListenerKey: Int? private var itemBadgeListenerKey: Int? @@ -116,9 +116,9 @@ open class NavigationBar: ASDisplayNode { self.itemRightButtonListenerKey = nil } - if let itemRightButtonSetEnabledListenerKey = self.itemRightButtonSetEnabledListenerKey { - previousValue.rightBarButtonItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey) - self.itemRightButtonSetEnabledListenerKey = nil + if let itemRightButtonsListenerKey = self.itemRightButtonsListenerKey { + previousValue.removeSetMultipleRightBarButtonItemsListener(itemRightButtonsListenerKey) + self.itemRightButtonsListenerKey = nil } if let itemBadgeListenerKey = self.itemBadgeListenerKey { @@ -165,19 +165,14 @@ open class NavigationBar: ASDisplayNode { self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, animated in if let strongSelf = self { - if let itemRightButtonSetEnabledListenerKey = strongSelf.itemRightButtonSetEnabledListenerKey { - previousItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey) - strongSelf.itemRightButtonSetEnabledListenerKey = nil - } - - if let currentItem = currentItem { - strongSelf.itemRightButtonSetEnabledListenerKey = currentItem.addSetEnabledListener { _ in - if let strongSelf = self { - strongSelf.updateRightButton(animated: false) - } - } - } - + strongSelf.updateRightButton(animated: animated) + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.itemRightButtonsListenerKey = item.addSetMultipleRightBarButtonItemsListener { [weak self] items, animated in + if let strongSelf = self { strongSelf.updateRightButton(animated: animated) strongSelf.invalidateCalculatedLayout() strongSelf.requestLayout() @@ -260,9 +255,9 @@ open class NavigationBar: ASDisplayNode { self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] _, _ in if let strongSelf = self, let previousItem = strongSelf.previousItem { if let backBarButtonItem = previousItem.backBarButtonItem { - strongSelf.backButtonNode.text = backBarButtonItem.title ?? "" + strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") } else { - strongSelf.backButtonNode.text = previousItem.title ?? "" + strongSelf.backButtonNode.updateManualText(previousItem.title ?? "") } strongSelf.invalidateCalculatedLayout() strongSelf.requestLayout() @@ -272,9 +267,9 @@ open class NavigationBar: ASDisplayNode { self.previousItemBackListenerKey = previousItem.addSetBackBarButtonItemListener { [weak self] _, _, _ in if let strongSelf = self, let previousItem = strongSelf.previousItem { if let backBarButtonItem = previousItem.backBarButtonItem { - strongSelf.backButtonNode.text = backBarButtonItem.title ?? "" + strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") } else { - strongSelf.backButtonNode.text = previousItem.title ?? "" + strongSelf.backButtonNode.updateManualText(previousItem.title ?? "") } strongSelf.invalidateCalculatedLayout() strongSelf.requestLayout() @@ -348,9 +343,8 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.removeFromSupernode() self.badgeNode.removeFromSupernode() - self.leftButtonNode.text = leftBarButtonItem.title ?? "" - self.leftButtonNode.bold = leftBarButtonItem.style == .done - self.leftButtonNode.isEnabled = leftBarButtonItem.isEnabled + self.leftButtonNode.updateItems([leftBarButtonItem]) + if self.leftButtonNode.supernode == nil { self.clippingNode.addSubnode(self.leftButtonNode) } @@ -384,7 +378,7 @@ open class NavigationBar: ASDisplayNode { } if let backTitle = backTitle { - self.backButtonNode.text = backTitle + self.backButtonNode.updateManualText(backTitle) if self.backButtonNode.supernode == nil { self.clippingNode.addSubnode(self.backButtonNode) self.clippingNode.addSubnode(self.backButtonArrow) @@ -414,7 +408,14 @@ open class NavigationBar: ASDisplayNode { private func updateRightButton(animated: Bool) { if let item = self.item { - if let rightBarButtonItem = item.rightBarButtonItem { + var items: [UIBarButtonItem] = [] + if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { + items = rightBarButtonItems + } else if let rightBarButtonItem = item.rightBarButtonItem { + items = [rightBarButtonItem] + } + + if !items.isEmpty { if animated, self.rightButtonNode.view.superview != nil { if let snapshotView = self.rightButtonNode.view.snapshotContentTree() { snapshotView.frame = self.rightButtonNode.frame @@ -424,11 +425,7 @@ open class NavigationBar: ASDisplayNode { }) } } - self.rightButtonNode.text = rightBarButtonItem.title ?? "" - self.rightButtonNode.image = rightBarButtonItem.image - self.rightButtonNode.bold = rightBarButtonItem.style == .done - self.rightButtonNode.isEnabled = rightBarButtonItem.isEnabled - self.rightButtonNode.node = rightBarButtonItem.customDisplayNode + self.rightButtonNode.updateItems(items) if self.rightButtonNode.supernode == nil { self.clippingNode.addSubnode(self.rightButtonNode) } @@ -565,13 +562,13 @@ open class NavigationBar: ASDisplayNode { self.titleNode.truncationMode = .byTruncatingTail self.titleNode.isOpaque = false - self.backButtonNode.highlightChanged = { [weak self] highlighted in - if let strongSelf = self { + self.backButtonNode.highlightChanged = { [weak self] index, highlighted in + if let strongSelf = self, index == 0 { strongSelf.backButtonArrow.alpha = (highlighted ? 0.4 : 1.0) } } - self.backButtonNode.pressed = { [weak self] in - if let strongSelf = self { + self.backButtonNode.pressed = { [weak self] index in + if let strongSelf = self, index == 0 { if let leftBarButtonItem = strongSelf.item?.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { leftBarButtonItem.performActionOnTarget() } else { @@ -580,15 +577,25 @@ open class NavigationBar: ASDisplayNode { } } - self.leftButtonNode.pressed = { [weak self] in - if let item = self?.item, let leftBarButtonItem = item.leftBarButtonItem { - leftBarButtonItem.performActionOnTarget() + self.leftButtonNode.pressed = { [weak self] index in + if let item = self?.item { + if index == 0 { + if let leftBarButtonItem = item.leftBarButtonItem { + leftBarButtonItem.performActionOnTarget() + } + } } } - self.rightButtonNode.pressed = { [weak self] in - if let item = self?.item, let rightBarButtonItem = item.rightBarButtonItem { - rightBarButtonItem.performActionOnTarget() + self.rightButtonNode.pressed = { [weak self] index in + if let item = self?.item { + if let rightBarButtonItems = item.rightBarButtonItems, !rightBarButtonItems.isEmpty { + if index < rightBarButtonItems.count { + rightBarButtonItems[index].performActionOnTarget() + } + } else if let rightBarButtonItem = item.rightBarButtonItem { + rightBarButtonItem.performActionOnTarget() + } } } } @@ -645,7 +652,7 @@ open class NavigationBar: ASDisplayNode { var leftTitleInset: CGFloat = leftInset + 4.0 var rightTitleInset: CGFloat = rightInset + 4.0 if self.backButtonNode.supernode != nil { - let backButtonSize = self.backButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) leftTitleInset += backButtonSize.width + backButtonInset + 4.0 + 4.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 @@ -690,7 +697,7 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.alpha = 1.0 } } else if self.leftButtonNode.supernode != nil { - let leftButtonSize = self.leftButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) leftTitleInset += leftButtonSize.width + leftButtonInset + 8.0 + 8.0 self.leftButtonNode.alpha = 1.0 @@ -702,7 +709,7 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.frame = CGRect(origin: backButtonArrowFrame.origin.offsetBy(dx: 7.0, dy: -9.0), size: badgeSize) if self.rightButtonNode.supernode != nil { - let rightButtonSize = self.rightButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight))) rightTitleInset += rightButtonSize.width + leftButtonInset + 8.0 + 8.0 self.rightButtonNode.alpha = 1.0 self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize) @@ -716,7 +723,7 @@ open class NavigationBar: ASDisplayNode { break case .bottom: if let transitionBackButtonNode = self.transitionBackButtonNode { - let transitionBackButtonSize = transitionBackButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + let transitionBackButtonSize = transitionBackButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) let initialX: CGFloat = backButtonInset + size.width * 0.3 let finalX: CGFloat = floor((size.width - transitionBackButtonSize.width) / 2.0) @@ -831,7 +838,7 @@ open class NavigationBar: ASDisplayNode { private func makeTransitionBackButtonNode(accentColor: UIColor) -> NavigationButtonNode? { if self.backButtonNode.supernode != nil { let node = NavigationButtonNode() - node.text = self.backButtonNode.text + node.updateManualText(self.backButtonNode.manualText) node.color = accentColor return node } else { diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 87afb05a70..7bda908127 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -1,7 +1,7 @@ import UIKit import AsyncDisplayKit -public class NavigationButtonNode: ASTextNode { +private final class NavigationButtonItemNode: ASTextNode { private func fontForCurrentState() -> UIFont { return self.bold ? UIFont.boldSystemFont(ofSize: 17.0) : UIFont.systemFont(ofSize: 17.0) } @@ -13,6 +13,25 @@ public class NavigationButtonNode: ASTextNode { ] } + private var setEnabledListener: Int? + + var item: UIBarButtonItem? { + didSet { + if self.item !== oldValue { + if let oldValue = oldValue, let setEnabledListener = self.setEnabledListener { + oldValue.removeSetEnabledListener(setEnabledListener) + self.setEnabledListener = nil + } + + if let item = self.item { + self.setEnabledListener = item.addSetEnabledListener { [weak self] value in + self?.isEnabled = value + } + } + } + } + } + private var _text: String? public var text: String { get { @@ -174,13 +193,13 @@ public class NavigationButtonNode: ASTextNode { let alpha: CGFloat = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) /*if animated { - UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.beginFromCurrentState, animations: { () -> Void in - self.alpha = alpha - }, completion: nil) - } - else {*/ - self.alpha = alpha - self.highlightChanged(highlighted) + UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.beginFromCurrentState, animations: { () -> Void in + self.alpha = alpha + }, completion: nil) + } + else {*/ + self.alpha = alpha + self.highlightChanged(highlighted) //} } } @@ -192,9 +211,133 @@ public class NavigationButtonNode: ASTextNode { set(value) { if self.isEnabled != value { super.isEnabled = value - + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) } } } } + + +final class NavigationButtonNode: ASDisplayNode { + private var nodes: [NavigationButtonItemNode] = [] + + public var pressed: (Int) -> () = { _ in } + public var highlightChanged: (Int, Bool) -> () = { _, _ in } + + public var color: UIColor = UIColor(rgb: 0x007ee5) { + didSet { + if !self.color.isEqual(oldValue) { + for node in self.nodes { + node.color = self.color + } + } + } + } + + override init() { + super.init() + } + + var manualText: String { + return self.nodes.first?.text ?? "" + } + + func updateManualText(_ text: String) { + let node: NavigationButtonItemNode + if self.nodes.count > 0 { + node = self.nodes[0] + } else { + node = NavigationButtonItemNode() + node.color = self.color + node.highlightChanged = { [weak node, weak self] value in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.index(where: { $0 === node }) { + strongSelf.highlightChanged(index, value) + } + } + } + node.pressed = { [weak self, weak node] in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.index(where: { $0 === node }) { + strongSelf.pressed(index) + } + } + } + self.nodes.append(node) + self.addSubnode(node) + } + node.item = nil + node.text = text + node.image = nil + node.bold = false + node.isEnabled = true + node.node = nil + + if 1 < self.nodes.count { + for i in 1 ..< self.nodes.count { + self.nodes[i].removeFromSupernode() + } + self.nodes.removeSubrange(1...) + } + } + + func updateItems(_ items: [UIBarButtonItem]) { + for i in 0 ..< items.count { + let node: NavigationButtonItemNode + if self.nodes.count > i { + node = self.nodes[i] + } else { + node = NavigationButtonItemNode() + node.color = self.color + node.highlightChanged = { [weak node, weak self] value in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.index(where: { $0 === node }) { + strongSelf.highlightChanged(index, value) + } + } + } + node.pressed = { [weak self, weak node] in + if let strongSelf = self, let node = node { + if let index = strongSelf.nodes.index(where: { $0 === node }) { + strongSelf.pressed(index) + } + } + } + self.nodes.append(node) + self.addSubnode(node) + } + node.item = items[i] + node.text = items[i].title ?? "" + node.image = items[i].image + node.bold = items[i].style == .done + node.isEnabled = items[i].isEnabled + node.node = items[i].customDisplayNode + } + if items.count < self.nodes.count { + for i in items.count ..< self.nodes.count { + self.nodes[i].removeFromSupernode() + } + self.nodes.removeSubrange(items.count...) + } + } + + func updateLayout(constrainedSize: CGSize) -> CGSize { + var nodeOrigin = CGPoint() + var totalSize = CGSize() + for node in self.nodes { + if !totalSize.width.isZero { + totalSize.width += 16.0 + nodeOrigin.x += 16.0 + } + var nodeSize = node.calculateSizeThatFits(constrainedSize) + nodeSize.width = ceil(nodeSize.width) + nodeSize.height = ceil(nodeSize.height) + totalSize.width += nodeSize.width + totalSize.height = max(totalSize.height, nodeSize.height) + node.frame = CGRect(origin: nodeOrigin, size: nodeSize) + nodeOrigin.x += node.bounds.width + } + return totalSize + } +} diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 1aae757ce6..8749945c1e 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -56,7 +56,7 @@ class NavigationTransitionCoordinator { self.dimView.backgroundColor = UIColor.black self.shadowView = UIImageView(image: shadowImage) - if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden { + if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.contentNode == nil, bottomNavigationBar.contentNode == nil { var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container) var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container) topFrame.origin.x = 0.0 diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index fce5275127..60ebfe9d12 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -24,10 +24,10 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { let action: TextAlertAction - init(action: TextAlertAction) { + init(theme: AlertControllerTheme, action: TextAlertAction) { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true - self.backgroundNode.backgroundColor = UIColor(rgb: 0xe0e5e6) + self.backgroundNode.backgroundColor = theme.highlightedItemColor self.backgroundNode.alpha = 0.0 self.action = action @@ -36,12 +36,12 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { self.titleNode.maximumNumberOfLines = 2 let font = Font.regular(17.0) - var color = UIColor(rgb: 0x007ee5) + var color = theme.accentColor switch action.type { case .defaultAction, .genericAction: break case .destructiveAction: - color = UIColor(rgb: 0xff3b30) + color = theme.destructiveColor } self.setAttributedTitle(NSAttributedString(string: action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) @@ -86,7 +86,7 @@ final class TextAlertContentNode: AlertContentNode { private let actionNodes: [TextAlertContentActionNode] private let actionVerticalSeparators: [ASDisplayNode] - init(title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) { + init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) { if let title = title { let titleNode = ASTextNode() titleNode.attributedText = title @@ -106,10 +106,10 @@ final class TextAlertContentNode: AlertContentNode { self.actionNodesSeparator = ASDisplayNode() self.actionNodesSeparator.isLayerBacked = true - self.actionNodesSeparator.backgroundColor = UIColor(rgb: 0xc9cdd7) + self.actionNodesSeparator.backgroundColor = theme.separatorColor self.actionNodes = actions.map { action -> TextAlertContentActionNode in - return TextAlertContentActionNode(action: action) + return TextAlertContentActionNode(theme: theme, action: action) } var actionVerticalSeparators: [ASDisplayNode] = [] @@ -117,7 +117,7 @@ final class TextAlertContentNode: AlertContentNode { for _ in 0 ..< actions.count - 1 { let separatorNode = ASDisplayNode() separatorNode.isLayerBacked = true - separatorNode.backgroundColor = UIColor(rgb: 0xc9cdd7) + separatorNode.backgroundColor = theme.separatorColor actionVerticalSeparators.append(separatorNode) } } @@ -216,13 +216,13 @@ final class TextAlertContentNode: AlertContentNode { } } -public func textAlertController(title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) -> AlertController { - return AlertController(contentNode: TextAlertContentNode(title: title, text: text, actions: actions)) +public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) -> AlertController { + return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions)) } -public func standardTextAlertController(title: String?, text: String, actions: [TextAlertAction]) -> AlertController { +public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction]) -> AlertController { var dismissImpl: (() -> Void)? - let controller = AlertController(contentNode: TextAlertContentNode(title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: .black, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.semibold(17.0) : Font.regular(13.0), textColor: .black, paragraphAlignment: .center), actions: actions.map { action in + let controller = AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.semibold(17.0) : Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center), actions: actions.map { action in return TextAlertAction(type: action.type, title: action.title, action: { dismissImpl?() action.action() diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift new file mode 100644 index 0000000000..ca828047a3 --- /dev/null +++ b/Display/TooltipController.swift @@ -0,0 +1,114 @@ +import Foundation +import AsyncDisplayKit +import SwiftSignalKit + +public final class TooltipControllerPresentationArguments { + fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect)? + + public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect)?) { + self.sourceNodeAndRect = sourceNodeAndRect + } +} + +public final class TooltipController: ViewController { + private var controllerNode: TooltipControllerNode { + return self.displayNode as! TooltipControllerNode + } + + public var text: String { + didSet { + if self.text != oldValue { + if self.isNodeLoaded { + self.controllerNode.updateText(self.text, transition: .animated(duration: 0.25, curve: .easeInOut)) + if self.timeoutTimer != nil { + self.timeoutTimer?.invalidate() + self.timeoutTimer = nil + self.beginTimeout() + } + } + } + } + } + + private let timeout: Double + private var timeoutTimer: SwiftSignalKit.Timer? + + private var layout: ContainerViewLayout? + + public var dismissed: (() -> Void)? + + public init(text: String, timeout: Double = 1.0) { + self.text = text + self.timeout = timeout + + super.init(navigationBarTheme: nil) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + self.timeoutTimer?.invalidate() + } + + open override func loadDisplayNode() { + self.displayNode = TooltipControllerNode(text: self.text, dismiss: { [weak self] in + self?.dismissed?() + self?.controllerNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + }) + self.displayNodeDidLoad() + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.controllerNode.animateIn() + self.beginTimeout() + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if self.layout != nil && self.layout! != layout { + self.dismissed?() + self.controllerNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + } else { + self.layout = layout + + if let presentationArguments = self.presentationArguments as? TooltipControllerPresentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { + self.controllerNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) + } else { + self.controllerNode.sourceRect = nil + } + + self.controllerNode.containerLayoutUpdated(layout, transition: transition) + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.controllerNode.animateIn() + self.beginTimeout() + } + + private func beginTimeout() { + if self.timeoutTimer == nil { + let timeoutTimer = SwiftSignalKit.Timer(timeout: self.timeout, repeat: false, completion: { [weak self] in + if let strongSelf = self { + strongSelf.dismissed?() + strongSelf.controllerNode.animateOut { + self?.presentingViewController?.dismiss(animated: false) + } + } + }, queue: Queue.mainQueue()) + self.timeoutTimer = timeoutTimer + timeoutTimer.start() + } + } +} diff --git a/Display/TooltipControllerNode.swift b/Display/TooltipControllerNode.swift new file mode 100644 index 0000000000..a41d943946 --- /dev/null +++ b/Display/TooltipControllerNode.swift @@ -0,0 +1,118 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +final class TooltipControllerNode: ASDisplayNode { + private let dismiss: () -> Void + + private var validLayout: ContainerViewLayout? + + private let containerNode: ContextMenuContainerNode + private let textNode: ASTextNode + + var sourceRect: CGRect? + var arrowOnBottom: Bool = true + + private var dismissedByTouchOutside = false + + init(text: String, dismiss: @escaping () -> Void) { + self.containerNode = ContextMenuContainerNode() + self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + + self.textNode = ASTextNode() + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) + self.textNode.isLayerBacked = true + self.textNode.displaysAsynchronously = false + + self.dismiss = dismiss + + super.init() + + self.containerNode.addSubnode(self.textNode) + + self.addSubnode(self.containerNode) + } + + func updateText(_ text: String, transition: ContainedViewLayoutTransition) { + if transition.isAnimated, let copyLayer = self.textNode.layer.snapshotContentTree() { + copyLayer.frame = self.textNode.layer.frame + self.textNode.layer.superlayer?.addSublayer(copyLayer) + transition.updateAlpha(layer: copyLayer, alpha: 0.0, completion: { [weak copyLayer] _ in + copyLayer?.removeFromSuperlayer() + }) + self.textNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.12) + } + self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: transition) + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + let maxActionsWidth = layout.size.width - 20.0 + + var textSize = self.textNode.measure(CGSize(width: maxActionsWidth, height: CGFloat.greatestFiniteMagnitude)) + textSize.width = ceil(textSize.width / 2.0) * 2.0 + textSize.height = ceil(textSize.height / 2.0) * 2.0 + let contentSize = CGSize(width: textSize.width + 12.0, height: textSize.height + 34.0) + + let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize()) + + let insets = layout.insets(options: [.statusBar, .input]) + + let verticalOrigin: CGFloat + var arrowOnBottom = true + if sourceRect.minY - 54.0 > insets.top { + verticalOrigin = sourceRect.minY - contentSize.height + } else { + verticalOrigin = min(layout.size.height - insets.bottom - contentSize.height, sourceRect.maxY) + arrowOnBottom = false + } + self.arrowOnBottom = arrowOnBottom + + let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - contentSize.width / 2.0), layout.size.width - contentSize.width - 8.0)) + + transition.updateFrame(node: self.containerNode, frame: CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: contentSize)) + self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) + + self.containerNode.updateLayout(transition: transition) + + let textFrame = CGRect(origin: CGPoint(x: 6.0, y: 17.0), size: textSize) + if transition.isAnimated, textFrame.size != self.textNode.frame.size { + transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textFrame.minX - self.textNode.frame.minX, y: 0.0)) + } + self.textNode.frame = textFrame + } + + func animateIn() { + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + } + + func animateOut(completion: @escaping () -> Void) { + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false, completion: { _ in + completion() + }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let event = event { + var eventIsPresses = false + if #available(iOSApplicationExtension 9.0, *) { + eventIsPresses = event.type == .presses + } + if event.type == .touches || eventIsPresses { + if self.containerNode.frame.contains(point) { + if !self.dismissedByTouchOutside { + self.dismissedByTouchOutside = true + self.dismiss() + } + } + return nil + } + } + return super.hitTest(point, with: event) + } +} + diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 08a1ef6da6..5d607cfb47 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -4,6 +4,7 @@ typedef void (^UINavigationItemSetTitleListener)(NSString * _Nullable, bool); typedef void (^UINavigationItemSetTitleViewListener)(UIView * _Nullable); typedef void (^UINavigationItemSetImageListener)(UIImage * _Nullable); typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem * _Nullable, UIBarButtonItem * _Nullable, BOOL); +typedef void (^UINavigationItemSetMutipleBarButtonItemsListener)(NSArray * _Nullable, BOOL); typedef void (^UITabBarItemSetBadgeListener)(NSString * _Nullable); @interface UINavigationItem (Proxy) @@ -20,6 +21,8 @@ typedef void (^UITabBarItemSetBadgeListener)(NSString * _Nullable); - (void)removeSetLeftBarButtonItemListener:(NSInteger)key; - (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener; - (void)removeSetRightBarButtonItemListener:(NSInteger)key; +- (NSInteger)addSetMultipleRightBarButtonItemsListener:(UINavigationItemSetMutipleBarButtonItemsListener _Nonnull)listener; +- (void)removeSetMultipleRightBarButtonItemsListener:(NSInteger)key; - (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener; - (void)removeSetBackBarButtonItemListener:(NSInteger)key; - (NSInteger)addSetBadgeListener:(UITabBarItemSetBadgeListener _Nonnull)listener; diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index 98871e7042..2682e8adfb 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -12,6 +12,7 @@ static const void *setSelectedImageListenerBagKey = &setSelectedImageListenerBag static const void *setTitleViewListenerBagKey = &setTitleViewListenerBagKey; static const void *setLeftBarButtonItemListenerBagKey = &setLeftBarButtonItemListenerBagKey; static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemListenerBagKey; +static const void *setMultipleRightBarButtonItemsListenerKey = &setMultipleRightBarButtonItemsListenerKey; static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey; static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; static const void *badgeKey = &badgeKey; @@ -29,6 +30,8 @@ static const void *badgeKey = &badgeKey; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItems:) newSelector:@selector(_ac91f40f_setRightBarButtonItems:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItems:animated:) newSelector:@selector(_ac91f40f_setRightBarButtonItems:animated:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setBackBarButtonItem:) newSelector:@selector(_ac91f40f_setBackBarButtonItem:)]; }); } @@ -114,6 +117,24 @@ static const void *badgeKey = &badgeKey; } } +- (void)_ac91f40f_setRightBarButtonItems:(NSArray *)rightBarButtonItems { + [self setRightBarButtonItems:rightBarButtonItems animated:false]; +} + +- (void)_ac91f40f_setRightBarButtonItems:(NSArray *)rightBarButtonItems animated:(BOOL)animated +{ + [self _ac91f40f_setRightBarButtonItems:rightBarButtonItems animated:animated]; + + UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey]; + if (targetItem != nil) { + [targetItem setRightBarButtonItems:rightBarButtonItems animated:animated]; + } else { + [(NSBag *)[self associatedObjectForKey:setMultipleRightBarButtonItemsListenerKey] enumerateItems:^(UINavigationItemSetMutipleBarButtonItemsListener listener) { + listener(rightBarButtonItems, animated); + }]; + } +} + - (void)_ac91f40f_setBackBarButtonItem:(UIBarButtonItem *)backBarButtonItem { UIBarButtonItem *previousItem = self.rightBarButtonItem; @@ -218,6 +239,20 @@ static const void *badgeKey = &badgeKey; [(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] removeItem:key]; } +- (NSInteger)addSetMultipleRightBarButtonItemsListener:(UINavigationItemSetMutipleBarButtonItemsListener _Nonnull)listener { + NSBag *bag = [self associatedObjectForKey:setMultipleRightBarButtonItemsListenerKey]; + if (bag == nil) + { + bag = [[NSBag alloc] init]; + [self setAssociatedObject:bag forKey:setMultipleRightBarButtonItemsListenerKey]; + } + return [bag addItem:[listener copy]]; +} + +- (void)removeSetMultipleRightBarButtonItemsListener:(NSInteger)key { + [(NSBag *)[self associatedObjectForKey:setMultipleRightBarButtonItemsListenerKey] removeItem:key]; +} + - (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener)listener { NSBag *bag = [self associatedObjectForKey:setBackBarButtonItemListenerBagKey]; if (bag == nil) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index d74be8d3a8..b2b1f9cdee 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -45,15 +45,11 @@ private struct WindowLayout: Equatable { return false } - if let lhsInputHeight = lhs.inputHeight { - if let rhsInputHeight = rhs.inputHeight { - if !lhsInputHeight.isEqual(to: rhsInputHeight) { - return false - } - } else { + if let lhsInputHeight = lhs.inputHeight, let rhsInputHeight = rhs.inputHeight { + if !lhsInputHeight.isEqual(to: rhsInputHeight) { return false } - } else if let _ = rhs.inputHeight { + } else if (lhs.inputHeight != nil) != (rhs.inputHeight != nil) { return false } @@ -345,6 +341,7 @@ public class Window1 { } } + private var windowPanRecognizer: WindowPanRecognizer? private let keyboardGestureRecognizerDelegate = KeyboardGestureRecognizerDelegate() private var keyboardGestureBeginLocation: CGPoint? private var keyboardGestureAccessoryHeight: CGFloat? @@ -431,7 +428,20 @@ public class Window1 { self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in if let strongSelf = self { let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect() - let keyboardHeight = max(0.0, UIScreen.main.bounds.size.height - keyboardFrame.minY) + + let screenHeight: CGFloat + + if true || !UIScreen.main.bounds.width.isEqual(to: strongSelf.windowLayout.size.width) { + if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) { + screenHeight = UIScreen.main.bounds.height + } else { + screenHeight = UIScreen.main.bounds.width + } + } else { + screenHeight = UIScreen.main.bounds.height + } + + let keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY) var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 if duration > Double.ulpOfOne { duration = 0.5 @@ -491,6 +501,7 @@ public class Window1 { recognizer.ended = { [weak self] point, velocity in self?.panGestureEnded(location: point, velocity: velocity) } + self.windowPanRecognizer = recognizer self.hostView.view.addGestureRecognizer(recognizer) } @@ -536,6 +547,9 @@ public class Window1 { } public func cancelInteractiveKeyboardGestures() { + self.windowPanRecognizer?.isEnabled = false + self.windowPanRecognizer?.isEnabled = true + if self.windowLayout.upperKeyboardInputPositionBound != nil { self.updateLayout { $0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false) @@ -594,7 +608,11 @@ public class Window1 { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) } - self.hostView.view.addSubview(rootController.view) + if let coveringView = self.coveringView { + self.hostView.view.insertSubview(rootController.view, belowSubview: coveringView) + } else { + self.hostView.view.addSubview(rootController.view) + } } } } @@ -613,13 +631,32 @@ public class Window1 { for controller in self._topLevelOverlayControllers { controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - self.hostView.view.addSubview(controller.view) + if let coveringView = self.coveringView { + self.hostView.view.insertSubview(controller.view, belowSubview: coveringView) + } else { + self.hostView.view.addSubview(controller.view) + } } self.presentationContext.topLevelSubview = self._topLevelOverlayControllers.first?.view } } + public var coveringView: WindowCoveringView? { + didSet { + if self.coveringView !== oldValue { + oldValue?.removeFromSuperview() + if let coveringView = self.coveringView { + self.hostView.view.addSubview(coveringView) + if !self.windowLayout.size.width.isZero { + coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) + coveringView.updateLayout(self.windowLayout.size) + } + } + } + } + } + private func layoutSubviews() { var hasPreview = false var updatedHasPreview = false @@ -783,6 +820,11 @@ public class Window1 { } }) } + + if let coveringView = self.coveringView { + coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) + coveringView.updateLayout(self.windowLayout.size) + } } } } diff --git a/Display/WindowCoveringView.swift b/Display/WindowCoveringView.swift new file mode 100644 index 0000000000..6ba76a89c6 --- /dev/null +++ b/Display/WindowCoveringView.swift @@ -0,0 +1,7 @@ +import Foundation +import UIKit + +open class WindowCoveringView: UIView { + open func updateLayout(_ size: CGSize) { + } +} diff --git a/DisplayMac/ASDisplayNode.swift b/DisplayMac/ASDisplayNode.swift index ed43abb43e..f6052f61ca 100644 --- a/DisplayMac/ASDisplayNode.swift +++ b/DisplayMac/ASDisplayNode.swift @@ -9,6 +9,9 @@ open class ASDisplayNode: NSObject { preconditionFailure() } + weak var supernode: ASDisplayNode? + private(set) var subnodes: [ASDisplayNode] = [] + open var frame: CGRect { get { return self.layer.frame @@ -55,6 +58,14 @@ open class ASDisplayNode: NSObject { var isLayerBacked: Bool = false + var clipsToBounds: Bool { + get { + return self.layer.masksToBounds + } set(value) { + self.layer.masksToBounds = value + } + } + override init() { super.init() } @@ -74,11 +85,27 @@ open class ASDisplayNode: NSObject { } - open func insertSubnode(belowSubnode: ASDisplayNode) { + open func insertSubnode(_ subnode: ASDisplayNode, belowSubnode: ASDisplayNode) { } - open func insertSubnode(aboveSubnode: ASDisplayNode) { + open func insertSubnode(_ subnode: ASDisplayNode, aboveSubnode: ASDisplayNode) { + + } + + open func insertSubnode(_ subnode: ASDisplayNode, at: Int) { + + } + + open func removeFromSupernode() { + + } + + func recursivelyEnsureDisplaySynchronously(_ synchronously: Bool) { } } + +func ASPerformMainThreadDeallocation(_ ref: inout AnyObject?) { + +} diff --git a/DisplayMac/CADisplayLink.swift b/DisplayMac/CADisplayLink.swift new file mode 100644 index 0000000000..36a084b4c8 --- /dev/null +++ b/DisplayMac/CADisplayLink.swift @@ -0,0 +1,89 @@ +import Foundation +import CoreVideo +import SwiftSignalKitMac + +private final class CADisplayLinkContext { + weak var impl: CADisplayLink? + + init(_ impl: CADisplayLink) { + self.impl = impl + } +} + +private final class CADisplayLinkContexts { + private var nextId: Int32 = 0 + var contexts: [Int32: CADisplayLinkContext] = [:] + + func add(_ impl: CADisplayLink) -> Int32 { + let id = self.nextId + self.nextId += 1 + self.contexts[id] = CADisplayLinkContext(impl) + return id + } + + func remove(_ id: Int32) { + self.contexts.removeValue(forKey: id) + } + + func get(id: Int32) -> CADisplayLink? { + return self.contexts[id]?.impl + } +} + +private let contexts = Atomic(value: CADisplayLinkContexts()) + +public final class CADisplayLink { + private var id: Int32? + private var displayLink: CVDisplayLink? + + public var isPaused: Bool = true { + didSet { + if self.isPaused != oldValue { + + } + } + } + + private let target: Any? + private let action: Selector? + + init(target: Any?, selector: Selector?) { + self.target = target + self.action = selector + + let id = contexts.with { contexts in + return contexts.add(self) + } + self.id = id + CVDisplayLinkCreateWithActiveCGDisplays(&self.displayLink) + if let displayLink = self.displayLink { + CVDisplayLinkSetOutputCallback(displayLink, { _, _, _, _, _, ref in + let id: Int32 = Int32(unsafeBitCast(ref, to: intptr_t.self)) + if let impl = (contexts.with { contexts in + return contexts.get(id: id) + }) { + impl.performAction() + } + return kCVReturnSuccess + }, UnsafeMutableRawPointer(bitPattern: Int(id))) + } + } + + deinit { + if let id = self.id { + contexts.with { contexts in + contexts.remove(id) + } + } + } + + public func invalidate() { + + } + + private func performAction() { + if let target = self.target, let action = self.action { + let _ = (target as? AnyObject)?.perform(action) + } + } +} diff --git a/DisplayMac/UIKit.swift b/DisplayMac/UIKit.swift index 54c06ae71c..d080662bc6 100644 --- a/DisplayMac/UIKit.swift +++ b/DisplayMac/UIKit.swift @@ -2,10 +2,10 @@ import Foundation import QuartzCore public struct UIEdgeInsets: Equatable { - public let top: CGFloat - public let left: CGFloat - public let bottom: CGFloat - public let right: CGFloat + public var top: CGFloat + public var left: CGFloat + public var bottom: CGFloat + public var right: CGFloat public init() { self.top = 0.0 diff --git a/DisplayMac/UIScrollView.swift b/DisplayMac/UIScrollView.swift index d3c4276004..a1ba22d1a3 100644 --- a/DisplayMac/UIScrollView.swift +++ b/DisplayMac/UIScrollView.swift @@ -19,6 +19,10 @@ open class UIScrollView: UIView { } } - public var alwaysBoundsVertical: Bool = false - public var alwaysBoundsHorizontal: Bool = false + public var alwaysBounceVertical: Bool = false + public var alwaysBounceHorizontal: Bool = false + + public func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { + self.contentOffset = contentOffset + } } diff --git a/DisplayMac/UIView.swift b/DisplayMac/UIView.swift index 5fa17be3da..3a323cd80c 100644 --- a/DisplayMac/UIView.swift +++ b/DisplayMac/UIView.swift @@ -28,7 +28,19 @@ open class UIView: NSObject { } } - init(frame: CGRect) { + open var isHidden: Bool { + get { + return self.layer.isHidden + } set(value) { + self.layer.isHidden = value + } + } + + open class var layerClass: AnyClass { + return CALayer.self + } + + public init(frame: CGRect) { self.layer = CALayer() self.layer.frame = frame @@ -46,4 +58,25 @@ open class UIView: NSObject { public func bringSubview(toFront: UIView) { } + + public func addSubview(_ subview: UIView) { + + } + + public func removeFromSuperview() { + + } + + open func setNeedsLayout() { + } + + open func layoutSubviews() { + } + + open func setNeedsDisplay() { + } + + open func snapshotView(afterScreenUpdates: Bool) -> UIView? { + return nil + } } From 14f0fc94f4b940cd070128e8340aa7f50737a0cd Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Wed, 21 Feb 2018 01:46:32 +0400 Subject: [PATCH 049/245] no message --- Display.xcodeproj/project.pbxproj | 64 ++++- .../xcschemes/xcschememanagement.plist | 4 +- Display/CATracingLayer.m | 8 + Display/ContainerViewLayout.swift | 24 +- .../GlobalOverlayPresentationContext.swift | 160 ++++++++++++ Display/GridNode.swift | 31 +-- Display/ListView.swift | 204 +++++++++++++--- Display/ListViewAnimation.swift | 13 + Display/ListViewIntermediateState.swift | 16 +- Display/ListViewItemNode.swift | 25 ++ .../ListViewReorderingGestureRecognizer.swift | 65 +++++ Display/ListViewReorderingItemNode.swift | 48 ++++ Display/ListViewTempItemNode.swift | 5 + Display/NativeWindowHostView.swift | 10 + Display/NavigationController.swift | 61 +++-- Display/PeekController.swift | 81 +++++++ Display/PeekControllerContent.swift | 23 ++ Display/PeekControllerGestureRecognizer.swift | 228 ++++++++++++++++++ Display/PeekControllerMenuItemNode.swift | 91 +++++++ Display/PeekControllerMenuNode.swift | 30 +++ Display/PeekControllerNode.swift | 206 ++++++++++++++++ Display/StatusBarManager.swift | 6 +- Display/StatusBarProxyNode.swift | 68 +++++- Display/TabBarController.swift | 36 ++- Display/TabBarNode.swift | 17 +- Display/TabBarTapRecognizer.swift | 58 +++++ Display/UIKitUtils.swift | 38 ++- Display/UIViewController+Navigation.h | 1 + Display/UIViewController+Navigation.m | 4 + Display/ViewController.swift | 41 +++- Display/ViewControllerPreviewing.swift | 118 +++++++++ Display/WindowContent.swift | 39 ++- 32 files changed, 1681 insertions(+), 142 deletions(-) create mode 100644 Display/GlobalOverlayPresentationContext.swift create mode 100644 Display/ListViewReorderingGestureRecognizer.swift create mode 100644 Display/ListViewReorderingItemNode.swift create mode 100644 Display/ListViewTempItemNode.swift create mode 100644 Display/PeekController.swift create mode 100644 Display/PeekControllerContent.swift create mode 100644 Display/PeekControllerGestureRecognizer.swift create mode 100644 Display/PeekControllerMenuItemNode.swift create mode 100644 Display/PeekControllerMenuNode.swift create mode 100644 Display/PeekControllerNode.swift create mode 100644 Display/TabBarTapRecognizer.swift create mode 100644 Display/ViewControllerPreviewing.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index aeccfbe154..1c641e2b50 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -42,6 +42,7 @@ D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE31D904A000066BF65 /* GridItem.swift */; }; + D01EA41B203227BA00B4B0B5 /* ViewControllerPreviewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EA41A203227BA00B4B0B5 /* ViewControllerPreviewing.swift */; }; D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */; }; D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */; }; D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; @@ -51,6 +52,16 @@ D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */; }; + D03AA4D5202C793E0056C405 /* PeekController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4D4202C793E0056C405 /* PeekController.swift */; }; + D03AA4D7202C79600056C405 /* PeekControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4D6202C79600056C405 /* PeekControllerNode.swift */; }; + D03AA4D9202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4D8202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift */; }; + D03AA4DB202DA6D60056C405 /* PeekControllerContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4DA202DA6D60056C405 /* PeekControllerContent.swift */; }; + D03AA4DD202DB1840056C405 /* PeekControllerGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4DC202DB1840056C405 /* PeekControllerGestureRecognizer.swift */; }; + D03AA4E1202DD4490056C405 /* PeekControllerMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4E0202DD4490056C405 /* PeekControllerMenuNode.swift */; }; + D03AA4E3202DD52E0056C405 /* PeekControllerMenuItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4E2202DD52E0056C405 /* PeekControllerMenuItemNode.swift */; }; + D03AA4E9202E02070056C405 /* ListViewReorderingItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4E8202E02070056C405 /* ListViewReorderingItemNode.swift */; }; + D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4EA202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift */; }; + D03AA5162030C5F80056C405 /* ListViewTempItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA5152030C5F80056C405 /* ListViewTempItemNode.swift */; }; D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */; }; D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* Theme.swift */; }; D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; @@ -71,7 +82,6 @@ D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* WindowContent.swift */; }; - D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; }; D05CC2ED1B69558A00E235A3 /* RuntimeUtils.h in Headers */ = {isa = PBXBuildFile; fileRef = D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -111,6 +121,8 @@ D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */; }; + D0A1346220336C350059716A /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A1346120336C350059716A /* ViewController.swift */; }; + D0A134642034DE580059716A /* TabBarTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A134632034DE580059716A /* TabBarTapRecognizer.swift */; }; D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; }; D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */; }; D0AA84101FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */; }; @@ -195,6 +207,7 @@ D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; D01E2BE31D904A000066BF65 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; + D01EA41A203227BA00B4B0B5 /* ViewControllerPreviewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerPreviewing.swift; sourceTree = ""; }; D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedController.swift; sourceTree = ""; }; D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedControllerNode.swift; sourceTree = ""; }; D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; @@ -204,6 +217,16 @@ D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuController.swift; sourceTree = ""; }; + D03AA4D4202C793E0056C405 /* PeekController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekController.swift; sourceTree = ""; }; + D03AA4D6202C79600056C405 /* PeekControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekControllerNode.swift; sourceTree = ""; }; + D03AA4D8202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlobalOverlayPresentationContext.swift; sourceTree = ""; }; + D03AA4DA202DA6D60056C405 /* PeekControllerContent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekControllerContent.swift; sourceTree = ""; }; + D03AA4DC202DB1840056C405 /* PeekControllerGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekControllerGestureRecognizer.swift; sourceTree = ""; }; + D03AA4E0202DD4490056C405 /* PeekControllerMenuNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekControllerMenuNode.swift; sourceTree = ""; }; + D03AA4E2202DD52E0056C405 /* PeekControllerMenuItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PeekControllerMenuItemNode.swift; sourceTree = ""; }; + D03AA4E8202E02070056C405 /* ListViewReorderingItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewReorderingItemNode.swift; sourceTree = ""; }; + D03AA4EA202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewReorderingGestureRecognizer.swift; sourceTree = ""; }; + D03AA5152030C5F80056C405 /* ListViewTempItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewTempItemNode.swift; sourceTree = ""; }; D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHost.swift; sourceTree = ""; }; D03BCCEA1C72AE590097A291 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; @@ -227,7 +250,6 @@ D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; D05CC2A11B69326C00E235A3 /* WindowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowContent.swift; sourceTree = ""; }; - D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; D05CC2EB1B69558A00E235A3 /* RuntimeUtils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RuntimeUtils.h; sourceTree = ""; }; @@ -267,6 +289,8 @@ D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetCheckboxItem.swift; sourceTree = ""; }; + D0A1346120336C350059716A /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; + D0A134632034DE580059716A /* TabBarTapRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TabBarTapRecognizer.swift; sourceTree = ""; }; D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = ""; }; D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewFloatingHeaderNode.swift; sourceTree = ""; }; D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewOverscrollBackgroundNode.swift; sourceTree = ""; }; @@ -381,6 +405,7 @@ D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */, D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */, D053DAD02018ECF900993D32 /* WindowCoveringView.swift */, + D03AA4D8202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift */, ); name = Window; sourceTree = ""; @@ -391,6 +416,7 @@ D0DC48531BF93D8A00F672FD /* TabBarController.swift */, D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */, D0DC48551BF945DD00F672FD /* TabBarNode.swift */, + D0A134632034DE580059716A /* TabBarTapRecognizer.swift */, ); name = "Tab Bar"; sourceTree = ""; @@ -458,6 +484,19 @@ name = "Context Menu"; sourceTree = ""; }; + D03AA4D3202C790D0056C405 /* Peek */ = { + isa = PBXGroup; + children = ( + D03AA4D4202C793E0056C405 /* PeekController.swift */, + D03AA4D6202C79600056C405 /* PeekControllerNode.swift */, + D03AA4DA202DA6D60056C405 /* PeekControllerContent.swift */, + D03AA4DC202DB1840056C405 /* PeekControllerGestureRecognizer.swift */, + D03AA4E0202DD4490056C405 /* PeekControllerMenuNode.swift */, + D03AA4E2202DD52E0056C405 /* PeekControllerMenuItemNode.swift */, + ); + name = Peek; + sourceTree = ""; + }; D03BCCE91C72AE4B0097A291 /* Theme */ = { isa = PBXGroup; children = ( @@ -637,11 +676,12 @@ D081229B1D19A9F0005F7395 /* Controllers */ = { isa = PBXGroup; children = ( + D0A1346120336C350059716A /* ViewController.swift */, D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */, D015F7511D1AE08D00E269B5 /* ContainableController.swift */, D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */, - D05CC2E21B69552C00E235A3 /* ViewController.swift */, D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */, + D01EA41A203227BA00B4B0B5 /* ViewControllerPreviewing.swift */, D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */, D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */, D081229A1D19A9EB005F7395 /* Navigation */, @@ -650,6 +690,7 @@ D03725BF1D6DF57B007FC290 /* Context Menu */, D00701962029CAC4006B9E34 /* Tooltip */, D0DA444A1E4DCA36005FDCA7 /* Alert */, + D03AA4D3202C790D0056C405 /* Peek */, ); name = Controllers; sourceTree = ""; @@ -671,6 +712,9 @@ D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */, D0AA840D1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift */, D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */, + D03AA4E8202E02070056C405 /* ListViewReorderingItemNode.swift */, + D03AA4EA202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift */, + D03AA5152030C5F80056C405 /* ListViewTempItemNode.swift */, ); name = "List Node"; sourceTree = ""; @@ -906,6 +950,7 @@ buildActionMask = 2147483647; files = ( D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */, + D03AA5162030C5F80056C405 /* ListViewTempItemNode.swift in Sources */, D087BFB51F75181D003FD209 /* ChildWindowHostView.swift in Sources */, D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, @@ -917,7 +962,6 @@ D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */, - D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */, D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, @@ -926,6 +970,7 @@ D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */, + D03AA4D9202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift in Sources */, D0F8C3932014FB7C00236FC5 /* ListView.swift in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, @@ -939,6 +984,7 @@ D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */, + D01EA41B203227BA00B4B0B5 /* ViewControllerPreviewing.swift in Sources */, D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */, D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, @@ -951,6 +997,7 @@ D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */, D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */, D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */, + D03AA4D5202C793E0056C405 /* PeekController.swift in Sources */, D05CC31A1B695A9600E235A3 /* NavigationButtonNode.swift in Sources */, D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */, D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */, @@ -963,12 +1010,15 @@ D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, D0CE8CE91F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift in Sources */, D0C12A1A1F3375B400B3F66D /* NavigationBarTitleTransitionNode.swift in Sources */, + D03AA4E1202DD4490056C405 /* PeekControllerMenuNode.swift in Sources */, D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, + D03AA4D7202C79600056C405 /* PeekControllerNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, + D03AA4DB202DA6D60056C405 /* PeekControllerContent.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */, D0DA44521E4DCC11005FDCA7 /* TextAlertController.swift in Sources */, @@ -977,20 +1027,26 @@ D0FF9B301E7196F6000C66DB /* KeyboardManager.swift in Sources */, D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */, D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */, + D0A1346220336C350059716A /* ViewController.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, + D03AA4E9202E02070056C405 /* ListViewReorderingItemNode.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, + D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, D0AA84101FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift in Sources */, D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, + D03AA4DD202DB1840056C405 /* PeekControllerGestureRecognizer.swift in Sources */, D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */, D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */, + D0A134642034DE580059716A /* TabBarTapRecognizer.swift in Sources */, + D03AA4E3202DD52E0056C405 /* PeekControllerMenuItemNode.swift in Sources */, D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */, D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */, D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 4b2e9b0571..250c9b5f47 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 11 + 7 DisplayMac.xcscheme @@ -17,7 +17,7 @@ DisplayTests.xcscheme orderHint - 12 + 9 SuppressBuildableAutocreation diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index bc8d2668bb..65389bdfd5 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -367,6 +367,14 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull @implementation UITracingLayerView +- (void)setFrame:(CGRect)frame { + [super setFrame:frame]; +} + +- (void)setAutoresizingMask:(UIViewAutoresizing)autoresizingMask { + [super setAutoresizingMask:0]; +} + + (Class)layerClass { return [CATracingLayer class]; } diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 4680a6c64a..50c384972a 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -45,25 +45,17 @@ public struct ContainerViewLayout: Equatable { public let safeInsets: UIEdgeInsets public let statusBarHeight: CGFloat? public let inputHeight: CGFloat? + public let standardInputHeight: CGFloat public let inputHeightIsInteractivellyChanging: Bool - public init() { - self.size = CGSize() - self.metrics = LayoutMetrics() - self.intrinsicInsets = UIEdgeInsets() - self.safeInsets = UIEdgeInsets() - self.statusBarHeight = nil - self.inputHeight = nil - self.inputHeightIsInteractivellyChanging = false - } - - public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, inputHeightIsInteractivellyChanging: Bool) { + public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, standardInputHeight: CGFloat, inputHeightIsInteractivellyChanging: Bool) { self.size = size self.metrics = metrics self.intrinsicInsets = intrinsicInsets self.safeInsets = safeInsets self.statusBarHeight = statusBarHeight self.inputHeight = inputHeight + self.standardInputHeight = standardInputHeight self.inputHeightIsInteractivellyChanging = inputHeightIsInteractivellyChanging } @@ -79,15 +71,15 @@ public struct ContainerViewLayout: Equatable { } public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) + return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } public static func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { @@ -131,6 +123,10 @@ public struct ContainerViewLayout: Equatable { return false } + if !lhs.standardInputHeight.isEqual(to: rhs.standardInputHeight) { + return false + } + if lhs.inputHeightIsInteractivellyChanging != rhs.inputHeightIsInteractivellyChanging { return false } diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift new file mode 100644 index 0000000000..95239f270a --- /dev/null +++ b/Display/GlobalOverlayPresentationContext.swift @@ -0,0 +1,160 @@ +import Foundation +import AsyncDisplayKit +import SwiftSignalKit + +final class GlobalOverlayPresentationContext { + private let statusBarHost: StatusBarHost? + + private var controllers: [ViewController] = [] + + private var presentationDisposables = DisposableSet() + private var layout: ContainerViewLayout? + + private var ready: Bool { + return self.currentPresentationView() != nil && self.layout != nil + } + + init(statusBarHost: StatusBarHost?) { + self.statusBarHost = statusBarHost + } + + private func currentPresentationView() -> UIView? { + if let statusBarHost = self.statusBarHost { + if let keyboardWindow = statusBarHost.keyboardWindow { + return keyboardWindow + } else { + return statusBarHost.statusBarWindow + } + } + return nil + } + + func present(_ controller: ViewController) { + let controllerReady = controller.ready.get() + |> filter({ $0 }) + |> take(1) + |> deliverOnMainQueue + |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) + + if let _ = self.currentPresentationView(), let initialLayout = self.layout { + controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) + controller.containerLayoutUpdated(initialLayout, transition: .immediate) + + self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in + if let strongSelf = self { + if strongSelf.controllers.contains(where: { $0 === controller }) { + return + } + + strongSelf.controllers.append(controller) + if let view = strongSelf.currentPresentationView(), let layout = strongSelf.layout { + controller.navigation_setDismiss({ [weak controller] in + if let strongSelf = self, let controller = controller { + strongSelf.dismiss(controller) + } + }, rootController: nil) + controller.setIgnoreAppearanceMethodInvocations(true) + if layout != initialLayout { + controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) + view.addSubview(controller.view) + controller.containerLayoutUpdated(layout, transition: .immediate) + } else { + view.addSubview(controller.view) + } + controller.setIgnoreAppearanceMethodInvocations(false) + view.layer.invalidateUpTheTree() + controller.viewWillAppear(false) + controller.viewDidAppear(false) + } + } + })) + } else { + self.controllers.append(controller) + } + } + + deinit { + self.presentationDisposables.dispose() + } + + private func dismiss(_ controller: ViewController) { + if let index = self.controllers.index(where: { $0 === controller }) { + self.controllers.remove(at: index) + controller.viewWillDisappear(false) + controller.view.removeFromSuperview() + controller.viewDidDisappear(false) + } + } + + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + let wasReady = self.ready + self.layout = layout + + if wasReady != self.ready { + self.readyChanged(wasReady: wasReady) + } else if self.ready { + for controller in self.controllers { + controller.containerLayoutUpdated(layout, transition: transition) + } + } + } + + private func readyChanged(wasReady: Bool) { + if !wasReady { + self.addViews() + } else { + self.removeViews() + } + } + + private func addViews() { + if let view = self.currentPresentationView(), let layout = self.layout { + for controller in self.controllers { + controller.viewWillAppear(false) + view.addSubview(controller.view) + controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) + controller.containerLayoutUpdated(layout, transition: .immediate) + controller.viewDidAppear(false) + } + } + } + + private func removeViews() { + for controller in self.controllers { + controller.viewWillDisappear(false) + controller.view.removeFromSuperview() + controller.viewDidDisappear(false) + } + } + + func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for controller in self.controllers.reversed() { + if controller.isViewLoaded { + if let result = controller.view.hitTest(point, with: event) { + return result + } + } + } + return nil + } + + func combinedSupportedOrientations() -> UIInterfaceOrientationMask { + var mask: UIInterfaceOrientationMask = .all + + for controller in self.controllers { + mask = mask.intersection(controller.supportedInterfaceOrientations) + } + + return mask + } + + func combinedDeferScreenEdgeGestures() -> UIRectEdge { + var edges: UIRectEdge = [] + + for controller in self.controllers { + edges = edges.union(controller.deferScreenEdgeGestures) + } + + return edges + } +} diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 4d36d1e715..e9368db585 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -102,34 +102,6 @@ public struct GridNodeUpdateLayout { } } -/*private func binarySearch(_ inputArr: [GridNodePresentationItem], searchItem: CGFloat) -> Int? { - if inputArr.isEmpty { - return nil - } - - var lowerPosition = inputArr[0].frame.origin.y + inputArr[0].frame.size.height - var upperPosition = inputArr[inputArr.count - 1].frame.origin.y - - if lowerPosition > upperPosition { - return nil - } - - while (true) { - let currentPosition = (lowerIndex + upperIndex) / 2 - if (inputArr[currentIndex] == searchItem) { - return currentIndex - } else if (lowerIndex > upperIndex) { - return nil - } else { - if (inputArr[currentIndex] > searchItem) { - upperIndex = currentIndex - 1 - } else { - lowerIndex = currentIndex + 1 - } - } - } -}*/ - public enum GridNodeStationaryItems { case none case all @@ -263,6 +235,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? + public var scrollingCompleted: (() -> Void)? public final var floatingSections = false @@ -397,11 +370,13 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { self.updateItemNodeVisibilititesAndScrolling() + self.scrollingCompleted?() } } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.updateItemNodeVisibilititesAndScrolling() + self.scrollingCompleted?() } public func scrollViewDidScroll(_ scrollView: UIScrollView) { diff --git a/Display/ListView.swift b/Display/ListView.swift index 5d12a1c7bf..4ede1d78bd 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1,10 +1,6 @@ -#if os(macOS) -import SwiftSignalKitMac -#else import UIKit import AsyncDisplayKit import SwiftSignalKit -#endif private let useBackgroundDeallocation = false @@ -48,7 +44,6 @@ final class ListViewBackingView: UIView { override func setNeedsDisplay() { } - #if os(iOS) override func touchesBegan(_ touches: Set, with event: UIEvent?) { self.target?.touchesBegan(touches, with: event) } @@ -73,7 +68,6 @@ final class ListViewBackingView: UIView { } return super.hitTest(point, with: event) } - #endif } private final class ListViewTimerProxy: NSObject { @@ -197,6 +191,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var beganInteractiveDragging: () -> Void = { } + public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in } + private final var animations: [ListViewAnimation] = [] private final var actionsForVSync: [() -> ()] = [] private final var inVSync = false @@ -211,6 +207,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var selectionLongTapDelayTimer: Foundation.Timer? private var flashNodesDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? + private var reorderNode: ListViewReorderingItemNode? private let waitingForNodesDisposable = MetaDisposable() @@ -266,13 +263,37 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel trackingRecognizer.delegate = self self.view.addGestureRecognizer(trackingRecognizer) + self.view.addGestureRecognizer(ListViewReorderingGestureRecognizer(shouldBegin: { [weak self] point in + if let strongSelf = self { + if let index = strongSelf.itemIndexAtPoint(point) { + for i in 0 ..< strongSelf.itemNodes.count { + if strongSelf.itemNodes[i].index == index { + let itemNode = strongSelf.itemNodes[i] + let itemNodeFrame = itemNode.frame + let itemNodeBounds = itemNode.bounds + if itemNode.isReorderable(at: point.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY)) { + strongSelf.beginReordering(itemNode: itemNode) + return true + } + break + } + } + } + } + return false + }, ended: { [weak self] in + self?.endReordering() + }, moved: { [weak self] offset in + self?.updateReordering(offset: offset) + })) + self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes) - #if os(iOS) + if #available(iOS 10.0, *) { self.displayLink.preferredFramesPerSecond = 60 } - #endif + self.displayLink.isPaused = true } @@ -334,6 +355,86 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func beginReordering(itemNode: ListViewItemNode) { + if let reorderNode = self.reorderNode { + reorderNode.removeFromSupernode() + } + let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin) + self.reorderNode = reorderNode + self.addSubnode(reorderNode) + itemNode.isHidden = true + } + + private func endReordering() { + if let reorderNode = self.reorderNode { + self.reorderNode = nil + if let itemNode = reorderNode.itemNode, itemNode.supernode == self { + self.view.bringSubview(toFront: itemNode.view) + reorderNode.animateCompletion(completion: { [weak itemNode, weak reorderNode] in + //itemNode?.isHidden = false + reorderNode?.removeFromSupernode() + }) + self.setNeedsAnimations() + } else { + reorderNode.removeFromSupernode() + } + } + } + + private func updateReordering(offset: CGFloat) { + if let reorderNode = self.reorderNode { + reorderNode.updateOffset(offset: offset) + self.checkItemReordering() + } + } + + private func checkItemReordering() { + if let reorderNode = self.reorderNode, let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self { + guard let verticalTopOffset = reorderNode.currentOffset() else { + return + } + let verticalOffset = verticalTopOffset + var closestIndex: (Int, CGFloat)? + for i in 0 ..< self.itemNodes.count { + if let itemNodeIndex = self.itemNodes[i].index, itemNodeIndex != reorderItemIndex { + let itemOffset = self.itemNodes[i].frame.midY + let deltaOffset = itemOffset - verticalOffset + if let (_, closestOffset) = closestIndex { + if abs(deltaOffset) < abs(closestOffset) { + closestIndex = (itemNodeIndex, deltaOffset) + } + } else { + closestIndex = (itemNodeIndex, deltaOffset) + } + } + } + if let (closestIndexValue, offset) = closestIndex { + //print("closest \(closestIndexValue) offset \(offset)") + var toIndex: Int + if offset > 0 { + toIndex = closestIndexValue + if toIndex > reorderItemIndex { + toIndex -= 1 + } + } else { + toIndex = closestIndexValue + 1 + if toIndex > reorderItemIndex { + toIndex -= 1 + } + } + if toIndex != reorderItemNode.index { + if reorderNode.currentState?.0 != reorderItemIndex || reorderNode.currentState?.1 != toIndex { + reorderNode.currentState = (reorderItemIndex, toIndex) + //print("reorder \(reorderItemIndex) to \(toIndex) offset \(offset)") + self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState) + } + } + } + + self.setNeedsAnimations() + } + } + private func resetHeaderItemsFlashTimer(start: Bool) { if let flashNodesDelayTimer = self.flashNodesDelayTimer { flashNodesDelayTimer.invalidate() @@ -1524,7 +1625,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var offsetHeight = node.apparentHeight var takenAnimation = false - if let _ = previousFrame , animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { + if let _ = previousFrame, animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { let nextNode = self.itemNodes[nodeIndex + 1] if nextNode.index == nil && nextNode.subnodes.isEmpty { let nextHeight = nextNode.apparentHeight @@ -1560,8 +1661,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if node.index == nil { - node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) - node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + if node.animationForKey("height") == nil || !(node is ListViewTempItemNode) { + node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } + if node.animationForKey("apparentHeight") == nil || !(node is ListViewTempItemNode) { + node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) + } node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor()) } else if animated { if takenAnimation { @@ -1656,7 +1761,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func lowestHeaderNode() -> ASDisplayNode? { + private func lowestNodeToInsertBelow() -> ASDisplayNode? { + if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self { + //return itemNode + } var lowestHeaderNode: ASDisplayNode? var lowestHeaderNodeIndex: Int? for (_, headerNode) in self.itemHeaderNodes { @@ -1709,6 +1817,18 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + /*if true { + print("----------") + for itemNode in self.itemNodes { + var anim = "" + if let animation = itemNode.animationForKey("apparentHeight") { + anim = "\(animation.from)->\(animation.to)" + } + print("\(itemNode.index) \(itemNode.apparentFrame.height) \(anim)") + } + print("----------") + }*/ + let timestamp = CACurrentMediaTime() let listInsets = updateSizeAndInsets?.insets ?? self.insets @@ -1718,14 +1838,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } var previousTopItemVerticalOrigin: CGFloat? - var previousBottomItemMaxY: CGFloat? var snapshotView: UIView? if animateCrossfade { snapshotView = self.view.snapshotView(afterScreenUpdates: false) } if animateTopItemVerticalOrigin { previousTopItemVerticalOrigin = self.topItemVerticalOrigin() - previousBottomItemMaxY = self.bottomItemMaxY() } var previousApparentFrames: [(ListViewItemNode, CGRect)] = [] @@ -1740,7 +1858,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - let lowestHeaderNode = self.lowestHeaderNode() + let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() for operation in operations { switch operation { @@ -1763,8 +1881,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets) if let _ = updatedPreviousFrame { - if let lowestHeaderNode = lowestHeaderNode { - self.insertSubnode(node, belowSubnode: lowestHeaderNode) + if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self { + self.insertSubnode(node, belowSubnode: itemNode) + } else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { + self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow) } else { self.addSubnode(node) } @@ -1776,8 +1896,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.insertSubnode(node, at: 0) } } else { - if let lowestHeaderNode = lowestHeaderNode { - self.insertSubnode(node, belowSubnode: lowestHeaderNode) + if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self { + self.insertSubnode(node, belowSubnode: itemNode) + } else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { + self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow) } else { self.addSubnode(node) } @@ -1797,7 +1919,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) + let tempNode = ListViewTempItemNode(layerBacked: true) + //referenceNode.copyHeightAndApparentHeightAnimations(to: tempNode) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) } else { referenceNode.index = nil self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) @@ -2212,11 +2336,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) if let offset = offset , abs(offset) > CGFloat.ulpOfOne { - let lowestHeaderNode = self.lowestHeaderNode() + let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() for itemNode in temporaryPreviousNodes { itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) - if let lowestHeaderNode = lowestHeaderNode { - self.insertSubnode(itemNode, belowSubnode: lowestHeaderNode) + if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { + self.insertSubnode(itemNode, belowSubnode: lowestNodeToInsertBelow) } else { self.addSubnode(itemNode) } @@ -2714,7 +2838,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func immediateDisplayedItemRange() -> ListViewDisplayedItemRange { var loadedRange: ListViewItemRange? - var visibleRange: ListViewItemRange? + var visibleRange: ListViewVisibleItemRange? if self.itemNodes.count != 0 { var firstIndex: (nodeIndex: Int, index: Int)? var lastIndex: (nodeIndex: Int, index: Int)? @@ -2736,12 +2860,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel i -= 1 } if let firstIndex = firstIndex, let lastIndex = lastIndex { - var firstVisibleIndex: Int? + var firstVisibleIndex: (Int, Bool)? for i in firstIndex.nodeIndex ... lastIndex.nodeIndex { if let index = self.itemNodes[i].index { let frame = self.itemNodes[i].apparentFrame if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height + self.insets.bottom { - firstVisibleIndex = index + firstVisibleIndex = (index, frame.minY >= self.insets.top) break } } @@ -2760,7 +2884,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if let lastVisibleIndex = lastVisibleIndex { - visibleRange = ListViewItemRange(firstIndex: firstVisibleIndex, lastIndex: lastVisibleIndex) + visibleRange = ListViewVisibleItemRange(firstIndex: firstVisibleIndex.0, firstIndexFullyVisible: firstVisibleIndex.1, lastIndex: lastVisibleIndex) } } @@ -2807,6 +2931,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var offsetRanges = OffsetRanges() + if let reorderOffset = self.reorderNode?.currentOffset(), !self.itemNodes.isEmpty { + if reorderOffset < self.insets.top + 10.0 { + if self.itemNodes[0].apparentFrame.minY < self.insets.top { + continueAnimations = true + offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: 6.0) + } + } else if reorderOffset > self.visibleSize.height - self.insets.bottom - 10.0 { + if self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { + continueAnimations = true + offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: -6.0) + } + } + } + var requestUpdateVisibleItems = false var index = 0 while index < self.itemNodes.count { @@ -2871,9 +3009,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if requestUpdateVisibleItems { self.enqueueUpdateVisibleItems() } + + self.checkItemReordering() } - #if os(iOS) override open func touchesBegan(_ touches: Set, with event: UIEvent?) { let touchesPosition = touches.first!.location(in: self.view) @@ -2963,7 +3102,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateScroller(transition: .immediate) } - #endif public func clearHighlightAnimated(_ animated: Bool) { if let highlightedItemIndex = self.highlightedItemIndex { @@ -3016,8 +3154,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } return nil } - - #if os(iOS) + override open func touchesMoved(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { let location = touches.first!.location(in: self.view) @@ -3099,7 +3236,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel break } } - #endif public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -3111,8 +3247,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } } - - #if os(iOS) + fileprivate func internalHitTest(_ point: CGPoint, with event: UIEvent?) -> Bool { if self.limitHitTestToNodes { var foundHit = false @@ -3128,5 +3263,4 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } return true } - #endif } diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index cf0ea9c09e..db07f932cf 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -124,6 +124,19 @@ public final class ListViewAnimation { self.completed = completed } + init(copying: ListViewAnimation, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) { + self.from = copying.from + self.to = copying.to + self.duration = copying.duration + self.curve = copying.curve + self.startTime = copying.startTime + self.interpolator = copying.interpolator + self.update = { progress, value in + update(progress, value as! T) + } + self.completed = completed + } + public func completeAt(_ timestamp: Double) -> Bool { if timestamp >= self.startTime + self.duration { self.completed(true) diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 8cf6609a95..92412b4d6b 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -145,15 +145,25 @@ public struct ListViewUpdateSizeAndInsets { public struct ListViewItemRange: Equatable { public let firstIndex: Int public let lastIndex: Int + + public static func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { + return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex + } } -public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { - return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex +public struct ListViewVisibleItemRange: Equatable { + public let firstIndex: Int + public let firstIndexFullyVisible: Bool + public let lastIndex: Int + + public static func ==(lhs: ListViewVisibleItemRange, rhs: ListViewVisibleItemRange) -> Bool { + return lhs.firstIndex == rhs.firstIndex && lhs.firstIndexFullyVisible == rhs.firstIndexFullyVisible && lhs.lastIndex == rhs.lastIndex + } } public struct ListViewDisplayedItemRange: Equatable { public let loadedRange: ListViewItemRange? - public let visibleRange: ListViewItemRange? + public let visibleRange: ListViewVisibleItemRange? } public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index e353a36b26..1bae95320d 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -430,6 +430,27 @@ open class ListViewItemNode: ASDisplayNode { self.setAnimationForKey("height", animation: animation) } + func copyHeightAndApparentHeightAnimations(to otherNode: ListViewItemNode) { + if let animation = self.animationForKey("apparentHeight") { + let updatedAnimation = ListViewAnimation(copying: animation, update: { [weak otherNode] (progress: CGFloat, currentValue: CGFloat) -> Void in + if let strongSelf = otherNode { + let frame = strongSelf.frame + strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue)) + } + }) + otherNode.setAnimationForKey("height", animation: updatedAnimation) + } + + if let animation = self.animationForKey("apparentHeight") { + let updatedAnimation = ListViewAnimation(copying: animation, update: { [weak otherNode] (progress: CGFloat, currentValue: CGFloat) -> Void in + if let strongSelf = otherNode { + strongSelf.apparentHeight = currentValue + } + }) + otherNode.setAnimationForKey("apparentHeight", animation: updatedAnimation) + } + } + public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) { let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in if let strongSelf = self { @@ -484,6 +505,10 @@ open class ListViewItemNode: ASDisplayNode { open func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) { } + open func isReorderable(at point: CGPoint) -> Bool { + return false + } + open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) { } diff --git a/Display/ListViewReorderingGestureRecognizer.swift b/Display/ListViewReorderingGestureRecognizer.swift new file mode 100644 index 0000000000..fa631b4d6f --- /dev/null +++ b/Display/ListViewReorderingGestureRecognizer.swift @@ -0,0 +1,65 @@ +import Foundation +import UIKit + +final class ListViewReorderingGestureRecognizer: UIGestureRecognizer { + private let shouldBegin: (CGPoint) -> Bool + private let ended: () -> Void + private let moved: (CGFloat) -> Void + + private var initialLocation: CGPoint? + + init(shouldBegin: @escaping (CGPoint) -> Bool, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) { + self.shouldBegin = shouldBegin + self.ended = ended + self.moved = moved + + super.init(target: nil, action: nil) + } + + override func reset() { + super.reset() + + self.initialLocation = nil + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if self.state == .possible { + if let location = touches.first?.location(in: self.view), self.shouldBegin(location) { + self.initialLocation = location + self.state = .began + } else { + self.state = .failed + } + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + if self.state == .began || self.state == .changed { + self.ended() + self.state = .failed + } + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + if self.state == .began || self.state == .changed { + self.ended() + self.state = .failed + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { + self.state = .changed + let offset = location.y - initialLocation.y + self.moved(offset) + } + } +} diff --git a/Display/ListViewReorderingItemNode.swift b/Display/ListViewReorderingItemNode.swift new file mode 100644 index 0000000000..e1abb43424 --- /dev/null +++ b/Display/ListViewReorderingItemNode.swift @@ -0,0 +1,48 @@ +import Foundation +import AsyncDisplayKit + +final class ListViewReorderingItemNode: ASDisplayNode { + weak var itemNode: ListViewItemNode? + + var currentState: (Int, Int)? + + private let copyView: UIView? + private let initialLocation: CGPoint + + init(itemNode: ListViewItemNode, initialLocation: CGPoint) { + self.itemNode = itemNode + self.copyView = itemNode.view.snapshotContentTree() + self.initialLocation = initialLocation + + super.init() + + if let copyView = self.copyView { + self.view.addSubview(copyView) + copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: copyView.bounds.size) + } + } + + func updateOffset(offset: CGFloat) { + if let copyView = self.copyView { + copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y + offset), size: copyView.bounds.size) + } + } + + func currentOffset() -> CGFloat? { + if let copyView = self.copyView { + return copyView.center.y + } + return nil + } + + func animateCompletion(completion: @escaping () -> Void) { + if let copyView = self.copyView, let itemNode = self.itemNode { + itemNode.isHidden = false + itemNode.transitionOffset = itemNode.apparentFrame.midY - copyView.frame.midY + itemNode.addTransitionOffsetAnimation(0.0, duration: 0.2, beginAt: CACurrentMediaTime()) + completion() + } else { + completion() + } + } +} diff --git a/Display/ListViewTempItemNode.swift b/Display/ListViewTempItemNode.swift new file mode 100644 index 0000000000..0a6ebc9e18 --- /dev/null +++ b/Display/ListViewTempItemNode.swift @@ -0,0 +1,5 @@ +import Foundation + +final class ListViewTempItemNode: ListViewItemNode { + +} diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index cd0288008b..6668b9f3d3 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -11,6 +11,7 @@ private let defaultOrientations: UIInterfaceOrientationMask = { private class WindowRootViewController: UIViewController { var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)? + var orientations: UIInterfaceOrientationMask = defaultOrientations { didSet { if oldValue != self.orientations { @@ -73,6 +74,7 @@ private final class NativeWindow: UIWindow, WindowHost { var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateToInterfaceOrientation: (() -> Void)? var presentController: ((ViewController, PresentationSurfaceLevel) -> Void)? + var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? @@ -150,6 +152,10 @@ private final class NativeWindow: UIWindow, WindowHost { self.presentController?(controller, level) } + func presentInGlobalOverlay(_ controller: ViewController) { + self.presentControllerInGlobalOverlay?(controller) + } + func presentNative(_ controller: UIViewController) { self.presentNativeImpl?(controller) } @@ -226,6 +232,10 @@ public func nativeWindowHostView() -> WindowHostView { hostView?.present?(controller, level) } + window.presentControllerInGlobalOverlay = { [weak hostView] controller in + hostView?.presentInGlobalOverlay?(controller) + } + window.presentNativeImpl = { [weak hostView] controller in hostView?.presentNative?(controller) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 531ccdc445..6cf72c003d 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -21,7 +21,7 @@ private class NavigationControllerView: UIView { open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { public private(set) weak var overlayPresentingController: ViewController? - private var containerLayout = ContainerViewLayout() + private var validLayout: ContainerViewLayout? private var navigationTransitionCoordinator: NavigationTransitionCoordinator? @@ -72,10 +72,10 @@ open class NavigationController: UINavigationController, ContainableController, if !self.isViewLoaded { self.loadView() } - self.containerLayout = layout + self.validLayout = layout transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) - let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) + let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) if let topViewController = self.topViewController { if let topViewController = topViewController as? ContainableController { @@ -101,6 +101,7 @@ open class NavigationController: UINavigationController, ContainableController, open override func loadView() { self.view = NavigationControllerView() self.view.clipsToBounds = true + self.view.autoresizingMask = [] if #available(iOSApplicationExtension 11.0, *) { self.navigationBar.prefersLargeTitles = false @@ -224,17 +225,21 @@ open class NavigationController: UINavigationController, ContainableController, if !controller.hasActiveInput { self.view.endEditing(true) } - let appliedLayout = self.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? self.containerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in - if let strongSelf = self { - let containerLayout = strongSelf.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? strongSelf.containerLayout.inputHeight : nil) - if containerLayout != appliedLayout { - controller.containerLayoutUpdated(containerLayout, transition: .immediate) + if let validLayout = self.validLayout { + let appliedLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in + if let strongSelf = self, let validLayout = strongSelf.validLayout { + let containerLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil) + if containerLayout != appliedLayout { + controller.containerLayoutUpdated(containerLayout, transition: .immediate) + } + strongSelf.pushViewController(controller, animated: true) } - strongSelf.pushViewController(controller, animated: true) - } - })) + })) + } else { + self.pushViewController(controller, animated: false) + } } open override func pushViewController(_ viewController: UIViewController, animated: Bool) { @@ -247,7 +252,9 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) - controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + if let validLayout = self.validLayout { + controller.containerLayoutUpdated(validLayout, transition: .immediate) + } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { ready?.set(true) @@ -261,7 +268,9 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) - controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + if let validLayout = self.validLayout { + controller.containerLayoutUpdated(validLayout, transition: .immediate) + } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { ready?.set(true) @@ -323,15 +332,17 @@ open class NavigationController: UINavigationController, ContainableController, let topViewController = viewControllers[viewControllers.count - 1] as UIViewController if let controller = topViewController as? ContainableController { - var layoutToApply = self.containerLayout - var hasActiveInput = false - if let controller = controller as? ViewController { - hasActiveInput = controller.hasActiveInput + if let validLayout = self.validLayout { + var layoutToApply = validLayout + var hasActiveInput = false + if let controller = controller as? ViewController { + hasActiveInput = controller.hasActiveInput + } + if !hasActiveInput { + layoutToApply = layoutToApply.withUpdatedInputHeight(nil) + } + controller.containerLayoutUpdated(layoutToApply, transition: .immediate) } - if !hasActiveInput { - layoutToApply = layoutToApply.withUpdatedInputHeight(nil) - } - controller.containerLayoutUpdated(layoutToApply, transition: .immediate) } else { topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) } @@ -452,7 +463,9 @@ open class NavigationController: UINavigationController, ContainableController, self._presentedViewController = controller self.view.endEditing(true) - controller.containerLayoutUpdated(self.containerLayout, transition: .immediate) + if let validLayout = self.validLayout { + controller.containerLayoutUpdated(validLayout, transition: .immediate) + } var ready: Signal = .single(true) diff --git a/Display/PeekController.swift b/Display/PeekController.swift new file mode 100644 index 0000000000..fb968c77aa --- /dev/null +++ b/Display/PeekController.swift @@ -0,0 +1,81 @@ +import Foundation +import AsyncDisplayKit + +public final class PeekControllerTheme { + public let isDark: Bool + public let menuBackgroundColor: UIColor + public let menuItemHighligtedColor: UIColor + public let menuItemSeparatorColor: UIColor + public let accentColor: UIColor + public let destructiveColor: UIColor + + public init(isDark: Bool, menuBackgroundColor: UIColor, menuItemHighligtedColor: UIColor, menuItemSeparatorColor: UIColor, accentColor: UIColor, destructiveColor: UIColor) { + self.isDark = isDark + self.menuBackgroundColor = menuBackgroundColor + self.menuItemHighligtedColor = menuItemHighligtedColor + self.menuItemSeparatorColor = menuItemSeparatorColor + self.accentColor = accentColor + self.destructiveColor = destructiveColor + } +} + +public final class PeekController: ViewController { + private var controllerNode: PeekControllerNode { + return self.displayNode as! PeekControllerNode + } + + private let theme: PeekControllerTheme + private let content: PeekControllerContent + private let sourceNode: () -> ASDisplayNode? + + private var animatedIn = false + + public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) { + self.theme = theme + self.content = content + self.sourceNode = sourceNode + + super.init(navigationBarTheme: nil) + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override public func loadDisplayNode() { + self.displayNode = PeekControllerNode(theme: self.theme, content: self.content, requestDismiss: { [weak self] in + self?.dismiss() + }) + self.displayNodeDidLoad() + } + + private func getSourceRect() -> CGRect { + if let sourceNode = self.sourceNode() { + return sourceNode.view.convert(sourceNode.bounds, to: self.view) + } else { + let size = self.displayNode.bounds.size + return CGRect(origin: CGPoint(x: floor((size.width - 10.0) / 2.0), y: floor((size.height - 10.0) / 2.0)), size: CGSize(width: 10.0, height: 10.0)) + } + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if !self.animatedIn { + self.animatedIn = true + self.controllerNode.animateIn(from: self.getSourceRect()) + } + } + + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, transition: transition) + } + + override public func dismiss(completion: (() -> Void)? = nil) { + self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in + self?.presentingViewController?.dismiss(animated: false, completion: nil) + }) + } +} diff --git a/Display/PeekControllerContent.swift b/Display/PeekControllerContent.swift new file mode 100644 index 0000000000..871b3cb18e --- /dev/null +++ b/Display/PeekControllerContent.swift @@ -0,0 +1,23 @@ +import Foundation +import AsyncDisplayKit + +public enum PeekControllerContentPresentation { + case contained + case freeform +} + +public enum PeerkControllerMenuActivation { + case drag + case press +} + +public protocol PeekControllerContent { + func presentation() -> PeekControllerContentPresentation + func menuActivation() -> PeerkControllerMenuActivation + func menuItems() -> [PeekControllerMenuItem] + func node() -> PeekControllerContentNode & ASDisplayNode +} + +public protocol PeekControllerContentNode { + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize +} diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift new file mode 100644 index 0000000000..2a4d69e3ec --- /dev/null +++ b/Display/PeekControllerGestureRecognizer.swift @@ -0,0 +1,228 @@ +import Foundation +import UIKit +import SwiftSignalKit + +private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> Bool { + if view.bounds.contains(point), let view = view as? UIScrollView, view.isDecelerating { + return true + } + for subview in view.subviews { + let subviewPoint = view.convert(point, to: subview) + if traceDeceleratingScrollView(subview, at: subviewPoint) { + return true + } + } + return false +} + +public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { + private let contentAtPoint: (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? + private let present: (PeekControllerContent, ASDisplayNode) -> PeekController? + + private var tapLocation: CGPoint? + private var longTapTimer: SwiftSignalKit.Timer? + private var pressTimer: SwiftSignalKit.Timer? + + private let candidateContentDisposable = MetaDisposable() + private var candidateContent: (ASDisplayNode, PeekControllerContent)? + + private var menuActivation: PeerkControllerMenuActivation? + private weak var presentedController: PeekController? + + public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> PeekController?) { + self.contentAtPoint = contentAtPoint + self.present = present + + super.init(target: nil, action: nil) + } + + deinit { + self.longTapTimer?.invalidate() + self.pressTimer?.invalidate() + self.candidateContentDisposable.dispose() + } + + private func startLongTapTimer() { + self.longTapTimer?.invalidate() + let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in + self?.longTapTimerFired() + }, queue: Queue.mainQueue()) + self.longTapTimer = longTapTimer + longTapTimer.start() + } + + private func startPressTimer() { + self.pressTimer?.invalidate() + let pressTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in + self?.pressTimerFired() + }, queue: Queue.mainQueue()) + self.pressTimer = pressTimer + pressTimer.start() + } + + private func stopLongTapTimer() { + self.longTapTimer?.invalidate() + self.longTapTimer = nil + } + + private func stopPressTimer() { + self.pressTimer?.invalidate() + self.pressTimer = nil + } + + override public func reset() { + super.reset() + + self.stopLongTapTimer() + self.stopPressTimer() + self.tapLocation = nil + self.candidateContent = nil + self.menuActivation = nil + self.presentedController = nil + } + + private func longTapTimerFired() { + guard let _ = self.tapLocation, let (sourceNode, content) = self.candidateContent else { + return + } + + self.state = .began + + if let presentedController = self.present(content, sourceNode) { + self.menuActivation = content.menuActivation() + self.presentedController = presentedController + + switch content.menuActivation() { + case .drag: + break + case .press: + if #available(iOSApplicationExtension 9.0, *) { + if presentedController.traitCollection.forceTouchCapability != .available { + self.startPressTimer() + } + } else { + self.startPressTimer() + } + } + } + } + + private func pressTimerFired() { + if let _ = self.tapLocation, let menuActivation = self.menuActivation, case .press = menuActivation { + if let presentedController = self.presentedController { + if presentedController.isNodeLoaded { + (presentedController.displayNode as? PeekControllerNode)?.activateMenu() + } + self.menuActivation = nil + self.presentedController = nil + self.state = .ended + } + } + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if let view = self.view, let tapLocation = touches.first?.location(in: view) { + if traceDeceleratingScrollView(view, at: tapLocation) { + self.candidateContent = nil + self.state = .failed + } else { + if let contentSignal = self.contentAtPoint(tapLocation) { + self.candidateContentDisposable.set((contentSignal |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + switch strongSelf.state { + case .possible, .changed: + if let (sourceNode, content) = result { + strongSelf.tapLocation = tapLocation + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + strongSelf.startLongTapTimer() + } else { + strongSelf.state = .failed + } + default: + break + } + } + })) + } else { + self.state = .failed + } + } + } + } + + override public func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + let velocity = self.velocity(in: self.view) + + if let presentedController = self.presentedController, presentedController.isNodeLoaded { + (presentedController.displayNode as? PeekControllerNode)?.endDraggingWithVelocity(velocity.y) + self.presentedController = nil + self.menuActivation = nil + } + + self.tapLocation = nil + self.candidateContent = nil + self.state = .failed + } + + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.tapLocation = nil + self.candidateContent = nil + self.state = .failed + + if let presentedController = self.presentedController { + self.menuActivation = nil + self.presentedController = nil + presentedController.dismiss() + } + } + + override public func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if let touch = touches.first, let initialTapLocation = self.tapLocation, let menuActivation = self.menuActivation { + let touchLocation = touch.location(in: self.view) + if let presentedController = self.presentedController { + switch menuActivation { + case .drag: + var offset = touchLocation.y - initialTapLocation.y + let delta = abs(offset) + let factor: CGFloat = 60.0 + offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0) + + if presentedController.isNodeLoaded { + (presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(offset) + } + case .press: + if #available(iOSApplicationExtension 9.0, *) { + if touch.force >= 2.5 { + if presentedController.isNodeLoaded { + (presentedController.displayNode as? PeekControllerNode)?.activateMenu() + self.menuActivation = nil + self.presentedController = nil + self.state = .ended + } + } + } + break + } + } else { + let dX = touchLocation.x - initialTapLocation.x + let dY = touchLocation.y - initialTapLocation.y + + if dX * dX + dY * dY > 3.0 * 3.0 { + self.stopLongTapTimer() + self.tapLocation = nil + self.candidateContent = nil + self.state = .failed + } + } + } + } +} diff --git a/Display/PeekControllerMenuItemNode.swift b/Display/PeekControllerMenuItemNode.swift new file mode 100644 index 0000000000..324004fbe9 --- /dev/null +++ b/Display/PeekControllerMenuItemNode.swift @@ -0,0 +1,91 @@ +import Foundation +import AsyncDisplayKit + +public enum PeekControllerMenuItemColor { + case accent + case destructive +} + +public struct PeekControllerMenuItem { + public let title: String + public let color: PeekControllerMenuItemColor + public let action: () -> Void + + public init(title: String, color: PeekControllerMenuItemColor, action: @escaping () -> Void) { + self.title = title + self.color = color + self.action = action + } +} + +final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { + private let item: PeekControllerMenuItem + private let activatedAction: () -> Void + + private let separatorNode: ASDisplayNode + private let highlightedBackgroundNode: ASDisplayNode + private let textNode: ASTextNode + + init(theme: PeekControllerTheme, item: PeekControllerMenuItem, activatedAction: @escaping () -> Void) { + self.item = item + self.activatedAction = activatedAction + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + self.separatorNode.backgroundColor = theme.menuItemSeparatorColor + + self.highlightedBackgroundNode = ASDisplayNode() + self.highlightedBackgroundNode.isLayerBacked = true + self.highlightedBackgroundNode.backgroundColor = theme.menuItemHighligtedColor + self.highlightedBackgroundNode.alpha = 0.0 + + self.textNode = ASTextNode() + self.textNode.isLayerBacked = true + self.textNode.displaysAsynchronously = false + + let textColor: UIColor + switch item.color { + case .accent: + textColor = theme.accentColor + case .destructive: + textColor = theme.destructiveColor + } + self.textNode.attributedText = NSAttributedString(string: item.title, font: Font.regular(20.0), textColor: textColor) + + super.init() + + self.addSubnode(self.separatorNode) + self.addSubnode(self.highlightedBackgroundNode) + self.addSubnode(self.textNode) + + self.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.view.superview?.bringSubview(toFront: strongSelf.view) + strongSelf.highlightedBackgroundNode.alpha = 1.0 + } else { + strongSelf.highlightedBackgroundNode.alpha = 0.0 + strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + } + + self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside) + } + + func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + let height: CGFloat = 57.0 + transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: height))) + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: width, height: UIScreenPixel))) + + let textSize = self.textNode.measure(CGSize(width: width - 10.0, height: height)) + transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor((height - textSize.height) / 2.0)), size: textSize)) + + return height + } + + @objc func buttonPressed() { + self.activatedAction() + self.item.action() + } +} diff --git a/Display/PeekControllerMenuNode.swift b/Display/PeekControllerMenuNode.swift new file mode 100644 index 0000000000..a89587d5ea --- /dev/null +++ b/Display/PeekControllerMenuNode.swift @@ -0,0 +1,30 @@ +import Foundation +import AsyncDisplayKit + +final class PeekControllerMenuNode: ASDisplayNode { + private let itemNodes: [PeekControllerMenuItemNode] + + init(theme: PeekControllerTheme, items: [PeekControllerMenuItem], activatedAction: @escaping () -> Void) { + self.itemNodes = items.map { PeekControllerMenuItemNode(theme: theme, item: $0, activatedAction: activatedAction) } + + super.init() + + self.backgroundColor = theme.menuBackgroundColor + self.cornerRadius = 16.0 + self.clipsToBounds = true + + for itemNode in self.itemNodes { + self.addSubnode(itemNode) + } + } + + func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat { + var verticalOffset: CGFloat = 0.0 + for itemNode in self.itemNodes { + let itemHeight = itemNode.updateLayout(width: width, transition: transition) + transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: width, height: itemHeight))) + verticalOffset += itemHeight + } + return verticalOffset - UIScreenPixel + } +} diff --git a/Display/PeekControllerNode.swift b/Display/PeekControllerNode.swift new file mode 100644 index 0000000000..2ca82a1fad --- /dev/null +++ b/Display/PeekControllerNode.swift @@ -0,0 +1,206 @@ +import Foundation +import AsyncDisplayKit + +final class PeekControllerNode: ViewControllerTracingNode { + private let requestDismiss: () -> Void + + private let theme: PeekControllerTheme + + private let blurView: UIView + private let dimNode: ASDisplayNode + private let containerBackgroundNode: ASImageNode + private let containerNode: ASDisplayNode + + private var validLayout: ContainerViewLayout? + private var containerOffset: CGFloat = 0.0 + + private let content: PeekControllerContent + private let contentNode: PeekControllerContentNode & ASDisplayNode + + private let menuNode: PeekControllerMenuNode? + private var displayingMenu = false + + init(theme: PeekControllerTheme, content: PeekControllerContent, requestDismiss: @escaping () -> Void) { + self.theme = theme + self.requestDismiss = requestDismiss + + self.dimNode = ASDisplayNode() + self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: theme.isDark ? .dark : .light)) + self.blurView.isUserInteractionEnabled = false + + switch content.menuActivation() { + case .drag: + self.dimNode.backgroundColor = nil + self.blurView.alpha = 1.0 + case .press: + self.dimNode.backgroundColor = UIColor(white: theme.isDark ? 0.0 : 1.0, alpha: 0.5) + self.blurView.alpha = 0.0 + } + + self.containerBackgroundNode = ASImageNode() + self.containerBackgroundNode.isLayerBacked = true + self.containerBackgroundNode.displayWithoutProcessing = true + self.containerBackgroundNode.displaysAsynchronously = false + + self.containerNode = ASDisplayNode() + self.containerNode.clipsToBounds = true + self.containerNode.cornerRadius = 16.0 + + self.content = content + self.contentNode = content.node() + + var activatedActionImpl: (() -> Void)? + let menuItems = content.menuItems() + if menuItems.isEmpty { + self.menuNode = nil + } else { + self.menuNode = PeekControllerMenuNode(theme: theme, items: menuItems, activatedAction: { + activatedActionImpl?() + }) + } + + super.init() + + self.addSubnode(self.dimNode) + self.view.addSubview(self.blurView) + self.containerNode.addSubnode(self.contentNode) + self.addSubnode(self.containerNode) + + if let menuNode = self.menuNode { + self.addSubnode(menuNode) + } + + activatedActionImpl = { [weak self] in + self?.requestDismiss() + } + } + + deinit { + } + + override func didLoad() { + super.didLoad() + + self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:)))) + self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) + transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: layout.size)) + + let layoutInsets = layout.insets(options: []) + let maxContainerSize = CGSize(width: layout.size.width - 14.0 * 2.0, height: layout.size.height - layoutInsets.top - layoutInsets.bottom - 90.0) + + var menuSize: CGSize? + + let contentSize = self.contentNode.updateLayout(size: maxContainerSize, transition: transition) + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentSize)) + + var containerFrame: CGRect + switch self.content.presentation() { + case .contained: + containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: self.containerOffset + floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) + case .freeform: + containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: self.containerOffset + floor((layout.size.height - contentSize.height) / 4.0)), size: contentSize) + } + + if let menuNode = self.menuNode { + let menuWidth = layout.size.width - layoutInsets.left - layoutInsets.right - 14.0 * 2.0 + let menuHeight = menuNode.updateLayout(width: menuWidth, transition: transition) + menuSize = CGSize(width: menuWidth, height: menuHeight) + + if self.displayingMenu { + containerFrame.origin.y = min(containerFrame.origin.y, layout.size.height - layoutInsets.bottom - menuHeight - 14.0 * 2.0 - containerFrame.height) + + transition.updateAlpha(layer: self.blurView.layer, alpha: 1.0) + } + } + + transition.updateFrame(node: self.containerNode, frame: containerFrame) + + if let menuNode = self.menuNode, let menuSize = menuSize { + let menuY: CGFloat + if self.displayingMenu { + menuY = max(containerFrame.maxY + 14.0, layout.size.height - layoutInsets.bottom - 14.0 - menuSize.height) + } else { + menuY = layout.size.height + 14.0 + } + + transition.updateFrame(node: menuNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - menuSize.width) / 2.0), y: menuY), size: menuSize)) + } + } + + func animateIn(from rect: CGRect) { + self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3) + + self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) + self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + + func animateOut(to rect: CGRect, completion: @escaping () -> Void) { + self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.blurView.layer.animateAlpha(from: self.blurView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false) + self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, additive: true, force: true, completion: { _ in + completion() + }) + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) + self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false) + if let menuNode = self.menuNode { + menuNode.layer.animatePosition(from: menuNode.position, to: CGPoint(x: menuNode.position.x, y: self.bounds.size.height + menuNode.bounds.size.height / 2.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false) + } + } + + @objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) { + if case .ended = recognizer.state { + self.requestDismiss() + } + } + + @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + switch recognizer.state { + case .changed: + break + case .cancelled, .ended: + break + default: + break + } + } + + func applyDraggingOffset(_ offset: CGFloat) { + self.containerOffset = min(0.0, offset) + if self.containerOffset < -25.0 { + //self.displayingMenu = true + } else { + //self.displayingMenu = false + } + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .immediate) + } + } + + func activateMenu() { + if let layout = self.validLayout { + self.displayingMenu = true + self.containerOffset = 0.0 + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.18, curve: .spring)) + } + } + + func endDraggingWithVelocity(_ velocity: CGFloat) { + if let _ = self.menuNode, velocity < -600.0 || self.containerOffset < -38.0 { + if let layout = self.validLayout { + self.displayingMenu = true + self.containerOffset = 0.0 + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.18, curve: .spring)) + } + } else { + self.requestDismiss() + } + } +} diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 4ab5f9483e..5546e940e2 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -226,15 +226,15 @@ class StatusBarManager { self.host.statusBarStyle = statusBarStyle } if let statusBarWindow = self.host.statusBarWindow { - statusBarWindow.alpha = globalStatusBar.1 + statusBarView.alpha = globalStatusBar.1 var statusBarBounds = statusBarWindow.bounds if !statusBarBounds.origin.y.isEqual(to: globalStatusBar.2) { statusBarBounds.origin.y = globalStatusBar.2 statusBarWindow.bounds = statusBarBounds } } - } else if let statusBarWindow = self.host.statusBarWindow { - statusBarWindow.alpha = 0.0 + } else { + statusBarView.alpha = 0.0 } } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index b6750b07fb..791332e55e 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -34,6 +34,7 @@ public enum StatusBarStyle { private enum StatusBarItemType { case Generic case Battery + case Activity } func makeStatusBarProxy(_ statusBarStyle: StatusBarStyle, statusBar: UIView) -> StatusBarProxyNode { @@ -90,7 +91,22 @@ private class StatusBarItemNode: ASDisplayNode { UIGraphicsPopContext() } } - let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic + //dumpViews(self.targetView) + var type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic + if case .Generic = type { + var hasActivityBackground = false + var hasText = false + for subview in self.targetView.subviews { + if let stringClass = stringClass, subview.checkIsKind(of: stringClass) { + hasText = true + } else if let activityClass = activityClass, subview.checkIsKind(of: activityClass) { + hasActivityBackground = true + } + } + if hasActivityBackground && hasText { + type = .Activity + } + } tintStatusBarItem(context, type: type, style: statusBarStyle) self.contents = context.generateImage()?.cgImage @@ -229,6 +245,8 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp } } } + case .Activity: + break case .Generic: var pixel = context.bytes.assumingMemoryBound(to: UInt32.self) let end = context.bytes.advanced(by: context.length).assumingMemoryBound(to: UInt32.self) @@ -259,7 +277,15 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp } } -private let batteryItemClass: AnyClass? = { () -> AnyClass? in +private let foregroundClass: AnyClass? = { + var nameString = "StatusBar" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "ForegroundView" + } + return NSClassFromString("_UI" + nameString) +}() + +private let batteryItemClass: AnyClass? = { var nameString = "StatusBarBattery" if CFAbsoluteTimeGetCurrent() > 0 { nameString += "ItemView" @@ -267,6 +293,22 @@ private let batteryItemClass: AnyClass? = { () -> AnyClass? in return NSClassFromString("UI" + nameString) }() +private let activityClass: AnyClass? = { + var nameString = "StatusBarBackground" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "ActivityView" + } + return NSClassFromString("_UI" + nameString) +}() + +private let stringClass: AnyClass? = { + var nameString = "StatusBar" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "StringView" + } + return NSClassFromString("_UI" + nameString) +}() + private class StatusBarProxyNodeTimerTarget: NSObject { let action: () -> Void @@ -327,7 +369,15 @@ class StatusBarProxyNode: ASDisplayNode { self.clipsToBounds = true //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) + var rootView: UIView = statusBar for subview in statusBar.subviews { + if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { + rootView = subview + break + } + } + + for subview in rootView.subviews { let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview) self.itemNodes.append(itemNode) self.addSubnode(itemNode) @@ -343,10 +393,20 @@ class StatusBarProxyNode: ASDisplayNode { private func updateItems() { let statusBar = self.statusBar + var rootView: UIView = statusBar + for subview in statusBar.subviews { + if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { + rootView = subview + break + } + } + + //dumpViews(self.statusBar) + var i = 0 while i < self.itemNodes.count { var found = false - for subview in statusBar.subviews { + for subview in rootView.subviews { if self.itemNodes[i].targetView == subview { found = true break @@ -362,7 +422,7 @@ class StatusBarProxyNode: ASDisplayNode { } } - for subview in statusBar.subviews { + for subview in rootView.subviews { var found = false for itemNode in self.itemNodes { if itemNode.targetView == subview { diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index daf9c043ee..5f1b1434db 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -26,7 +26,7 @@ public final class TabBarControllerTheme { } open class TabBarController: ViewController { - private var containerLayout = ContainerViewLayout() + private var validLayout: ContainerViewLayout? private var tabBarControllerNode: TabBarControllerNode { get { @@ -88,10 +88,32 @@ open class TabBarController: ViewController { } } + private var debugTapCounter: (Double, Int) = (0.0, 0) + override open func loadDisplayNode() { self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index in if let strongSelf = self { - strongSelf.controllers[index].containerLayoutUpdated(strongSelf.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) + if strongSelf.selectedIndex == index { + let timestamp = CACurrentMediaTime() + if strongSelf.debugTapCounter.0 < timestamp - 0.4 { + strongSelf.debugTapCounter.0 = timestamp + strongSelf.debugTapCounter.1 = 0 + } + + if strongSelf.debugTapCounter.0 >= timestamp - 0.4 { + strongSelf.debugTapCounter.0 = timestamp + strongSelf.debugTapCounter.1 += 1 + } + + if strongSelf.debugTapCounter.1 >= 10 { + strongSelf.debugTapCounter.1 = 0 + + strongSelf.controllers[index].tabBarItemDebugTapAction?() + } + } + if let validLayout = strongSelf.validLayout { + strongSelf.controllers[index].containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) + } strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in if let strongSelf = self { strongSelf.selectedIndex = index @@ -127,7 +149,9 @@ open class TabBarController: ViewController { var displayNavigationBar = false if let currentController = self.currentController { currentController.willMove(toParentViewController: self) - currentController.containerLayoutUpdated(self.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) + if let validLayout = self.validLayout { + currentController.containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) + } self.tabBarControllerNode.currentControllerView = currentController.view currentController.navigationBar?.isHidden = true self.addChildViewController(currentController) @@ -149,13 +173,15 @@ open class TabBarController: ViewController { self.setDisplayNavigationBar(displayNavigationBar) } - self.tabBarControllerNode.containerLayoutUpdated(self.containerLayout, transition: .immediate) + if let validLayout = self.validLayout { + self.tabBarControllerNode.containerLayoutUpdated(validLayout, transition: .immediate) + } } override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) - self.containerLayout = layout + self.validLayout = layout self.tabBarControllerNode.containerLayoutUpdated(layout, transition: transition) diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 1b19c3152a..36e91edae6 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -197,6 +197,16 @@ class TabBarNode: ASDisplayNode { self.addSubnode(self.separatorNode) } + override func didLoad() { + super.didLoad() + + self.view.addGestureRecognizer(TabBarTapRecognizer(tap: { [weak self] point in + if let strongSelf = self { + strongSelf.tapped(at: point) + } + })) + } + func updateTheme(_ theme: TabBarControllerTheme) { if self.theme !== theme { self.theme = theme @@ -354,11 +364,8 @@ class TabBarNode: ASDisplayNode { } } - override func touchesBegan(_ touches: Set, with event: UIEvent?) { - super.touchesBegan(touches, with: event) - - if let touch = touches.first, let bottomInset = self.validLayout?.3 { - let location = touch.location(in: self.view) + private func tapped(at location: CGPoint) { + if let bottomInset = self.validLayout?.3 { if location.y > self.bounds.size.height - bottomInset { return } diff --git a/Display/TabBarTapRecognizer.swift b/Display/TabBarTapRecognizer.swift new file mode 100644 index 0000000000..4cbe02b777 --- /dev/null +++ b/Display/TabBarTapRecognizer.swift @@ -0,0 +1,58 @@ +import Foundation +import UIKit + +final class TabBarTapRecognizer: UIGestureRecognizer { + private let tap: (CGPoint) -> Void + + private var initialLocation: CGPoint? + + init(tap: @escaping (CGPoint) -> Void) { + self.tap = tap + + super.init(target: nil, action: nil) + } + + override func reset() { + super.reset() + + self.initialLocation = nil + } + + override func touchesBegan(_ touches: Set, with event: UIEvent) { + super.touchesBegan(touches, with: event) + + if self.initialLocation == nil { + self.initialLocation = touches.first?.location(in: self.view) + } + } + + override func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + if let initialLocation = self.initialLocation { + self.initialLocation = nil + self.tap(initialLocation) + self.state = .ended + } + } + + override func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + if let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) { + let deltaX = initialLocation.x - location.x + let deltaY = initialLocation.y - location.y + if deltaX * deltaX + deltaY * deltaY > 4.0 { + self.initialLocation = nil + self.state = .failed + } + } + } + + override func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.initialLocation = nil + self.state = .failed + } +} diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 1fb1150a23..9299e63fee 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -134,7 +134,7 @@ public extension UIImage { private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { let view = UIView() - //view.layer.isHidden = layer.isHidden + view.layer.isHidden = layer.isHidden view.layer.opacity = layer.opacity view.layer.contents = layer.contents view.layer.contentsRect = layer.contentsRect @@ -143,6 +143,7 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { view.layer.contentsGravity = layer.contentsGravity view.layer.masksToBounds = layer.masksToBounds view.layer.cornerRadius = layer.cornerRadius + view.layer.backgroundColor = layer.backgroundColor if let sublayers = layer.sublayers { for sublayer in sublayers { let subtree = makeSubtreeSnapshot(layer: sublayer) @@ -160,7 +161,7 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { let view = CALayer() - //view.layer.isHidden = layer.isHidden + view.isHidden = layer.isHidden view.opacity = layer.opacity view.contents = layer.contents view.contentsRect = layer.contentsRect @@ -169,6 +170,7 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { view.contentsGravity = layer.contentsGravity view.masksToBounds = layer.masksToBounds view.cornerRadius = layer.cornerRadius + view.backgroundColor = layer.backgroundColor if let sublayers = layer.sublayers { for sublayer in sublayers { let subtree = makeLayerSubtreeSnapshot(layer: sublayer) @@ -185,24 +187,40 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { } public extension UIView { - public func snapshotContentTree() -> UIView? { - if let snapshot = makeSubtreeSnapshot(layer: self.layer) { + public func snapshotContentTree(unhide: Bool = false) -> UIView? { + let wasHidden = self.isHidden + if unhide && wasHidden { + self.isHidden = false + } + let snapshot = makeSubtreeSnapshot(layer: self.layer) + if unhide && wasHidden { + self.isHidden = true + } + if let snapshot = snapshot { snapshot.frame = self.frame return snapshot - } else { - return nil } + + return nil } } public extension CALayer { - public func snapshotContentTree() -> CALayer? { - if let snapshot = makeLayerSubtreeSnapshot(layer: self) { + public func snapshotContentTree(unhide: Bool = false) -> CALayer? { + let wasHidden = self.isHidden + if unhide && wasHidden { + self.isHidden = false + } + let snapshot = makeLayerSubtreeSnapshot(layer: self) + if unhide && wasHidden { + self.isHidden = true + } + if let snapshot = snapshot { snapshot.frame = self.frame return snapshot - } else { - return nil } + + return nil } } diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 316fc4bca2..5926a69e3e 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -7,6 +7,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { @interface UIViewController (Navigation) +- (BOOL)isPresentedInPreviewingContext; - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; - (BOOL)ignoreAppearanceMethodInvocations; - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 085c7be66d..332d4fc0f1 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -106,6 +106,10 @@ static bool notyfyingShiftState = false; }); } +- (BOOL)isPresentedInPreviewingContext { + return ![self.presentingViewController isKindOfClass:[UIViewControllerPresentingProxy class]]; +} + - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations { [self setAssociatedObject:@(ignoreAppearanceMethodInvocations) forKey:UIViewControllerIgnoreAppearanceMethodInvocationsKey]; diff --git a/Display/ViewController.swift b/Display/ViewController.swift index c00c6f1ba1..4c12b141c1 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -30,7 +30,7 @@ open class ViewControllerPresentationArguments { } @objc open class ViewController: UIViewController, ContainableController { - private var containerLayout = ContainerViewLayout() + private var validLayout: ContainerViewLayout? private let presentationContext: PresentationContext public final var supportedOrientations: UIInterfaceOrientationMask = .all @@ -60,6 +60,8 @@ open class ViewControllerPresentationArguments { public private(set) var presentationArguments: Any? + public var tabBarItemDebugTapAction: (() -> Void)? + private var _displayNode: ASDisplayNode? public final var displayNode: ASDisplayNode { get { @@ -86,6 +88,8 @@ open class ViewControllerPresentationArguments { public let statusBar: StatusBar public let navigationBar: NavigationBar? + private var previewingContext: Any? + public var displayNavigationBar = true private weak var activeInputViewCandidate: UIResponder? @@ -172,7 +176,7 @@ open class ViewControllerPresentationArguments { } open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - self.containerLayout = layout + self.validLayout = layout if !self.isViewLoaded { self.loadView() @@ -221,6 +225,7 @@ open class ViewControllerPresentationArguments { self.displayNode.addSubnode(navigationBar) } } + self.view.autoresizingMask = [] self.view.addSubview(self.statusBar.view) self.presentationContext.view = self.view } @@ -238,8 +243,8 @@ open class ViewControllerPresentationArguments { } public func requestLayout(transition: ContainedViewLayoutTransition) { - if self.isViewLoaded { - self.containerLayoutUpdated(self.containerLayout, transition: transition) + if self.isViewLoaded, let validLayout = self.validLayout { + self.containerLayoutUpdated(validLayout, transition: transition) } } @@ -293,6 +298,11 @@ open class ViewControllerPresentationArguments { } } + public func presentInGlobalOverlay(_ controller: ViewController, with arguments: Any? = nil) { + controller.presentationArguments = arguments + self.window?.presentInGlobalOverlay(controller) + } + open override func viewWillDisappear(_ animated: Bool) { self.activeInputViewCandidate = findCurrentResponder(self.view) @@ -313,4 +323,27 @@ open class ViewControllerPresentationArguments { open func dismiss(completion: (() -> Void)? = nil) { } + + @available(iOSApplicationExtension 9.0, *) + open func registerForPreviewing(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme, onlyNative: Bool) { + if self.traitCollection.forceTouchCapability == .available { + let _ = super.registerForPreviewing(with: delegate, sourceView: sourceView) + } else if !onlyNative { + if self.previewingContext == nil { + let previewingContext = SimulatedViewControllerPreviewing(theme: theme, delegate: delegate, sourceView: sourceView, node: self.displayNode, present: { [weak self] c, a in + self?.presentInGlobalOverlay(c, with: a) + }) + self.previewingContext = previewingContext + } + } + } + + @available(iOSApplicationExtension 9.0, *) + open override func unregisterForPreviewing(withContext previewing: UIViewControllerPreviewing) { + if self.previewingContext != nil { + self.previewingContext = nil + } else { + super.unregisterForPreviewing(withContext: previewing) + } + } } diff --git a/Display/ViewControllerPreviewing.swift b/Display/ViewControllerPreviewing.swift new file mode 100644 index 0000000000..137384e6dc --- /dev/null +++ b/Display/ViewControllerPreviewing.swift @@ -0,0 +1,118 @@ +import Foundation +import UIKit +import SwiftSignalKit + +@available(iOSApplicationExtension 9.0, *) +private final class ViewControllerPeekContent: PeekControllerContent { + private let controller: ViewController + private let menu: [PeekControllerMenuItem] + + init(controller: ViewController) { + self.controller = controller + var menu: [PeekControllerMenuItem] = [] + for item in controller.previewActionItems { + menu.append(PeekControllerMenuItem(title: item.title, color: .accent, action: { [weak controller] in + if let controller = controller, let item = item as? UIPreviewAction { + item.handler(item, controller) + } + })) + } + self.menu = menu + } + + func presentation() -> PeekControllerContentPresentation { + return .contained + } + + func menuActivation() -> PeerkControllerMenuActivation { + return .drag + } + + func menuItems() -> [PeekControllerMenuItem] { + return self.menu + } + + func node() -> PeekControllerContentNode & ASDisplayNode { + return ViewControllerPeekContentNode(controller: self.controller) + } +} + +private final class ViewControllerPeekContentNode: ASDisplayNode, PeekControllerContentNode { + private let controller: ViewController + private var hasValidLayout = false + + init(controller: ViewController) { + self.controller = controller + + super.init() + } + + func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + if !self.hasValidLayout { + self.hasValidLayout = true + self.controller.view.frame = CGRect(origin: CGPoint(), size: size) + self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate) + self.controller.setIgnoreAppearanceMethodInvocations(true) + self.view.addSubview(self.controller.view) + self.controller.setIgnoreAppearanceMethodInvocations(false) + self.controller.viewWillAppear(false) + self.controller.viewDidAppear(false) + } else { + self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: transition) + } + + return size + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.contains(point) { + return self.view + } + return nil + } +} + +@available(iOSApplicationExtension 9.0, *) +final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreviewing { + weak var delegateImpl: UIViewControllerPreviewingDelegate? + var delegate: UIViewControllerPreviewingDelegate { + return self.delegateImpl! + } + let recognizer: PeekControllerGestureRecognizer + var previewingGestureRecognizerForFailureRelationship: UIGestureRecognizer { + return self.recognizer + } + let sourceView: UIView + let node: ASDisplayNode + + var sourceRect: CGRect = CGRect() + + init(theme: PeekControllerTheme, delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, node: ASDisplayNode, present: @escaping (ViewController, Any?) -> Void) { + self.delegateImpl = delegate + self.sourceView = sourceView + self.node = node + var contentAtPointImpl: ((CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?)? + self.recognizer = PeekControllerGestureRecognizer(contentAtPoint: { point in + return contentAtPointImpl?(point) + }, present: { content, sourceNode in + let controller = PeekController(theme: theme, content: content, sourceNode: { + return sourceNode + }) + present(controller, nil) + return controller + }) + + node.view.addGestureRecognizer(self.recognizer) + + super.init() + + contentAtPointImpl = { [weak self] point in + if let strongSelf = self, let delegate = strongSelf.delegateImpl { + if let controller = delegate.previewingContext(strongSelf, viewControllerForLocation: point) as? ViewController { + return .single((strongSelf.node, ViewControllerPeekContent(controller: controller))) + } + } + return nil + } + } +} diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index b2b1f9cdee..a085e496ac 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -162,7 +162,26 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container resolvedSafeInsets.right = 44.0 } - return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil) + var standardInputHeight: CGFloat = 216.0 + var predictiveHeight: CGFloat = 42.0 + + if layout.size.width.isEqual(to: 320.0) || layout.size.width.isEqual(to: 375.0) { + standardInputHeight = 216.0 + predictiveHeight = 42.0 + } else if layout.size.width.isEqual(to: 414.0) { + standardInputHeight = 226.0 + predictiveHeight = 42.0 + } else if layout.size.width.isEqual(to: 480.0) || layout.size.width.isEqual(to: 568.0) || layout.size.width.isEqual(to: 667.0) || layout.size.width.isEqual(to: 736.0) { + standardInputHeight = 162.0 + predictiveHeight = 38.0 + } else if layout.size.width.isEqual(to: 768.0) || layout.size.width.isEqual(to: 1024.0) { + standardInputHeight = 264.0 + predictiveHeight = 42.0 + } + + standardInputHeight += predictiveHeight + + return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, standardInputHeight: standardInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil) } private func encodeText(_ string: String, _ key: Int) -> String { @@ -250,6 +269,7 @@ public final class WindowHostView { let updatePreferNavigationUIHidden: (Bool) -> Void var present: ((ViewController, PresentationSurfaceLevel) -> Void)? + var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize) -> Void)? var layoutSubviews: (() -> Void)? @@ -276,6 +296,7 @@ public struct WindowTracingTags { public protocol WindowHost { func present(_ controller: ViewController, on level: PresentationSurfaceLevel) + func presentInGlobalOverlay(_ controller: ViewController) func invalidateDeferScreenEdgeGestures() func invalidatePreferNavigationUIHidden() func cancelInteractiveKeyboardGestures() @@ -324,6 +345,7 @@ public class Window1 { private var cachedHasPreview: Bool = false private let presentationContext: PresentationContext + private let overlayPresentationContext: GlobalOverlayPresentationContext private var tracingStatusBarsInvalidated = false private var shouldUpdateDeferScreenEdgeGestures = false @@ -372,11 +394,16 @@ public class Window1 { self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) self.presentationContext = PresentationContext() + self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost) self.hostView.present = { [weak self] controller, level in self?.present(controller, on: level) } + self.hostView.presentInGlobalOverlay = { [weak self] controller in + self?.presentInGlobalOverlay(controller) + } + self.hostView.presentNative = { [weak self] controller in self?.presentNative(controller) } @@ -415,6 +442,7 @@ public class Window1 { self.presentationContext.view = self.hostView.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { @@ -570,6 +598,10 @@ public class Window1 { } } + if let result = self.overlayPresentationContext.hitTest(point, with: event) { + return result + } + for controller in self._topLevelOverlayControllers.reversed() { if let result = controller.view.hitTest(point, with: event) { return result @@ -802,6 +834,7 @@ public class Window1 { if childLayoutUpdated { self._rootController?.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) + self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) for controller in self.topLevelOverlayControllers { controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) @@ -833,6 +866,10 @@ public class Window1 { self.presentationContext.present(controller, on: level) } + public func presentInGlobalOverlay(_ controller: ViewController) { + self.overlayPresentationContext.present(controller) + } + public func presentNative(_ controller: UIViewController) { } From 377c25943b069a36ed6d190e0544b6d56d0288c2 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 2 Mar 2018 20:43:26 +0400 Subject: [PATCH 050/245] no message --- Display.xcodeproj/project.pbxproj | 12 + Display/ContainedViewLayoutTransition.swift | 75 ++- Display/ContextMenuController.swift | 23 +- Display/ContextMenuNode.swift | 13 +- Display/HapticFeedback.swift | 111 ++++ Display/ImmediateTextNode.swift | 13 + Display/ListView.swift | 105 +++- Display/ListViewIntermediateState.swift | 4 +- Display/ListViewItemNode.swift | 2 + Display/NavigationBarBadge.swift | 4 +- Display/NavigationTitleNode.swift | 2 +- Display/PeekController.swift | 2 +- Display/PeekControllerContent.swift | 2 + Display/PeekControllerGestureRecognizer.swift | 82 ++- Display/PeekControllerNode.swift | 129 ++++- Display/TextNode.swift | 493 ++++++++++++++++++ Display/TooltipControllerNode.swift | 6 +- Display/UIKitUtils.swift | 14 + Display/UIViewController+Navigation.h | 1 + Display/UIViewController+Navigation.m | 11 +- Display/ViewControllerPreviewing.swift | 8 + Display/WindowContent.swift | 8 +- 22 files changed, 1056 insertions(+), 64 deletions(-) create mode 100644 Display/HapticFeedback.swift create mode 100644 Display/ImmediateTextNode.swift create mode 100644 Display/TextNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 1c641e2b50..387e79182f 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -169,6 +169,9 @@ D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; D0F8C3932014FB7C00236FC5 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; + D0FA08C220487A8600DD23FC /* HapticFeedback.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08C120487A8600DD23FC /* HapticFeedback.swift */; }; + D0FA08C42048803C00DD23FC /* TextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08C32048803C00DD23FC /* TextNode.swift */; }; + D0FA08C6204880C900DD23FC /* ImmediateTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */; }; D0FF9B301E7196F6000C66DB /* KeyboardManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */; }; /* End PBXBuildFile section */ @@ -337,6 +340,9 @@ D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CADisplayLink.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; + D0FA08C120487A8600DD23FC /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = ""; }; + D0FA08C32048803C00DD23FC /* TextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextNode.swift; sourceTree = ""; }; + D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImmediateTextNode.swift; sourceTree = ""; }; D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardManager.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -468,6 +474,8 @@ D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */, D0A749941E3A9E7B00AD786E /* SwitchNode.swift */, D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */, + D0FA08C32048803C00DD23FC /* TextNode.swift */, + D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */, ); name = Nodes; sourceTree = ""; @@ -513,6 +521,7 @@ D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */, D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */, D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */, + D0FA08C120487A8600DD23FC /* HapticFeedback.swift */, ); name = Utils; sourceTree = ""; @@ -950,8 +959,10 @@ buildActionMask = 2147483647; files = ( D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */, + D0FA08C220487A8600DD23FC /* HapticFeedback.swift in Sources */, D03AA5162030C5F80056C405 /* ListViewTempItemNode.swift in Sources */, D087BFB51F75181D003FD209 /* ChildWindowHostView.swift in Sources */, + D0FA08C6204880C900DD23FC /* ImmediateTextNode.swift in Sources */, D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, @@ -1032,6 +1043,7 @@ D03AA4E9202E02070056C405 /* ListViewReorderingItemNode.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, + D0FA08C42048803C00DD23FC /* TextNode.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */, diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index 0a8f51cd1c..a63dc7e556 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -487,6 +487,10 @@ public extension ContainedViewLayoutTransition { } func updateSublayerTransformScale(node: ASDisplayNode, scale: CGFloat, completion: ((Bool) -> Void)? = nil) { + if !node.isNodeLoaded { + node.subnodeTransform = CATransform3DMakeScale(scale, scale, 1.0) + return + } let t = node.layer.sublayerTransform let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) if currentScale.isEqual(to: scale) { @@ -513,8 +517,77 @@ public extension ContainedViewLayoutTransition { } } + func updateSublayerTransformScale(node: ASDisplayNode, scale: CGPoint, completion: ((Bool) -> Void)? = nil) { + if !node.isNodeLoaded { + node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + return + } + let t = node.layer.sublayerTransform + let currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23)) + if t.m22 < 0.0 { + currentScaleY = -currentScaleY + } + if CGPoint(x: currentScaleX, y: currentScaleY) == scale { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateTransformScale(node: ASDisplayNode, scale: CGPoint, completion: ((Bool) -> Void)? = nil) { + if !node.isNodeLoaded { + node.subnodeTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + return + } + let t = node.layer.transform + var currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23)) + if t.m22 < 0.0 { + currentScaleY = -currentScaleY + } + if CGPoint(x: currentScaleX, y: currentScaleY) == scale { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) + } + } + func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) { - print("update to \(offset) animated: \(self.isAnimated)") let t = layer.transform let currentOffset = CGPoint(x: t.m41, y: t.m42) if currentOffset == offset { diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index a845ca5a34..9fa6eb662d 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -2,9 +2,9 @@ import Foundation import AsyncDisplayKit public final class ContextMenuControllerPresentationArguments { - fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect)? + fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect, ASDisplayNode, CGRect)? - public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect)?) { + public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect, ASDisplayNode, CGRect)?) { self.sourceNodeAndRect = sourceNodeAndRect } } @@ -15,13 +15,15 @@ public final class ContextMenuController: ViewController { } private let actions: [ContextMenuAction] + private let catchTapsOutside: Bool private var layout: ContainerViewLayout? public var dismissed: (() -> Void)? - public init(actions: [ContextMenuAction]) { + public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false) { self.actions = actions + self.catchTapsOutside = catchTapsOutside super.init(navigationBarTheme: nil) } @@ -33,10 +35,10 @@ public final class ContextMenuController: ViewController { open override func loadDisplayNode() { self.displayNode = ContextMenuNode(actions: self.actions, dismiss: { [weak self] in self?.dismissed?() - self?.contextMenuNode.animateOut { [weak self] in + self?.contextMenuNode.animateOut { self?.presentingViewController?.dismiss(animated: false) } - }) + }, catchTapsOutside: self.catchTapsOutside) self.displayNodeDidLoad() } @@ -46,6 +48,13 @@ public final class ContextMenuController: ViewController { self.contextMenuNode.animateIn() } + override public func dismiss(completion: (() -> Void)? = nil) { + self.dismissed?() + self.contextMenuNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + } + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) @@ -57,10 +66,12 @@ public final class ContextMenuController: ViewController { } else { self.layout = layout - if let presentationArguments = self.presentationArguments as? ContextMenuControllerPresentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { + if let presentationArguments = self.presentationArguments as? ContextMenuControllerPresentationArguments, let (sourceNode, sourceRect, containerNode, containerRect) = presentationArguments.sourceNodeAndRect() { self.contextMenuNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) + self.contextMenuNode.containerRect = containerNode.view.convert(containerRect, to: nil) } else { self.contextMenuNode.sourceRect = nil + self.contextMenuNode.containerRect = nil } self.contextMenuNode.containerLayoutUpdated(layout, transition: transition) diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index 21f7fadd53..a20d03af4d 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -138,13 +138,16 @@ final class ContextMenuNode: ASDisplayNode { private let actionNodes: [ContextMenuActionNode] var sourceRect: CGRect? + var containerRect: CGRect? var arrowOnBottom: Bool = true private var dismissedByTouchOutside = false + private let catchTapsOutside: Bool - init(actions: [ContextMenuAction], dismiss: @escaping () -> Void) { + init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, catchTapsOutside: Bool) { self.actions = actions self.dismiss = dismiss + self.catchTapsOutside = catchTapsOutside self.containerNode = ContextMenuContainerNode() self.scrollNode = ContextMenuContentScrollNode() @@ -183,15 +186,16 @@ final class ContextMenuNode: ASDisplayNode { let actionsWidth = min(unboundActionsWidth, maxActionsWidth) let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize()) + let containerRect: CGRect = self.containerRect ?? self.bounds let insets = layout.insets(options: [.statusBar, .input]) let verticalOrigin: CGFloat var arrowOnBottom = true - if sourceRect.minY - 54.0 > insets.top { + if sourceRect.minY - 54.0 > containerRect.minY + insets.top { verticalOrigin = sourceRect.minY - 54.0 } else { - verticalOrigin = min(layout.size.height - insets.bottom - 54.0, sourceRect.maxY) + verticalOrigin = min(containerRect.maxY - insets.bottom - 54.0, sourceRect.maxY) arrowOnBottom = false } self.arrowOnBottom = arrowOnBottom @@ -235,6 +239,9 @@ final class ContextMenuNode: ASDisplayNode { self.dismissedByTouchOutside = true self.dismiss() } + if self.catchTapsOutside { + return self.view + } return nil } } diff --git a/Display/HapticFeedback.swift b/Display/HapticFeedback.swift new file mode 100644 index 0000000000..8255642ee8 --- /dev/null +++ b/Display/HapticFeedback.swift @@ -0,0 +1,111 @@ +import Foundation +import UIKit + +@available(iOSApplicationExtension 10.0, *) +private final class HapticFeedbackImpl { + private lazy var impactGenerator = { UIImpactFeedbackGenerator(style: .medium) }() + private lazy var selectionGenerator = { UISelectionFeedbackGenerator() }() + private lazy var notificationGenerator = { UINotificationFeedbackGenerator() }() + + func prepareTap() { + self.selectionGenerator.prepare() + } + + func tap() { + self.selectionGenerator.selectionChanged() + } + + func prepareImpact() { + self.impactGenerator.prepare() + } + + func impact() { + self.impactGenerator.impactOccurred() + } + + func success() { + self.notificationGenerator.notificationOccurred(.success) + } + + func error() { + self.notificationGenerator.notificationOccurred(.error) + } + + @objc dynamic func f() { + } +} + +public final class HapticFeedback { + private var impl: AnyObject? + + public init() { + } + + deinit { + let impl = self.impl + DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: { + if #available(iOSApplicationExtension 10.0, *) { + if let impl = impl as? HapticFeedbackImpl { + impl.f() + } + } + }) + } + + @available(iOSApplicationExtension 10.0, *) + private func withImpl(_ f: (HapticFeedbackImpl) -> Void) { + if self.impl == nil { + self.impl = HapticFeedbackImpl() + } + f(self.impl as! HapticFeedbackImpl) + } + + public func prepareTap() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.prepareTap() + } + } + } + + public func tap() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.tap() + } + } + } + + public func prepareImpact() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.prepareImpact() + } + } + } + + public func impact() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.impact() + } + } + } + + public func success() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.success() + } + } + } + + public func error() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.error() + } + } + } +} + diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift new file mode 100644 index 0000000000..828191286f --- /dev/null +++ b/Display/ImmediateTextNode.swift @@ -0,0 +1,13 @@ +import Foundation + +public final class ImmediateTextNode: TextNode { + public var attributedText: NSAttributedString? + public var textAlignment: NSTextAlignment = .natural + + public func updateLayout(_ constrainedSize: CGSize) -> CGSize { + let makeLayout = TextNode.asyncLayout(self) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) + let _ = apply() + return layout.size + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index 4ede1d78bd..1218fcc63c 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -113,6 +113,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() public private(set) final var insets = UIEdgeInsets() + private final var ensureTopInsetForOverlayHighlightedItems: CGFloat? private final var lastContentOffset: CGPoint = CGPoint() private final var lastContentOffsetTimestamp: CFAbsoluteTime = 0.0 private final var ignoreScrollingEvents: Bool = false @@ -163,6 +164,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var topItemOverscrollBackground: ListViewOverscrollBackgroundNode? private var bottomItemOverscrollBackground: ASDisplayNode? + private var itemHighlightOverlayBackground: ASDisplayNode? + private var touchesPosition = CGPoint() public private(set) var isTracking = false public private(set) var trackingOffset: CGFloat = 0.0 @@ -369,7 +372,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let reorderNode = self.reorderNode { self.reorderNode = nil if let itemNode = reorderNode.itemNode, itemNode.supernode == self { - self.view.bringSubview(toFront: itemNode.view) + self.reorderItemNodeToFront(itemNode) reorderNode.animateCompletion(completion: { [weak itemNode, weak reorderNode] in //itemNode?.isHidden = false reorderNode?.removeFromSupernode() @@ -944,8 +947,47 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func updateOverlayHighlight(transition: ContainedViewLayoutTransition) { + var lowestOverlayNode: ListViewItemNode? + + for itemNode in self.itemNodes { + if itemNode.isHighligtedInOverlay { + lowestOverlayNode = itemNode + itemNode.view.superview?.bringSubview(toFront: itemNode.view) + } + } + + if let lowestOverlayNode = lowestOverlayNode { + let itemHighlightOverlayBackground: ASDisplayNode + if let current = self.itemHighlightOverlayBackground { + itemHighlightOverlayBackground = current + } else { + itemHighlightOverlayBackground = ASDisplayNode() + itemHighlightOverlayBackground.frame = CGRect(origin: CGPoint(x: 0.0, y: -self.visibleSize.height), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height * 3.0)) + itemHighlightOverlayBackground.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + self.itemHighlightOverlayBackground = itemHighlightOverlayBackground + self.insertSubnode(itemHighlightOverlayBackground, belowSubnode: lowestOverlayNode) + itemHighlightOverlayBackground.alpha = 0.0 + transition.updateAlpha(node: itemHighlightOverlayBackground, alpha: 1.0) + } + } else if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { + self.itemHighlightOverlayBackground = nil + transition.updateAlpha(node: itemHighlightOverlayBackground, alpha: 0.0, completion: { [weak itemHighlightOverlayBackground] _ in + itemHighlightOverlayBackground?.removeFromSupernode() + }) + } + + /*if let ensureInset = self.ensureTopInsetForOverlayHighlightedItems { + transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: -ensureInset)) + } else { + transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint()) + }*/ + } + private func updateScroller(transition: ContainedViewLayoutTransition) { - if itemNodes.count == 0 { + self.updateOverlayHighlight(transition: transition) + + if self.itemNodes.count == 0 { return } @@ -1116,6 +1158,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets + self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true @@ -1988,19 +2031,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.addInsetsAnimationToValue(updatedInsets, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } - if abs(updatedApparentHeight - previousApparentHeight) > CGFloat.ulpOfOne { - node.apparentHeight = previousApparentHeight - node.animateFrameTransition(0.0, previousApparentHeight) - node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in - if let node = node { - node.animateFrameTransition(progress, currentValue) + if !abs(updatedApparentHeight - previousApparentHeight).isZero { + let currentAnimation = node.animationForKey("apparentHeight") + if let currentAnimation = currentAnimation, let toFloat = currentAnimation.to as? CGFloat, toFloat.isEqual(to: updatedApparentHeight) { + } else { + node.apparentHeight = previousApparentHeight + node.animateFrameTransition(0.0, previousApparentHeight) + node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in + if let node = node { + node.animateFrameTransition(progress, currentValue) + } + }) + + if node.rotated && currentAnimation == nil { + let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom + node.transitionOffset += previousApparentHeight - layout.size.height - insetPart + node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } - }) - - if node.rotated { - let insetPart: CGFloat = previousInsets.bottom - layout.insets.bottom - node.transitionOffset += previousApparentHeight - layout.size.height - insetPart - node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp) } } else { if node.shouldAnimateHorizontalFrameTransition() { @@ -2141,6 +2188,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel offsetFix += additionalScrollDistance self.insets = updateSizeAndInsets.insets + self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems self.visibleSize = updateSizeAndInsets.size for itemNode in self.itemNodes { @@ -2865,7 +2913,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let index = self.itemNodes[i].index { let frame = self.itemNodes[i].apparentFrame if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height + self.insets.bottom { - firstVisibleIndex = (index, frame.minY >= self.insets.top) + firstVisibleIndex = (index, frame.minY >= self.insets.top - 10.0) break } } @@ -3060,9 +3108,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if itemNode.index == index && itemNode.canBeSelected { if true { if !itemNode.isLayerBacked { - strongSelf.view.bringSubview(toFront: itemNode.view) + strongSelf.reorderItemNodeToFront(itemNode) for (_, headerNode) in strongSelf.itemHeaderNodes { - strongSelf.view.bringSubview(toFront: headerNode.view) + strongSelf.reorderHeaderNodeToFront(headerNode) } } let itemNodeFrame = itemNode.frame @@ -3115,6 +3163,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.highlightedItemIndex = nil } + public func updateNodeHighlightsAnimated(_ animated: Bool) { + let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.35, curve: .spring) : .immediate + self.updateOverlayHighlight(transition: transition) + } + private func itemIndexAtPoint(_ point: CGPoint) -> Int? { for itemNode in self.itemNodes { if itemNode.apparentContentFrame.contains(point) { @@ -3187,9 +3240,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if itemNode.index == index { if itemNode.canBeSelected { if !itemNode.isLayerBacked { - self.view.bringSubview(toFront: itemNode.view) + self.reorderItemNodeToFront(itemNode) for (_, headerNode) in self.itemHeaderNodes { - self.view.bringSubview(toFront: headerNode.view) + self.reorderHeaderNodeToFront(headerNode) } } let itemNodeFrame = itemNode.frame @@ -3263,4 +3316,18 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } return true } + + private func reorderItemNodeToFront(_ itemNode: ListViewItemNode) { + itemNode.view.superview?.bringSubview(toFront: itemNode.view) + if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { + itemHighlightOverlayBackground.view.superview?.bringSubview(toFront: itemHighlightOverlayBackground.view) + } + } + + private func reorderHeaderNodeToFront(_ headerNode: ListViewItemHeaderNode) { + headerNode.view.superview?.bringSubview(toFront: headerNode.view) + if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { + itemHighlightOverlayBackground.view.superview?.bringSubview(toFront: itemHighlightOverlayBackground.view) + } + } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 92412b4d6b..966fa3a1ef 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -133,12 +133,14 @@ public struct ListViewUpdateSizeAndInsets { public let insets: UIEdgeInsets public let duration: Double public let curve: ListViewAnimationCurve + public let ensureTopInsetForOverlayHighlightedItems: CGFloat? - public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve) { + public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) { self.size = size self.insets = insets self.duration = duration self.curve = curve + self.ensureTopInsetForOverlayHighlightedItems = ensureTopInsetForOverlayHighlightedItems } } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 1bae95320d..8260768d83 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -72,6 +72,8 @@ open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? + public var isHighligtedInOverlay: Bool = false + public private(set) var accessoryItemNode: ListViewAccessoryItemNode? func setAccessoryItemNode(_ accessoryItemNode: ListViewAccessoryItemNode?, leftInset: CGFloat, rightInset: CGFloat) { diff --git a/Display/NavigationBarBadge.swift b/Display/NavigationBarBadge.swift index e52e565149..eb5c6e4849 100644 --- a/Display/NavigationBarBadge.swift +++ b/Display/NavigationBarBadge.swift @@ -6,7 +6,7 @@ final class NavigationBarBadgeNode: ASDisplayNode { private var strokeColor: UIColor private var textColor: UIColor - private let textNode: ASTextNode2 + private let textNode: ASTextNode private let backgroundNode: ASImageNode private let font: UIFont = Font.regular(13.0) @@ -23,7 +23,7 @@ final class NavigationBarBadgeNode: ASDisplayNode { self.strokeColor = strokeColor self.textColor = textColor - self.textNode = ASTextNode2() + self.textNode = ASTextNode() self.textNode.isLayerBacked = true self.textNode.displaysAsynchronously = false diff --git a/Display/NavigationTitleNode.swift b/Display/NavigationTitleNode.swift index 4064f61498..5ef72daed8 100644 --- a/Display/NavigationTitleNode.swift +++ b/Display/NavigationTitleNode.swift @@ -43,7 +43,7 @@ public class NavigationTitleNode: ASDisplayNode { titleAttributes[NSAttributedStringKey.font] = UIFont.boldSystemFont(ofSize: 17.0) titleAttributes[NSAttributedStringKey.foregroundColor] = self.color let titleString = NSAttributedString(string: text as String, attributes: titleAttributes) - self.label.attributedString = titleString + self.label.attributedText = titleString self.invalidateCalculatedLayout() } diff --git a/Display/PeekController.swift b/Display/PeekController.swift index fb968c77aa..091e253a70 100644 --- a/Display/PeekController.swift +++ b/Display/PeekController.swift @@ -26,7 +26,7 @@ public final class PeekController: ViewController { private let theme: PeekControllerTheme private let content: PeekControllerContent - private let sourceNode: () -> ASDisplayNode? + var sourceNode: () -> ASDisplayNode? private var animatedIn = false diff --git a/Display/PeekControllerContent.swift b/Display/PeekControllerContent.swift index 871b3cb18e..68a6d546eb 100644 --- a/Display/PeekControllerContent.swift +++ b/Display/PeekControllerContent.swift @@ -16,6 +16,8 @@ public protocol PeekControllerContent { func menuActivation() -> PeerkControllerMenuActivation func menuItems() -> [PeekControllerMenuItem] func node() -> PeekControllerContentNode & ASDisplayNode + + func isEqual(to: PeekControllerContent) -> Bool } public protocol PeekControllerContentNode { diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index 2a4d69e3ec..307d1310f1 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -18,20 +18,28 @@ private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> B public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { private let contentAtPoint: (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>? private let present: (PeekControllerContent, ASDisplayNode) -> PeekController? + private let updateContent: (PeekControllerContent?) -> Void + private let activateBySingleTap: Bool private var tapLocation: CGPoint? private var longTapTimer: SwiftSignalKit.Timer? private var pressTimer: SwiftSignalKit.Timer? private let candidateContentDisposable = MetaDisposable() - private var candidateContent: (ASDisplayNode, PeekControllerContent)? + private var candidateContent: (ASDisplayNode, PeekControllerContent)? { + didSet { + self.updateContent(self.candidateContent?.1) + } + } private var menuActivation: PeerkControllerMenuActivation? private weak var presentedController: PeekController? - public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> PeekController?) { + public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> PeekController?, updateContent: @escaping (PeekControllerContent?) -> Void = { _ in }, activateBySingleTap: Bool = false) { self.contentAtPoint = contentAtPoint self.present = present + self.updateContent = updateContent + self.activateBySingleTap = activateBySingleTap super.init(target: nil, action: nil) } @@ -156,17 +164,22 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) - let velocity = self.velocity(in: self.view) - - if let presentedController = self.presentedController, presentedController.isNodeLoaded { - (presentedController.displayNode as? PeekControllerNode)?.endDraggingWithVelocity(velocity.y) - self.presentedController = nil - self.menuActivation = nil + if self.activateBySingleTap, candidateContent != nil { + self.longTapTimerFired() + self.pressTimerFired() + } else { + let velocity = self.velocity(in: self.view) + + if let presentedController = self.presentedController, presentedController.isNodeLoaded { + (presentedController.displayNode as? PeekControllerNode)?.endDraggingWithVelocity(velocity.y) + self.presentedController = nil + self.menuActivation = nil + } + + self.tapLocation = nil + self.candidateContent = nil + self.state = .failed } - - self.tapLocation = nil - self.candidateContent = nil - self.state = .failed } override public func touchesCancelled(_ touches: Set, with event: UIEvent) { @@ -210,7 +223,19 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } } } - break + + if self.pressTimer != nil { + let dX = touchLocation.x - initialTapLocation.x + let dY = touchLocation.y - initialTapLocation.y + + if dX * dX + dY * dY > 3.0 * 3.0 { + self.startPressTimer() + } + } + + if self.presentedController != nil { + self.checkCandidateContent(at: touchLocation) + } } } else { let dX = touchLocation.x - initialTapLocation.x @@ -225,4 +250,35 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } } } + + private func checkCandidateContent(at touchLocation: CGPoint) { + if let contentSignal = self.contentAtPoint(touchLocation) { + self.candidateContentDisposable.set((contentSignal |> deliverOnMainQueue).start(next: { [weak self] result in + if let strongSelf = self { + switch strongSelf.state { + case .possible, .changed: + if let (sourceNode, content) = result, let currentContent = strongSelf.candidateContent, !currentContent.1.isEqual(to: content) { + strongSelf.tapLocation = touchLocation + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { + presentedController.sourceNode = { + return sourceNode + } + (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) + } else { + strongSelf.startLongTapTimer() + } + } else if strongSelf.presentedController == nil { + strongSelf.state = .failed + } + default: + break + } + } + })) + } else if self.presentedController == nil { + self.state = .failed + } + } } diff --git a/Display/PeekControllerNode.swift b/Display/PeekControllerNode.swift index 2ca82a1fad..9b16ce4cf9 100644 --- a/Display/PeekControllerNode.swift +++ b/Display/PeekControllerNode.swift @@ -13,13 +13,17 @@ final class PeekControllerNode: ViewControllerTracingNode { private var validLayout: ContainerViewLayout? private var containerOffset: CGFloat = 0.0 + private var panInitialContainerOffset: CGFloat? - private let content: PeekControllerContent - private let contentNode: PeekControllerContentNode & ASDisplayNode + private var content: PeekControllerContent + private var contentNode: PeekControllerContentNode & ASDisplayNode + private var contentNodeHasValidLayout = false - private let menuNode: PeekControllerMenuNode? + private var menuNode: PeekControllerMenuNode? private var displayingMenu = false + private var hapticFeedback: HapticFeedback? + init(theme: PeekControllerTheme, content: PeekControllerContent, requestDismiss: @escaping () -> Void) { self.theme = theme self.requestDismiss = requestDismiss @@ -43,8 +47,6 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerBackgroundNode.displaysAsynchronously = false self.containerNode = ASDisplayNode() - self.containerNode.clipsToBounds = true - self.containerNode.cornerRadius = 16.0 self.content = content self.contentNode = content.node() @@ -61,6 +63,13 @@ final class PeekControllerNode: ViewControllerTracingNode { super.init() + if content.presentation() == .freeform { + self.containerNode.isUserInteractionEnabled = false + } else { + self.containerNode.clipsToBounds = true + self.containerNode.cornerRadius = 16.0 + } + self.addSubnode(self.dimNode) self.view.addSubview(self.blurView) self.containerNode.addSubnode(self.contentNode) @@ -73,6 +82,9 @@ final class PeekControllerNode: ViewControllerTracingNode { activatedActionImpl = { [weak self] in self?.requestDismiss() } + + self.hapticFeedback = HapticFeedback() + self.hapticFeedback?.prepareTap() } deinit { @@ -96,8 +108,12 @@ final class PeekControllerNode: ViewControllerTracingNode { var menuSize: CGSize? - let contentSize = self.contentNode.updateLayout(size: maxContainerSize, transition: transition) - transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentSize)) + let contentSize = self.contentNode.updateLayout(size: maxContainerSize, transition: self.contentNodeHasValidLayout ? transition : .immediate) + if self.contentNodeHasValidLayout { + transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentSize)) + } else { + self.contentNode.frame = CGRect(origin: CGPoint(), size: contentSize) + } var containerFrame: CGRect switch self.content.presentation() { @@ -113,7 +129,14 @@ final class PeekControllerNode: ViewControllerTracingNode { menuSize = CGSize(width: menuWidth, height: menuHeight) if self.displayingMenu { - containerFrame.origin.y = min(containerFrame.origin.y, layout.size.height - layoutInsets.bottom - menuHeight - 14.0 * 2.0 - containerFrame.height) + let upperBound = layout.size.height - layoutInsets.bottom - menuHeight - 14.0 * 2.0 - containerFrame.height + if containerFrame.origin.y > upperBound { + var offset = upperBound - containerFrame.origin.y + let delta = abs(offset) + let factor: CGFloat = 60.0 + offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0) + containerFrame.origin.y = upperBound - offset + } transition.updateAlpha(layer: self.blurView.layer, alpha: 1.0) } @@ -129,8 +152,16 @@ final class PeekControllerNode: ViewControllerTracingNode { menuY = layout.size.height + 14.0 } - transition.updateFrame(node: menuNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - menuSize.width) / 2.0), y: menuY), size: menuSize)) + let menuFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - menuSize.width) / 2.0), y: menuY), size: menuSize) + + if self.contentNodeHasValidLayout { + transition.updateFrame(node: menuNode, frame: menuFrame) + } else { + menuNode.frame = menuFrame + } } + + self.contentNodeHasValidLayout = true } func animateIn(from rect: CGRect) { @@ -140,6 +171,12 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + + if case .press = self.content.menuActivation() { + self.hapticFeedback?.tap() + } else { + self.hapticFeedback?.impact() + } } func animateOut(to rect: CGRect, completion: @escaping () -> Void) { @@ -162,18 +199,40 @@ final class PeekControllerNode: ViewControllerTracingNode { } @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { + guard case .drag = self.content.menuActivation() else { + return + } + switch recognizer.state { + case .began: + self.panInitialContainerOffset = self.containerOffset case .changed: - break + if let panInitialContainerOffset = self.panInitialContainerOffset { + let translation = recognizer.translation(in: self.view) + var offset = panInitialContainerOffset + translation.y + if offset < 0.0 { + let delta = abs(offset) + let factor: CGFloat = 60.0 + offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0) + } + self.applyDraggingOffset(offset) + } case .cancelled, .ended: - break + if let _ = self.panInitialContainerOffset { + self.panInitialContainerOffset = nil + if self.containerOffset < 0.0 { + self.activateMenu() + } else { + self.requestDismiss() + } + } default: break } } func applyDraggingOffset(_ offset: CGFloat) { - self.containerOffset = min(0.0, offset) + self.containerOffset = offset if self.containerOffset < -25.0 { //self.displayingMenu = true } else { @@ -185,6 +244,9 @@ final class PeekControllerNode: ViewControllerTracingNode { } func activateMenu() { + if case .press = self.content.menuActivation() { + self.hapticFeedback?.impact() + } if let layout = self.validLayout { self.displayingMenu = true self.containerOffset = 0.0 @@ -203,4 +265,47 @@ final class PeekControllerNode: ViewControllerTracingNode { self.requestDismiss() } } + + func updateContent(content: PeekControllerContent) { + let contentNode = self.contentNode + contentNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, removeOnCompletion: false, completion: { [weak contentNode] _ in + contentNode?.removeFromSupernode() + }) + contentNode.layer.animateScale(from: 1.0, to: 0.2, duration: 0.15, removeOnCompletion: false) + + self.menuNode?.removeFromSupernode() + self.menuNode = nil + + self.content = content + self.contentNode = content.node() + self.containerNode.addSubnode(self.contentNode) + self.contentNodeHasValidLayout = false + + var activatedActionImpl: (() -> Void)? + let menuItems = content.menuItems() + if menuItems.isEmpty { + self.menuNode = nil + } else { + self.menuNode = PeekControllerMenuNode(theme: self.theme, items: menuItems, activatedAction: { + activatedActionImpl?() + }) + } + + if let menuNode = self.menuNode { + self.addSubnode(menuNode) + } + + activatedActionImpl = { [weak self] in + self?.requestDismiss() + } + + self.contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + self.contentNode.layer.animateSpring(from: 0.35 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5) + + if let layout = self.validLayout { + self.containerLayoutUpdated(layout, transition: .animated(duration: 0.15, curve: .easeInOut)) + } + + self.hapticFeedback?.tap() + } } diff --git a/Display/TextNode.swift b/Display/TextNode.swift new file mode 100644 index 0000000000..cb6291687b --- /dev/null +++ b/Display/TextNode.swift @@ -0,0 +1,493 @@ +import Foundation +import AsyncDisplayKit + +private let defaultFont = UIFont.systemFont(ofSize: 15.0) + +private final class TextNodeLine { + let line: CTLine + let frame: CGRect + let range: NSRange + + init(line: CTLine, frame: CGRect, range: NSRange) { + self.line = line + self.frame = frame + self.range = range + } +} + +public enum TextNodeCutoutPosition { + case TopLeft + case TopRight +} + +public struct TextNodeCutout: Equatable { + public let position: TextNodeCutoutPosition + public let size: CGSize + + public init(position: TextNodeCutoutPosition, size: CGSize) { + self.position = position + self.size = size + } + + public static func ==(lhs: TextNodeCutout, rhs: TextNodeCutout) -> Bool { + return lhs.position == rhs.position && lhs.size == rhs.size + } +} + +public final class TextNodeLayoutArguments { + public let attributedString: NSAttributedString? + public let backgroundColor: UIColor? + public let maximumNumberOfLines: Int + public let truncationType: CTLineTruncationType + public let constrainedSize: CGSize + public let alignment: NSTextAlignment + public let lineSpacing: CGFloat + public let cutout: TextNodeCutout? + public let insets: UIEdgeInsets + + public init(attributedString: NSAttributedString?, backgroundColor: UIColor? = nil, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment = .natural, lineSpacing: CGFloat = 0.12, cutout: TextNodeCutout? = nil, insets: UIEdgeInsets = UIEdgeInsets()) { + self.attributedString = attributedString + self.backgroundColor = backgroundColor + self.maximumNumberOfLines = maximumNumberOfLines + self.truncationType = truncationType + self.constrainedSize = constrainedSize + self.alignment = alignment + self.lineSpacing = lineSpacing + self.cutout = cutout + self.insets = insets + } +} + +public final class TextNodeLayout: NSObject { + fileprivate let attributedString: NSAttributedString? + fileprivate let maximumNumberOfLines: Int + fileprivate let truncationType: CTLineTruncationType + fileprivate let backgroundColor: UIColor? + fileprivate let constrainedSize: CGSize + fileprivate let alignment: NSTextAlignment + fileprivate let lineSpacing: CGFloat + fileprivate let cutout: TextNodeCutout? + fileprivate let insets: UIEdgeInsets + public let size: CGSize + fileprivate let firstLineOffset: CGFloat + fileprivate let lines: [TextNodeLine] + + fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, firstLineOffset: CGFloat, lines: [TextNodeLine], backgroundColor: UIColor?) { + self.attributedString = attributedString + self.maximumNumberOfLines = maximumNumberOfLines + self.truncationType = truncationType + self.constrainedSize = constrainedSize + self.alignment = alignment + self.lineSpacing = lineSpacing + self.cutout = cutout + self.insets = insets + self.size = size + self.firstLineOffset = firstLineOffset + self.lines = lines + self.backgroundColor = backgroundColor + } + + public var numberOfLines: Int { + return self.lines.count + } + + public var trailingLineWidth: CGFloat { + if let lastLine = self.lines.last { + return lastLine.frame.width + } else { + return 0.0 + } + } + + public func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedStringKey: Any])? { + if let attributedString = self.attributedString { + let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top) + for line in self.lines { + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + switch self.alignment { + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + default: + break + } + if lineFrame.contains(transformedPoint) { + var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) + if index == attributedString.length { + index -= 1 + } else if index != 0 { + var glyphStart: CGFloat = 0.0 + CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) + if transformedPoint.x < glyphStart { + index -= 1 + } + } + if index >= 0 && index < attributedString.length { + return (index, attributedString.attributes(at: index, effectiveRange: nil)) + } + break + } + } + for line in self.lines { + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + switch self.alignment { + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + default: + break + } + if lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.size.height).insetBy(dx: -3.0, dy: -3.0).contains(transformedPoint) { + var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) + if index == attributedString.length { + index -= 1 + } else if index != 0 { + var glyphStart: CGFloat = 0.0 + CTLineGetOffsetForStringIndex(line.line, index, &glyphStart) + if transformedPoint.x < glyphStart { + index -= 1 + } + } + if index >= 0 && index < attributedString.length { + return (index, attributedString.attributes(at: index, effectiveRange: nil)) + } + break + } + } + } + return nil + } + + public func linesRects() -> [CGRect] { + var rects: [CGRect] = [] + for line in self.lines { + rects.append(line.frame) + } + return rects + } + + public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { + if let attributedString = self.attributedString { + var range = NSRange() + let _ = attributedString.attribute(NSAttributedStringKey(rawValue: name), at: index, effectiveRange: &range) + if range.length != 0 { + var rects: [(CGRect, CGRect)] = [] + for line in self.lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != line.range.length { + rightOffset = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) + } + let lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height)))) + } + } + if !rects.isEmpty { + return rects + } + } + } + return nil + } +} + +public class TextNode: ASDisplayNode { + public private(set) var cachedLayout: TextNodeLayout? + + override public init() { + super.init() + + self.backgroundColor = UIColor.clear + self.isOpaque = false + self.clipsToBounds = false + } + + public func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedStringKey: Any])? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.attributesAtPoint(point) + } else { + return nil + } + } + + public func attributeRects(name: String, at index: Int) -> [CGRect]? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.lineAndAttributeRects(name: name, at: index)?.map { $0.1 } + } else { + return nil + } + } + + public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { + if let cachedLayout = self.cachedLayout { + return cachedLayout.lineAndAttributeRects(name: name, at: index) + } else { + return nil + } + } + + private class func calculateLayout(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, backgroundColor: UIColor?, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacingFactor: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets) -> TextNodeLayout { + if let attributedString = attributedString { + let stringLength = attributedString.length + + let font: CTFont + if stringLength != 0 { + if let stringFont = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) { + font = stringFont as! CTFont + } else { + font = defaultFont + } + } else { + font = defaultFont + } + + let fontAscent = CTFontGetAscent(font) + let fontDescent = CTFontGetDescent(font) + let fontLineHeight = floor(fontAscent + fontDescent) + let fontLineSpacing = floor(fontLineHeight * lineSpacingFactor) + + var lines: [TextNodeLine] = [] + + var maybeTypesetter: CTTypesetter? + maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) + if maybeTypesetter == nil { + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) + } + + let typesetter = maybeTypesetter! + + var lastLineCharacterIndex: CFIndex = 0 + var layoutSize = CGSize() + + var cutoutEnabled = false + var cutoutMinY: CGFloat = 0.0 + var cutoutMaxY: CGFloat = 0.0 + var cutoutWidth: CGFloat = 0.0 + var cutoutOffset: CGFloat = 0.0 + if let cutout = cutout { + cutoutMinY = -fontLineSpacing + cutoutMaxY = cutout.size.height + fontLineSpacing + cutoutWidth = cutout.size.width + if case .TopLeft = cutout.position { + cutoutOffset = cutoutWidth + } + cutoutEnabled = true + } + + let firstLineOffset = floorToScreenPixels(fontLineSpacing * 2.0) + + var first = true + while true { + var lineConstrainedWidth = constrainedSize.width + //var lineOriginY = floorToScreenPixels(layoutSize.height + fontLineHeight - fontLineSpacing * 2.0) + var lineOriginY = floorToScreenPixels(layoutSize.height + fontAscent) + if !first { + lineOriginY += fontLineSpacing + } + var lineCutoutOffset: CGFloat = 0.0 + var lineAdditionalWidth: CGFloat = 0.0 + + if cutoutEnabled { + if lineOriginY - fontLineHeight < cutoutMaxY && lineOriginY + fontLineHeight > cutoutMinY { + lineConstrainedWidth = max(1.0, lineConstrainedWidth - cutoutWidth) + lineCutoutOffset = cutoutOffset + lineAdditionalWidth = cutoutWidth + } + } + + let lineCharacterCount = CTTypesetterSuggestLineBreak(typesetter, lastLineCharacterIndex, Double(lineConstrainedWidth)) + + var isLastLine = false + if maximumNumberOfLines != 0 && lines.count == maximumNumberOfLines - 1 && lineCharacterCount > 0 { + isLastLine = true + } else if layoutSize.height + (fontLineSpacing + fontLineHeight) * 2.0 > constrainedSize.height { + isLastLine = true + } + + if isLastLine { + if first { + first = false + } else { + layoutSize.height += fontLineSpacing + } + + let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex) + + if lineRange.length == 0 { + break + } + + let coreTextLine: CTLine + + let originalLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 0.0) + + if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedSize.width) { + coreTextLine = originalLine + } else { + var truncationTokenAttributes: [NSAttributedStringKey : AnyObject] = [:] + truncationTokenAttributes[NSAttributedStringKey.font] = font + truncationTokenAttributes[NSAttributedStringKey(rawValue: kCTForegroundColorFromContextAttributeName as String)] = true as NSNumber + let tokenString = "\u{2026}" + let truncatedTokenString = NSAttributedString(string: tokenString, attributes: truncationTokenAttributes) + let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) + + coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(constrainedSize.width), truncationType, truncationToken) ?? truncationToken + } + + let lineWidth = min(constrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine)))) + let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: fontLineHeight) + layoutSize.height += fontLineHeight + fontLineSpacing + layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) + + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length))) + + break + } else { + if lineCharacterCount > 0 { + if first { + first = false + } else { + layoutSize.height += fontLineSpacing + } + + let lineRange = CFRangeMake(lastLineCharacterIndex, lineCharacterCount) + let coreTextLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 100.0) + lastLineCharacterIndex += lineCharacterCount + + let lineWidth = ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine))) + let lineFrame = CGRect(x: lineCutoutOffset, y: lineOriginY, width: lineWidth, height: fontLineHeight) + layoutSize.height += fontLineHeight + layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) + + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length))) + } else { + if !lines.isEmpty { + layoutSize.height += fontLineSpacing + } + break + } + } + } + + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) + } else { + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) + } + } + + override public func drawParameters(forAsyncLayer layer: _ASDisplayLayer) -> NSObjectProtocol? { + return self.cachedLayout + } + + @objc override public class func draw(_ bounds: CGRect, withParameters parameters: Any?, isCancelled: () -> Bool, isRasterizing: Bool) { + if isCancelled() { + return + } + + let context = UIGraphicsGetCurrentContext()! + + context.setAllowsAntialiasing(true) + + context.setAllowsFontSmoothing(false) + context.setShouldSmoothFonts(false) + + context.setAllowsFontSubpixelPositioning(false) + context.setShouldSubpixelPositionFonts(false) + + context.setAllowsFontSubpixelQuantization(true) + context.setShouldSubpixelQuantizeFonts(true) + + if let layout = parameters as? TextNodeLayout { + if !isRasterizing || layout.backgroundColor != nil { + context.setBlendMode(.copy) + context.setFillColor((layout.backgroundColor ?? UIColor.clear).cgColor) + context.fill(bounds) + } + + let textMatrix = context.textMatrix + let textPosition = context.textPosition + //CGContextSaveGState(context) + + context.textMatrix = CGAffineTransform(scaleX: 1.0, y: -1.0) + + //let clipRect = CGContextGetClipBoundingBox(context) + + let alignment = layout.alignment + let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) + + for i in 0 ..< layout.lines.count { + let line = layout.lines[i] + let lineOffset: CGFloat + if alignment == .center { + lineOffset = floor((bounds.size.width - line.frame.size.width) / 2.0) + } else { + lineOffset = 0.0 + } + context.textPosition = CGPoint(x: line.frame.origin.x + lineOffset + offset.x, y: line.frame.origin.y + offset.y) + CTLineDraw(line.line, context) + } + + //CGContextRestoreGState(context) + context.textMatrix = textMatrix + context.textPosition = CGPoint(x: textPosition.x, y: textPosition.y) + } + + context.setBlendMode(.normal) + } + + public static func asyncLayout(_ maybeNode: TextNode?) -> (TextNodeLayoutArguments) -> (TextNodeLayout, () -> TextNode) { + let existingLayout: TextNodeLayout? = maybeNode?.cachedLayout + + return { arguments in + let layout: TextNodeLayout + + var updated = false + if let existingLayout = existingLayout, existingLayout.constrainedSize == arguments.constrainedSize && existingLayout.maximumNumberOfLines == arguments.maximumNumberOfLines && existingLayout.truncationType == arguments.truncationType && existingLayout.cutout == arguments.cutout && existingLayout.alignment == arguments.alignment && existingLayout.lineSpacing.isEqual(to: arguments.lineSpacing) { + let stringMatch: Bool + + var colorMatch: Bool = true + if let backgroundColor = arguments.backgroundColor, let previousBackgroundColor = existingLayout.backgroundColor { + if !backgroundColor.isEqual(previousBackgroundColor) { + colorMatch = false + } + } else if (arguments.backgroundColor != nil) != (existingLayout.backgroundColor != nil) { + colorMatch = false + } + + if !colorMatch { + stringMatch = false + } else if let existingString = existingLayout.attributedString, let string = arguments.attributedString { + stringMatch = existingString.isEqual(to: string) + } else if existingLayout.attributedString == nil && arguments.attributedString == nil { + stringMatch = true + } else { + stringMatch = false + } + + if stringMatch { + layout = existingLayout + } else { + layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets) + updated = true + } + } else { + layout = TextNode.calculateLayout(attributedString: arguments.attributedString, maximumNumberOfLines: arguments.maximumNumberOfLines, truncationType: arguments.truncationType, backgroundColor: arguments.backgroundColor, constrainedSize: arguments.constrainedSize, alignment: arguments.alignment, lineSpacingFactor: arguments.lineSpacing, cutout: arguments.cutout, insets: arguments.insets) + updated = true + } + + let node = maybeNode ?? TextNode() + + return (layout, { + node.cachedLayout = layout + if updated { + node.setNeedsDisplay() + } + + return node + }) + } + } +} diff --git a/Display/TooltipControllerNode.swift b/Display/TooltipControllerNode.swift index a41d943946..e753f25fa0 100644 --- a/Display/TooltipControllerNode.swift +++ b/Display/TooltipControllerNode.swift @@ -8,7 +8,7 @@ final class TooltipControllerNode: ASDisplayNode { private var validLayout: ContainerViewLayout? private let containerNode: ContextMenuContainerNode - private let textNode: ASTextNode + private let textNode: ImmediateTextNode var sourceRect: CGRect? var arrowOnBottom: Bool = true @@ -19,7 +19,7 @@ final class TooltipControllerNode: ASDisplayNode { self.containerNode = ContextMenuContainerNode() self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) self.textNode.isLayerBacked = true self.textNode.displaysAsynchronously = false @@ -53,7 +53,7 @@ final class TooltipControllerNode: ASDisplayNode { let maxActionsWidth = layout.size.width - 20.0 - var textSize = self.textNode.measure(CGSize(width: maxActionsWidth, height: CGFloat.greatestFiniteMagnitude)) + var textSize = self.textNode.updateLayout(CGSize(width: maxActionsWidth, height: CGFloat.greatestFiniteMagnitude)) textSize.width = ceil(textSize.width / 2.0) * 2.0 textSize.height = ceil(textSize.height / 2.0) * 2.0 let contentSize = CGSize(width: textSize.width + 12.0, height: textSize.height + 34.0) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 9299e63fee..bcf5edde7e 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -95,6 +95,20 @@ public extension CGSize { return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } + public func aspectFittedWithOverflow(_ size: CGSize, leeway: CGFloat) -> CGSize { + let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) + var result = CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) + if result.width < size.width && result.width > size.width - leeway { + result.height += size.width - result.width + result.width = size.width + } + if result.height < size.height && result.height > size.height - leeway { + result.width += size.height - result.height + result.height = size.height + } + return result + } + public func fittedToWidthOrSmaller(_ width: CGFloat) -> CGSize { let scale = min(1.0, width / max(1.0, self.width)) return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 5926a69e3e..3d3ba93667 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -7,6 +7,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { @interface UIViewController (Navigation) +- (void)setHintWillBePresentedInPreviewingContext:(BOOL)value; - (BOOL)isPresentedInPreviewingContext; - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; - (BOOL)ignoreAppearanceMethodInvocations; diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 332d4fc0f1..4293086dc9 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -38,6 +38,7 @@ static const void *disablesInteractiveTransitionGestureRecognizerKey = &disables static const void *disableAutomaticKeyboardHandlingKey = &disableAutomaticKeyboardHandlingKey; static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey; static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey; +static const void *UIViewControllerHintWillBePresentedInPreviewingContextKey = &UIViewControllerHintWillBePresentedInPreviewingContextKey; static bool notyfyingShiftState = false; @@ -106,8 +107,16 @@ static bool notyfyingShiftState = false; }); } +- (void)setHintWillBePresentedInPreviewingContext:(BOOL)value { + [self setAssociatedObject:@(value) forKey:UIViewControllerHintWillBePresentedInPreviewingContextKey]; +} + - (BOOL)isPresentedInPreviewingContext { - return ![self.presentingViewController isKindOfClass:[UIViewControllerPresentingProxy class]]; + if ([[self associatedObjectForKey:UIViewControllerHintWillBePresentedInPreviewingContextKey] boolValue]) { + return true; + } else { + return false; + } } - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations diff --git a/Display/ViewControllerPreviewing.swift b/Display/ViewControllerPreviewing.swift index 137384e6dc..1ad481ae40 100644 --- a/Display/ViewControllerPreviewing.swift +++ b/Display/ViewControllerPreviewing.swift @@ -35,6 +35,14 @@ private final class ViewControllerPeekContent: PeekControllerContent { func node() -> PeekControllerContentNode & ASDisplayNode { return ViewControllerPeekContentNode(controller: self.controller) } + + func isEqual(to: PeekControllerContent) -> Bool { + if let to = to as? ViewControllerPeekContent { + return self.controller === to.controller + } else { + return false + } + } } private final class ViewControllerPeekContentNode: ASDisplayNode, PeekControllerContentNode { diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index a085e496ac..586f3820c2 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -165,15 +165,21 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container var standardInputHeight: CGFloat = 216.0 var predictiveHeight: CGFloat = 42.0 - if layout.size.width.isEqual(to: 320.0) || layout.size.width.isEqual(to: 375.0) { + if layout.size.width.isEqual(to: 320.0) { standardInputHeight = 216.0 predictiveHeight = 42.0 + } else if layout.size.width.isEqual(to: 375.0) { + standardInputHeight = 291.0 + predictiveHeight = 42.0 } else if layout.size.width.isEqual(to: 414.0) { standardInputHeight = 226.0 predictiveHeight = 42.0 } else if layout.size.width.isEqual(to: 480.0) || layout.size.width.isEqual(to: 568.0) || layout.size.width.isEqual(to: 667.0) || layout.size.width.isEqual(to: 736.0) { standardInputHeight = 162.0 predictiveHeight = 38.0 + } else if layout.size.width.isEqual(to: 812.0) { + standardInputHeight = 171.0 + predictiveHeight = 38.0 } else if layout.size.width.isEqual(to: 768.0) || layout.size.width.isEqual(to: 1024.0) { standardInputHeight = 264.0 predictiveHeight = 42.0 From 139fb8fc7c835240e3d0b0598db49e88915f0497 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Sat, 3 Mar 2018 11:21:09 +0400 Subject: [PATCH 051/245] no message --- Display/ImmediateTextNode.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index 828191286f..796cf76dd0 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -3,10 +3,12 @@ import Foundation public final class ImmediateTextNode: TextNode { public var attributedText: NSAttributedString? public var textAlignment: NSTextAlignment = .natural + public var maximumNumberOfLines: Int = 1 + public var lineSpacing: CGFloat = 0.0 public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self) - let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: 1, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: 0.0, cutout: nil, insets: UIEdgeInsets())) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: UIEdgeInsets())) let _ = apply() return layout.size } From cad4955b71f2665a82c2dc64b65bd0c43105d154 Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 12 Mar 2018 21:48:56 +0300 Subject: [PATCH 052/245] no message --- .../xcschemes/xcschememanagement.plist | 4 ++-- Display/ContextMenuContainerNode.swift | 2 +- Display/ImmediateTextNode.swift | 2 +- Display/PeekControllerGestureRecognizer.swift | 2 +- Display/PeekControllerNode.swift | 20 ++++++++++++------- 5 files changed, 18 insertions(+), 12 deletions(-) diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 250c9b5f47..f568be7cc9 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 7 + 5 DisplayMac.xcscheme @@ -17,7 +17,7 @@ DisplayTests.xcscheme orderHint - 9 + 7 SuppressBuildableAutocreation diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift index 74d7b9e28f..39abfa5ee3 100644 --- a/Display/ContextMenuContainerNode.swift +++ b/Display/ContextMenuContainerNode.swift @@ -62,7 +62,7 @@ final class ContextMenuContainerNode: ASDisplayNode { let arrowOnBottom = maskParams.arrowOnBottom path.move(to: CGPoint(x: 0.0, y: verticalInset + cornerRadius)) - path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(M_PI), endAngle: CGFloat(3 * M_PI / 2), clockwise: true) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat.pi, endAngle: CGFloat(3 * M_PI / 2), clockwise: true) if !arrowOnBottom { path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: verticalInset)) path.addLine(to: CGPoint(x: arrowPosition, y: 0.0)) diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index 796cf76dd0..c25c5b63fc 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -1,6 +1,6 @@ import Foundation -public final class ImmediateTextNode: TextNode { +public class ImmediateTextNode: TextNode { public var attributedText: NSAttributedString? public var textAlignment: NSTextAlignment = .natural public var maximumNumberOfLines: Int = 1 diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index 307d1310f1..25ae8c9ffd 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -164,7 +164,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) - if self.activateBySingleTap, candidateContent != nil { + if self.activateBySingleTap, self.candidateContent != nil, self.presentedController == nil { self.longTapTimerFired() self.pressTimerFired() } else { diff --git a/Display/PeekControllerNode.swift b/Display/PeekControllerNode.swift index 9b16ce4cf9..2c4f54ff58 100644 --- a/Display/PeekControllerNode.swift +++ b/Display/PeekControllerNode.swift @@ -118,9 +118,9 @@ final class PeekControllerNode: ViewControllerTracingNode { var containerFrame: CGRect switch self.content.presentation() { case .contained: - containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: self.containerOffset + floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) + containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize) case .freeform: - containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: self.containerOffset + floor((layout.size.height - contentSize.height) / 4.0)), size: contentSize) + containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: floor((layout.size.height - contentSize.height) / 4.0)), size: contentSize) } if let menuNode = self.menuNode { @@ -131,17 +131,23 @@ final class PeekControllerNode: ViewControllerTracingNode { if self.displayingMenu { let upperBound = layout.size.height - layoutInsets.bottom - menuHeight - 14.0 * 2.0 - containerFrame.height if containerFrame.origin.y > upperBound { - var offset = upperBound - containerFrame.origin.y - let delta = abs(offset) - let factor: CGFloat = 60.0 - offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0) - containerFrame.origin.y = upperBound - offset + containerFrame.origin.y = upperBound } transition.updateAlpha(layer: self.blurView.layer, alpha: 1.0) } } + if self.displayingMenu { + var offset = self.containerOffset + let delta = abs(offset) + let factor: CGFloat = 60.0 + offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0) + containerFrame = containerFrame.offsetBy(dx: 0.0, dy: offset) + } else { + containerFrame = containerFrame.offsetBy(dx: 0.0, dy: self.containerOffset) + } + transition.updateFrame(node: self.containerNode, frame: containerFrame) if let menuNode = self.menuNode, let menuSize = menuSize { From 95614c81215d3e89e129b96af1a73c5eb4715b6d Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Wed, 4 Apr 2018 11:00:29 +0400 Subject: [PATCH 053/245] no message --- Display.xcodeproj/project.pbxproj | 17 + .../xcschemes/xcschememanagement.plist | 4 +- Display/ActionSheetController.swift | 2 +- Display/ActionSheetControllerNode.swift | 30 +- Display/AlertController.swift | 2 +- Display/CATracingLayer.h | 3 +- Display/CATracingLayer.m | 6 +- Display/ContainedViewLayoutTransition.swift | 72 +- Display/ContextMenuController.swift | 2 +- Display/ContextMenuNode.swift | 2 +- Display/ImmediateTextNode.swift | 84 ++- Display/LayoutSizes.swift | 9 + Display/LegacyPresentedController.swift | 2 +- Display/LinkHighlightingNode.swift | 237 +++++++ Display/ListView.swift | 6 +- Display/ListViewItemNode.swift | 2 +- Display/NavigationBar.swift | 228 ++++-- Display/NavigationBarTitleView.swift | 2 + Display/NavigationController.swift | 668 ++++++++++++++---- Display/NavigationTransitionCoordinator.swift | 8 +- Display/PeekController.swift | 2 +- Display/PeekControllerGestureRecognizer.swift | 91 +-- Display/StatusBar.swift | 2 +- Display/TabBarController.swift | 15 +- ...pLongTapOrDoubleTapGestureRecognizer.swift | 257 +++++++ Display/TooltipController.swift | 15 +- Display/ViewController.swift | 28 +- Display/WindowContent.swift | 18 +- 28 files changed, 1448 insertions(+), 366 deletions(-) create mode 100644 Display/LayoutSizes.swift create mode 100644 Display/LinkHighlightingNode.swift create mode 100644 Display/TapLongTapOrDoubleTapGestureRecognizer.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 387e79182f..ad08084c1c 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -111,6 +111,7 @@ D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */; }; D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; + D06B76DB20592A97006E9EEA /* LayoutSizes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06B76DA20592A97006E9EEA /* LayoutSizes.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D077B8E81F4637040046D27A /* NavigationBarBadge.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; @@ -149,6 +150,8 @@ D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; }; D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; + D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */; }; + D0CA3F8C2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */; }; D0CB78901F9822F8004AB79B /* WindowPanRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */; }; @@ -282,6 +285,7 @@ D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationBarProxy.m; sourceTree = ""; }; D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; + D06B76DA20592A97006E9EEA /* LayoutSizes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutSizes.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D077B8E81F4637040046D27A /* NavigationBarBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarBadge.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; @@ -321,6 +325,8 @@ D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = ""; }; D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; + D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkHighlightingNode.swift; sourceTree = ""; }; + D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapLongTapOrDoubleTapGestureRecognizer.swift; sourceTree = ""; }; D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowPanRecognizer.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetTheme.swift; sourceTree = ""; }; @@ -476,6 +482,7 @@ D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */, D0FA08C32048803C00DD23FC /* TextNode.swift */, D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */, + D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */, ); name = Nodes; sourceTree = ""; @@ -522,6 +529,8 @@ D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */, D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */, D0FA08C120487A8600DD23FC /* HapticFeedback.swift */, + D06B76DA20592A97006E9EEA /* LayoutSizes.swift */, + D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */, ); name = Utils; sourceTree = ""; @@ -977,6 +986,7 @@ D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, + D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, @@ -992,6 +1002,7 @@ D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */, D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, + D06B76DB20592A97006E9EEA /* LayoutSizes.swift in Sources */, D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */, @@ -1044,6 +1055,7 @@ D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0FA08C42048803C00DD23FC /* TextNode.swift in Sources */, + D0CA3F8C2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */, @@ -1388,6 +1400,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1429,6 +1442,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1554,6 +1568,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1633,6 +1648,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1712,6 +1728,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index f568be7cc9..e65b00a6be 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 5 + 6 DisplayMac.xcscheme @@ -17,7 +17,7 @@ DisplayTests.xcscheme orderHint - 7 + 8 SuppressBuildableAutocreation diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index b3643e8b07..f6f3532784 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -12,7 +12,7 @@ open class ActionSheetController: ViewController { public init(theme: ActionSheetControllerTheme) { self.theme = theme - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 90830cbe5a..76a8227cc7 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -79,8 +79,11 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { var insets = layout.insets(options: [.statusBar]) - insets.left += layout.safeInsets.left - insets.right += layout.safeInsets.right + + let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + + insets.left = floor((layout.size.width - containerWidth) / 2.0) + insets.right = insets.left self.validLayout = layout @@ -91,7 +94,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.itemGroupsContainerNode.frame = CGRect(origin: CGPoint(x: insets.left + containerInsets.left, y: layout.size.height - insets.bottom - containerInsets.bottom - self.itemGroupsContainerNode.calculatedSize.height), size: self.itemGroupsContainerNode.calculatedSize) self.itemGroupsContainerNode.layout() - self.updateScrollDimViews(size: layout.size, safeInsets: layout.safeInsets) + self.updateScrollDimViews(size: layout.size, insets: insets) } func animateIn() { @@ -141,8 +144,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func scrollViewDidScroll(_ scrollView: UIScrollView) { - if let validLayout = self.validLayout { - self.updateScrollDimViews(size: validLayout.size, safeInsets: validLayout.safeInsets) + if let layout = self.validLayout { + var insets = layout.insets(options: [.statusBar]) + + let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + + insets.left = floor((layout.size.width - containerWidth) / 2.0) + insets.right = insets.left + + self.updateScrollDimViews(size: layout.size, insets: insets) } } @@ -155,15 +165,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateScrollDimViews(size: CGSize, safeInsets: UIEdgeInsets) { + func updateScrollDimViews(size: CGSize, insets: UIEdgeInsets) { let additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) let additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) - self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) - self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) + self.topDimView.frame = CGRect(x: containerInsets.left + insets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right - insets.left - insets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) + self.bottomDimView.frame = CGRect(x: containerInsets.left + insets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right - insets.left - insets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) - self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left + safeInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) - self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right + safeInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) + self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left + insets.left, height: size.height + additionalTopHeight + additionalBottomHeight) + self.rightDimView.frame = CGRect(x: size.width - containerInsets.right - insets.right, y: -additionalTopHeight, width: containerInsets.right + insets.right, height: size.height + additionalTopHeight + additionalBottomHeight) } func setGroups(_ groups: [ActionSheetItemGroup]) { diff --git a/Display/AlertController.swift b/Display/AlertController.swift index c8a1db2987..2f467f6f3a 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -33,7 +33,7 @@ open class AlertController: ViewController { self.theme = theme self.contentNode = contentNode - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .Ignore } diff --git a/Display/CATracingLayer.h b/Display/CATracingLayer.h index 0ca59876d3..47d364fb0d 100644 --- a/Display/CATracingLayer.h +++ b/Display/CATracingLayer.h @@ -9,8 +9,9 @@ @property (nonatomic, readonly) bool shouldBeAdjustedToInverseTransform; @property (nonatomic, weak, readonly) id _Nullable userData; @property (nonatomic, readonly) int32_t tracingTag; +@property (nonatomic, readonly) int32_t disableChildrenTracingTags; -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag; +- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags; @end diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 65389bdfd5..78f4fd1827 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -50,6 +50,9 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull [array addObject:sublayer]; hadTraceableSublayers = true; } + if (sublayerTraceableInfo.disableChildrenTracingTags & tracingTag) { + return; + } } if (!skipIfNoTraceableSublayers || !hadTraceableSublayers) { @@ -347,12 +350,13 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull @implementation CATracingLayerInfo -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag { +- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags { self = [super init]; if (self != nil) { _shouldBeAdjustedToInverseTransform = shouldBeAdjustedToInverseTransform; _userData = userData; _tracingTag = tracingTag; + _disableChildrenTracingTags = disableChildrenTracingTags; } return self; } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index a63dc7e556..1a9d824924 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -233,23 +233,39 @@ public extension ContainedViewLayoutTransition { } } - func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { + func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) { switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } } - func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, completion: (() -> Void)? = nil) { + func animatePositionAdditive(layer: CALayer, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) { switch self { case .immediate: break @@ -261,27 +277,27 @@ public extension ContainedViewLayoutTransition { case .spring: timingFunction = kCAMediaTimingFunctionSpring } - node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in + node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in completion?() }) } } - func animatePositionAdditive(layer: CALayer, offset: CGPoint, completion: (() -> Void)? = nil) { + func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) { switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in - completion?() - }) + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in + completion?() + }) } } diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 9fa6eb662d..732968f662 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -25,7 +25,7 @@ public final class ContextMenuController: ViewController { self.actions = actions self.catchTapsOutside = catchTapsOutside - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index a20d03af4d..9ec3f64072 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -200,7 +200,7 @@ final class ContextMenuNode: ASDisplayNode { } self.arrowOnBottom = arrowOnBottom - let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) + let horizontalOrigin: CGFloat = floor(min(max(sourceRect.minX + 8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0)) self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index c25c5b63fc..842eb7a01d 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -5,11 +5,93 @@ public class ImmediateTextNode: TextNode { public var textAlignment: NSTextAlignment = .natural public var maximumNumberOfLines: Int = 1 public var lineSpacing: CGFloat = 0.0 + public var insets: UIEdgeInsets = UIEdgeInsets() + + private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? + private var linkHighlightingNode: LinkHighlightingNode? + + public var linkHighlightColor: UIColor? + + public var highlightAttributeAction: (([NSAttributedStringKey: Any]) -> NSAttributedStringKey?)? { + didSet { + if self.isNodeLoaded { + self.updateInteractiveActions() + } + } + } + + public var tapAttributeAction: (([NSAttributedStringKey: Any]) -> Void)? public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self) - let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: UIEdgeInsets())) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) let _ = apply() return layout.size } + + override public func didLoad() { + super.didLoad() + + self.updateInteractiveActions() + } + + private func updateInteractiveActions() { + if self.highlightAttributeAction != nil { + if self.tapRecognizer == nil { + let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapAction(_:))) + tapRecognizer.highlight = { [weak self] point in + if let strongSelf = self { + var rects: [CGRect]? + if let point = point { + if let (index, attributes) = strongSelf.attributesAtPoint(CGPoint(x: point.x, y: point.y)) { + if let selectedAttribute = strongSelf.highlightAttributeAction?(attributes) { + rects = strongSelf.attributeRects(name: selectedAttribute.rawValue, at: index) + } + } + } + + if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = strongSelf.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: strongSelf.linkHighlightColor ?? .clear) + strongSelf.linkHighlightingNode = linkHighlightingNode + strongSelf.addSubnode(linkHighlightingNode) + } + linkHighlightingNode.frame = strongSelf.bounds + linkHighlightingNode.updateRects(rects.map { $0.offsetBy(dx: 0.0, dy: -3.0) }) + } else if let linkHighlightingNode = strongSelf.linkHighlightingNode { + strongSelf.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + } + self.view.addGestureRecognizer(tapRecognizer) + } + } else if let tapRecognizer = self.tapRecognizer { + self.tapRecognizer = nil + self.view.removeGestureRecognizer(tapRecognizer) + } + } + + @objc private func tapAction(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + if let (_, attributes) = self.attributesAtPoint(CGPoint(x: location.x, y: location.y)) { + self.tapAttributeAction?(attributes) + } + default: + break + } + } + default: + break + } + } } diff --git a/Display/LayoutSizes.swift b/Display/LayoutSizes.swift new file mode 100644 index 0000000000..d03f8bb1aa --- /dev/null +++ b/Display/LayoutSizes.swift @@ -0,0 +1,9 @@ +import Foundation + +public func horizontalContainerFillingSizeForLayout(layout: ContainerViewLayout, sideInset: CGFloat) -> CGFloat { + if case .regular = layout.metrics.widthClass { + return min(layout.size.width, 414.0) - sideInset * 2.0 + } else { + return layout.size.width - sideInset * 2.0 + } +} diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index cf53e6d78b..43e2775fd6 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -33,7 +33,7 @@ open class LegacyPresentedController: ViewController { self.legacyController = legacyController self.presentation = presentation - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) /*legacyController.navigation_setDismiss { [weak self] in self?.dismiss() diff --git a/Display/LinkHighlightingNode.swift b/Display/LinkHighlightingNode.swift new file mode 100644 index 0000000000..df0420976f --- /dev/null +++ b/Display/LinkHighlightingNode.swift @@ -0,0 +1,237 @@ +import Foundation +import AsyncDisplayKit +import Display + +private enum CornerType { + case topLeft + case topRight + case bottomLeft + case bottomRight +} + +private func drawFullCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) { + context.setFillColor(color.cgColor) + switch type { + case .topLeft: + context.clear(CGRect(origin: point, size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: point, size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .topRight: + context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomLeft: + context.clear(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomRight: + context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + } +} + +private func drawConnectingCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) { + context.setFillColor(color.cgColor) + switch type { + case .topLeft: + context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .topRight: + context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomLeft: + context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomRight: + context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + } +} + +private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, UIImage?) { + if rects.isEmpty { + return (CGPoint(), nil) + } + + var topLeft = rects[0].origin + var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY) + for i in 1 ..< rects.count { + topLeft.x = min(topLeft.x, rects[i].origin.x) + topLeft.y = min(topLeft.y, rects[i].origin.y) + bottomRight.x = max(bottomRight.x, rects[i].maxX) + bottomRight.y = max(bottomRight.y, rects[i].maxY) + } + + topLeft.x -= inset + topLeft.y -= inset + bottomRight.x += inset * 2.0 + bottomRight.y += inset * 2.0 + + return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + + context.setBlendMode(.copy) + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset) + context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) + } + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + + var previous: CGRect? + if i != 0 { + previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + var next: CGRect? + if i != rects.count - 1 { + next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + if let previous = previous { + if previous.contains(rect.topLeft) { + if abs(rect.topLeft.x - previous.minX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + } + if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.topRight.x - previous.maxX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + + if let next = next { + if next.contains(rect.bottomLeft) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + } + if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } + })) + +} + +public final class LinkHighlightingNode: ASDisplayNode { + private var rects: [CGRect] = [] + private let imageNode: ASImageNode + + public var innerRadius: CGFloat = 4.0 + public var outerRadius: CGFloat = 4.0 + public var inset: CGFloat = 2.0 + + private var _color: UIColor + public var color: UIColor { + get { + return _color + } set(value) { + self._color = value + if !self.rects.isEmpty { + self.updateImage() + } + } + } + + public init(color: UIColor) { + self._color = color + + self.imageNode = ASImageNode() + self.imageNode.isLayerBacked = true + self.imageNode.displaysAsynchronously = false + self.imageNode.displayWithoutProcessing = true + + super.init() + + self.addSubnode(self.imageNode) + } + + public func updateRects(_ rects: [CGRect]) { + if self.rects != rects { + self.rects = rects + + self.updateImage() + } + } + + private func updateImage() { + if rects.isEmpty { + self.imageNode.image = nil + } + let (offset, image) = generateRectsImage(color: self.color, rects: self.rects, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius) + + if let image = image { + self.imageNode.image = image + self.imageNode.frame = CGRect(origin: offset, size: image.size) + } + } + + public func asyncLayout() -> (UIColor, [CGRect], CGFloat, CGFloat, CGFloat) -> () -> Void { + let currentRects = self.rects + let currentColor = self._color + let currentInnerRadius = self.innerRadius + let currentOuterRadius = self.outerRadius + let currentInset = self.inset + + return { [weak self] color, rects, innerRadius, outerRadius, inset in + var updatedImage: (CGPoint, UIImage?)? + if currentRects != rects || !currentColor.isEqual(color) || currentInnerRadius != innerRadius || currentOuterRadius != outerRadius || currentInset != inset { + updatedImage = generateRectsImage(color: color, rects: rects, inset: inset, outerRadius: outerRadius, innerRadius: innerRadius) + } + + return { + if let strongSelf = self { + strongSelf._color = color + strongSelf.rects = rects + strongSelf.innerRadius = innerRadius + strongSelf.outerRadius = outerRadius + strongSelf.inset = inset + + if let (offset, maybeImage) = updatedImage, let image = maybeImage { + strongSelf.imageNode.image = image + strongSelf.imageNode.frame = CGRect(origin: offset, size: image.size) + } + } + } + } + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index 1218fcc63c..8974ca3b54 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -951,7 +951,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var lowestOverlayNode: ListViewItemNode? for itemNode in self.itemNodes { - if itemNode.isHighligtedInOverlay { + if itemNode.isHighlightedInOverlay { lowestOverlayNode = itemNode itemNode.view.superview?.bringSubview(toFront: itemNode.view) } @@ -2472,7 +2472,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateScroller(transition: headerNodesTransition.0) if let topItemOverscrollBackground = self.topItemOverscrollBackground { - headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) } self.setNeedsAnimations() @@ -2496,7 +2496,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateScroller(transition: headerNodesTransition.0) if let topItemOverscrollBackground = self.topItemOverscrollBackground { - headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) } self.updateVisibleContentOffset() diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 8260768d83..1b41f683a4 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -72,7 +72,7 @@ open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? - public var isHighligtedInOverlay: Bool = false + public var isHighlightedInOverlay: Bool = false public private(set) var accessoryItemNode: ListViewAccessoryItemNode? diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 1fd46152f3..602098ce4d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -38,6 +38,26 @@ public final class NavigationBarTheme { } } +public final class NavigationBarStrings { + public let back: String + public let close: String + + public init(back: String, close: String) { + self.back = back + self.close = close + } +} + +public final class NavigationBarPresentationData { + public let theme: NavigationBarTheme + public let strings: NavigationBarStrings + + public init(theme: NavigationBarTheme, strings: NavigationBarStrings) { + self.theme = theme + self.strings = strings + } +} + private func backArrowImage(color: UIColor) -> UIImage? { var red: CGFloat = 0.0 var green: CGFloat = 0.0 @@ -58,8 +78,30 @@ private func backArrowImage(color: UIColor) -> UIImage? { } } +enum NavigationPreviousAction: Equatable { + case item(UINavigationItem) + case close + + static func ==(lhs: NavigationPreviousAction, rhs: NavigationPreviousAction) -> Bool { + switch lhs { + case let .item(lhsItem): + if case let .item(rhsItem) = rhs, lhsItem === rhsItem { + return true + } else { + return false + } + case .close: + if case .close = rhs { + return true + } else { + return false + } + } + } +} + open class NavigationBar: ASDisplayNode { - private var theme: NavigationBarTheme + private var presentationData: NavigationBarPresentationData private var validLayout: (CGSize, CGFloat, CGFloat)? private var requestedLayout: Bool = false @@ -201,7 +243,7 @@ open class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -234,52 +276,59 @@ open class NavigationBar: ASDisplayNode { var previousItemListenerKey: Int? var previousItemBackListenerKey: Int? - var _previousItem: UINavigationItem? - var previousItem: UINavigationItem? { + var _previousItem: NavigationPreviousAction? + var previousItem: NavigationPreviousAction? { get { return self._previousItem } set(value) { - if let previousValue = self._previousItem { - if let previousItemListenerKey = self.previousItemListenerKey { - previousValue.removeSetTitleListener(previousItemListenerKey) - self.previousItemListenerKey = nil - } - if let previousItemBackListenerKey = self.previousItemBackListenerKey { - previousValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) - self.previousItemBackListenerKey = nil - } - } - self._previousItem = value - - if let previousItem = value { - self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] _, _ in - if let strongSelf = self, let previousItem = strongSelf.previousItem { - if let backBarButtonItem = previousItem.backBarButtonItem { - strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") - } else { - strongSelf.backButtonNode.updateManualText(previousItem.title ?? "") - } - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() + if self._previousItem != value { + if let previousValue = self._previousItem, case let .item(itemValue) = previousValue { + if let previousItemListenerKey = self.previousItemListenerKey { + itemValue.removeSetTitleListener(previousItemListenerKey) + self.previousItemListenerKey = nil + } + if let previousItemBackListenerKey = self.previousItemBackListenerKey { + itemValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) + self.previousItemBackListenerKey = nil } } + self._previousItem = value - self.previousItemBackListenerKey = previousItem.addSetBackBarButtonItemListener { [weak self] _, _, _ in - if let strongSelf = self, let previousItem = strongSelf.previousItem { - if let backBarButtonItem = previousItem.backBarButtonItem { - strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") - } else { - strongSelf.backButtonNode.updateManualText(previousItem.title ?? "") - } - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() + if let previousItem = value { + switch previousItem { + case let .item(itemValue): + self.previousItemListenerKey = itemValue.addSetTitleListener { [weak self] _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { + if let backBarButtonItem = itemValue.backBarButtonItem { + strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") + } else { + strongSelf.backButtonNode.updateManualText(itemValue.title ?? "") + } + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.previousItemBackListenerKey = itemValue.addSetBackBarButtonItemListener { [weak self] _, _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { + if let backBarButtonItem = itemValue.backBarButtonItem { + strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") + } else { + strongSelf.backButtonNode.updateManualText(itemValue.title ?? "") + } + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + case .close: + break } } + self.updateLeftButton(animated: false) + + self.invalidateCalculatedLayout() + self.requestLayout() } - self.updateLeftButton(animated: false) - - self.invalidateCalculatedLayout() - self.requestLayout() } } @@ -296,7 +345,14 @@ open class NavigationBar: ASDisplayNode { private func updateLeftButton(animated: Bool) { if let item = self.item { + var needsLeftButton = false if let leftBarButtonItem = item.leftBarButtonItem, !leftBarButtonItem.backButtonAppearance { + needsLeftButton = true + } else if let previousItem = self.previousItem, case .close = previousItem { + needsLeftButton = true + } + + if needsLeftButton { if animated { if self.leftButtonNode.view.superview != nil { if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { @@ -343,7 +399,11 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.removeFromSupernode() self.badgeNode.removeFromSupernode() - self.leftButtonNode.updateItems([leftBarButtonItem]) + if let leftBarButtonItem = item.leftBarButtonItem { + self.leftButtonNode.updateItems([leftBarButtonItem]) + } else { + self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)]) + } if self.leftButtonNode.supernode == nil { self.clippingNode.addSubnode(self.leftButtonNode) @@ -370,10 +430,15 @@ open class NavigationBar: ASDisplayNode { if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { backTitle = leftBarButtonItem.title } else if let previousItem = self.previousItem { - if let backBarButtonItem = previousItem.backBarButtonItem { - backTitle = backBarButtonItem.title ?? "Back" - } else { - backTitle = previousItem.title ?? "Back" + switch previousItem { + case let .item(itemValue): + if let backBarButtonItem = itemValue.backBarButtonItem { + backTitle = backBarButtonItem.title ?? self.presentationData.strings.back + } else { + backTitle = itemValue.title ?? self.presentationData.strings.back + } + case .close: + backTitle = nil } } @@ -483,7 +548,7 @@ open class NavigationBar: ASDisplayNode { if let value = value { switch value.role { case .top: - if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.theme.primaryTextColor) { + if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.presentationData.theme.primaryTextColor) { self.transitionTitleNode = transitionTitleNode if self.leftButtonNode.supernode != nil { self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) @@ -494,11 +559,11 @@ open class NavigationBar: ASDisplayNode { } } case .bottom: - if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.theme.buttonColor) { + if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackButtonNode = transitionBackButtonNode self.clippingNode.addSubnode(transitionBackButtonNode) } - if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.theme.buttonColor) { + if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackArrowNode = transitionBackArrowNode self.clippingNode.addSubnode(transitionBackArrowNode) } @@ -520,13 +585,13 @@ open class NavigationBar: ASDisplayNode { private var transitionBackArrowNode: ASDisplayNode? private var transitionBadgeNode: ASDisplayNode? - public init(theme: NavigationBarTheme) { - self.theme = theme + public init(presentationData: NavigationBarPresentationData) { + self.presentationData = presentationData self.stripeNode = ASDisplayNode() self.titleNode = ASTextNode() self.backButtonNode = NavigationButtonNode() - self.badgeNode = NavigationBarBadgeNode(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor) + self.badgeNode = NavigationBarBadgeNode(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) self.badgeNode.isUserInteractionEnabled = false self.badgeNode.isHidden = true self.backButtonArrow = ASImageNode() @@ -538,20 +603,20 @@ open class NavigationBar: ASDisplayNode { self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true - self.backButtonNode.color = self.theme.buttonColor - self.leftButtonNode.color = self.theme.buttonColor - self.rightButtonNode.color = self.theme.buttonColor - self.backButtonArrow.image = backArrowImage(color: self.theme.buttonColor) + self.backButtonNode.color = self.presentationData.theme.buttonColor + self.leftButtonNode.color = self.presentationData.theme.buttonColor + self.rightButtonNode.color = self.presentationData.theme.buttonColor + self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) } - self.stripeNode.backgroundColor = self.theme.separatorColor + self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor super.init() self.addSubnode(self.clippingNode) - self.backgroundColor = self.theme.backgroundColor + self.backgroundColor = self.presentationData.theme.backgroundColor self.stripeNode.isLayerBacked = true self.stripeNode.displaysAsynchronously = false @@ -580,8 +645,10 @@ open class NavigationBar: ASDisplayNode { self.leftButtonNode.pressed = { [weak self] index in if let item = self?.item { if index == 0 { - if let leftBarButtonItem = item.leftBarButtonItem { - leftBarButtonItem.performActionOnTarget() + if let leftBarButtonItem = item.leftBarButtonItem { + leftBarButtonItem.performActionOnTarget() + } else if let previousItem = self?.previousItem, case .close = previousItem { + self?.backPressed() } } } @@ -600,22 +667,22 @@ open class NavigationBar: ASDisplayNode { } } - public func updateTheme(_ theme: NavigationBarTheme) { - if theme !== self.theme { - self.theme = theme + public func updatePresentationData(_ presentationData: NavigationBarPresentationData) { + if presentationData.theme !== self.presentationData.theme || presentationData.strings !== self.presentationData.strings { + self.presentationData = presentationData - self.backgroundColor = self.theme.backgroundColor + self.backgroundColor = self.presentationData.theme.backgroundColor - self.backButtonNode.color = self.theme.buttonColor - self.leftButtonNode.color = self.theme.buttonColor - self.rightButtonNode.color = self.theme.buttonColor - self.backButtonArrow.image = backArrowImage(color: self.theme.buttonColor) + self.backButtonNode.color = self.presentationData.theme.buttonColor + self.leftButtonNode.color = self.presentationData.theme.buttonColor + self.rightButtonNode.color = self.presentationData.theme.buttonColor + self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) } - self.stripeNode.backgroundColor = self.theme.separatorColor + self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor - self.badgeNode.updateTheme(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor) + self.badgeNode.updateTheme(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) } } @@ -649,11 +716,11 @@ open class NavigationBar: ASDisplayNode { let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 let contentVerticalOrigin = size.height - nominalHeight - var leftTitleInset: CGFloat = leftInset + 4.0 - var rightTitleInset: CGFloat = rightInset + 4.0 + var leftTitleInset: CGFloat = leftInset + 1.0 + var rightTitleInset: CGFloat = rightInset + 1.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) - leftTitleInset += backButtonSize.width + backButtonInset + 4.0 + 4.0 + leftTitleInset += backButtonSize.width + backButtonInset + 1.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 self.backButtonNode.hitTestSlop = UIEdgeInsetsMake(-topHitTestSlop, -27.0, -topHitTestSlop, -8.0) @@ -698,7 +765,7 @@ open class NavigationBar: ASDisplayNode { } } else if self.leftButtonNode.supernode != nil { let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) - leftTitleInset += leftButtonSize.width + leftButtonInset + 8.0 + 8.0 + leftTitleInset += leftButtonSize.width + leftButtonInset + 1.0 self.leftButtonNode.alpha = 1.0 self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize) @@ -710,7 +777,7 @@ open class NavigationBar: ASDisplayNode { if self.rightButtonNode.supernode != nil { let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight))) - rightTitleInset += rightButtonSize.width + leftButtonInset + 8.0 + 8.0 + rightTitleInset += rightButtonSize.width + leftButtonInset + 1.0 self.rightButtonNode.alpha = 1.0 self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize) } @@ -783,7 +850,14 @@ open class NavigationBar: ASDisplayNode { if let titleView = self.titleView { let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) - titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) + titleView.frame = titleFrame + + if let titleView = titleView as? NavigationBarTitleView { + let titleWidth = size.width - leftTitleInset - rightTitleInset + + titleView.updateLayout(size: titleFrame.size, clearBounds: CGRect(origin: CGPoint(x: leftTitleInset - titleFrame.minX, y: 0.0), size: CGSize(width: titleWidth, height: titleFrame.height)), transition: transition) + } if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { let progress = transitionState.progress @@ -861,7 +935,7 @@ open class NavigationBar: ASDisplayNode { private func makeTransitionBadgeNode() -> ASDisplayNode? { if self.badgeNode.supernode != nil && !self.badgeNode.isHidden { - let node = NavigationBarBadgeNode(fillColor: self.theme.badgeBackgroundColor, strokeColor: self.theme.badgeStrokeColor, textColor: self.theme.badgeTextColor) + let node = NavigationBarBadgeNode(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) node.text = self.badgeNode.text let nodeSize = node.measure(CGSize(width: 200.0, height: 100.0)) node.frame = CGRect(origin: CGPoint(), size: nodeSize) diff --git a/Display/NavigationBarTitleView.swift b/Display/NavigationBarTitleView.swift index 630e23606e..ee8fa822e5 100644 --- a/Display/NavigationBarTitleView.swift +++ b/Display/NavigationBarTitleView.swift @@ -3,4 +3,6 @@ import UIKit public protocol NavigationBarTitleView { func animateLayoutTransition() + + func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 6cf72c003d..ac34943e2d 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -3,9 +3,48 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -private class NavigationControllerView: UIView { +public final class NavigationControllerTheme { + public let navigationBar: NavigationBarTheme + public let emptyAreaColor: UIColor + public let emptyDetailIcon: UIImage? + + public init(navigationBar: NavigationBarTheme, emptyAreaColor: UIColor, emptyDetailIcon: UIImage?) { + self.navigationBar = navigationBar + self.emptyAreaColor = emptyAreaColor + self.emptyDetailIcon = emptyDetailIcon + } +} + +private final class NavigationControllerContainerView: UIView { + override class var layerClass: AnyClass { + return CATracingLayer.self + } +} + +private final class NavigationControllerView: UIView { var inTransition = false + let sharedStatusBar: StatusBar + let containerView: NavigationControllerContainerView + let separatorView: UIView + var navigationBackgroundView: UIView? + var navigationSeparatorView: UIView? + var emptyDetailView: UIImageView? + + override init(frame: CGRect) { + self.containerView = NavigationControllerContainerView() + self.separatorView = UIView() + self.sharedStatusBar = StatusBar() + + super.init(frame: frame) + + self.addSubview(self.containerView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override class var layerClass: AnyClass { return CATracingLayer.self } @@ -18,9 +57,40 @@ private class NavigationControllerView: UIView { } } +private enum ControllerTransition { + case none + case appearance +} + +private final class ControllerRecord { + let controller: UIViewController + var transition: ControllerTransition = .none + + init(controller: UIViewController) { + self.controller = controller + } +} + +private enum ControllerLayoutConfiguration { + case single + case masterDetail +} + +public enum NavigationControllerMode { + case single + case automaticMasterDetail +} + open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { + private let mode: NavigationControllerMode + private var theme: NavigationControllerTheme + public private(set) weak var overlayPresentingController: ViewController? + private var controllerView: NavigationControllerView { + return self.view as! NavigationControllerView + } + private var validLayout: ContainerViewLayout? private var navigationTransitionCoordinator: NavigationTransitionCoordinator? @@ -28,35 +98,33 @@ open class NavigationController: UINavigationController, ContainableController, private var currentPushDisposable = MetaDisposable() private var currentPresentDisposable = MetaDisposable() - private var statusBarChangeObserver: AnyObject? - - //private var layout: NavigationControllerLayout? - //private var pendingLayout: (NavigationControllerLayout, Double, Bool)? - private var _presentedViewController: UIViewController? open override var presentedViewController: UIViewController? { return self._presentedViewController } - private var _viewControllers: [UIViewController] = [] + private var _viewControllers: [ControllerRecord] = [] open override var viewControllers: [UIViewController] { get { - return self._viewControllers + return self._viewControllers.map { $0.controller } } set(value) { - self.setViewControllers(_viewControllers, animated: false) + self.setViewControllers(value, animated: false) } } open override var topViewController: UIViewController? { - return self._viewControllers.last + return self._viewControllers.last?.controller } - public init() { + public init(mode: NavigationControllerMode, theme: NavigationControllerTheme) { + self.mode = mode + self.theme = theme + super.init(nibName: nil, bundle: nil) } public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + preconditionFailure() } public required init(coder aDecoder: NSCoder) { @@ -68,6 +136,384 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPresentDisposable.dispose() } + public func updateTheme(_ theme: NavigationControllerTheme) { + self.theme = theme + if self.isViewLoaded { + self.controllerView.backgroundColor = theme.emptyAreaColor + self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor + self.controllerView.navigationBackgroundView?.backgroundColor = theme.navigationBar.backgroundColor + self.controllerView.navigationSeparatorView?.backgroundColor = theme.navigationBar.separatorColor + if let emptyDetailView = self.controllerView.emptyDetailView { + emptyDetailView.image = theme.emptyDetailIcon + if let image = theme.emptyDetailIcon { + emptyDetailView.frame = CGRect(origin: CGPoint(x: floor((self.controllerView.containerView.bounds.size.width - image.size.width) / 2.0), y: floor((self.controllerView.containerView.bounds.size.height - image.size.height) / 2.0)), size: image.size) + } + } + } + } + + private var previouslyLaidOutMasterController: UIViewController? + private var previouslyLaidOutTopController: UIViewController? + + private func layoutConfiguration(for layout: ContainerViewLayout) -> ControllerLayoutConfiguration { + switch self.mode { + case .single: + return .single + case .automaticMasterDetail: + if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { + if layout.size.width > 690.0 { + return .masterDetail + } + } + return .single + } + } + + private func layoutDataForConfiguration(_ layoutConfiguration: ControllerLayoutConfiguration, layout: ContainerViewLayout, index: Int) -> (CGRect, ContainerViewLayout) { + switch layoutConfiguration { + case .masterDetail: + let masterWidth: CGFloat = max(320.0, floor(layout.size.width / 3.0)) + let detailWidth: CGFloat = layout.size.width - masterWidth + if index == 0 { + return (CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: masterWidth, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + } else { + let detailFrame = CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)) + return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + } + case .single: + return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + } + } + + private func updateControllerLayouts(previousControllers: [ControllerRecord], layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var firstControllerFrameAndLayout: (CGRect, ContainerViewLayout)? + let lastControllerFrameAndLayout: (CGRect, ContainerViewLayout) + + let layoutConfiguration = self.layoutConfiguration(for: layout) + + switch layoutConfiguration { + case .masterDetail: + self.viewControllers.first?.view.clipsToBounds = true + self.controllerView.containerView.clipsToBounds = true + let masterData = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 0) + firstControllerFrameAndLayout = masterData + lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) + if self.controllerView.separatorView.superview == nil { + self.controllerView.addSubview(self.controllerView.separatorView) + } + + let navigationBackgroundFrame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: CGSize(width: lastControllerFrameAndLayout.0.width, height: (layout.statusBarHeight ?? 0.0) + 44.0)) + + if let navigationBackgroundView = self.controllerView.navigationBackgroundView, let navigationSeparatorView = self.controllerView.navigationSeparatorView, let emptyDetailView = self.controllerView.emptyDetailView { + transition.updateFrame(view: navigationBackgroundView, frame: navigationBackgroundFrame) + transition.updateFrame(view: navigationSeparatorView, frame: CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel))) + if let image = emptyDetailView.image { + transition.updateFrame(view: emptyDetailView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size)) + } + } else { + let navigationBackgroundView = UIView() + navigationBackgroundView.backgroundColor = self.theme.navigationBar.backgroundColor + let navigationSeparatorView = UIView() + navigationSeparatorView.backgroundColor = self.theme.navigationBar.separatorColor + let emptyDetailView = UIImageView() + emptyDetailView.image = self.theme.emptyDetailIcon + emptyDetailView.alpha = 0.0 + + self.controllerView.navigationBackgroundView = navigationBackgroundView + self.controllerView.navigationSeparatorView = navigationSeparatorView + self.controllerView.emptyDetailView = emptyDetailView + + self.controllerView.insertSubview(navigationBackgroundView, at: 0) + self.controllerView.insertSubview(navigationSeparatorView, at: 1) + self.controllerView.insertSubview(emptyDetailView, at: 2) + + navigationBackgroundView.frame = navigationBackgroundFrame + navigationSeparatorView.frame = CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel)) + + transition.animatePositionAdditive(layer: navigationBackgroundView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0)) + transition.animatePositionAdditive(layer: navigationSeparatorView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0)) + + if let image = emptyDetailView.image { + emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size) + } + + transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0) + } + transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + case .single: + self.viewControllers.first?.view.clipsToBounds = false + if let navigationBackgroundView = self.controllerView.navigationBackgroundView, let navigationSeparatorView = self.controllerView.navigationSeparatorView { + self.controllerView.navigationBackgroundView = nil + self.controllerView.navigationSeparatorView = nil + + transition.updatePosition(layer: navigationBackgroundView.layer, position: CGPoint(x: layout.size.width + navigationBackgroundView.bounds.size.width / 2.0, y: navigationBackgroundView.center.y), completion: { [weak navigationBackgroundView] _ in + navigationBackgroundView?.removeFromSuperview() + }) + transition.updatePosition(layer: navigationSeparatorView.layer, position: CGPoint(x: layout.size.width + navigationSeparatorView.bounds.size.width / 2.0, y: navigationSeparatorView.center.y), completion: { [weak navigationSeparatorView] _ in + navigationSeparatorView?.removeFromSuperview() + }) + if let emptyDetailView = self.controllerView.emptyDetailView { + self.controllerView.emptyDetailView = nil + transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in + emptyDetailView?.removeFromSuperview() + }) + } + } + self.controllerView.containerView.clipsToBounds = false + lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) + transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)), completion: { [weak self] completed in + if let strongSelf = self, completed { + strongSelf.controllerView.separatorView.removeFromSuperview() + } + }) + } + transition.updateFrame(view: self.controllerView.containerView, frame: CGRect(origin: CGPoint(x: firstControllerFrameAndLayout?.0.maxX ?? 0.0, y: 0.0), size: lastControllerFrameAndLayout.0.size)) + + switch layoutConfiguration { + case .single: + if self.controllerView.sharedStatusBar.view.superview != nil { + self.controllerView.sharedStatusBar.removeFromSupernode() + self.controllerView.containerView.layer.setTraceableInfo(nil) + } + case .masterDetail: + if self.controllerView.sharedStatusBar.view.superview == nil { + self.controllerView.addSubnode(self.controllerView.sharedStatusBar) + self.controllerView.containerView.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: 0, disableChildrenTracingTags: WindowTracingTags.statusBar | WindowTracingTags.keyboard)) + } + } + + if let _ = layout.statusBarHeight { + self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + } + + var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] + for i in 0 ..< self._viewControllers.count { + if let controller = self._viewControllers[i].controller as? ViewController { + if i == 0 { + controller.navigationBar?.previousItem = nil + } else if case .masterDetail = layoutConfiguration, i == 1 { + controller.navigationBar?.previousItem = .close + } else { + controller.navigationBar?.previousItem = .item(viewControllers[i - 1].navigationItem) + } + } + viewControllers[i].navigation_setNavigationController(self) + + if i == 0, let (_, layout) = firstControllerFrameAndLayout { + controllersAndFrames.append((true, self._viewControllers[i], layout)) + } else if i == self._viewControllers.count - 1 { + controllersAndFrames.append((false, self._viewControllers[i], lastControllerFrameAndLayout.1)) + } + } + + var masterController: UIViewController? + var appearingMasterController: ControllerRecord? + var appearingDetailController: ControllerRecord? + + for (isMaster, record, layout) in controllersAndFrames { + let frame: CGRect + if isMaster, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { + masterController = record.controller + frame = firstControllerFrameAndLayout.0 + } else { + frame = lastControllerFrameAndLayout.0 + } + let isAppearing = record.controller.view.superview == nil + (record.controller as? ViewController)?.containerLayoutUpdated(layout, transition: isAppearing ? .immediate : transition) + if isAppearing { + if isMaster { + appearingMasterController = record + } else { + appearingDetailController = record + } + } else if record.controller.view.superview !== (isMaster ? self.controllerView : self.controllerView.containerView) { + record.controller.setIgnoreAppearanceMethodInvocations(true) + if isMaster { + self.controllerView.insertSubview(record.controller.view, at: 0) + } else { + self.controllerView.containerView.addSubview(record.controller.view) + } + record.controller.setIgnoreAppearanceMethodInvocations(false) + } + if !isAppearing { + var isPartOfTransition = false + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if navigationTransitionCoordinator.topView == record.controller.view || navigationTransitionCoordinator.bottomView == record.controller.view { + isPartOfTransition = true + } + } + if !isPartOfTransition { + transition.updateFrame(view: record.controller.view, frame: frame) + } + } + } + + var animatedAppearingDetailController = false + + if let previousController = self.previouslyLaidOutTopController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { + if transition.isAnimated, let record = appearingDetailController { + animatedAppearingDetailController = true + + previousController.viewWillDisappear(true) + record.controller.setIgnoreAppearanceMethodInvocations(true) + self.controllerView.containerView.addSubview(record.controller.view) + record.controller.setIgnoreAppearanceMethodInvocations(false) + + if let _ = previousControllers.index(where: { $0.controller === record.controller }) { + //previousControllers[index].transition = .appearance + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + self.controllerView.inTransition = true + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.navigationTransitionCoordinator = nil + strongSelf.controllerView.inTransition = false + + record.controller.viewDidAppear(true) + + previousController.setIgnoreAppearanceMethodInvocations(true) + previousController.view.removeFromSuperview() + previousController.setIgnoreAppearanceMethodInvocations(false) + previousController.viewDidDisappear(true) + } + }) + } else { + if let index = self._viewControllers.index(where: { $0.controller === previousController }) { + self._viewControllers[index].transition = .appearance + } + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.containerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + self.controllerView.inTransition = true + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + if let index = strongSelf._viewControllers.index(where: { $0.controller === previousController }) { + strongSelf._viewControllers[index].transition = .none + } + strongSelf.navigationTransitionCoordinator = nil + strongSelf.controllerView.inTransition = false + + record.controller.viewDidAppear(true) + + previousController.setIgnoreAppearanceMethodInvocations(true) + previousController.view.removeFromSuperview() + previousController.setIgnoreAppearanceMethodInvocations(false) + previousController.viewDidDisappear(true) + } + }) + } + } else { + previousController.viewWillDisappear(false) + previousController.view.removeFromSuperview() + previousController.viewDidDisappear(false) + } + } + + if !animatedAppearingDetailController, let record = appearingDetailController { + record.controller.viewWillAppear(false) + record.controller.setIgnoreAppearanceMethodInvocations(true) + self.controllerView.containerView.addSubview(record.controller.view) + record.controller.setIgnoreAppearanceMethodInvocations(false) + record.controller.viewDidAppear(false) + if let controller = record.controller as? ViewController { + controller.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + } + + if let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { + record.controller.viewWillAppear(false) + record.controller.setIgnoreAppearanceMethodInvocations(true) + self.controllerView.insertSubview(record.controller.view, belowSubview: self.controllerView.containerView) + record.controller.setIgnoreAppearanceMethodInvocations(false) + record.controller.viewDidAppear(false) + if let controller = record.controller as? ViewController { + controller.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + + record.controller.view.frame = firstControllerFrameAndLayout.0 + record.controller.viewDidAppear(transition.isAnimated) + transition.animatePositionAdditive(layer: record.controller.view.layer, offset: CGPoint(x: -firstControllerFrameAndLayout.0.width, y: 0.0)) + } + + for record in self._viewControllers { + let controller = record.controller + if case .none = record.transition, !controllersAndFrames.contains(where: { $0.1.controller === controller }) { + if controller === self.previouslyLaidOutMasterController { + controller.viewWillDisappear(true) + record.transition = .appearance + transition.animatePositionAdditive(layer: controller.view.layer, offset: CGPoint(), to: CGPoint(x: -controller.view.bounds.size.width, y: 0.0), removeOnCompletion: false, completion: { [weak self] in + if let strongSelf = self { + controller.setIgnoreAppearanceMethodInvocations(true) + controller.view.removeFromSuperview() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(true) + controller.view.layer.removeAllAnimations() + for r in strongSelf._viewControllers { + if r.controller === controller { + r.transition = .none + } + } + } + }) + } else { + if controller.isViewLoaded && controller.view.superview != nil { + var isPartOfTransition = false + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if navigationTransitionCoordinator.topView == controller.view || navigationTransitionCoordinator.bottomView == controller.view { + isPartOfTransition = true + } + } + + if !isPartOfTransition { + controller.viewWillDisappear(false) + controller.setIgnoreAppearanceMethodInvocations(true) + controller.view.removeFromSuperview() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(false) + } + } + } + } + } + + for previous in previousControllers { + var isFound = false + inner: for current in self._viewControllers { + if previous.controller === current.controller { + isFound = true + break inner + } + } + if !isFound { + (previous.controller as? ViewController)?.navigationStackConfigurationUpdated(next: []) + } + } + + for i in 0 ..< self._viewControllers.count { + var currentNext: UIViewController? = (i == (self._viewControllers.count - 1)) ? nil : self._viewControllers[i + 1].controller + if case .single = layoutConfiguration { + currentNext = nil + } + + var previousNext: UIViewController? + inner: for j in 0 ..< previousControllers.count { + if previousControllers[j].controller === self._viewControllers[i].controller { + previousNext = (j == (previousControllers.count - 1)) ? nil : previousControllers[j + 1].controller + break inner + } + } + + if currentNext !== previousNext { + let next = currentNext as? ViewController + (self._viewControllers[i].controller as? ViewController)?.navigationStackConfigurationUpdated(next: next == nil ? [] : [next!]) + } + } + + self.previouslyLaidOutMasterController = masterController + self.previouslyLaidOutTopController = self._viewControllers.last?.controller + } + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if !self.isViewLoaded { self.loadView() @@ -75,17 +521,11 @@ open class NavigationController: UINavigationController, ContainableController, self.validLayout = layout transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) - let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) - - if let topViewController = self.topViewController { - if let topViewController = topViewController as? ContainableController { - topViewController.containerLayoutUpdated(containedLayout, transition: transition) - } else { - transition.updateFrame(view: topViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) - } - } + self.updateControllerLayouts(previousControllers: self._viewControllers, layout: layout, transition: transition) if let presentedViewController = self.presentedViewController { + let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) + if let presentedViewController = presentedViewController as? ContainableController { presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) } else { @@ -103,6 +543,9 @@ open class NavigationController: UINavigationController, ContainableController, self.view.clipsToBounds = true self.view.autoresizingMask = [] + self.controllerView.backgroundColor = self.theme.emptyAreaColor + self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor + if #available(iOSApplicationExtension 11.0, *) { self.navigationBar.prefersLargeTitles = false } @@ -122,7 +565,26 @@ open class NavigationController: UINavigationController, ContainableController, @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { case UIGestureRecognizerState.began: - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + guard let layout = self.validLayout else { + return + } + guard self.navigationTransitionCoordinator == nil else { + return + } + let beginGesture: Bool + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + let location = recognizer.location(in: self.controllerView.containerView) + if self.controllerView.containerView.bounds.contains(location) { + beginGesture = self._viewControllers.count >= 3 + } else { + beginGesture = false + } + case .single: + beginGesture = self._viewControllers.count >= 2 + } + + if beginGesture { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -131,7 +593,7 @@ open class NavigationController: UINavigationController, ContainableController, bottomController.viewWillAppear(true) let bottomView = bottomController.view! - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator } case UIGestureRecognizerState.changed: @@ -147,9 +609,8 @@ open class NavigationController: UINavigationController, ContainableController, (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCompletion(velocity, completion: { (self.view as! NavigationControllerView).inTransition = false - self.navigationTransitionCoordinator = nil - //self._navigationBar.endInteractivePopProgress() + self.navigationTransitionCoordinator = nil if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -165,8 +626,7 @@ open class NavigationController: UINavigationController, ContainableController, bottomController.viewDidAppear(true) } }) - } - else { + } else { if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -180,8 +640,6 @@ open class NavigationController: UINavigationController, ContainableController, (self.view as! NavigationControllerView).inTransition = false self.navigationTransitionCoordinator = nil - //self._navigationBar.endInteractivePopProgress() - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -226,11 +684,15 @@ open class NavigationController: UINavigationController, ContainableController, self.view.endEditing(true) } if let validLayout = self.validLayout { - let appliedLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil) + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) controller.containerLayoutUpdated(appliedLayout, transition: .immediate) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self, let validLayout = strongSelf.validLayout { - let containerLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil) + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) if containerLayout != appliedLayout { controller.containerLayoutUpdated(containerLayout, transition: .immediate) } @@ -253,7 +715,8 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) if let validLayout = self.validLayout { - controller.containerLayoutUpdated(validLayout, transition: .immediate) + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { @@ -269,7 +732,8 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) if let validLayout = self.validLayout { - controller.containerLayoutUpdated(validLayout, transition: .immediate) + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { @@ -324,132 +788,24 @@ open class NavigationController: UINavigationController, ContainableController, } open override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + var resultControllers: [ControllerRecord] = [] for controller in viewControllers { - controller.navigation_setNavigationController(self) - } - - if viewControllers.count > 0 { - let topViewController = viewControllers[viewControllers.count - 1] as UIViewController - - if let controller = topViewController as? ContainableController { - if let validLayout = self.validLayout { - var layoutToApply = validLayout - var hasActiveInput = false - if let controller = controller as? ViewController { - hasActiveInput = controller.hasActiveInput - } - if !hasActiveInput { - layoutToApply = layoutToApply.withUpdatedInputHeight(nil) - } - controller.containerLayoutUpdated(layoutToApply, transition: .immediate) + var found = false + inner: for current in self._viewControllers { + if current.controller === controller { + resultControllers.append(current) + found = true + break inner } - } else { - topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) + } + if !found { + resultControllers.append(ControllerRecord(controller: controller)) } } - - if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! { - if self.viewControllers.contains(where: { $0 === viewControllers.last }) { - let bottomController = viewControllers.last! as UIViewController - let topController = self.viewControllers.last! as UIViewController - - if let bottomController = bottomController as? ViewController { - if viewControllers.count >= 2 { - bottomController.navigationBar?.previousItem = viewControllers[viewControllers.count - 2].navigationItem - } else { - bottomController.navigationBar?.previousItem = nil - } - } - - bottomController.viewWillAppear(true) - let bottomView = bottomController.view! - topController.viewWillDisappear(true) - let topView = topController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - (strongSelf.view as! NavigationControllerView).inTransition = false - strongSelf.navigationTransitionCoordinator = nil - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - strongSelf.setViewControllers(viewControllers, animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.viewDidDisappear(true) - bottomController.viewDidAppear(true) - - topView.removeFromSuperview() - } - }) - } else { - let topController = viewControllers.last! as UIViewController - let bottomController = self.viewControllers.last! as UIViewController - - if let topController = topController as? ViewController { - topController.navigationBar?.previousItem = bottomController.navigationItem - } - - bottomController.viewWillDisappear(true) - let bottomView = bottomController.view! - topController.viewWillAppear(true) - let topView = topController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - topView.isUserInteractionEnabled = false - - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - (strongSelf.view as! NavigationControllerView).inTransition = false - strongSelf.navigationTransitionCoordinator = nil - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - strongSelf.setViewControllers(viewControllers, animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.view.isUserInteractionEnabled = true - - bottomController.viewDidDisappear(true) - topController.viewDidAppear(true) - - bottomView.removeFromSuperview() - } - }) - } - } else { - if let topController = self.viewControllers.last , topController.isViewLoaded { - topController.navigation_setNavigationController(nil) - topController.viewWillDisappear(false) - topController.view.removeFromSuperview() - topController.viewDidDisappear(false) - } - - self._viewControllers = viewControllers - - if let topController = viewControllers.last { - if let topController = topController as? ViewController { - if viewControllers.count >= 2 { - topController.navigationBar?.previousItem = viewControllers[viewControllers.count - 2].navigationItem - } else { - topController.navigationBar?.previousItem = nil - } - } - - topController.navigation_setNavigationController(self) - topController.viewWillAppear(false) - self.view.addSubview(topController.view) - topController.viewDidAppear(false) - } + let previousControllers = self._viewControllers + self._viewControllers = resultControllers + if let layout = self.validLayout { + self.updateControllerLayouts(previousControllers: previousControllers, layout: layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) } } @@ -527,7 +883,7 @@ open class NavigationController: UINavigationController, ContainableController, } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if let panRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer { + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { return true } return false diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 8749945c1e..d13b498d3d 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -27,9 +27,9 @@ class NavigationTransitionCoordinator { private let container: UIView private let transition: NavigationTransition - private let topView: UIView + let topView: UIView private let viewSuperview: UIView? - private let bottomView: UIView + let bottomView: UIView private let topNavigationBar: NavigationBar? private let bottomNavigationBar: NavigationBar? private let dimView: UIView @@ -181,7 +181,7 @@ class NavigationTransitionCoordinator { self.animatingCompletion = true let distance = (1.0 - self.progress) * self.container.bounds.size.width let f = { - switch self.transition { + /*switch self.transition { case .Push: if let viewSuperview = self.viewSuperview { viewSuperview.addSubview(self.bottomView) @@ -194,7 +194,7 @@ class NavigationTransitionCoordinator { } else { self.topView.removeFromSuperview() } - } + }*/ self.dimView.removeFromSuperview() self.shadowView.removeFromSuperview() diff --git a/Display/PeekController.swift b/Display/PeekController.swift index 091e253a70..14b2c71c13 100644 --- a/Display/PeekController.swift +++ b/Display/PeekController.swift @@ -35,7 +35,7 @@ public final class PeekController: ViewController { self.content = content self.sourceNode = sourceNode - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index 25ae8c9ffd..ba8c9bf4ce 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -90,29 +90,11 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } private func longTapTimerFired() { - guard let _ = self.tapLocation, let (sourceNode, content) = self.candidateContent else { + guard let tapLocation = self.tapLocation else { return } - self.state = .began - - if let presentedController = self.present(content, sourceNode) { - self.menuActivation = content.menuActivation() - self.presentedController = presentedController - - switch content.menuActivation() { - case .drag: - break - case .press: - if #available(iOSApplicationExtension 9.0, *) { - if presentedController.traitCollection.forceTouchCapability != .available { - self.startPressTimer() - } - } else { - self.startPressTimer() - } - } - } + self.checkCandidateContent(at: tapLocation) } private func pressTimerFired() { @@ -136,27 +118,8 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { self.candidateContent = nil self.state = .failed } else { - if let contentSignal = self.contentAtPoint(tapLocation) { - self.candidateContentDisposable.set((contentSignal |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - switch strongSelf.state { - case .possible, .changed: - if let (sourceNode, content) = result { - strongSelf.tapLocation = tapLocation - strongSelf.candidateContent = (sourceNode, content) - strongSelf.menuActivation = content.menuActivation() - strongSelf.startLongTapTimer() - } else { - strongSelf.state = .failed - } - default: - break - } - } - })) - } else { - self.state = .failed - } + self.tapLocation = tapLocation + self.startLongTapTimer() } } } @@ -178,6 +141,8 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { self.tapLocation = nil self.candidateContent = nil + self.longTapTimer?.invalidate() + self.pressTimer?.invalidate() self.state = .failed } } @@ -199,9 +164,9 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { override public func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) - if let touch = touches.first, let initialTapLocation = self.tapLocation, let menuActivation = self.menuActivation { + if let touch = touches.first, let initialTapLocation = self.tapLocation { let touchLocation = touch.location(in: self.view) - if let presentedController = self.presentedController { + if let menuActivation = self.menuActivation, let presentedController = self.presentedController { switch menuActivation { case .drag: var offset = touchLocation.y - initialTapLocation.y @@ -257,17 +222,39 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { if let strongSelf = self { switch strongSelf.state { case .possible, .changed: - if let (sourceNode, content) = result, let currentContent = strongSelf.candidateContent, !currentContent.1.isEqual(to: content) { - strongSelf.tapLocation = touchLocation - strongSelf.candidateContent = (sourceNode, content) - strongSelf.menuActivation = content.menuActivation() - if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { - presentedController.sourceNode = { - return sourceNode + if let (sourceNode, content) = result { + if let currentContent = strongSelf.candidateContent { + if !currentContent.1.isEqual(to: content) { + strongSelf.tapLocation = touchLocation + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { + presentedController.sourceNode = { + return sourceNode + } + (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) + } } - (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) } else { - strongSelf.startLongTapTimer() + if let presentedController = strongSelf.present(content, sourceNode) { + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + strongSelf.presentedController = presentedController + strongSelf.state = .began + + switch content.menuActivation() { + case .drag: + break + case .press: + if #available(iOSApplicationExtension 9.0, *) { + if presentedController.traitCollection.forceTouchCapability != .available { + strongSelf.startPressTimer() + } + } else { + strongSelf.startPressTimer() + } + } + } } } else if strongSelf.presentedController == nil { strongSelf.state = .failed diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 0cb39d4d89..ed325bacbd 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -113,7 +113,7 @@ public final class StatusBar: ASDisplayNode { self.addSubnode(self.offsetNode) self.addSubnode(self.inCallBackgroundNode) - self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar)) + self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar, disableChildrenTracingTags: 0)) self.clipsToBounds = true diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 5f1b1434db..2117f752be 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -64,10 +64,10 @@ open class TabBarController: ViewController { private var theme: TabBarControllerTheme - public init(navigationBarTheme: NavigationBarTheme, theme: TabBarControllerTheme) { + public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) { self.theme = theme - super.init(navigationBarTheme: navigationBarTheme) + super.init(navigationBarPresentationData: navigationBarPresentationData) } required public init(coder aDecoder: NSCoder) { @@ -78,8 +78,8 @@ open class TabBarController: ViewController { self.pendingControllerDisposable.dispose() } - public func updateTheme(navigationBarTheme: NavigationBarTheme, theme: TabBarControllerTheme) { - self.navigationBar?.updateTheme(navigationBarTheme) + public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) { + self.navigationBar?.updatePresentationData(navigationBarPresentationData) if self.theme !== theme { self.theme = theme if self.isNodeLoaded { @@ -192,6 +192,13 @@ open class TabBarController: ViewController { } } + override open func navigationStackConfigurationUpdated(next: [ViewController]) { + super.navigationStackConfigurationUpdated(next: next) + for controller in self.controllers { + controller.navigationStackConfigurationUpdated(next: next) + } + } + override open func viewDidAppear(_ animated: Bool) { if let currentController = self.currentController { currentController.viewDidAppear(animated) diff --git a/Display/TapLongTapOrDoubleTapGestureRecognizer.swift b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift new file mode 100644 index 0000000000..a8a5fce557 --- /dev/null +++ b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift @@ -0,0 +1,257 @@ +import Foundation +import UIKit.UIGestureRecognizerSubclass +import Display + +private class TapLongTapOrDoubleTapGestureRecognizerTimerTarget: NSObject { + weak var target: TapLongTapOrDoubleTapGestureRecognizer? + + init(target: TapLongTapOrDoubleTapGestureRecognizer) { + self.target = target + + super.init() + } + + @objc func longTapEvent() { + self.target?.longTapEvent() + } + + @objc func tapEvent() { + self.target?.tapEvent() + } + + @objc func holdEvent() { + self.target?.holdEvent() + } +} + +enum TapLongTapOrDoubleTapGesture { + case tap + case doubleTap + case longTap + case hold +} + +enum TapLongTapOrDoubleTapGestureRecognizerAction { + case waitForDoubleTap + case waitForSingleTap + case waitForHold(timeout: Double, acceptTap: Bool) + case fail +} + +public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + private var touchLocationAndTimestamp: (CGPoint, Double)? + private var touchCount: Int = 0 + private var tapCount: Int = 0 + + private var timer: Foundation.Timer? + private(set) var lastRecognizedGestureAndLocation: (TapLongTapOrDoubleTapGesture, CGPoint)? + + var tapActionAtPoint: ((CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction)? + var highlight: ((CGPoint?) -> Void)? + + var hapticFeedback: HapticFeedback? + + private var highlightPoint: CGPoint? + + override public init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.delegate = self + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPanGestureRecognizer { + return false + } + return false + } + + override public func reset() { + self.timer?.invalidate() + self.timer = nil + self.touchLocationAndTimestamp = nil + self.tapCount = 0 + self.touchCount = 0 + self.hapticFeedback = nil + + if self.highlightPoint != nil { + self.highlightPoint = nil + self.highlight?(nil) + } + + super.reset() + } + + fileprivate func longTapEvent() { + self.timer?.invalidate() + self.timer = nil + if let (location, _) = self.touchLocationAndTimestamp { + self.lastRecognizedGestureAndLocation = (.longTap, location) + } else { + self.lastRecognizedGestureAndLocation = nil + } + self.state = .ended + } + + fileprivate func tapEvent() { + self.timer?.invalidate() + self.timer = nil + if let (location, _) = self.touchLocationAndTimestamp { + self.lastRecognizedGestureAndLocation = (.tap, location) + } else { + self.lastRecognizedGestureAndLocation = nil + } + self.state = .ended + } + + fileprivate func holdEvent() { + self.timer?.invalidate() + self.timer = nil + if let (location, _) = self.touchLocationAndTimestamp { + self.hapticFeedback?.tap() + self.lastRecognizedGestureAndLocation = (.hold, location) + } else { + self.lastRecognizedGestureAndLocation = nil + } + self.state = .began + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { + self.lastRecognizedGestureAndLocation = nil + + super.touchesBegan(touches, with: event) + + self.touchCount += touches.count + + if let touch = touches.first { + let touchLocation = touch.location(in: self.view) + + if self.highlightPoint != touchLocation { + self.highlightPoint = touchLocation + self.highlight?(touchLocation) + } + + if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton { + self.state = .failed + return + } + + self.tapCount += 1 + if self.tapCount == 2 && self.touchCount == 1 { + self.timer?.invalidate() + self.timer = nil + self.lastRecognizedGestureAndLocation = (.doubleTap, self.location(in: self.view)) + self.state = .ended + } else { + let touchLocationAndTimestamp = (touch.location(in: self.view), CACurrentMediaTime()) + self.touchLocationAndTimestamp = touchLocationAndTimestamp + + var tapAction: TapLongTapOrDoubleTapGestureRecognizerAction = .waitForDoubleTap + if let tapActionAtPoint = self.tapActionAtPoint { + tapAction = tapActionAtPoint(touchLocationAndTimestamp.0) + } + + switch tapAction { + case .waitForSingleTap, .waitForDoubleTap: + self.timer?.invalidate() + let timer = Timer(timeInterval: 0.3, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.longTapEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + case let .waitForHold(timeout, _): + self.hapticFeedback = HapticFeedback() + self.hapticFeedback?.prepareTap() + let timer = Timer(timeInterval: timeout, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.holdEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + case .fail: + self.state = .failed + } + } + } + } + + override public func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + guard let touch = touches.first else { + return + } + + if let (gesture, _) = self.lastRecognizedGestureAndLocation, case .hold = gesture { + let location = touch.location(in: self.view) + self.lastRecognizedGestureAndLocation = (.hold, location) + self.state = .changed + return + } + + if let touch = touches.first, let (touchLocation, _) = self.touchLocationAndTimestamp { + let location = touch.location(in: self.view) + let distance = CGPoint(x: location.x - touchLocation.x, y: location.y - touchLocation.y) + if distance.x * distance.x + distance.y * distance.y > 4.0 { + self.state = .cancelled + } + } + } + + override public func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.touchCount -= touches.count + + if self.highlightPoint != nil { + self.highlightPoint = nil + self.highlight?(nil) + } + + self.timer?.invalidate() + + if let (gesture, location) = self.lastRecognizedGestureAndLocation, case .hold = gesture { + self.lastRecognizedGestureAndLocation = (.hold, location) + self.state = .ended + return + } + + if self.tapCount == 1 { + var tapAction: TapLongTapOrDoubleTapGestureRecognizerAction = .waitForDoubleTap + if let tapActionAtPoint = self.tapActionAtPoint, let (touchLocation, _) = self.touchLocationAndTimestamp { + tapAction = tapActionAtPoint(touchLocation) + } + + switch tapAction { + case .waitForSingleTap: + if let (touchLocation, _) = self.touchLocationAndTimestamp { + self.lastRecognizedGestureAndLocation = (.tap, touchLocation) + } + self.state = .ended + case .waitForDoubleTap: + self.state = .began + let timer = Timer(timeInterval: 0.2, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.tapEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + case let .waitForHold(_, acceptTap): + if let (touchLocation, _) = self.touchLocationAndTimestamp, acceptTap { + if self.state != .began { + self.lastRecognizedGestureAndLocation = (.tap, touchLocation) + self.state = .began + } + } + self.state = .ended + case .fail: + self.state = .failed + } + } + } + + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.touchCount -= touches.count + + if self.highlightPoint != nil { + self.highlightPoint = nil + self.highlight?(nil) + } + + self.state = .cancelled + } +} diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index ca828047a3..10ba66edcc 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -41,7 +41,7 @@ public final class TooltipController: ViewController { self.text = text self.timeout = timeout - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { @@ -54,10 +54,7 @@ public final class TooltipController: ViewController { open override func loadDisplayNode() { self.displayNode = TooltipControllerNode(text: self.text, dismiss: { [weak self] in - self?.dismissed?() - self?.controllerNode.animateOut { [weak self] in - self?.presentingViewController?.dismiss(animated: false) - } + self?.dismiss() }) self.displayNodeDidLoad() } @@ -111,4 +108,12 @@ public final class TooltipController: ViewController { timeoutTimer.start() } } + + override public func dismiss(completion: (() -> Void)? = nil) { + self.dismissed?() + self.controllerNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + completion?() + } + } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 4c12b141c1..0dffe12438 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -149,10 +149,10 @@ open class ViewControllerPresentationArguments { } } - public init(navigationBarTheme: NavigationBarTheme?) { + public init(navigationBarPresentationData: NavigationBarPresentationData?) { self.statusBar = StatusBar() - if let navigationBarTheme = navigationBarTheme { - self.navigationBar = NavigationBar(theme: navigationBarTheme) + if let navigationBarPresentationData = navigationBarPresentationData { + self.navigationBar = NavigationBar(presentationData: navigationBarPresentationData) } else { self.navigationBar = nil } @@ -218,6 +218,9 @@ open class ViewControllerPresentationArguments { } } + open func navigationStackConfigurationUpdated(next: [ViewController]) { + } + open override func loadView() { self.view = self.displayNode.view if let navigationBar = self.navigationBar { @@ -237,7 +240,7 @@ open class ViewControllerPresentationArguments { open func displayNodeDidLoad() { if let layer = self.displayNode.layer as? CATracingLayer { - layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard)) + layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard, disableChildrenTracingTags: 0)) } self.updateScrollToTopView() } @@ -248,9 +251,9 @@ open class ViewControllerPresentationArguments { } } - public func setDisplayNavigationBar(_ displayNavigtionBar: Bool, transition: ContainedViewLayoutTransition = .immediate) { - if displayNavigtionBar != self.displayNavigationBar { - self.displayNavigationBar = displayNavigtionBar + public func setDisplayNavigationBar(_ displayNavigationBar: Bool, transition: ContainedViewLayoutTransition = .immediate) { + if displayNavigationBar != self.displayNavigationBar { + self.displayNavigationBar = displayNavigationBar if let parent = self.parent as? TabBarController { if parent.currentController === self { parent.displayNavigationBar = displayNavigationBar @@ -346,4 +349,15 @@ open class ViewControllerPresentationArguments { super.unregisterForPreviewing(withContext: previewing) } } + + public final func navigationNextSibling() -> UIViewController? { + if let navigationController = self.navigationController as? NavigationController { + if let index = navigationController.viewControllers.index(where: { $0 === self }) { + if index != navigationController.viewControllers.count - 1 { + return navigationController.viewControllers[index + 1] + } + } + } + return nil + } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 586f3820c2..e77c611266 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -296,8 +296,8 @@ public final class WindowHostView { } public struct WindowTracingTags { - public static let statusBar: Int32 = 0 - public static let keyboard: Int32 = 1 + public static let statusBar: Int32 = 1 << 0 + public static let keyboard: Int32 = 1 << 1 } public protocol WindowHost { @@ -309,7 +309,11 @@ public protocol WindowHost { } private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { - return LayoutMetrics(widthClass: .compact, heightClass: .compact) + if size.width > 690.0 && size.height > 690.0 { + return LayoutMetrics(widthClass: .regular, heightClass: .regular) + } else { + return LayoutMetrics(widthClass: .compact, heightClass: .compact) + } } private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { @@ -465,14 +469,14 @@ public class Window1 { let screenHeight: CGFloat - if true || !UIScreen.main.bounds.width.isEqual(to: strongSelf.windowLayout.size.width) { - if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) { + if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) { + if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 { screenHeight = UIScreen.main.bounds.height } else { - screenHeight = UIScreen.main.bounds.width + screenHeight = strongSelf.windowLayout.size.height } } else { - screenHeight = UIScreen.main.bounds.height + screenHeight = UIScreen.main.bounds.width } let keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY) From a85bfb20331d15dcf30476b0936b83174971aa77 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 11 Apr 2018 23:41:52 +0400 Subject: [PATCH 054/245] no message --- Display.xcodeproj/project.pbxproj | 4 + Display/ContainerViewLayout.swift | 56 ---------- Display/ContextMenuContainerNode.swift | 4 - Display/GridNode.swift | 27 +---- Display/ListViewIntermediateState.swift | 35 ------ Display/StatusBarManager.swift | 66 +++++++++++- Display/TextNode.swift | 4 - Display/VolumeControlStatusBar.swift | 136 ++++++++++++++++++++++++ Display/WindowContent.swift | 81 ++++++-------- DisplayMac/UIKit.swift | 16 --- 10 files changed, 235 insertions(+), 194 deletions(-) create mode 100644 Display/VolumeControlStatusBar.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index ad08084c1c..6ba682acd3 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -112,6 +112,7 @@ D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; D06B76DB20592A97006E9EEA /* LayoutSizes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06B76DA20592A97006E9EEA /* LayoutSizes.swift */; }; + D06D37A220779C82009219B6 /* VolumeControlStatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06D37A120779C82009219B6 /* VolumeControlStatusBar.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D077B8E81F4637040046D27A /* NavigationBarBadge.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; @@ -286,6 +287,7 @@ D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; D06B76DA20592A97006E9EEA /* LayoutSizes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutSizes.swift; sourceTree = ""; }; + D06D37A120779C82009219B6 /* VolumeControlStatusBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VolumeControlStatusBar.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D077B8E81F4637040046D27A /* NavigationBarBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarBadge.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; @@ -669,6 +671,7 @@ D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */, D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */, D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */, + D06D37A120779C82009219B6 /* VolumeControlStatusBar.swift */, ); name = "Status Bar"; sourceTree = ""; @@ -984,6 +987,7 @@ D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */, D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */, D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, + D06D37A220779C82009219B6 /* VolumeControlStatusBar.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */, diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 50c384972a..51b6ae322d 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -32,10 +32,6 @@ public struct LayoutMetrics: Equatable { self.widthClass = .compact self.heightClass = .compact } - - public static func ==(lhs: LayoutMetrics, rhs: LayoutMetrics) -> Bool { - return lhs.widthClass == rhs.widthClass && lhs.heightClass == rhs.heightClass - } } public struct ContainerViewLayout: Equatable { @@ -81,56 +77,4 @@ public struct ContainerViewLayout: Equatable { public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout { return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) } - - public static func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { - if !lhs.size.equalTo(rhs.size) { - return false - } - - if lhs.metrics != rhs.metrics { - return false - } - - if lhs.intrinsicInsets != rhs.intrinsicInsets { - return false - } - - if lhs.safeInsets != rhs.safeInsets { - return false - } - - if let lhsStatusBarHeight = lhs.statusBarHeight { - if let rhsStatusBarHeight = rhs.statusBarHeight { - if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { - return false - } - } else { - return false - } - } else if let _ = rhs.statusBarHeight { - return false - } - - if let lhsInputHeight = lhs.inputHeight { - if let rhsInputHeight = rhs.inputHeight { - if !lhsInputHeight.isEqual(to: rhsInputHeight) { - return false - } - } else { - return false - } - } else if let _ = rhs.inputHeight { - return false - } - - if !lhs.standardInputHeight.isEqual(to: rhs.standardInputHeight) { - return false - } - - if lhs.inputHeightIsInteractivellyChanging != rhs.inputHeightIsInteractivellyChanging { - return false - } - - return true - } } diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift index 39abfa5ee3..3ba11b0d10 100644 --- a/Display/ContextMenuContainerNode.swift +++ b/Display/ContextMenuContainerNode.swift @@ -7,10 +7,6 @@ private struct CachedMaskParams: Equatable { let arrowOnBottom: Bool } -private func ==(lhs: CachedMaskParams, rhs: CachedMaskParams) -> Bool { - return lhs.size.equalTo(rhs.size) && lhs.relativeArrowPosition.isEqual(to: rhs.relativeArrowPosition) && lhs.arrowOnBottom == rhs.arrowOnBottom -} - private final class ContextMenuContainerMaskView: UIView { override class var layerClass: AnyClass { return CAShapeLayer.self diff --git a/Display/GridNode.swift b/Display/GridNode.swift index e9368db585..290c0f9aa8 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -53,23 +53,6 @@ public struct GridNodeScrollToItem { public enum GridNodeLayoutType: Equatable { case fixed(itemSize: CGSize, lineSpacing: CGFloat) case balanced(idealHeight: CGFloat) - - public static func ==(lhs: GridNodeLayoutType, rhs: GridNodeLayoutType) -> Bool { - switch lhs { - case let .fixed(itemSize, lineSpacing): - if case .fixed(itemSize, lineSpacing) = rhs { - return true - } else { - return false - } - case let .balanced(idealHeight): - if case .balanced(idealHeight) = rhs { - return true - } else { - return false - } - } - } } public struct GridNodeLayout: Equatable { @@ -86,10 +69,6 @@ public struct GridNodeLayout: Equatable { self.preloadSize = preloadSize self.type = type } - - public static func ==(lhs: GridNodeLayout, rhs: GridNodeLayout) -> Bool { - return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.type == rhs.type - } } public struct GridNodeUpdateLayout { @@ -180,13 +159,9 @@ private final class GridNodeItemLayout { public struct GridNodeDisplayedItemRange: Equatable { public let loadedRange: Range? public let visibleRange: Range? - - public static func ==(lhs: GridNodeDisplayedItemRange, rhs: GridNodeDisplayedItemRange) -> Bool { - return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange - } } -private struct WrappedGridSection: Hashable { +private struct WrappedGridSection: Equatable, Hashable { let section: GridSection init(_ section: GridSection) { diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 966fa3a1ef..c8ed85bdd5 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -14,29 +14,6 @@ public enum ListViewScrollPosition: Equatable { case top(CGFloat) case bottom(CGFloat) case center(ListViewCenterScrollPositionOverflow) - - public static func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { - switch lhs { - case let .top(offset): - if case .top(offset) = rhs { - return true - } else { - return false - } - case let .bottom(offset): - if case .bottom(offset) = rhs { - return true - } else { - return false - } - case let .center(overflow): - if case .center(overflow) = rhs { - return true - } else { - return false - } - } - } } public enum ListViewScrollToItemDirectionHint { @@ -147,20 +124,12 @@ public struct ListViewUpdateSizeAndInsets { public struct ListViewItemRange: Equatable { public let firstIndex: Int public let lastIndex: Int - - public static func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { - return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex - } } public struct ListViewVisibleItemRange: Equatable { public let firstIndex: Int public let firstIndexFullyVisible: Bool public let lastIndex: Int - - public static func ==(lhs: ListViewVisibleItemRange, rhs: ListViewVisibleItemRange) -> Bool { - return lhs.firstIndex == rhs.firstIndex && lhs.firstIndexFullyVisible == rhs.firstIndexFullyVisible && lhs.lastIndex == rhs.lastIndex - } } public struct ListViewDisplayedItemRange: Equatable { @@ -168,10 +137,6 @@ public struct ListViewDisplayedItemRange: Equatable { public let visibleRange: ListViewVisibleItemRange? } -public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { - return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange -} - struct IndexRange { let first: Int let last: Int diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 5546e940e2..a8fcf6f8a6 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -1,5 +1,6 @@ import Foundation import AsyncDisplayKit +import SwiftSignalKit private struct MappedStatusBar { let style: StatusBarStyle @@ -70,27 +71,79 @@ private func displayHiddenAnimation() -> CAAnimation { class StatusBarManager { private var host: StatusBarHost + private let volumeControlStatusBar: VolumeControlStatusBar + private let volumeControlStatusBarNode: VolumeControlStatusBarNode private var surfaces: [StatusBarSurface] = [] + private var validParams: (withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool)? var inCallNavigate: (() -> Void)? - init(host: StatusBarHost) { + private var volumeTimer: SwiftSignalKit.Timer? + + init(host: StatusBarHost, volumeControlStatusBar: VolumeControlStatusBar, volumeControlStatusBarNode: VolumeControlStatusBarNode) { self.host = host + self.volumeControlStatusBar = volumeControlStatusBar + self.volumeControlStatusBarNode = volumeControlStatusBarNode + self.volumeControlStatusBarNode.isHidden = true + + self.volumeControlStatusBar.valueChanged = { [weak self] previous, updated in + if let strongSelf = self { + strongSelf.startVolumeTimer() + strongSelf.volumeControlStatusBarNode.updateValue(from: CGFloat(previous), to: CGFloat(updated)) + } + } + } + + private func startVolumeTimer() { + self.volumeTimer?.invalidate() + let timer = SwiftSignalKit.Timer(timeout: 2.0, repeat: false, completion: { [weak self] in + self?.endVolumeTimer() + }, queue: Queue.mainQueue()) + self.volumeTimer = timer + timer.start() + if self.volumeControlStatusBarNode.isHidden { + self.volumeControlStatusBarNode.isHidden = false + self.volumeControlStatusBarNode.alpha = 1.0 + self.volumeControlStatusBarNode.allowsGroupOpacity = true + self.volumeControlStatusBarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2, delay: 0.18, completion: { [weak self] _ in + self?.volumeControlStatusBarNode.allowsGroupOpacity = false + }) + } + if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams { + self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut)) + } + } + + private func endVolumeTimer() { + self.volumeControlStatusBarNode.alpha = 0.0 + self.volumeControlStatusBarNode.allowsGroupOpacity = true + self.volumeControlStatusBarNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.15, completion: { [weak self] completed in + if let strongSelf = self, completed { + strongSelf.volumeControlStatusBarNode.isHidden = true + strongSelf.volumeControlStatusBarNode.allowsGroupOpacity = false + } + }) + self.volumeTimer = nil + if let (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) = self.validParams { + self.updateSurfaces(self.surfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: false, alphaTransition: .animated(duration: 0.2, curve: .easeInOut)) + } } func updateState(surfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { let previousSurfaces = self.surfaces self.surfaces = surfaces - self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated) + self.updateSurfaces(previousSurfaces, withSafeInsets: withSafeInsets, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated, alphaTransition: .immediate) } - private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) { + private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], withSafeInsets: Bool, forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool, alphaTransition: ContainedViewLayoutTransition) { let statusBarFrame = self.host.statusBarFrame guard let statusBarView = self.host.statusBarView else { return } + self.validParams = (withSafeInsets, forceInCallStatusBarText, forceHiddenBySystemWindows) + if self.host.statusBarWindow?.isUserInteractionEnabled != (forceInCallStatusBarText == nil) { self.host.statusBarWindow?.isUserInteractionEnabled = (forceInCallStatusBarText == nil) } @@ -215,6 +268,11 @@ class StatusBarManager { statusBar.updateState(statusBar: statusBarView, withSafeInsets: withSafeInsets, inCallText: forceInCallStatusBarText, animated: animated) } + if self.volumeTimer != nil { + globalStatusBar?.1 = 0.0 + } + self.volumeControlStatusBarNode.isDark = globalStatusBar?.0.systemStyle == UIStatusBarStyle.lightContent + if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { let statusBarStyle: UIStatusBarStyle if forceInCallStatusBarText != nil { @@ -226,7 +284,7 @@ class StatusBarManager { self.host.statusBarStyle = statusBarStyle } if let statusBarWindow = self.host.statusBarWindow { - statusBarView.alpha = globalStatusBar.1 + alphaTransition.updateAlpha(layer: statusBarView.layer, alpha: globalStatusBar.1) var statusBarBounds = statusBarWindow.bounds if !statusBarBounds.origin.y.isEqual(to: globalStatusBar.2) { statusBarBounds.origin.y = globalStatusBar.2 diff --git a/Display/TextNode.swift b/Display/TextNode.swift index cb6291687b..1699232272 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -28,10 +28,6 @@ public struct TextNodeCutout: Equatable { self.position = position self.size = size } - - public static func ==(lhs: TextNodeCutout, rhs: TextNodeCutout) -> Bool { - return lhs.position == rhs.position && lhs.size == rhs.size - } } public final class TextNodeLayoutArguments { diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift new file mode 100644 index 0000000000..5b55afb5c5 --- /dev/null +++ b/Display/VolumeControlStatusBar.swift @@ -0,0 +1,136 @@ +import Foundation +import UIKit +import AsyncDisplayKit +import MediaPlayer + +private let volumeNotificationKey = "AVSystemController_SystemVolumeDidChangeNotification" +private let volumeParameterKey = "AVSystemController_AudioVolumeNotificationParameter" + +final class VolumeControlStatusBar: UIView { + private let control: MPVolumeView + private var observer: Any? + private var currentValue: Float + + var valueChanged: ((Float, Float) -> Void)? + + override init(frame: CGRect) { + self.control = MPVolumeView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 20.0))) + self.currentValue = AVAudioSession.sharedInstance().outputVolume + + super.init(frame: frame) + + self.addSubview(self.control) + self.observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: volumeNotificationKey), object: nil, queue: OperationQueue.main, using: { [weak self] notification in + if let strongSelf = self { + if let volume = notification.userInfo?[volumeParameterKey] as? Float { + let previous = strongSelf.currentValue + strongSelf.currentValue = volume + strongSelf.valueChanged?(previous, volume) + } + } + }) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + if let observer = self.observer { + NotificationCenter.default.removeObserver(observer) + } + } +} + +final class VolumeControlStatusBarNode: ASDisplayNode { + private let backgroundNode: ASImageNode + private let foregroundNode: ASImageNode + private let foregroundClippingNode: ASDisplayNode + + private var validLayout: ContainerViewLayout? + + var isDark: Bool = false { + didSet { + if self.isDark != oldValue { + if self.isDark { + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) + self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .white) + } else { + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) + self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) + } + } + } + } + private var value: CGFloat = 1.0 + + override init() { + self.backgroundNode = ASImageNode() + self.backgroundNode.isLayerBacked = true + self.backgroundNode.displaysAsynchronously = false + self.backgroundNode.displayWithoutProcessing = true + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .gray) + + self.foregroundNode = ASImageNode() + self.foregroundNode.isLayerBacked = true + self.foregroundNode.displaysAsynchronously = false + self.foregroundNode.displayWithoutProcessing = true + self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) + + self.foregroundClippingNode = ASDisplayNode() + self.foregroundClippingNode.clipsToBounds = true + self.foregroundClippingNode.addSubnode(self.foregroundNode) + + super.init() + + self.isUserInteractionEnabled = false + + self.addSubnode(self.backgroundNode) + self.addSubnode(self.foregroundClippingNode) + } + + func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.validLayout = layout + + let barHeight: CGFloat = 4.0 + let barWidth: CGFloat + + let statusBarHeight: CGFloat + let sideInset: CGFloat + if let actual = layout.statusBarHeight { + statusBarHeight = actual + } else { + statusBarHeight = 20.0 + } + if layout.safeInsets.left.isZero && layout.safeInsets.top.isZero && layout.intrinsicInsets.left.isZero && layout.intrinsicInsets.top.isZero { + sideInset = 4.0 + } else { + sideInset = 12.0 + } + + if !layout.intrinsicInsets.bottom.isZero { + barWidth = 92.0 - sideInset * 2.0 + } else { + barWidth = layout.size.width - sideInset * 2.0 + } + + let boundingRect = CGRect(origin: CGPoint(x: sideInset, y: floor((statusBarHeight - barHeight) / 2.0)), size: CGSize(width: barWidth, height: barHeight)) + + transition.updateFrame(node: self.backgroundNode, frame: boundingRect) + transition.updateFrame(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: boundingRect.size)) + transition.updateFrame(node: self.foregroundClippingNode, frame: CGRect(origin: boundingRect.origin, size: CGSize(width: self.value * boundingRect.width, height: boundingRect.height))) + } + + func updateValue(from fromValue: CGFloat, to toValue: CGFloat) { + if let layout = self.validLayout { + if self.foregroundClippingNode.layer.animation(forKey: "bounds") == nil { + self.value = fromValue + self.updateLayout(layout: layout, transition: .immediate) + } + self.value = toValue + self.updateLayout(layout: layout, transition: .animated(duration: 0.25, curve: .spring)) + } else { + self.value = toValue + } + } +} diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index e77c611266..1803920f76 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -23,50 +23,6 @@ private struct WindowLayout: Equatable { let safeInsets: UIEdgeInsets let onScreenNavigationHeight: CGFloat? let upperKeyboardInputPositionBound: CGFloat? - - static func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool { - if !lhs.size.equalTo(rhs.size) { - return false - } - - if let lhsStatusBarHeight = lhs.statusBarHeight { - if let rhsStatusBarHeight = rhs.statusBarHeight { - if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { - return false - } - } else { - return false - } - } else if let _ = rhs.statusBarHeight { - return false - } - - if lhs.forceInCallStatusBarText != rhs.forceInCallStatusBarText { - return false - } - - if let lhsInputHeight = lhs.inputHeight, let rhsInputHeight = rhs.inputHeight { - if !lhsInputHeight.isEqual(to: rhsInputHeight) { - return false - } - } else if (lhs.inputHeight != nil) != (rhs.inputHeight != nil) { - return false - } - - if lhs.safeInsets != rhs.safeInsets { - return false - } - - if lhs.onScreenNavigationHeight != rhs.onScreenNavigationHeight { - return false - } - - if lhs.upperKeyboardInputPositionBound != rhs.upperKeyboardInputPositionBound { - return false - } - - return true - } } private struct UpdatingLayout { @@ -337,6 +293,16 @@ private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecogn } } +public final class StatusBarVolumeColors { + public let background: UIColor + public let foreground: UIColor + + public init(background: UIColor, foreground: UIColor) { + self.background = background + self.foreground = foreground + } +} + public class Window1 { public let hostView: WindowHostView @@ -365,6 +331,7 @@ public class Window1 { public var previewThemeAccentColor: UIColor = .blue public var previewThemeDarkBlur: Bool = false + public var statusBarVolumeColors: StatusBarVolumeColors = StatusBarVolumeColors(background: .lightGray, foreground: .black) public private(set) var forceInCallStatusBarText: String? = nil public var inCallNavigate: (() -> Void)? { @@ -380,13 +347,19 @@ public class Window1 { private var keyboardTypeChangeTimer: SwiftSignalKit.Timer? + private let volumeControlStatusBar: VolumeControlStatusBar + private let volumeControlStatusBarNode: VolumeControlStatusBarNode + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView + self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0))) + self.volumeControlStatusBarNode = VolumeControlStatusBarNode() + self.statusBarHost = statusBarHost let statusBarHeight: CGFloat if let statusBarHost = statusBarHost { - self.statusBarManager = StatusBarManager(host: statusBarHost) + self.statusBarManager = StatusBarManager(host: statusBarHost, volumeControlStatusBar: self.volumeControlStatusBar, volumeControlStatusBarNode: self.volumeControlStatusBarNode) statusBarHeight = statusBarHost.statusBarFrame.size.height self.keyboardManager = KeyboardManager(host: statusBarHost) } else { @@ -403,6 +376,7 @@ public class Window1 { } self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) + self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) self.presentationContext = PresentationContext() self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost) @@ -541,6 +515,9 @@ public class Window1 { } self.windowPanRecognizer = recognizer self.hostView.view.addGestureRecognizer(recognizer) + + self.hostView.view.addSubview(self.volumeControlStatusBar) + self.hostView.view.addSubview(self.volumeControlStatusBarNode.view) } public required init(coder aDecoder: NSCoder) { @@ -653,7 +630,7 @@ public class Window1 { if let coveringView = self.coveringView { self.hostView.view.insertSubview(rootController.view, belowSubview: coveringView) } else { - self.hostView.view.addSubview(rootController.view) + self.hostView.view.insertSubview(rootController.view, belowSubview: self.volumeControlStatusBarNode.view) } } } @@ -676,7 +653,7 @@ public class Window1 { if let coveringView = self.coveringView { self.hostView.view.insertSubview(controller.view, belowSubview: coveringView) } else { - self.hostView.view.addSubview(controller.view) + self.hostView.view.insertSubview(controller.view, belowSubview: self.volumeControlStatusBarNode.view) } } @@ -689,7 +666,7 @@ public class Window1 { if self.coveringView !== oldValue { oldValue?.removeFromSuperview() if let coveringView = self.coveringView { - self.hostView.view.addSubview(coveringView) + self.hostView.view.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view) if !self.windowLayout.size.width.isZero { coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) coveringView.updateLayout(self.windowLayout.size) @@ -813,10 +790,13 @@ public class Window1 { } } + private var isFirstLayout = true + private func commitUpdatingLayout() { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil - if updatingLayout.layout != self.windowLayout { + if updatingLayout.layout != self.windowLayout || self.isFirstLayout { + self.isFirstLayout = false var statusBarHeight: CGFloat? if let statusBarHost = self.statusBarHost { statusBarHeight = statusBarHost.statusBarFrame.size.height @@ -864,6 +844,9 @@ public class Window1 { }) } + self.volumeControlStatusBarNode.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) + self.volumeControlStatusBarNode.updateLayout(layout: childLayout, transition: updatingLayout.transition) + if let coveringView = self.coveringView { coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) coveringView.updateLayout(self.windowLayout.size) diff --git a/DisplayMac/UIKit.swift b/DisplayMac/UIKit.swift index d080662bc6..09f6956cce 100644 --- a/DisplayMac/UIKit.swift +++ b/DisplayMac/UIKit.swift @@ -20,22 +20,6 @@ public struct UIEdgeInsets: Equatable { self.bottom = bottom self.right = right } - - public static func ==(lhs: UIEdgeInsets, rhs: UIEdgeInsets) -> Bool { - if !lhs.top.isEqual(to: rhs.top) { - return false - } - if !lhs.left.isEqual(to: rhs.left) { - return false - } - if !lhs.bottom.isEqual(to: rhs.bottom) { - return false - } - if !lhs.right.isEqual(to: rhs.right) { - return false - } - return true - } } public final class UIColor: NSObject { From eb412cdc3dada2c66f3971c72c8585a0e1890713 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Sat, 21 Apr 2018 00:17:49 +0400 Subject: [PATCH 055/245] no message --- Display/CAAnimationUtils.swift | 8 ++++---- Display/GridNode.swift | 29 ++++++++++++++++------------- Display/ImmediateTextNode.swift | 3 ++- Display/NavigationBar.swift | 6 ++++++ Display/TabBarController.swift | 1 + Display/UINavigationItem+Proxy.h | 1 + Display/UINavigationItem+Proxy.m | 4 ++++ Display/WindowContent.swift | 11 ----------- 8 files changed, 34 insertions(+), 29 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 140dc121e6..1aa1688315 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -202,18 +202,18 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) } - public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + public func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) } - func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { completion(true) } return } - self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 290c0f9aa8..2dab6c4d8b 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -210,10 +210,13 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? + public var scrollingInitiated: (() -> Void)? public var scrollingCompleted: (() -> Void)? public final var floatingSections = false + public final var initialOffset: CGFloat = 0.0 + public var showVerticalScrollIndicator: Bool = false { didSet { self.scrollView.showsVerticalScrollIndicator = self.showVerticalScrollIndicator @@ -340,6 +343,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.updateItemNodeVisibilititesAndScrolling() + self.scrollingInitiated?() } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { @@ -403,11 +407,6 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var previousSection: GridSection? for item in self.items { var itemSize = defaultItemSize - if let height = item.fillsRowWithHeight { - nextItemOrigin.x = 0.0 - itemSize.width = gridLayout.size.width - itemSize.height = height - } let section = item.section var keepSection = true @@ -432,17 +431,21 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } previousSection = section - if !incrementedCurrentRow { - incrementedCurrentRow = true - contentSize.height += itemSize.height + lineSpacing - } - - if index == 0 { + if let height = item.fillsRowWithHeight { + nextItemOrigin.x = 0.0 + itemSize.width = gridLayout.size.width + itemSize.height = height + } else if index == 0 { let itemsInRow = max(1, Int(effectiveWidth) / Int(itemSize.width)) let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) } + if !incrementedCurrentRow { + incrementedCurrentRow = true + contentSize.height += itemSize.height + lineSpacing + } + items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: itemSize))) index += 1 @@ -557,7 +560,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if self.itemLayout.items.isEmpty { transitionDirectionHint = scrollToItem.directionHint transition = scrollToItem.transition - contentOffset = CGPoint(x: 0.0, y: -self.gridLayout.insets.top) + contentOffset = CGPoint(x: 0.0, y: -self.gridLayout.insets.top + self.initialOffset) } else { let itemFrame = self.itemLayout.items[scrollToItem.index] @@ -585,7 +588,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } if scrollToItem.adjustForTopInset { - additionalOffset += -gridLayout.insets.top + additionalOffset += -gridLayout.insets.top + self.initialOffset } } else if scrollToItem.adjustForTopInset { additionalOffset = -gridLayout.insets.top diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index 842eb7a01d..b6c2cdbc16 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -3,6 +3,7 @@ import Foundation public class ImmediateTextNode: TextNode { public var attributedText: NSAttributedString? public var textAlignment: NSTextAlignment = .natural + public var truncationType: CTLineTruncationType = .end public var maximumNumberOfLines: Int = 1 public var lineSpacing: CGFloat = 0.0 public var insets: UIEdgeInsets = UIEdgeInsets() @@ -24,7 +25,7 @@ public class ImmediateTextNode: TextNode { public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self) - let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) let _ = apply() return layout.size } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 602098ce4d..905c1de1e5 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -271,6 +271,8 @@ open class NavigationBar: ASDisplayNode { } } + public var layoutSuspended: Bool = false + private let titleNode: ASTextNode var previousItemListenerKey: Int? @@ -701,6 +703,10 @@ open class NavigationBar: ASDisplayNode { } func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + if self.layoutSuspended { + return + } + self.validLayout = (size, leftInset, rightInset) let leftButtonInset: CGFloat = leftInset + 16.0 diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 2117f752be..fec4d9e742 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -157,6 +157,7 @@ open class TabBarController: ViewController { self.addChildViewController(currentController) currentController.didMove(toParentViewController: self) + currentController.navigationBar?.layoutSuspended = true currentController.navigationItem.setTarget(self.navigationItem) displayNavigationBar = currentController.displayNavigationBar currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 5d607cfb47..8a891d8a2f 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -10,6 +10,7 @@ typedef void (^UITabBarItemSetBadgeListener)(NSString * _Nullable); @interface UINavigationItem (Proxy) - (void)setTargetItem:(UINavigationItem * _Nullable)targetItem; +- (BOOL)hasTargetItem; - (void)setTitle:(NSString * _Nullable)title animated:(bool)animated; diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index 2682e8adfb..f52fcfa669 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -175,6 +175,10 @@ static const void *badgeKey = &badgeKey; } } +- (BOOL)hasTargetItem { + return [self associatedObjectForKey:targetItemKey] != nil; +} + - (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener)listener { NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey]; diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 1803920f76..8f79c5605d 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -293,16 +293,6 @@ private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecogn } } -public final class StatusBarVolumeColors { - public let background: UIColor - public let foreground: UIColor - - public init(background: UIColor, foreground: UIColor) { - self.background = background - self.foreground = foreground - } -} - public class Window1 { public let hostView: WindowHostView @@ -331,7 +321,6 @@ public class Window1 { public var previewThemeAccentColor: UIColor = .blue public var previewThemeDarkBlur: Bool = false - public var statusBarVolumeColors: StatusBarVolumeColors = StatusBarVolumeColors(background: .lightGray, foreground: .black) public private(set) var forceInCallStatusBarText: String? = nil public var inCallNavigate: (() -> Void)? { From 3dfd2af198b1c0bd5ac39e383d7976db30f24558 Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 16 Jun 2018 20:00:34 +0300 Subject: [PATCH 056/245] no message --- .../xcschemes/xcschememanagement.plist | 2 +- Display/ActionSheetTextItem.swift | 4 +- Display/AlertController.swift | 6 +- Display/AlertControllerNode.swift | 10 +- Display/ContainerViewLayout.swift | 2 +- Display/HapticFeedback.swift | 12 ++ Display/ImmediateTextNode.swift | 7 +- Display/LinkHighlightingNode.swift | 1 - Display/ListView.swift | 48 ++++++-- Display/ListViewIntermediateState.swift | 8 +- Display/ListViewReorderingItemNode.swift | 1 + Display/NavigationController.swift | 7 +- Display/PeekControllerMenuItemNode.swift | 18 ++- Display/StatusBarHost.swift | 3 + Display/TabBarController.swift | 2 +- Display/TabBarNode.swift | 2 +- ...pLongTapOrDoubleTapGestureRecognizer.swift | 1 - Display/TextAlertController.swift | 111 ++++++++++++++---- Display/TextFieldNode.swift | 8 +- Display/TextNode.swift | 2 +- Display/TooltipController.swift | 43 +++++-- Display/TooltipControllerNode.swift | 9 +- Display/ViewController.swift | 5 + Display/VolumeControlStatusBar.swift | 24 +++- Display/WindowContent.swift | 4 +- 25 files changed, 276 insertions(+), 64 deletions(-) diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index e65b00a6be..17298f558b 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayMac.xcscheme orderHint - 24 + 25 DisplayTests.xcscheme diff --git a/Display/ActionSheetTextItem.swift b/Display/ActionSheetTextItem.swift index 6155bb1072..8d2d9dc63d 100644 --- a/Display/ActionSheetTextItem.swift +++ b/Display/ActionSheetTextItem.swift @@ -38,7 +38,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { self.label = ASTextNode() self.label.isLayerBacked = true - self.label.maximumNumberOfLines = 1 + self.label.maximumNumberOfLines = 0 self.label.displaysAsynchronously = false self.label.truncationMode = .byTruncatingTail @@ -51,7 +51,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { func setItem(_ item: ActionSheetTextItem) { self.item = item - self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor) + self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center) self.setNeedsLayout() } diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 2f467f6f3a..93c70a428d 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -28,10 +28,12 @@ open class AlertController: ViewController { private let theme: AlertControllerTheme private let contentNode: AlertContentNode + private let allowInputInset: Bool - public init(theme: AlertControllerTheme, contentNode: AlertContentNode) { + public init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool = true) { self.theme = theme self.contentNode = contentNode + self.allowInputInset = allowInputInset super.init(navigationBarPresentationData: nil) @@ -43,7 +45,7 @@ open class AlertController: ViewController { } override open func loadDisplayNode() { - self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme) + self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme, allowInputInset: self.allowInputInset) self.displayNodeDidLoad() self.controllerNode.dismiss = { [weak self] in diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 65ca70aacc..387c815d0c 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -6,10 +6,12 @@ final class AlertControllerNode: ASDisplayNode { private let containerNode: ASDisplayNode private let effectNode: ASDisplayNode private let contentNode: AlertContentNode + private let allowInputInset: Bool var dismiss: (() -> Void)? - init(contentNode: AlertContentNode, theme: AlertControllerTheme) { + init(contentNode: AlertContentNode, theme: AlertControllerTheme, allowInputInset: Bool) { + self.allowInputInset = allowInputInset self.dimmingNode = ASDisplayNode() self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) @@ -58,7 +60,11 @@ final class AlertControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - var insets = layout.insets(options: [.statusBar, .input]) + var insetOptions: ContainerViewLayoutInsetOptions = [.statusBar] + if self.allowInputInset { + insetOptions.insert(.input) + } + var insets = layout.insets(options: insetOptions) let maxWidth = min(240.0, layout.size.width - 70.0) insets.left = floor((layout.size.width - maxWidth) / 2.0) insets.right = floor((layout.size.width - maxWidth) / 2.0) diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 51b6ae322d..06eeacaa4e 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -40,7 +40,7 @@ public struct ContainerViewLayout: Equatable { public let intrinsicInsets: UIEdgeInsets public let safeInsets: UIEdgeInsets public let statusBarHeight: CGFloat? - public let inputHeight: CGFloat? + public var inputHeight: CGFloat? public let standardInputHeight: CGFloat public let inputHeightIsInteractivellyChanging: Bool diff --git a/Display/HapticFeedback.swift b/Display/HapticFeedback.swift index 8255642ee8..281b263bba 100644 --- a/Display/HapticFeedback.swift +++ b/Display/HapticFeedback.swift @@ -27,6 +27,10 @@ private final class HapticFeedbackImpl { self.notificationGenerator.notificationOccurred(.success) } + func prepareError() { + self.notificationGenerator.prepare() + } + func error() { self.notificationGenerator.notificationOccurred(.error) } @@ -100,6 +104,14 @@ public final class HapticFeedback { } } + public func prepareError() { + if #available(iOSApplicationExtension 10.0, *) { + self.withImpl { impl in + impl.prepareError() + } + } + } + public func error() { if #available(iOSApplicationExtension 10.0, *) { self.withImpl { impl in diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index b6c2cdbc16..a9ffdf3f4c 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -22,6 +22,7 @@ public class ImmediateTextNode: TextNode { } public var tapAttributeAction: (([NSAttributedStringKey: Any]) -> Void)? + public var longTapAttributeAction: (([NSAttributedStringKey: Any]) -> Void)? public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self) @@ -61,7 +62,7 @@ public class ImmediateTextNode: TextNode { strongSelf.addSubnode(linkHighlightingNode) } linkHighlightingNode.frame = strongSelf.bounds - linkHighlightingNode.updateRects(rects.map { $0.offsetBy(dx: 0.0, dy: -3.0) }) + linkHighlightingNode.updateRects(rects.map { $0.offsetBy(dx: 0.0, dy: 0.0) }) } else if let linkHighlightingNode = strongSelf.linkHighlightingNode { strongSelf.linkHighlightingNode = nil linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in @@ -87,6 +88,10 @@ public class ImmediateTextNode: TextNode { if let (_, attributes) = self.attributesAtPoint(CGPoint(x: location.x, y: location.y)) { self.tapAttributeAction?(attributes) } + case .longTap: + if let (_, attributes) = self.attributesAtPoint(CGPoint(x: location.x, y: location.y)) { + self.longTapAttributeAction?(attributes) + } default: break } diff --git a/Display/LinkHighlightingNode.swift b/Display/LinkHighlightingNode.swift index df0420976f..3517fd4f66 100644 --- a/Display/LinkHighlightingNode.swift +++ b/Display/LinkHighlightingNode.swift @@ -1,6 +1,5 @@ import Foundation import AsyncDisplayKit -import Display private enum CornerType { case topLeft diff --git a/Display/ListView.swift b/Display/ListView.swift index 8974ca3b54..33cef91732 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -189,9 +189,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var displayedItemRangeChanged: (ListViewDisplayedItemRange, Any?) -> Void = { _, _ in } public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) - private final var opaqueTransactionState: Any? + public private(set) final var opaqueTransactionState: Any? public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } + public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var beganInteractiveDragging: () -> Void = { } public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in } @@ -400,7 +401,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var closestIndex: (Int, CGFloat)? for i in 0 ..< self.itemNodes.count { if let itemNodeIndex = self.itemNodes[i].index, itemNodeIndex != reorderItemIndex { - let itemOffset = self.itemNodes[i].frame.midY + let itemFrame = self.itemNodes[i].apparentContentFrame + let itemOffset = itemFrame.midY let deltaOffset = itemOffset - verticalOffset if let (_, closestOffset) = closestIndex { if abs(deltaOffset) < abs(closestOffset) { @@ -793,8 +795,26 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return offset } + public func visibleBottomContentOffset() -> ListViewVisibleContentOffset { + var offset: ListViewVisibleContentOffset = .unknown + var bottomItemIndexAndFrame: (Int, CGRect) = (-1, CGRect()) + for itemNode in self.itemNodes.reversed() { + if let index = itemNode.index { + bottomItemIndexAndFrame = (index, itemNode.apparentFrame) + break + } + } + if bottomItemIndexAndFrame.0 == self.items.count - 1 { + offset = .known(bottomItemIndexAndFrame.1.maxY - (self.visibleSize.height - self.insets.bottom)) + } else if bottomItemIndexAndFrame.0 == -1 { + offset = .none + } + return offset + } + private func updateVisibleContentOffset() { self.visibleContentOffsetChanged(self.visibleContentOffset()) + self.visibleBottomContentOffsetChanged(self.visibleBottomContentOffset()) } private func stopScrolling() { @@ -1188,11 +1208,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel widthUpdated = false } - if let scrollToItem = scrollToItem { - state.scrollPosition = (scrollToItem.index, scrollToItem.position) - } - state.fixScrollPostition(self.items.count) - let sortedDeleteIndices = deleteIndices.sorted(by: {$0.index < $1.index}) for deleteItem in sortedDeleteIndices.reversed() { self.items.remove(at: deleteItem.index) @@ -1227,6 +1242,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + if let scrollToItem = scrollToItem { + state.scrollPosition = (scrollToItem.index, scrollToItem.position) + } + let itemsCount = self.items.count + state.fixScrollPostition(itemsCount) + let actions = { var previousFrames: [Int: CGRect] = [:] for i in 0 ..< state.nodes.count { @@ -1371,6 +1392,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel state.setupStationaryOffset(index, boundary: boundary, frames: previousFrames) } + if let _ = scrollToItem { + state.fixScrollPostition(itemsCount) + } + if self.debugInfo { print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms") } @@ -3177,6 +3202,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return nil } + public func itemNodeAtIndex(_ index: Int) -> ListViewItemNode? { + for itemNode in self.itemNodes { + if itemNode.index == index { + return itemNode + } + } + return nil + } + public func forEachItemNode(_ f: (ASDisplayNode) -> Void) { for itemNode in self.itemNodes { if itemNode.index != nil { diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index c8ed85bdd5..9cef7ce086 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -506,8 +506,12 @@ struct ListViewState { if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat.ulpOfOne { var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat.ulpOfOne { - directionHint = ListViewInsertionOffsetDirection(hint) + if let hint = insertDirectionHints[currentUpperNode.index - 1] { + if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne { + directionHint = ListViewInsertionOffsetDirection(hint) + } + } else if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne { + directionHint = .Down } return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) diff --git a/Display/ListViewReorderingItemNode.swift b/Display/ListViewReorderingItemNode.swift index e1abb43424..536e73f84c 100644 --- a/Display/ListViewReorderingItemNode.swift +++ b/Display/ListViewReorderingItemNode.swift @@ -19,6 +19,7 @@ final class ListViewReorderingItemNode: ASDisplayNode { if let copyView = self.copyView { self.view.addSubview(copyView) copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: copyView.bounds.size) + copyView.bounds = itemNode.bounds } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index ac34943e2d..5514ad7bc9 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -315,6 +315,9 @@ open class NavigationController: UINavigationController, ContainableController, if isMaster, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { masterController = record.controller frame = firstControllerFrameAndLayout.0 + if let controller = masterController as? ViewController { + self.controllerView.sharedStatusBar.statusBarStyle = controller.statusBar.statusBarStyle + } } else { frame = lastControllerFrameAndLayout.0 } @@ -355,6 +358,7 @@ open class NavigationController: UINavigationController, ContainableController, animatedAppearingDetailController = true previousController.viewWillDisappear(true) + record.controller.viewWillAppear(true) record.controller.setIgnoreAppearanceMethodInvocations(true) self.controllerView.containerView.addSubview(record.controller.view) record.controller.setIgnoreAppearanceMethodInvocations(false) @@ -732,7 +736,8 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) if let validLayout = self.validLayout { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + var (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + controllerLayout.inputHeight = nil controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in diff --git a/Display/PeekControllerMenuItemNode.swift b/Display/PeekControllerMenuItemNode.swift index 324004fbe9..419fdc0bc1 100644 --- a/Display/PeekControllerMenuItemNode.swift +++ b/Display/PeekControllerMenuItemNode.swift @@ -6,14 +6,21 @@ public enum PeekControllerMenuItemColor { case destructive } +public enum PeekControllerMenuItemFont { + case `default` + case bold +} + public struct PeekControllerMenuItem { public let title: String public let color: PeekControllerMenuItemColor + public let font: PeekControllerMenuItemFont public let action: () -> Void - public init(title: String, color: PeekControllerMenuItemColor, action: @escaping () -> Void) { + public init(title: String, color: PeekControllerMenuItemColor, font: PeekControllerMenuItemFont = .default, action: @escaping () -> Void) { self.title = title self.color = color + self.font = font self.action = action } } @@ -44,13 +51,20 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { self.textNode.displaysAsynchronously = false let textColor: UIColor + let textFont: UIFont switch item.color { case .accent: textColor = theme.accentColor case .destructive: textColor = theme.destructiveColor } - self.textNode.attributedText = NSAttributedString(string: item.title, font: Font.regular(20.0), textColor: textColor) + switch item.font { + case .default: + textFont = Font.regular(20.0) + case .bold: + textFont = Font.semibold(20.0) + } + self.textNode.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor) super.init() diff --git a/Display/StatusBarHost.swift b/Display/StatusBarHost.swift index 4b79aa1845..35a7299d4e 100644 --- a/Display/StatusBarHost.swift +++ b/Display/StatusBarHost.swift @@ -1,4 +1,5 @@ import UIKit +import SwiftSignalKit public protocol StatusBarHost { var statusBarFrame: CGRect { get } @@ -8,4 +9,6 @@ public protocol StatusBarHost { var keyboardWindow: UIWindow? { get } var keyboardView: UIView? { get } + + var handleVolumeControl: Signal { get } } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index fec4d9e742..439722e0cb 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -52,7 +52,7 @@ open class TabBarController: ViewController { self.updateSelectedIndex() } else { if let controller = self.currentController { - controller.scrollToTop?() + controller.scrollToTopWithTabBar?() } } } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 36e91edae6..463ed3e057 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -351,7 +351,7 @@ class TabBarNode: ASDisplayNode { if horizontal { backgroundFrame = CGRect(origin: CGPoint(x: originX, y: 2.0), size: backgroundSize) } else { - backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) + backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 1.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) } transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame) container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) diff --git a/Display/TapLongTapOrDoubleTapGestureRecognizer.swift b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift index a8a5fce557..b2de010908 100644 --- a/Display/TapLongTapOrDoubleTapGestureRecognizer.swift +++ b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift @@ -1,6 +1,5 @@ import Foundation import UIKit.UIGestureRecognizerSubclass -import Display private class TapLongTapOrDoubleTapGestureRecognizerTimerTarget: NSObject { weak var target: TapLongTapOrDoubleTapGestureRecognizer? diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 60ebfe9d12..7664f9df26 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -78,15 +78,48 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { } } -final class TextAlertContentNode: AlertContentNode { +public enum TextAlertContentActionLayout { + case horizontal + case vertical +} + +public final class TextAlertContentNode: AlertContentNode { + private let theme: AlertControllerTheme + private let actionLayout: TextAlertContentActionLayout + private let titleNode: ASTextNode? - private let textNode: ASTextNode + private let textNode: ImmediateTextNode private let actionNodesSeparator: ASDisplayNode private let actionNodes: [TextAlertContentActionNode] private let actionVerticalSeparators: [ASDisplayNode] - init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) { + public var textAttributeAction: (NSAttributedStringKey, (Any) -> Void)? { + didSet { + if let (attribute, textAttributeAction) = self.textAttributeAction { + self.textNode.highlightAttributeAction = { attributes in + if let _ = attributes[attribute] { + return attribute + } else { + return nil + } + } + self.textNode.tapAttributeAction = { attributes in + if let value = attributes[attribute] { + textAttributeAction(value) + } + } + self.textNode.linkHighlightColor = self.theme.accentColor.withAlphaComponent(0.5) + } else { + self.textNode.highlightAttributeAction = nil + self.textNode.tapAttributeAction = nil + } + } + } + + public init(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout) { + self.theme = theme + self.actionLayout = actionLayout if let title = title { let titleNode = ASTextNode() titleNode.attributedText = title @@ -99,10 +132,16 @@ final class TextAlertContentNode: AlertContentNode { self.titleNode = nil } - self.textNode = ASTextNode() + self.textNode = ImmediateTextNode() + self.textNode.maximumNumberOfLines = 0 self.textNode.attributedText = text self.textNode.displaysAsynchronously = false - self.textNode.isLayerBacked = true + self.textNode.isLayerBacked = false + if text.length != 0 { + if let paragraphStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle { + self.textNode.textAlignment = paragraphStyle.alignment + } + } self.actionNodesSeparator = ASDisplayNode() self.actionNodesSeparator.isLayerBacked = true @@ -141,27 +180,40 @@ final class TextAlertContentNode: AlertContentNode { } } - override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) var titleSize: CGSize? if let titleNode = self.titleNode { titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) } - let textSize = self.textNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) + let textSize = self.textNode.updateLayout(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) - let actionsHeight: CGFloat = 44.0 + let actionButtonHeight: CGFloat = 44.0 var minActionsWidth: CGFloat = 0.0 let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) let actionTitleInsets: CGFloat = 8.0 for actionNode in self.actionNodes { - let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionsHeight)) - minActionsWidth += actionTitleSize.width + actionTitleInsets + let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight)) + switch self.actionLayout { + case .horizontal: + minActionsWidth += actionTitleSize.width + actionTitleInsets + case .vertical: + minActionsWidth = max(minActionsWidth, actionTitleSize.width + actionTitleInsets) + } } let resultSize: CGSize + var actionsHeight: CGFloat = 0.0 + switch self.actionLayout { + case .horizontal: + actionsHeight = actionButtonHeight + case .vertical: + actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) + } + if let titleNode = titleNode, let titleSize = titleSize { var contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) contentWidth = max(contentWidth, 150.0) @@ -193,20 +245,37 @@ final class TextAlertContentNode: AlertContentNode { for actionNode in self.actionNodes { if separatorIndex >= 0 { let separatorNode = self.actionVerticalSeparators[separatorIndex] - transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + switch self.actionLayout { + case .horizontal: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) + case .vertical: + transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))) + } } separatorIndex += 1 let currentActionWidth: CGFloat - if nodeIndex == self.actionNodes.count - 1 { - currentActionWidth = resultSize.width - actionOffset - } else { - currentActionWidth = actionWidth + switch self.actionLayout { + case .horizontal: + if nodeIndex == self.actionNodes.count - 1 { + currentActionWidth = resultSize.width - actionOffset + } else { + currentActionWidth = actionWidth + } + case .vertical: + currentActionWidth = resultSize.width } - let actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight)) + let actionNodeFrame: CGRect + switch self.actionLayout { + case .horizontal: + actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += currentActionWidth + case .vertical: + actionNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight + actionOffset), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) + actionOffset += actionButtonHeight + } - actionOffset += currentActionWidth transition.updateFrame(node: actionNode, frame: actionNodeFrame) nodeIndex += 1 @@ -216,18 +285,18 @@ final class TextAlertContentNode: AlertContentNode { } } -public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) -> AlertController { - return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions)) +public func textAlertController(theme: AlertControllerTheme, title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController { + return AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title, text: text, actions: actions, actionLayout: actionLayout)) } -public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction]) -> AlertController { +public func standardTextAlertController(theme: AlertControllerTheme, title: String?, text: String, actions: [TextAlertAction], actionLayout: TextAlertContentActionLayout = .horizontal) -> AlertController { var dismissImpl: (() -> Void)? let controller = AlertController(theme: theme, contentNode: TextAlertContentNode(theme: theme, title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.semibold(17.0) : Font.regular(13.0), textColor: theme.primaryColor, paragraphAlignment: .center), actions: actions.map { action in return TextAlertAction(type: action.type, title: action.title, action: { dismissImpl?() action.action() }) - })) + }, actionLayout: actionLayout)) dismissImpl = { [weak controller] in controller?.dismissAnimated() } diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index cdf2ebabc4..4c32dae583 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -7,11 +7,15 @@ public final class TextFieldNodeView: UITextField { var fixOffset: Bool = true override public func editingRect(forBounds bounds: CGRect) -> CGRect { - return bounds.offsetBy(dx: 0.0, dy: -UIScreenPixel) + return bounds.offsetBy(dx: 0.0, dy: 0.0).integral + } + + override public func textRect(forBounds bounds: CGRect) -> CGRect { + return bounds.offsetBy(dx: 0.0, dy: 0.0).integral } override public func placeholderRect(forBounds bounds: CGRect) -> CGRect { - return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: self.fixOffset ? 0.0 : -UIScreenPixel)) + return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: self.fixOffset ? 0.0 : 0.0)) } override public func deleteBackward() { diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 1699232272..fed6925ed3 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -273,7 +273,7 @@ public class TextNode: ASDisplayNode { cutoutEnabled = true } - let firstLineOffset = floorToScreenPixels(fontLineSpacing * 2.0) + let firstLineOffset = floorToScreenPixels(fontDescent) var first = true while true { diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index 10ba66edcc..af8c47c2d3 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -2,11 +2,34 @@ import Foundation import AsyncDisplayKit import SwiftSignalKit +private enum SourceAndRect { + case node(() -> (ASDisplayNode, CGRect)?) + case view(() -> (UIView, CGRect)?) + + func globalRect() -> CGRect? { + switch self { + case let .node(node): + if let (sourceNode, sourceRect) = node() { + return sourceNode.view.convert(sourceRect, to: nil) + } + case let .view(view): + if let (sourceView, sourceRect) = view() { + return sourceView.convert(sourceRect, to: nil) + } + } + return nil + } +} + public final class TooltipControllerPresentationArguments { - fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect)? + fileprivate let sourceAndRect: SourceAndRect public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect)?) { - self.sourceNodeAndRect = sourceNodeAndRect + self.sourceAndRect = .node(sourceNodeAndRect) + } + + public init(sourceViewAndRect: @escaping () -> (UIView, CGRect)?) { + self.sourceAndRect = .view(sourceViewAndRect) } } @@ -31,15 +54,17 @@ public final class TooltipController: ViewController { } private let timeout: Double + private let dismissByTapOutside: Bool private var timeoutTimer: SwiftSignalKit.Timer? private var layout: ContainerViewLayout? public var dismissed: (() -> Void)? - public init(text: String, timeout: Double = 1.0) { + public init(text: String, timeout: Double = 1.0, dismissByTapOutside: Bool = false) { self.text = text self.timeout = timeout + self.dismissByTapOutside = dismissByTapOutside super.init(navigationBarPresentationData: nil) } @@ -52,10 +77,10 @@ public final class TooltipController: ViewController { self.timeoutTimer?.invalidate() } - open override func loadDisplayNode() { + public override func loadDisplayNode() { self.displayNode = TooltipControllerNode(text: self.text, dismiss: { [weak self] in self?.dismiss() - }) + }, dismissByTapOutside: self.dismissByTapOutside) self.displayNodeDidLoad() } @@ -66,7 +91,7 @@ public final class TooltipController: ViewController { self.beginTimeout() } - override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) if self.layout != nil && self.layout! != layout { @@ -77,8 +102,8 @@ public final class TooltipController: ViewController { } else { self.layout = layout - if let presentationArguments = self.presentationArguments as? TooltipControllerPresentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { - self.controllerNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) + if let presentationArguments = self.presentationArguments as? TooltipControllerPresentationArguments, let sourceRect = presentationArguments.sourceAndRect.globalRect() { + self.controllerNode.sourceRect = sourceRect } else { self.controllerNode.sourceRect = nil } @@ -87,7 +112,7 @@ public final class TooltipController: ViewController { } } - open override func viewWillAppear(_ animated: Bool) { + public override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.controllerNode.animateIn() diff --git a/Display/TooltipControllerNode.swift b/Display/TooltipControllerNode.swift index e753f25fa0..a9e194a316 100644 --- a/Display/TooltipControllerNode.swift +++ b/Display/TooltipControllerNode.swift @@ -10,12 +10,16 @@ final class TooltipControllerNode: ASDisplayNode { private let containerNode: ContextMenuContainerNode private let textNode: ImmediateTextNode + private let dismissByTapOutside: Bool + var sourceRect: CGRect? var arrowOnBottom: Bool = true private var dismissedByTouchOutside = false - init(text: String, dismiss: @escaping () -> Void) { + init(text: String, dismiss: @escaping () -> Void, dismissByTapOutside: Bool) { + self.dismissByTapOutside = dismissByTapOutside + self.containerNode = ContextMenuContainerNode() self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) @@ -23,6 +27,7 @@ final class TooltipControllerNode: ASDisplayNode { self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) self.textNode.isLayerBacked = true self.textNode.displaysAsynchronously = false + self.textNode.maximumNumberOfLines = 0 self.dismiss = dismiss @@ -103,7 +108,7 @@ final class TooltipControllerNode: ASDisplayNode { eventIsPresses = event.type == .presses } if event.type == .touches || eventIsPresses { - if self.containerNode.frame.contains(point) { + if self.containerNode.frame.contains(point) || self.dismissByTapOutside { if !self.dismissedByTouchOutside { self.dismissedByTouchOutside = true self.dismiss() diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 0dffe12438..466cabdae1 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -130,6 +130,7 @@ open class ViewControllerPresentationArguments { } } } + public var scrollToTopWithTabBar: (() -> Void)? private func updateScrollToTopView() { if self.scrollToTop != nil { @@ -165,6 +166,10 @@ open class ViewControllerPresentationArguments { } self.navigationBar?.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false + + self.scrollToTopWithTabBar = { [weak self] in + self?.scrollToTop?() + } } required public init(coder aDecoder: NSCoder) { diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 5b55afb5c5..923bd6ed2f 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -2,6 +2,7 @@ import Foundation import UIKit import AsyncDisplayKit import MediaPlayer +import SwiftSignalKit private let volumeNotificationKey = "AVSystemController_SystemVolumeDidChangeNotification" private let volumeParameterKey = "AVSystemController_AudioVolumeNotificationParameter" @@ -13,7 +14,9 @@ final class VolumeControlStatusBar: UIView { var valueChanged: ((Float, Float) -> Void)? - override init(frame: CGRect) { + private var disposable: Disposable? + + init(frame: CGRect, shouldBeVisible: Signal) { self.control = MPVolumeView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 20.0))) self.currentValue = AVAudioSession.sharedInstance().outputVolume @@ -25,10 +28,26 @@ final class VolumeControlStatusBar: UIView { if let volume = notification.userInfo?[volumeParameterKey] as? Float { let previous = strongSelf.currentValue strongSelf.currentValue = volume - strongSelf.valueChanged?(previous, volume) + if strongSelf.control.superview != nil { + strongSelf.valueChanged?(previous, volume) + } } } }) + + self.disposable = (shouldBeVisible + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self else { + return + } + if value { + if strongSelf.control.superview == nil { + strongSelf.addSubview(strongSelf.control) + } + } else { + strongSelf.control.removeFromSuperview() + } + }) } required init?(coder aDecoder: NSCoder) { @@ -39,6 +58,7 @@ final class VolumeControlStatusBar: UIView { if let observer = self.observer { NotificationCenter.default.removeObserver(observer) } + self.disposable?.dispose() } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 8f79c5605d..7b03ce1768 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -123,7 +123,7 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container if layout.size.width.isEqual(to: 320.0) { standardInputHeight = 216.0 - predictiveHeight = 42.0 + predictiveHeight = 37.0 } else if layout.size.width.isEqual(to: 375.0) { standardInputHeight = 291.0 predictiveHeight = 42.0 @@ -342,7 +342,7 @@ public class Window1 { public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView - self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0))) + self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false)) self.volumeControlStatusBarNode = VolumeControlStatusBarNode() self.statusBarHost = statusBarHost From f3ec180e4d8fbbd15490189f3673db1e0cc04569 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 29 Jun 2018 20:14:18 +0300 Subject: [PATCH 057/245] no message --- Display.xcodeproj/project.pbxproj | 10 +-- .../xcschemes/xcschememanagement.plist | 2 +- Display/ActionSheetCheckboxItem.swift | 13 ++-- Display/GridNode.swift | 6 +- Display/ListView.swift | 6 +- Display/NavigationBarBadge.swift | 8 +- Display/NavigationButtonNode.swift | 18 ++--- Display/PeekControllerGestureRecognizer.swift | 74 +++++++++++++------ Display/TabBarController.swift | 14 ++-- Display/TextAlertController.swift | 8 +- Display/TextNode.swift | 3 + Display/VolumeControlStatusBar.swift | 23 ++++-- Display/WindowContent.swift | 12 +-- Display/WindowPanRecognizer.swift | 18 ++--- 14 files changed, 127 insertions(+), 88 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 6ba682acd3..54df2cdfeb 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -1404,7 +1404,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "-DMINIMAL_ASDK"; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1446,7 +1446,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "-DMINIMAL_ASDK"; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1572,7 +1572,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; - OTHER_CFLAGS = "-DMINIMAL_ASDK"; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1652,7 +1652,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "-DMINIMAL_ASDK"; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1732,7 +1732,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; - OTHER_CFLAGS = "-DMINIMAL_ASDK"; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 17298f558b..e65b00a6be 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayMac.xcscheme orderHint - 25 + 24 DisplayTests.xcscheme diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift index 36a5b2e340..80c89e559b 100644 --- a/Display/ActionSheetCheckboxItem.swift +++ b/Display/ActionSheetCheckboxItem.swift @@ -38,8 +38,8 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { private var item: ActionSheetCheckboxItem? private let button: HighlightTrackingButton - private let titleNode: ASTextNode - private let labelNode: ASTextNode + private let titleNode: ImmediateTextNode + private let labelNode: ImmediateTextNode private let checkNode: ASImageNode override public init(theme: ActionSheetControllerTheme) { @@ -47,14 +47,13 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.button = HighlightTrackingButton() - self.titleNode = ASTextNode() + self.titleNode = ImmediateTextNode() self.titleNode.maximumNumberOfLines = 1 self.titleNode.isUserInteractionEnabled = false self.titleNode.displaysAsynchronously = false - self.labelNode = ASTextNode() + self.labelNode = ImmediateTextNode() self.labelNode.maximumNumberOfLines = 1 - self.labelNode.truncationMode = .byTruncatingTail self.labelNode.isUserInteractionEnabled = false self.labelNode.displaysAsynchronously = false @@ -115,8 +114,8 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.button.frame = CGRect(origin: CGPoint(), size: size) - let labelSize = self.labelNode.measure(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height)) - let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height)) + let labelSize = self.labelNode.updateLayout(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height)) + let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height)) self.titleNode.frame = CGRect(origin: CGPoint(x: 44.0, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 2dab6c4d8b..cc3c7c9b37 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -588,7 +588,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } if scrollToItem.adjustForTopInset { - additionalOffset += -gridLayout.insets.top + self.initialOffset + additionalOffset += -gridLayout.insets.top// + self.initialOffset } } else if scrollToItem.adjustForTopInset { additionalOffset = -gridLayout.insets.top @@ -712,7 +712,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var lowestHeaderNode: ASDisplayNode? var lowestHeaderNodeIndex: Int? for (_, headerNode) in self.sectionNodes { - if let index = self.subnodes.index(of: headerNode) { + if let index = self.subnodes?.index(of: headerNode) { if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! { lowestHeaderNodeIndex = index lowestHeaderNode = headerNode @@ -1039,7 +1039,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var topVisible: (Int, GridItem) = (topIndex, self.items[topIndex]) let bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) - let lowerDisplayBound = presentationLayoutTransition.layout.contentOffset.y + let lowerDisplayBound = presentationLayoutTransition.layout.contentOffset.y + presentationLayoutTransition.layout.layout.insets.top //let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height for item in presentationLayoutTransition.layout.items { diff --git a/Display/ListView.swift b/Display/ListView.swift index 33cef91732..bd5063b751 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -61,7 +61,7 @@ final class ListViewBackingView: UIView { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let target = target, target.limitHitTestToNodes { + if let target = self.target, target.limitHitTestToNodes { if !target.internalHitTest(point, with: event) { return nil } @@ -1695,7 +1695,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let _ = previousFrame, animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 { let nextNode = self.itemNodes[nodeIndex + 1] - if nextNode.index == nil && nextNode.subnodes.isEmpty { + if nextNode.index == nil && nextNode.subnodes == nil || nextNode.subnodes!.isEmpty { let nextHeight = nextNode.apparentHeight if abs(nextHeight - previousApparentHeight) < CGFloat.ulpOfOne { if let animation = nextNode.animationForKey("apparentHeight") { @@ -1836,7 +1836,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var lowestHeaderNode: ASDisplayNode? var lowestHeaderNodeIndex: Int? for (_, headerNode) in self.itemHeaderNodes { - if let index = self.subnodes.index(of: headerNode) { + if let index = self.subnodes?.index(of: headerNode) { if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! { lowestHeaderNodeIndex = index lowestHeaderNode = headerNode diff --git a/Display/NavigationBarBadge.swift b/Display/NavigationBarBadge.swift index eb5c6e4849..d55b1c7924 100644 --- a/Display/NavigationBarBadge.swift +++ b/Display/NavigationBarBadge.swift @@ -52,13 +52,7 @@ final class NavigationBarBadgeNode: ASDisplayNode { let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize) self.backgroundNode.frame = backgroundFrame - let textOffset: CGFloat - if UIScreenPixel.isLessThanOrEqualTo(1.0 / 3.0) { - textOffset = UIScreenPixel * 2.0 - } else { - textOffset = UIScreenPixel - } - self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0) + textOffset), size: badgeSize) + self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0)), size: badgeSize) return backgroundSize } diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 7bda908127..b6a74ce8b6 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -120,14 +120,16 @@ private final class NavigationButtonItemNode: ASTextNode { self.displaysAsynchronously = false } - override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + func updateLayout(_ constrainedSize: CGSize) -> CGSize { let superSize = super.calculateSizeThatFits(constrainedSize) if let node = self.node { let nodeSize = node.measure(constrainedSize) - return CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + node.frame = CGRect(origin: CGPoint(), size: nodeSize) + return size } else if let imageNode = self.imageNode { - let nodeSize = imageNode.measure(constrainedSize) + let nodeSize = imageNode.image?.size ?? CGSize() let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0) + 5.0, y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize) return size @@ -135,14 +137,6 @@ private final class NavigationButtonItemNode: ASTextNode { return superSize } - override public func layout() { - super.layout() - - if let node = self.node { - node.frame = CGRect(origin: CGPoint(), size: node.calculatedSize) - } - } - private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop @@ -330,7 +324,7 @@ final class NavigationButtonNode: ASDisplayNode { totalSize.width += 16.0 nodeOrigin.x += 16.0 } - var nodeSize = node.calculateSizeThatFits(constrainedSize) + var nodeSize = node.updateLayout(constrainedSize) nodeSize.width = ceil(nodeSize.width) nodeSize.height = ceil(nodeSize.height) totalSize.width += nodeSize.width diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index ba8c9bf4ce..7dcce11f9b 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -127,9 +127,13 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) - if self.activateBySingleTap, self.candidateContent != nil, self.presentedController == nil { - self.longTapTimerFired() - self.pressTimerFired() + if self.activateBySingleTap, self.presentedController == nil { + self.longTapTimer?.invalidate() + self.pressTimer?.invalidate() + if let tapLocation = self.tapLocation { + self.checkCandidateContent(at: tapLocation, forceActivate: true) + } + self.state = .ended } else { let velocity = self.velocity(in: self.view) @@ -184,7 +188,10 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { (presentedController.displayNode as? PeekControllerNode)?.activateMenu() self.menuActivation = nil self.presentedController = nil + self.candidateContent = nil self.state = .ended + self.candidateContentDisposable.set(nil) + return } } } @@ -216,30 +223,50 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } } - private func checkCandidateContent(at touchLocation: CGPoint) { + private func checkCandidateContent(at touchLocation: CGPoint, forceActivate: Bool = false) { + //print("check begin") if let contentSignal = self.contentAtPoint(touchLocation) { - self.candidateContentDisposable.set((contentSignal |> deliverOnMainQueue).start(next: { [weak self] result in + self.candidateContentDisposable.set((contentSignal + |> deliverOnMainQueue).start(next: { [weak self] result in if let strongSelf = self { - switch strongSelf.state { - case .possible, .changed: - if let (sourceNode, content) = result { - if let currentContent = strongSelf.candidateContent { - if !currentContent.1.isEqual(to: content) { - strongSelf.tapLocation = touchLocation - strongSelf.candidateContent = (sourceNode, content) - strongSelf.menuActivation = content.menuActivation() - if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { - presentedController.sourceNode = { - return sourceNode - } - (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) + let processResult: Bool + if forceActivate { + processResult = true + } else { + switch strongSelf.state { + case .possible, .changed: + processResult = true + default: + processResult = false + } + } + //print("check received, will process: \(processResult), force: \(forceActivate), state: \(strongSelf.state)") + if processResult { + if let (sourceNode, content) = result { + if let currentContent = strongSelf.candidateContent { + if !currentContent.1.isEqual(to: content) { + strongSelf.tapLocation = touchLocation + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { + presentedController.sourceNode = { + return sourceNode } + (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) } - } else { - if let presentedController = strongSelf.present(content, sourceNode) { + } + } else { + if let presentedController = strongSelf.present(content, sourceNode) { + if forceActivate { + strongSelf.candidateContent = nil + if case .press = content.menuActivation() { + (presentedController.displayNode as? PeekControllerNode)?.activateMenu() + } + } else { strongSelf.candidateContent = (sourceNode, content) strongSelf.menuActivation = content.menuActivation() strongSelf.presentedController = presentedController + strongSelf.state = .began switch content.menuActivation() { @@ -256,11 +283,12 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } } } - } else if strongSelf.presentedController == nil { + } + } else if strongSelf.presentedController == nil { + if strongSelf.state != .possible && strongSelf.state != .ended { strongSelf.state = .failed } - default: - break + } } } })) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 439722e0cb..d3770e25c8 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -34,7 +34,7 @@ open class TabBarController: ViewController { } } - private var controllers: [ViewController] = [] + public private(set) var controllers: [ViewController] = [] private var _selectedIndex: Int? public var selectedIndex: Int { @@ -50,10 +50,6 @@ open class TabBarController: ViewController { _selectedIndex = index self.updateSelectedIndex() - } else { - if let controller = self.currentController { - controller.scrollToTopWithTabBar?() - } } } } @@ -116,7 +112,13 @@ open class TabBarController: ViewController { } strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in if let strongSelf = self { - strongSelf.selectedIndex = index + if strongSelf.selectedIndex == index { + if let controller = strongSelf.currentController { + controller.scrollToTopWithTabBar?() + } + } else { + strongSelf.selectedIndex = index + } } })) } diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 7664f9df26..bd456c204a 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -35,7 +35,7 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { super.init() self.titleNode.maximumNumberOfLines = 2 - let font = Font.regular(17.0) + var font = Font.regular(17.0) var color = theme.accentColor switch action.type { case .defaultAction, .genericAction: @@ -43,6 +43,12 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { case .destructiveAction: color = theme.destructiveColor } + switch action.type { + case .defaultAction: + font = Font.semibold(17.0) + case .destructiveAction, .genericAction: + break + } self.setAttributedTitle(NSAttributedString(string: action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) self.highligthedChanged = { [weak self] value in diff --git a/Display/TextNode.swift b/Display/TextNode.swift index fed6925ed3..a3d0a260fd 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -479,6 +479,9 @@ public class TextNode: ASDisplayNode { return (layout, { node.cachedLayout = layout if updated { + if layout.size.width.isZero && layout.size.height.isZero { + node.contents = nil + } node.setNeedsDisplay() } diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 923bd6ed2f..c7cdea2f5f 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -15,6 +15,7 @@ final class VolumeControlStatusBar: UIView { var valueChanged: ((Float, Float) -> Void)? private var disposable: Disposable? + private var ignoreAdjustmentOnce = false init(frame: CGRect, shouldBeVisible: Signal) { self.control = MPVolumeView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 20.0))) @@ -24,12 +25,22 @@ final class VolumeControlStatusBar: UIView { self.addSubview(self.control) self.observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: volumeNotificationKey), object: nil, queue: OperationQueue.main, using: { [weak self] notification in - if let strongSelf = self { - if let volume = notification.userInfo?[volumeParameterKey] as? Float { + if let strongSelf = self, let userInfo = notification.userInfo { + /*guard let category = userInfo["AVSystemController_AudioCategoryNotificationParameter"] as? String else { + return + }*/ + + if let volume = userInfo[volumeParameterKey] as? Float { let previous = strongSelf.currentValue - strongSelf.currentValue = volume - if strongSelf.control.superview != nil { - strongSelf.valueChanged?(previous, volume) + if !previous.isEqual(to: volume) { + strongSelf.currentValue = volume + if strongSelf.ignoreAdjustmentOnce { + strongSelf.ignoreAdjustmentOnce = false + } else { + if strongSelf.control.superview != nil { + strongSelf.valueChanged?(previous, volume) + } + } } } } @@ -42,10 +53,12 @@ final class VolumeControlStatusBar: UIView { } if value { if strongSelf.control.superview == nil { + strongSelf.ignoreAdjustmentOnce = true strongSelf.addSubview(strongSelf.control) } } else { strongSelf.control.removeFromSuperview() + strongSelf.ignoreAdjustmentOnce = false } }) } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 7b03ce1768..d22af96f4f 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -154,7 +154,7 @@ private func encodeText(_ string: String, _ key: Int) -> String { return result } -private func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UIView) -> Bool { +public func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UIView) -> Bool { if view.disablesInteractiveTransitionGestureRecognizer { return true } @@ -204,7 +204,7 @@ private func applyThemeToPreviewingEffectView(_ view: UIView) { } } -private func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeight: CGFloat? = nil) -> (UIView?, CGFloat?) { +public func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeight: CGFloat? = nil) -> (UIView?, CGFloat?) { if view.isFirstResponder { return (view, accessoryHeight) } else { @@ -283,12 +283,12 @@ private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { return UIEdgeInsets() } -private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { +public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true } - func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool { + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } } @@ -330,7 +330,7 @@ public class Window1 { } private var windowPanRecognizer: WindowPanRecognizer? - private let keyboardGestureRecognizerDelegate = KeyboardGestureRecognizerDelegate() + private let keyboardGestureRecognizerDelegate = WindowKeyboardGestureRecognizerDelegate() private var keyboardGestureBeginLocation: CGPoint? private var keyboardGestureAccessoryHeight: CGFloat? diff --git a/Display/WindowPanRecognizer.swift b/Display/WindowPanRecognizer.swift index 85efc21149..7d92acd37a 100644 --- a/Display/WindowPanRecognizer.swift +++ b/Display/WindowPanRecognizer.swift @@ -1,13 +1,13 @@ import Foundation -final class WindowPanRecognizer: UIGestureRecognizer { - var began: ((CGPoint) -> Void)? - var moved: ((CGPoint) -> Void)? - var ended: ((CGPoint, CGPoint?) -> Void)? +public final class WindowPanRecognizer: UIGestureRecognizer { + public var began: ((CGPoint) -> Void)? + public var moved: ((CGPoint) -> Void)? + public var ended: ((CGPoint, CGPoint?) -> Void)? private var previousPoints: [(CGPoint, Double)] = [] - override func reset() { + override public func reset() { super.reset() self.previousPoints.removeAll() @@ -40,7 +40,7 @@ final class WindowPanRecognizer: UIGestureRecognizer { } } - override func touchesBegan(_ touches: Set, with event: UIEvent) { + override public func touchesBegan(_ touches: Set, with event: UIEvent) { super.touchesBegan(touches, with: event) if let touch = touches.first { @@ -50,7 +50,7 @@ final class WindowPanRecognizer: UIGestureRecognizer { } } - override func touchesMoved(_ touches: Set, with event: UIEvent) { + override public func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) if let touch = touches.first { @@ -60,7 +60,7 @@ final class WindowPanRecognizer: UIGestureRecognizer { } } - override func touchesEnded(_ touches: Set, with event: UIEvent) { + override public func touchesEnded(_ touches: Set, with event: UIEvent) { super.touchesEnded(touches, with: event) if let touch = touches.first { @@ -70,7 +70,7 @@ final class WindowPanRecognizer: UIGestureRecognizer { } } - override func touchesCancelled(_ touches: Set, with event: UIEvent) { + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { super.touchesCancelled(touches, with: event) if let touch = touches.first { From 749db46e690491f1772488f9bf61c6fcf2c12ac9 Mon Sep 17 00:00:00 2001 From: Peter Date: Sat, 21 Jul 2018 21:27:57 +0300 Subject: [PATCH 058/245] no message --- Display/CAAnimationUtils.swift | 25 +++++--- Display/GenerateImage.swift | 4 +- .../GlobalOverlayPresentationContext.swift | 26 +++++++-- Display/GridNode.swift | 10 +++- Display/NavigationController.swift | 58 +++++++++++++------ Display/PeekControllerGestureRecognizer.swift | 1 + Display/TextFieldNode.swift | 2 +- Display/TextNode.swift | 7 ++- Display/TooltipController.swift | 2 +- Display/ViewController.swift | 10 +++- 10 files changed, 107 insertions(+), 38 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 1aa1688315..297b7b9a04 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -5,15 +5,26 @@ #endif @objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { + private let keyPath: String? var completion: ((Bool) -> Void)? - init(completion: ((Bool) -> Void)?) { + init(animation: CAAnimation, completion: ((Bool) -> Void)?) { + if let animation = animation as? CABasicAnimation { + self.keyPath = animation.keyPath + } else { + self.keyPath = nil + } self.completion = completion super.init() } @objc func animationDidStop(_ anim: CAAnimation, finished flag: Bool) { + if let anim = anim as? CABasicAnimation { + if anim.keyPath != self.keyPath { + return + } + } if let completion = self.completion { completion(flag) } @@ -36,7 +47,7 @@ public extension CAAnimation { if let delegate = self.delegate as? CALayerAnimationDelegate { delegate.completion = value } else { - self.delegate = CALayerAnimationDelegate(completion: value) + self.delegate = CALayerAnimationDelegate(animation: self, completion: value) } } } @@ -51,7 +62,7 @@ public extension CALayer { animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } let k = Float(UIView.animationDurationFactor()) @@ -90,7 +101,7 @@ public extension CALayer { animation.speed = speed animation.isAdditive = additive if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } if !delay.isZero { @@ -142,7 +153,7 @@ public extension CALayer { animation.speed = speed animation.duration = duration if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } self.add(animation, forKey: keyPath) @@ -160,7 +171,7 @@ public extension CALayer { animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } let k = Float(UIView.animationDurationFactor()) @@ -192,7 +203,7 @@ public extension CALayer { animation.speed = speed animation.isAdditive = true if let completion = completion { - animation.delegate = CALayerAnimationDelegate(completion: completion) + animation.delegate = CALayerAnimationDelegate(animation: animation, completion: completion) } self.add(animation, forKey: key) diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 0115279078..23e9fd43fc 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -299,7 +299,7 @@ public class DrawingContext { }) assert(self.bytesPerRow % 16 == 0) - assert(unsafeBitCast(self.bytes, to: Int64.self) % 16 == 0) + assert(Int64(Int(bitPattern: self.bytes)) % 16 == 0) } public func generateImage() -> UIImage? { @@ -408,7 +408,7 @@ public func readCGFloat(_ index: inout UnsafePointer, end: UnsafePointer< throw ParsingError.Generic } - if let value = NSString(bytes: UnsafePointer(begin), length: index - begin, encoding: String.Encoding.utf8.rawValue)?.floatValue { + if let value = NSString(bytes: UnsafeRawPointer(begin), length: index - begin, encoding: String.Encoding.utf8.rawValue)?.floatValue { return CGFloat(value) } else { throw ParsingError.Generic diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index 95239f270a..a6449bcfab 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -2,6 +2,22 @@ import Foundation import AsyncDisplayKit import SwiftSignalKit +private func isViewVisibleInHierarchy(_ view: UIView) -> Bool { + guard let window = view.window else { + return false + } + if view.isHidden || view.alpha == 0.0 { + return false + } + if view.superview === window { + return true + } else if let superview = view.superview { + return isViewVisibleInHierarchy(superview) + } else { + return false + } +} + final class GlobalOverlayPresentationContext { private let statusBarHost: StatusBarHost? @@ -20,7 +36,7 @@ final class GlobalOverlayPresentationContext { private func currentPresentationView() -> UIView? { if let statusBarHost = self.statusBarHost { - if let keyboardWindow = statusBarHost.keyboardWindow { + if let keyboardWindow = statusBarHost.keyboardWindow, let keyboardView = statusBarHost.keyboardView, !keyboardView.frame.height.isZero, isViewVisibleInHierarchy(keyboardView) { return keyboardWindow } else { return statusBarHost.statusBarWindow @@ -31,10 +47,10 @@ final class GlobalOverlayPresentationContext { func present(_ controller: ViewController) { let controllerReady = controller.ready.get() - |> filter({ $0 }) - |> take(1) - |> deliverOnMainQueue - |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) + |> filter({ $0 }) + |> take(1) + |> deliverOnMainQueue + |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) if let _ = self.currentPresentationView(), let initialLayout = self.layout { controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index cc3c7c9b37..68a3c54fb0 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -96,8 +96,9 @@ public struct GridNodeTransaction { public let itemTransition: ContainedViewLayoutTransition public let stationaryItems: GridNodeStationaryItems public let updateFirstIndexInSectionOffset: Int? + public let updateOpaqueState: Any? - public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?) { + public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?, updateOpaqueState: Any? = nil) { self.deleteItems = deleteItems self.insertItems = insertItems self.updateItems = updateItems @@ -106,6 +107,7 @@ public struct GridNodeTransaction { self.itemTransition = itemTransition self.stationaryItems = stationaryItems self.updateFirstIndexInSectionOffset = updateFirstIndexInSectionOffset + self.updateOpaqueState = updateOpaqueState } } @@ -223,6 +225,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + public private(set) var opaqueState: Any? + public override init() { super.init() @@ -237,6 +241,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } public func transaction(_ transaction: GridNodeTransaction, completion: (GridNodeDisplayedItemRange) -> Void) { + if let updateOpaqueState = transaction.updateOpaqueState { + self.opaqueState = updateOpaqueState + } + if transaction.deleteItems.isEmpty && transaction.insertItems.isEmpty && transaction.scrollToItem == nil && transaction.updateItems.isEmpty && (transaction.updateLayout == nil || transaction.updateLayout!.layout == self.gridLayout && (transaction.updateFirstIndexInSectionOffset == nil || transaction.updateFirstIndexInSectionOffset == self.firstIndexInSectionOffset)) { if let presentationLayoutUpdated = self.presentationLayoutUpdated { presentationLayoutUpdated(GridNodeCurrentPresentationLayout(layout: self.gridLayout, contentOffset: self.scrollView.contentOffset, contentSize: self.itemLayout.contentSize), transaction.updateLayout?.transition ?? .immediate) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 5514ad7bc9..b376d42815 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -592,6 +592,14 @@ open class NavigationController: UINavigationController, ContainableController, let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController + if let topController = topController as? ViewController { + if !topController.attemptNavigation({ [weak self] in + self?.popViewController(animated: true) + }) { + return + } + } + topController.viewWillDisappear(true) let topView = topController.view! bottomController.viewWillAppear(true) @@ -684,27 +692,39 @@ open class NavigationController: UINavigationController, ContainableController, } public func pushViewController(_ controller: ViewController) { - if !controller.hasActiveInput { - self.view.endEditing(true) - } - if let validLayout = self.validLayout { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in - if let strongSelf = self, let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - if containerLayout != appliedLayout { - controller.containerLayoutUpdated(containerLayout, transition: .immediate) + let navigateAction: () -> Void = { [weak self] in + guard let strongSelf = self else { + return + } + + if let validLayout = strongSelf.validLayout { + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + if let strongSelf = self, let validLayout = strongSelf.validLayout { + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + if containerLayout != appliedLayout { + controller.containerLayoutUpdated(containerLayout, transition: .immediate) + } + strongSelf.pushViewController(controller, animated: true) } - strongSelf.pushViewController(controller, animated: true) - } - })) + })) + } else { + strongSelf.pushViewController(controller, animated: false) + } + + if !controller.hasActiveInput { + strongSelf.view.endEditing(true) + } + } + + if let lastController = self.viewControllers.last as? ViewController, !lastController.attemptNavigation(navigateAction) { } else { - self.pushViewController(controller, animated: false) + navigateAction() } } diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index 7dcce11f9b..12c103046a 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -147,6 +147,7 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { self.candidateContent = nil self.longTapTimer?.invalidate() self.pressTimer?.invalidate() + self.candidateContentDisposable.set(nil) self.state = .failed } } diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index 4c32dae583..97eceaa129 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -15,7 +15,7 @@ public final class TextFieldNodeView: UITextField { } override public func placeholderRect(forBounds bounds: CGRect) -> CGRect { - return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: self.fixOffset ? 0.0 : 0.0)) + return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: -1.0)) } override public func deleteBackward() { diff --git a/Display/TextNode.swift b/Display/TextNode.swift index a3d0a260fd..3097770ccf 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -229,6 +229,12 @@ public class TextNode: ASDisplayNode { if let attributedString = attributedString { let stringLength = attributedString.length + #if DEBUG + if attributedString.string == "مثلاً مثلاً مثلاً" { + assert(true) + } + #endif + let font: CTFont if stringLength != 0 { if let stringFont = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) { @@ -278,7 +284,6 @@ public class TextNode: ASDisplayNode { var first = true while true { var lineConstrainedWidth = constrainedSize.width - //var lineOriginY = floorToScreenPixels(layoutSize.height + fontLineHeight - fontLineSpacing * 2.0) var lineOriginY = floorToScreenPixels(layoutSize.height + fontAscent) if !first { lineOriginY += fontLineSpacing diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index af8c47c2d3..df87e4eda0 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -61,7 +61,7 @@ public final class TooltipController: ViewController { public var dismissed: (() -> Void)? - public init(text: String, timeout: Double = 1.0, dismissByTapOutside: Bool = false) { + public init(text: String, timeout: Double = 2.0, dismissByTapOutside: Bool = false) { self.text = text self.timeout = timeout self.dismissByTapOutside = dismissByTapOutside diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 466cabdae1..3d7d13900a 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -132,6 +132,10 @@ open class ViewControllerPresentationArguments { } public var scrollToTopWithTabBar: (() -> Void)? + public var attemptNavigation: (@escaping () -> Void) -> Bool = { _ in + return true + } + private func updateScrollToTopView() { if self.scrollToTop != nil { if let displayNode = self._displayNode , self.scrollToTopView == nil { @@ -162,7 +166,11 @@ open class ViewControllerPresentationArguments { super.init(nibName: nil, bundle: nil) self.navigationBar?.backPressed = { [weak self] in - self?.navigationController?.popViewController(animated: true) + if let strongSelf = self, strongSelf.attemptNavigation({ + self?.navigationController?.popViewController(animated: true) + }) { + strongSelf.navigationController?.popViewController(animated: true) + } } self.navigationBar?.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false From eea5853ef12cc99507a09ff9609c23d05335f8ec Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 3 Aug 2018 23:17:28 +0300 Subject: [PATCH 059/245] no message --- Display/GenerateImage.swift | 10 ++ Display/ListView.swift | 21 +++++ Display/NavigationController.swift | 95 ++++++++++++++----- Display/StatusBarProxyNode.swift | 147 +++++++++++++++++++++-------- Display/ViewController.swift | 2 +- Display/WindowContent.swift | 6 +- 6 files changed, 218 insertions(+), 63 deletions(-) diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index 23e9fd43fc..f3a4bf6414 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -217,6 +217,16 @@ public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor return tintedImage } +public func generateScaledImage(image: UIImage?, size: CGSize, scale: CGFloat? = nil) -> UIImage? { + guard let image = image else { + return nil + } + + return generateImage(size, opaque: true, scale: scale, rotatedContext: { size, context in + context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) + }) +} + private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { return generateImage(size, contextGenerator: { size, context in context.setFillColor(color.cgColor) diff --git a/Display/ListView.swift b/Display/ListView.swift index bd5063b751..f7f23eb69a 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -194,6 +194,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var visibleBottomContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } public final var beganInteractiveDragging: () -> Void = { } + public final var didEndScrolling: (() -> Void)? public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in } @@ -497,6 +498,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateHeaderItemsFlashing(animated: true) self.lastContentOffsetTimestamp = 0.0 + self.didEndScrolling?() } } @@ -505,6 +507,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.isDeceleratingAfterTracking = false self.resetHeaderItemsFlashTimer(start: true) self.updateHeaderItemsFlashing(animated: true) + self.didEndScrolling?() } public func scrollViewDidScroll(_ scrollView: UIScrollView) { @@ -1413,6 +1416,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + /*if !insertedIndexSet.intersection(updateIndices).isEmpty { + print("int") + }*/ + let explicitelyUpdateIndices = Set(updateIndicesAndItems.map({$0.index})) + /*if !explicitelyUpdateIndices.intersection(updateIndices).isEmpty { + print("int") + }*/ + + updateIndices.subtract(explicitelyUpdateIndices) + self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in var updatedState = state @@ -3235,6 +3248,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public func ensureItemNodeVisibleAtTopInset(_ node: ListViewItemNode) { + if let index = node.index { + if node.frame.minY != self.insets.top { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } + } + } + public func itemNodeRelativeOffset(_ node: ListViewItemNode) -> CGFloat? { if let _ = node.index { return node.frame.minY - self.insets.top diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index b376d42815..7f69e7deda 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -21,7 +21,7 @@ private final class NavigationControllerContainerView: UIView { } } -private final class NavigationControllerView: UIView { +private final class NavigationControllerView: UITracingLayerView { var inTransition = false let sharedStatusBar: StatusBar @@ -93,6 +93,9 @@ open class NavigationController: UINavigationController, ContainableController, private var validLayout: ContainerViewLayout? + private var scheduledLayoutTransitionRequestId: Int = 0 + private var scheduledLayoutTransitionRequest: (Int, ContainedViewLayoutTransition)? + private var navigationTransitionCoordinator: NavigationTransitionCoordinator? private var currentPushDisposable = MetaDisposable() @@ -283,7 +286,7 @@ open class NavigationController: UINavigationController, ContainableController, } if let _ = layout.statusBarHeight { - self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0)) } var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] @@ -360,6 +363,13 @@ open class NavigationController: UINavigationController, ContainableController, previousController.viewWillDisappear(true) record.controller.viewWillAppear(true) record.controller.setIgnoreAppearanceMethodInvocations(true) + + if let controller = record.controller as? ViewController, !controller.hasActiveInput { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: 1) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + } self.controllerView.containerView.addSubview(record.controller.view) record.controller.setIgnoreAppearanceMethodInvocations(false) @@ -602,6 +612,12 @@ open class NavigationController: UINavigationController, ContainableController, topController.viewWillDisappear(true) let topView = topController.view! + if let bottomController = bottomController as? ViewController { + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: layout), layout: layout, index: self.viewControllers.count - 2) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(bottomController.hasActiveInput ? controllerLayout.inputHeight : nil) + bottomController.containerLayoutUpdated(appliedLayout, transition: .immediate) + } bottomController.viewWillAppear(true) let bottomView = bottomController.view! @@ -696,30 +712,35 @@ open class NavigationController: UINavigationController, ContainableController, guard let strongSelf = self else { return } - - if let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in - if let strongSelf = self, let validLayout = strongSelf.validLayout { - let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) - - let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) - if containerLayout != appliedLayout { - controller.containerLayoutUpdated(containerLayout, transition: .immediate) - } - strongSelf.pushViewController(controller, animated: true) - } - })) - } else { - strongSelf.pushViewController(controller, animated: false) - } - + if !controller.hasActiveInput { strongSelf.view.endEditing(true) } + strongSelf.scheduleAfterLayout({ + guard let strongSelf = self else { + return + } + + if let validLayout = strongSelf.validLayout { + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + controller.containerLayoutUpdated(appliedLayout, transition: .immediate) + strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + if let strongSelf = self, let validLayout = strongSelf.validLayout { + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) + if containerLayout != appliedLayout { + controller.containerLayoutUpdated(containerLayout, transition: .immediate) + } + strongSelf.pushViewController(controller, animated: true) + } + })) + } else { + strongSelf.pushViewController(controller, animated: false) + } + }) } if let lastController = self.viewControllers.last as? ViewController, !lastController.attemptNavigation(navigateAction) { @@ -926,4 +947,32 @@ open class NavigationController: UINavigationController, ContainableController, } return nil } + + private func scheduleAfterLayout(_ f: @escaping () -> Void) { + (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in + f() + }) + self.view.setNeedsLayout() + } + + private func scheduleLayoutTransitionRequest(_ transition: ContainedViewLayoutTransition) { + let requestId = self.scheduledLayoutTransitionRequestId + self.scheduledLayoutTransitionRequestId += 1 + self.scheduledLayoutTransitionRequest = (requestId, transition) + (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in + if let strongSelf = self { + if let (currentRequestId, currentRequestTransition) = strongSelf.scheduledLayoutTransitionRequest, currentRequestId == requestId { + strongSelf.scheduledLayoutTransitionRequest = nil + strongSelf.requestLayout(transition: currentRequestTransition) + } + } + }) + self.view.setNeedsLayout() + } + + private func requestLayout(transition: ContainedViewLayoutTransition) { + if self.isViewLoaded, let validLayout = self.validLayout { + self.containerLayoutUpdated(validLayout, transition: transition) + } + } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 791332e55e..95df717884 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -41,19 +41,37 @@ func makeStatusBarProxy(_ statusBarStyle: StatusBarStyle, statusBar: UIView) -> return StatusBarProxyNode(statusBarStyle: statusBarStyle, statusBar: statusBar) } +private func maxSubviewBounds(_ view: UIView) -> CGRect { + var bounds = view.bounds + for subview in view.subviews { + let subviewFrame = subview.frame + let subviewBounds = maxSubviewBounds(subview).offsetBy(dx: subviewFrame.minX, dy: subviewFrame.minY) + bounds = bounds.union(subviewBounds) + } + return bounds +} + private class StatusBarItemNode: ASDisplayNode { var statusBarStyle: StatusBarStyle var targetView: UIView + var rootView: UIView + private let contentNode: ASDisplayNode - init(statusBarStyle: StatusBarStyle, targetView: UIView) { + init(statusBarStyle: StatusBarStyle, targetView: UIView, rootView: UIView) { self.statusBarStyle = statusBarStyle self.targetView = targetView + self.rootView = rootView + self.contentNode = ASDisplayNode() + self.contentNode.isLayerBacked = true super.init() + + self.addSubnode(self.contentNode) } func update() { - let context = DrawingContext(size: self.targetView.frame.size, clear: true) + let containingBounds = maxSubviewBounds(self.targetView) + let context = DrawingContext(size: containingBounds.size, clear: true) if let contents = self.targetView.layer.contents, (self.targetView.layer.sublayers?.count ?? 0) == 0 && CFGetTypeID(contents as CFTypeRef) == CGImage.typeID && false { let image = contents as! CGImage @@ -86,13 +104,24 @@ private class StatusBarItemNode: ASDisplayNode { } } else { context.withContext { c in + c.translateBy(x: containingBounds.minX, y: -containingBounds.minY) UIGraphicsPushContext(c) self.targetView.layer.render(in: c) UIGraphicsPopContext() } } //dumpViews(self.targetView) - var type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic + var type: StatusBarItemType = .Generic + if let batteryItemClass = batteryItemClass { + if self.targetView.checkIsKind(of: batteryItemClass) { + type = .Battery + } + } + if let batteryViewClass = batteryViewClass { + if self.targetView.checkIsKind(of: batteryViewClass) { + type = .Battery + } + } if case .Generic = type { var hasActivityBackground = false var hasText = false @@ -108,9 +137,11 @@ private class StatusBarItemNode: ASDisplayNode { } } tintStatusBarItem(context, type: type, style: statusBarStyle) - self.contents = context.generateImage()?.cgImage + self.contentNode.contents = context.generateImage()?.cgImage - self.frame = self.targetView.frame + let mappedFrame = self.targetView.convert(self.targetView.bounds, to: self.rootView) + self.frame = mappedFrame + self.contentNode.frame = containingBounds } } @@ -253,10 +284,10 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseColor: UInt32 switch style { - case .Black, .Ignore, .Hide: - baseColor = 0x000000 - case .White: - baseColor = 0xffffff + case .Black, .Ignore, .Hide: + baseColor = 0x000000 + case .White: + baseColor = 0xffffff } let baseR = (baseColor >> 16) & 0xff @@ -285,6 +316,14 @@ private let foregroundClass: AnyClass? = { return NSClassFromString("_UI" + nameString) }() +private let foregroundClass2: AnyClass? = { + var nameString = "StatusBar" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "ForegroundView" + } + return NSClassFromString("UI" + nameString) +}() + private let batteryItemClass: AnyClass? = { var nameString = "StatusBarBattery" if CFAbsoluteTimeGetCurrent() > 0 { @@ -293,6 +332,14 @@ private let batteryItemClass: AnyClass? = { return NSClassFromString("UI" + nameString) }() +private let batteryViewClass: AnyClass? = { + var nameString = "Battery" + if CFAbsoluteTimeGetCurrent() > 0 { + nameString += "View" + } + return NSClassFromString("_UI" + nameString) +}() + private let activityClass: AnyClass? = { var nameString = "StatusBarBackground" if CFAbsoluteTimeGetCurrent() > 0 { @@ -309,6 +356,18 @@ private let stringClass: AnyClass? = { return NSClassFromString("_UI" + nameString) }() +private func containsSubviewOfClass(view: UIView, of subviewClass: AnyClass?) -> Bool { + guard let subviewClass = subviewClass else { + return false + } + for subview in view.subviews { + if subview.checkIsKind(of: subviewClass) { + return true + } + } + return false +} + private class StatusBarProxyNodeTimerTarget: NSObject { let action: () -> Void @@ -321,6 +380,32 @@ private class StatusBarProxyNodeTimerTarget: NSObject { } } +private func forEachSubview(statusBar: UIView, _ f: (UIView, UIView) -> Bool) { + var rootView: UIView = statusBar + for subview in statusBar.subviews { + if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { + rootView = subview + break + } else if let foregroundClass2 = foregroundClass2, subview.checkIsKind(of: foregroundClass2) { + rootView = subview + break + } + } + for subview in rootView.subviews { + if true || subview.subviews.isEmpty { + if !f(rootView, subview) { + break + } + } else { + for subSubview in subview.subviews { + if !f(rootView, subSubview) { + break + } + } + } + } +} + class StatusBarProxyNode: ASDisplayNode { private let statusBar: UIView @@ -369,19 +454,13 @@ class StatusBarProxyNode: ASDisplayNode { self.clipsToBounds = true //self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2) - var rootView: UIView = statusBar - for subview in statusBar.subviews { - if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { - rootView = subview - break - } - } - - for subview in rootView.subviews { - let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview) + //dumpViews(statusBar) + forEachSubview(statusBar: statusBar, { rootView, subview in + let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview, rootView: rootView) self.itemNodes.append(itemNode) self.addSubnode(itemNode) - } + return true + }) self.frame = statusBar.bounds } @@ -393,25 +472,17 @@ class StatusBarProxyNode: ASDisplayNode { private func updateItems() { let statusBar = self.statusBar - var rootView: UIView = statusBar - for subview in statusBar.subviews { - if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) { - rootView = subview - break - } - } - - //dumpViews(self.statusBar) - var i = 0 while i < self.itemNodes.count { var found = false - for subview in rootView.subviews { - if self.itemNodes[i].targetView == subview { + forEachSubview(statusBar: statusBar, { rootView, subview in + if self.itemNodes[i].rootView === rootView && self.itemNodes[i].targetView === subview { found = true - break + return false + } else { + return true } - } + }) if !found { self.itemNodes[i].removeFromSupernode() self.itemNodes.remove(at: i) @@ -422,7 +493,7 @@ class StatusBarProxyNode: ASDisplayNode { } } - for subview in rootView.subviews { + forEachSubview(statusBar: statusBar, { rootView, subview in var found = false for itemNode in self.itemNodes { if itemNode.targetView == subview { @@ -430,13 +501,13 @@ class StatusBarProxyNode: ASDisplayNode { break } } - if !found { - let itemNode = StatusBarItemNode(statusBarStyle: self.statusBarStyle, targetView: subview) + let itemNode = StatusBarItemNode(statusBarStyle: self.statusBarStyle, targetView: subview, rootView: rootView) itemNode.update() self.itemNodes.append(itemNode) self.addSubnode(itemNode) } - } + return true + }) } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 3d7d13900a..d065c6564f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -196,7 +196,7 @@ open class ViewControllerPresentationArguments { } transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) if let _ = layout.statusBarHeight { - self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0)) } let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index d22af96f4f..866e63e072 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -653,7 +653,11 @@ public class Window1 { public var coveringView: WindowCoveringView? { didSet { if self.coveringView !== oldValue { - oldValue?.removeFromSuperview() + if let oldValue = oldValue { + oldValue.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak oldValue] _ in + oldValue?.removeFromSuperview() + }) + } if let coveringView = self.coveringView { self.hostView.view.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view) if !self.windowLayout.size.width.isZero { From e3bfc2003c02b43e150ed0026737dd10371692c3 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 31 Aug 2018 04:22:15 +0300 Subject: [PATCH 060/245] no message --- Display.xcodeproj/project.pbxproj | 157 ++++++++++++++++++ .../xcschemes/xcschememanagement.plist | 2 +- Display/ActionSheetController.swift | 7 +- Display/ChildWindowHostView.swift | 2 +- Display/ContainableController.swift | 2 + .../GlobalOverlayPresentationContext.swift | 6 +- Display/GridItem.swift | 5 + Display/GridNode.swift | 26 ++- Display/ListView.swift | 8 + Display/NativeWindowHostView.swift | 26 ++- Display/NavigationController.swift | 19 ++- Display/PresentationContext.swift | 6 +- Display/TextNode.swift | 15 ++ Display/UIKitUtils.swift | 10 ++ Display/ViewController.swift | 25 ++- Display/WindowContent.swift | 59 ++++--- 16 files changed, 331 insertions(+), 44 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 54df2cdfeb..b370dbc105 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -1838,6 +1838,159 @@ }; name = "Release Hockeyapp Internal"; }; + D0ADF920212B3ABC00310BBC /* Debug AppStore LLC */ = { + 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug AppStore LLC"; + }; + D0ADF921212B3ABC00310BBC /* Debug AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + }; + name = "Debug AppStore LLC"; + }; + D0ADF922212B3ABC00310BBC /* Debug AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = "Debug AppStore LLC"; + }; + D0ADF923212B3ABC00310BBC /* Debug AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug AppStore LLC"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -1846,6 +1999,7 @@ buildConfigurations = ( D01159BC1F40E96C0039383E /* Debug Hockeyapp */, D01159BD1F40E96C0039383E /* Debug AppStore */, + D0ADF923212B3ABC00310BBC /* Debug AppStore LLC */, D01159BE1F40E96C0039383E /* Release Hockeyapp */, D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */, D01159BF1F40E96C0039383E /* Release AppStore */, @@ -1858,6 +2012,7 @@ buildConfigurations = ( D05CC2751B69316F00E235A3 /* Debug Hockeyapp */, D079FD091F06BD9C0038FADE /* Debug AppStore */, + D0ADF920212B3ABC00310BBC /* Debug AppStore LLC */, D05CC2761B69316F00E235A3 /* Release Hockeyapp */, D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56E1CC0115D00F08284 /* Release AppStore */, @@ -1870,6 +2025,7 @@ buildConfigurations = ( D05CC2781B69316F00E235A3 /* Debug Hockeyapp */, D079FD0A1F06BD9C0038FADE /* Debug AppStore */, + D0ADF921212B3ABC00310BBC /* Debug AppStore LLC */, D05CC2791B69316F00E235A3 /* Release Hockeyapp */, D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56F1CC0115D00F08284 /* Release AppStore */, @@ -1882,6 +2038,7 @@ buildConfigurations = ( D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */, D079FD0B1F06BD9C0038FADE /* Debug AppStore */, + D0ADF922212B3ABC00310BBC /* Debug AppStore LLC */, D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */, D086A5701CC0115D00F08284 /* Release AppStore */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index e65b00a6be..17298f558b 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -12,7 +12,7 @@ DisplayMac.xcscheme orderHint - 24 + 25 DisplayTests.xcscheme diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index f6f3532784..3c5177032a 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -9,6 +9,8 @@ open class ActionSheetController: ViewController { private var groups: [ActionSheetItemGroup] = [] + private var isDismissed: Bool = false + public init(theme: ActionSheetControllerTheme) { self.theme = theme @@ -20,7 +22,10 @@ open class ActionSheetController: ViewController { } public func dismissAnimated() { - self.actionSheetNode.animateOut() + if !self.isDismissed { + self.isDismissed = true + self.actionSheetNode.animateOut() + } } open override func loadDisplayNode() { diff --git a/Display/ChildWindowHostView.swift b/Display/ChildWindowHostView.swift index 071334be6a..2581956144 100644 --- a/Display/ChildWindowHostView.swift +++ b/Display/ChildWindowHostView.swift @@ -37,7 +37,7 @@ public func childWindowHostView(parent: UIView) -> WindowHostView { }) view.updateSize = { [weak hostView] size in - hostView?.updateSize?(size) + hostView?.updateSize?(size, 0.0) } view.layoutSubviewsEvent = { [weak hostView] in diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index fd0aba9305..6fbbfb8633 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -4,5 +4,7 @@ import AsyncDisplayKit public protocol ContainableController: class { var view: UIView! { get } + func combinedSupportedOrientations() -> ViewControllerSupportedOrientations + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) } diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index a6449bcfab..6ae8082f40 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -154,11 +154,11 @@ final class GlobalOverlayPresentationContext { return nil } - func combinedSupportedOrientations() -> UIInterfaceOrientationMask { - var mask: UIInterfaceOrientationMask = .all + func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) for controller in self.controllers { - mask = mask.intersection(controller.supportedInterfaceOrientations) + mask = mask.intersection(controller.supportedOrientations) } return mask diff --git a/Display/GridItem.swift b/Display/GridItem.swift index 0076ac16d1..ec201d2edd 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -14,6 +14,7 @@ public protocol GridItem { func update(node: GridItemNode) var aspectRatio: CGFloat { get } var fillsRowWithHeight: CGFloat? { get } + var fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? { get } } public extension GridItem { @@ -24,4 +25,8 @@ public extension GridItem { var fillsRowWithHeight: CGFloat? { return nil } + + var fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? { + return nil + } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 68a3c54fb0..a32e98134f 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -203,7 +203,7 @@ private struct WrappedGridItemNode: Hashable { open class GridNode: GridNodeScroller, UIScrollViewDelegate { private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), lineSpacing: 0.0)) private var firstIndexInSectionOffset: Int = 0 - private var items: [GridItem] = [] + public private(set) var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] private var sectionNodes: [WrappedGridSection: ASDisplayNode] = [:] private var itemLayout = GridNodeItemLayout(contentSize: CGSize(), items: [], sections: []) @@ -346,7 +346,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, completion: completion) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, updatingLayout: transaction.updateLayout != nil, completion: completion) } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { @@ -368,7 +368,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, completion: { _ in }) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, updatingLayout: false, completion: { _ in }) } } @@ -443,6 +443,11 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { nextItemOrigin.x = 0.0 itemSize.width = gridLayout.size.width itemSize.height = height + } else if let fillsRowWithDynamicHeight = item.fillsRowWithDynamicHeight { + let height = fillsRowWithDynamicHeight(gridLayout.size.width) + nextItemOrigin.x = 0.0 + itemSize.width = gridLayout.size.width + itemSize.height = height } else if index == 0 { let itemsInRow = max(1, Int(effectiveWidth) / Int(itemSize.width)) let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow @@ -554,13 +559,16 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } private func generatePresentationLayoutTransition(stationaryItems: GridNodeStationaryItems = .none, layoutTransactionOffset: CGFloat, scrollToItem: GridNodeScrollToItem? = nil) -> GridNodePresentationLayoutTransition { - if CGFloat(0.0).isLess(than: gridLayout.size.width) && CGFloat(0.0).isLess(than: gridLayout.size.height) { + if CGFloat(0.0).isLess(than: self.gridLayout.size.width) && CGFloat(0.0).isLess(than: self.gridLayout.size.height) { var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up var transition: ContainedViewLayoutTransition = .immediate let contentOffset: CGPoint var updatedStationaryItems = stationaryItems - if scrollToItem != nil { + if let scrollToItem = scrollToItem { updatedStationaryItems = .none + if case .immediate = transition { + transition = scrollToItem.transition + } } switch updatedStationaryItems { case .none: @@ -730,7 +738,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { return lowestHeaderNode } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) { + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) { let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate var previousItemFrames: [WrappedGridItemNode: CGRect]? @@ -826,8 +834,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = presentationLayoutTransition.transition { let contentOffset = presentationLayoutTransition.layout.contentOffset - boundsOffset = 0.0 - shouldAnimateBounds = false + if !updatingLayout { + boundsOffset = 0.0 + shouldAnimateBounds = false + } var offset: CGFloat? for (index, itemNode) in self.itemNodes { diff --git a/Display/ListView.swift b/Display/ListView.swift index f7f23eb69a..1aadb8ed55 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -3238,6 +3238,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public func forEachAccessoryItemNode(_ f: (ListViewAccessoryItemNode) -> Void) { + for itemNode in self.itemNodes { + if let accessoryItemNode = itemNode.accessoryItemNode { + f(accessoryItemNode) + } + } + } + public func ensureItemNodeVisible(_ node: ListViewItemNode) { if let index = node.index { if node.frame.minY < self.insets.top { diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 6668b9f3d3..8c4871553d 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -1,6 +1,8 @@ import Foundation import SwiftSignalKit +private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3 + private let defaultOrientations: UIInterfaceOrientationMask = { if UIDevice.current.userInterfaceIdiom == .pad { return .all @@ -11,6 +13,7 @@ private let defaultOrientations: UIInterfaceOrientationMask = { private class WindowRootViewController: UIViewController { var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)? + var transitionToSize: ((CGSize, Double) -> Void)? var orientations: UIInterfaceOrientationMask = defaultOrientations { didSet { @@ -66,6 +69,13 @@ private class WindowRootViewController: UIViewController { override func prefersHomeIndicatorAutoHidden() -> Bool { return self.preferNavigationUIHidden } + + override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) { + super.viewWillTransition(to: size, with: coordinator) + UIView.performWithoutAnimation { + self.transitionToSize?(size, coordinator.transitionDuration) + } + } } private final class NativeWindow: UIWindow, WindowHost { @@ -80,6 +90,7 @@ private final class NativeWindow: UIWindow, WindowHost { var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? + var forEachControllerImpl: (((ViewController) -> Void) -> Void)? private var frameTransition: ContainedViewLayoutTransition? @@ -191,6 +202,10 @@ private final class NativeWindow: UIWindow, WindowHost { func cancelInteractiveKeyboardGestures() { self.cancelInteractiveKeyboardGesturesImpl?() } + + func forEachController(_ f: (ViewController) -> Void) { + self.forEachControllerImpl?(f) + } } public func nativeWindowHostView() -> WindowHostView { @@ -212,8 +227,13 @@ public func nativeWindowHostView() -> WindowHostView { rootViewController.preferNavigationUIHidden = value }) + rootViewController.transitionToSize = { [weak hostView] size, duration in + hostView?.updateSize?(size, duration) + } + window.updateSize = { [weak hostView] size in - hostView?.updateSize?(size) + //hostView?.updateSize?(size) + assert(true) } window.layoutSubviewsEvent = { [weak hostView] in @@ -256,6 +276,10 @@ public func nativeWindowHostView() -> WindowHostView { hostView?.cancelInteractiveKeyboardGestures?() } + window.forEachControllerImpl = { [weak hostView] f in + hostView?.forEachController?(f) + } + rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let strongSelf = hostView { strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 7f69e7deda..ea304a4319 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -139,6 +139,16 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPresentDisposable.dispose() } + public func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) + if let controller = self.viewControllers.last { + if let controller = controller as? ViewController { + supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations) + } + } + return supportedOrientations + } + public func updateTheme(_ theme: NavigationControllerTheme) { self.theme = theme if self.isViewLoaded { @@ -286,7 +296,7 @@ open class NavigationController: UINavigationController, ContainableController, } if let _ = layout.statusBarHeight { - self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0)) + self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) } var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] @@ -869,10 +879,13 @@ open class NavigationController: UINavigationController, ContainableController, controller.containerLayoutUpdated(validLayout, transition: .immediate) } - var ready: Signal = .single(true) + var ready: Signal = .single(true) if let controller = controller.topViewController as? ViewController { - ready = controller.ready.get() |> filter { $0 } |> take(1) |> deliverOnMainQueue + ready = controller.ready.get() + |> filter { $0 } + |> take(1) + |> deliverOnMainQueue } self.currentPresentDisposable.set(ready.start(next: { [weak self] _ in diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index bf91c03433..83625ab65f 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -164,11 +164,11 @@ final class PresentationContext { return nil } - func combinedSupportedOrientations() -> UIInterfaceOrientationMask { - var mask: UIInterfaceOrientationMask = .all + func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) for controller in self.controllers { - mask = mask.intersection(controller.supportedInterfaceOrientations) + mask = mask.intersection(controller.supportedOrientations) } return mask diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 3097770ccf..be1b1272cf 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -160,6 +160,17 @@ public final class TextNodeLayout: NSObject { return rects } + public func attributeSubstring(name: String, index: Int) -> String? { + if let attributedString = self.attributedString { + var range = NSRange() + let _ = attributedString.attribute(NSAttributedStringKey(rawValue: name), at: index, effectiveRange: &range) + if range.length != 0 { + return (attributedString.string as NSString).substring(with: range) + } + } + return nil + } + public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { if let attributedString = self.attributedString { var range = NSRange() @@ -209,6 +220,10 @@ public class TextNode: ASDisplayNode { } } + public func attributeSubstring(name: String, index: Int) -> String? { + return self.cachedLayout?.attributeSubstring(name: name, index: index) + } + public func attributeRects(name: String, at index: Int) -> [CGRect]? { if let cachedLayout = self.cachedLayout { return cachedLayout.lineAndAttributeRects(name: name, at: index)?.map { $0.1 } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index bcf5edde7e..9af84b77ef 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -57,6 +57,16 @@ public extension UIColor { return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) } + + func withMultipliedBrightnessBy(_ factor: CGFloat) -> UIColor { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var brightness: CGFloat = 0.0 + var alpha: CGFloat = 0.0 + self.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + + return UIColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha) + } } public extension CGSize { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index d065c6564f..6c5ceb128f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -21,6 +21,20 @@ public enum ViewControllerPresentationAnimation { case modalSheet } +public struct ViewControllerSupportedOrientations { + public var regularSize: UIInterfaceOrientationMask + public var compactSize: UIInterfaceOrientationMask + + public init(regularSize: UIInterfaceOrientationMask, compactSize: UIInterfaceOrientationMask) { + self.regularSize = regularSize + self.compactSize = compactSize + } + + public func intersection(_ other: ViewControllerSupportedOrientations) -> ViewControllerSupportedOrientations { + return ViewControllerSupportedOrientations(regularSize: self.regularSize.intersection(other.regularSize), compactSize: self.compactSize.intersection(other.compactSize)) + } +} + open class ViewControllerPresentationArguments { public let presentationAnimation: ViewControllerPresentationAnimation @@ -31,10 +45,15 @@ open class ViewControllerPresentationArguments { @objc open class ViewController: UIViewController, ContainableController { private var validLayout: ContainerViewLayout? + public var currentlyAppliedLayout: ContainerViewLayout? { + return self.validLayout + } + private let presentationContext: PresentationContext - public final var supportedOrientations: UIInterfaceOrientationMask = .all - override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { + public final var supportedOrientations: ViewControllerSupportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) + + public func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { return self.supportedOrientations } @@ -196,7 +215,7 @@ open class ViewControllerPresentationArguments { } transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) if let _ = layout.statusBarHeight { - self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 60.0)) + self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) } let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 866e63e072..45adeb81be 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -2,18 +2,6 @@ import Foundation import AsyncDisplayKit import SwiftSignalKit -private class WindowRootViewController: UIViewController { - var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? - - override var preferredStatusBarStyle: UIStatusBarStyle { - return .default - } - - override var prefersStatusBarHidden: Bool { - return false - } -} - private struct WindowLayout: Equatable { let size: CGSize let metrics: LayoutMetrics @@ -85,7 +73,6 @@ private struct UpdatingLayout { } } -private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3 private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone private func inputHeightOffsetForLayout(_ layout: WindowLayout) -> CGFloat { @@ -233,7 +220,7 @@ public final class WindowHostView { var present: ((ViewController, PresentationSurfaceLevel) -> Void)? var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? var presentNative: ((UIViewController) -> Void)? - var updateSize: ((CGSize) -> Void)? + var updateSize: ((CGSize, Double) -> Void)? var layoutSubviews: (() -> Void)? var updateToInterfaceOrientation: (() -> Void)? var isUpdatingOrientationLayout = false @@ -241,6 +228,7 @@ public final class WindowHostView { var invalidateDeferScreenEdgeGesture: (() -> Void)? var invalidatePreferNavigationUIHidden: (() -> Void)? var cancelInteractiveKeyboardGestures: (() -> Void)? + var forEachController: (((ViewController) -> Void) -> Void)? init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { self.view = view @@ -257,6 +245,7 @@ public struct WindowTracingTags { } public protocol WindowHost { + func forEachController(_ f: (ViewController) -> Void) func present(_ controller: ViewController, on level: PresentationSurfaceLevel) func presentInGlobalOverlay(_ controller: ViewController) func invalidateDeferScreenEdgeGestures() @@ -344,6 +333,7 @@ public class Window1 { self.volumeControlStatusBar = VolumeControlStatusBar(frame: CGRect(origin: CGPoint(x: 0.0, y: -20.0), size: CGSize(width: 100.0, height: 20.0)), shouldBeVisible: statusBarHost?.handleVolumeControl ?? .single(false)) self.volumeControlStatusBarNode = VolumeControlStatusBarNode() + self.volumeControlStatusBarNode.isHidden = true self.statusBarHost = statusBarHost let statusBarHeight: CGFloat @@ -381,8 +371,8 @@ public class Window1 { self?.presentNative(controller) } - self.hostView.updateSize = { [weak self] size in - self?.updateSize(size) + self.hostView.updateSize = { [weak self] size, duration in + self?.updateSize(size, duration: duration) } self.hostView.view.layer.setInvalidateTracingSublayers { [weak self] in @@ -413,6 +403,13 @@ public class Window1 { self?.cancelInteractiveKeyboardGestures() } + self.hostView.forEachController = { [weak self] f in + self?.forEachViewController({ controller in + f(controller) + return true + }) + } + self.presentationContext.view = self.hostView.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) @@ -590,10 +587,10 @@ public class Window1 { return self.viewController?.view.hitTest(point, with: event) } - func updateSize(_ value: CGSize) { + func updateSize(_ value: CGSize, duration: Double) { let transition: ContainedViewLayoutTransition - if self.hostView.isRotating() { - transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) + if !duration.isZero { + transition = .animated(duration: duration, curve: .easeInOut) } else { transition = .immediate } @@ -654,11 +651,15 @@ public class Window1 { didSet { if self.coveringView !== oldValue { if let oldValue = oldValue { + oldValue.layer.allowsGroupOpacity = true oldValue.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { [weak oldValue] _ in oldValue?.removeFromSuperview() }) } if let coveringView = self.coveringView { + coveringView.layer.removeAnimation(forKey: "opacity") + coveringView.layer.allowsGroupOpacity = false + coveringView.alpha = 1.0 self.hostView.view.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view) if !self.windowLayout.size.width.isZero { coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) @@ -721,7 +722,25 @@ public class Window1 { } } keyboardManager.surfaces = keyboardSurfaces - self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) + + var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) + if let _rootController = self._rootController { + supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations()) + } + supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations()) + supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations()) + + var resolvedOrientations: UIInterfaceOrientationMask + switch self.windowLayout.metrics.widthClass { + case .regular: + resolvedOrientations = supportedOrientations.regularSize + case .compact: + resolvedOrientations = supportedOrientations.compactSize + } + if resolvedOrientations.isEmpty { + resolvedOrientations = [.portrait] + } + self.hostView.updateSupportedInterfaceOrientations(resolvedOrientations) self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures()) self.hostView.updatePreferNavigationUIHidden(self.collectPreferNavigationUIHidden()) From 1913e16c6d181f7513982d8a1683e06c988a0b35 Mon Sep 17 00:00:00 2001 From: Peter Date: Wed, 5 Sep 2018 00:20:47 +0300 Subject: [PATCH 061/245] no message --- Display.xcodeproj/project.pbxproj | 146 +++++++++++++++++++++ Display/ListView.swift | 17 ++- Display/ListViewScroller.swift | 3 + Display/ListViewTapGestureRecognizer.swift | 6 + Display/PresentationContext.swift | 18 ++- Display/ViewController.swift | 1 + 6 files changed, 185 insertions(+), 6 deletions(-) create mode 100644 Display/ListViewTapGestureRecognizer.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index b370dbc105..67c62e82eb 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -48,6 +48,7 @@ D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D03310B3213F232600FC83CD /* ListViewTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */; }; D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; @@ -220,6 +221,7 @@ D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewTapGestureRecognizer.swift; sourceTree = ""; }; D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimizeKeyboardGestureRecognizer.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; @@ -736,6 +738,7 @@ D03AA4E8202E02070056C405 /* ListViewReorderingItemNode.swift */, D03AA4EA202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift */, D03AA5152030C5F80056C405 /* ListViewTempItemNode.swift */, + D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */, ); name = "List Node"; sourceTree = ""; @@ -978,6 +981,7 @@ D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, + D03310B3213F232600FC83CD /* ListViewTapGestureRecognizer.swift in Sources */, D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */, D053DAD12018ECF900993D32 /* WindowCoveringView.swift in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, @@ -1991,6 +1995,144 @@ }; name = "Debug AppStore LLC"; }; + D0CE6EE1213DB54B00BCD44B /* Release AppStore LLC */ = { + 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release AppStore LLC"; + }; + D0CE6EE2213DB54B00BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + }; + name = "Release AppStore LLC"; + }; + D0CE6EE3213DB54B00BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = "Release AppStore LLC"; + }; + D0CE6EE4213DB54B00BCD44B /* Release AppStore LLC */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = NO; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Release AppStore LLC"; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -2003,6 +2145,7 @@ D01159BE1F40E96C0039383E /* Release Hockeyapp */, D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */, D01159BF1F40E96C0039383E /* Release AppStore */, + D0CE6EE4213DB54B00BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Release Hockeyapp"; @@ -2016,6 +2159,7 @@ D05CC2761B69316F00E235A3 /* Release Hockeyapp */, D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56E1CC0115D00F08284 /* Release AppStore */, + D0CE6EE1213DB54B00BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Release Hockeyapp"; @@ -2029,6 +2173,7 @@ D05CC2791B69316F00E235A3 /* Release Hockeyapp */, D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */, D086A56F1CC0115D00F08284 /* Release AppStore */, + D0CE6EE2213DB54B00BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Release Hockeyapp"; @@ -2042,6 +2187,7 @@ D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */, D086A5701CC0115D00F08284 /* Release AppStore */, + D0CE6EE3213DB54B00BCD44B /* Release AppStore LLC */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = "Release Hockeyapp"; diff --git a/Display/ListView.swift b/Display/ListView.swift index 1aadb8ed55..ef9a251be7 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -61,10 +61,13 @@ final class ListViewBackingView: UIView { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let target = self.target, target.limitHitTestToNodes { - if !target.internalHitTest(point, with: event) { + if let target = self.target { + if target.limitHitTestToNodes, !target.internalHitTest(point, with: event) { return nil } + if let result = target.headerHitTest(point, with: event) { + return result + } } return super.hitTest(point, with: event) } @@ -3380,6 +3383,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return true } + fileprivate func headerHitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for (_, headerNode) in self.itemHeaderNodes { + let headerNodeFrame = headerNode.frame + if headerNodeFrame.contains(point) { + return headerNode.hitTest(point.offsetBy(dx: -headerNodeFrame.minX, dy: -headerNodeFrame.minY), with: event) + } + } + return nil + } + private func reorderItemNodeToFront(_ itemNode: ListViewItemNode) { itemNode.view.superview?.bringSubview(toFront: itemNode.view) if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 55ac809eea..b2b021e567 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -20,6 +20,9 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { } @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is ListViewTapGestureRecognizer { + return true + } return false } diff --git a/Display/ListViewTapGestureRecognizer.swift b/Display/ListViewTapGestureRecognizer.swift new file mode 100644 index 0000000000..f5090f99d6 --- /dev/null +++ b/Display/ListViewTapGestureRecognizer.swift @@ -0,0 +1,6 @@ +import Foundation +import UIKit + +public final class ListViewTapGestureRecognizer: UITapGestureRecognizer { + +} diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 83625ab65f..9262ea5546 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -45,12 +45,22 @@ final class PresentationContext { public func present(_ controller: ViewController, on: PresentationSurfaceLevel) { let controllerReady = controller.ready.get() - |> filter({ $0 }) - |> take(1) - |> deliverOnMainQueue - |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) + |> filter({ $0 }) + |> take(1) + |> deliverOnMainQueue + |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) if let _ = self.view, let initialLayout = self.layout { + if controller.lockOrientation { + let orientations: UIInterfaceOrientationMask + if initialLayout.size.width < initialLayout.size.height { + orientations = .portrait + } else { + orientations = .landscape + } + + controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations) + } controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 6c5ceb128f..e2d883bc35 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -52,6 +52,7 @@ open class ViewControllerPresentationArguments { private let presentationContext: PresentationContext public final var supportedOrientations: ViewControllerSupportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) + public final var lockOrientation: Bool = false public func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { return self.supportedOrientations From 7f1f2d08e6cd07beb6d14713806d8e138d953c08 Mon Sep 17 00:00:00 2001 From: overtake <> Date: Tue, 4 Sep 2018 22:37:57 +0100 Subject: [PATCH 062/245] no message --- Display/ListView.swift | 4 +-- Display/ListViewIntermediateState.swift | 4 +-- Display/WindowContent.swift | 43 +++++++++++++------------ 3 files changed, 27 insertions(+), 24 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 1aadb8ed55..1d6e7629ba 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1249,7 +1249,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel state.scrollPosition = (scrollToItem.index, scrollToItem.position) } let itemsCount = self.items.count - state.fixScrollPostition(itemsCount) + state.fixScrollPosition(itemsCount) let actions = { var previousFrames: [Int: CGRect] = [:] @@ -1396,7 +1396,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if let _ = scrollToItem { - state.fixScrollPostition(itemsCount) + state.fixScrollPosition(itemsCount) } if self.debugInfo { diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 9cef7ce086..b8f48c166f 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -279,7 +279,7 @@ struct ListViewState { var stationaryOffset: (Int, CGFloat)? let stackFromBottom: Bool - mutating func fixScrollPostition(_ itemCount: Int) { + mutating func fixScrollPosition(_ itemCount: Int) { if let (fixedIndex, fixedPosition) = self.scrollPosition { for node in self.nodes { if let index = node.index , index == fixedIndex { @@ -740,7 +740,7 @@ struct ListViewState { } if let _ = self.scrollPosition { - self.fixScrollPostition(itemCount) + self.fixScrollPosition(itemCount) } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 45adeb81be..d09a965e1c 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -459,29 +459,32 @@ public class Window1 { if #available(iOSApplicationExtension 11.0, *) { self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: nil, using: { [weak self] notification in - if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { - if firstResponder.textInputMode?.primaryLanguage != nil { - return - } - - strongSelf.keyboardTypeChangeTimer?.invalidate() - let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: { - if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { - if firstResponder.textInputMode?.primaryLanguage != nil { - return - } - - if let keyboardManager = strongSelf.keyboardManager { - let updatedKeyboardHeight = keyboardManager.getCurrentKeyboardHeight() - if !updatedKeyboardHeight.isEqual(to: initialInputHeight) { - strongSelf.updateLayout({ $0.update(inputHeight: updatedKeyboardHeight, transition: .immediate, overrideTransition: false) }) + Queue.mainQueue().async { + if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if firstResponder.textInputMode?.primaryLanguage != nil { + return + } + + strongSelf.keyboardTypeChangeTimer?.invalidate() + let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: { + if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if firstResponder.textInputMode?.primaryLanguage != nil { + return + } + + if let keyboardManager = strongSelf.keyboardManager { + let updatedKeyboardHeight = keyboardManager.getCurrentKeyboardHeight() + if !updatedKeyboardHeight.isEqual(to: initialInputHeight) { + strongSelf.updateLayout({ $0.update(inputHeight: updatedKeyboardHeight, transition: .immediate, overrideTransition: false) }) + } } } - } - }, queue: Queue.mainQueue()) - strongSelf.keyboardTypeChangeTimer = timer - timer.start() + }, queue: Queue.mainQueue()) + strongSelf.keyboardTypeChangeTimer = timer + timer.start() + } } + }) } From 96e457ef28b0e5261f0811a07d89082ef05d3858 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 5 Sep 2018 00:59:04 +0300 Subject: [PATCH 063/245] no message --- Display/WindowContent.swift | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 45adeb81be..39d131fc3b 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -350,7 +350,7 @@ public class Window1 { let boundsSize = self.hostView.view.bounds.size var onScreenNavigationHeight: CGFloat? - if (boundsSize.width.isEqual(to: 375.0) && boundsSize.height.isEqual(to: 812.0)) || boundsSize.height.isEqual(to: 375.0) && boundsSize.width.isEqual(to: 812.0) { + if (isSizeOfScreenWithOnScreenNavigation(boundsSize)) { onScreenNavigationHeight = 34.0 } @@ -458,7 +458,7 @@ public class Window1 { }) if #available(iOSApplicationExtension 11.0, *) { - self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: nil, using: { [weak self] notification in + self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { if firstResponder.textInputMode?.primaryLanguage != nil { return @@ -804,6 +804,10 @@ public class Window1 { private var isFirstLayout = true + private func isSizeOfScreenWithOnScreenNavigation(_ size: CGSize) -> Bool { + return (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) + } + private func commitUpdatingLayout() { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil @@ -827,7 +831,15 @@ public class Window1 { self.hostView.view.setNeedsLayout() } let previousInputOffset = inputHeightOffsetForLayout(self.windowLayout) - self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: updatingLayout.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound) + + var onScreenNavigationHeight: CGFloat? + if let height = updatingLayout.layout.onScreenNavigationHeight { + onScreenNavigationHeight = height + } else if isSizeOfScreenWithOnScreenNavigation(updatingLayout.layout.size) { + onScreenNavigationHeight = 34.0 + } + + self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound) let childLayout = containedLayoutForWindowLayout(self.windowLayout) let childLayoutUpdated = self.updatedContainerLayout != childLayout From 5786eb3bcd447ae27e2493c573ffa50380571626 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 5 Sep 2018 01:27:48 +0300 Subject: [PATCH 064/245] no message --- Display/WindowContent.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 39d131fc3b..ed12e1b793 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -272,6 +272,10 @@ private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { return UIEdgeInsets() } +private func isSizeOfScreenWithOnScreenNavigation(_ size: CGSize) -> Bool { + return (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) +} + public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return true @@ -804,10 +808,6 @@ public class Window1 { private var isFirstLayout = true - private func isSizeOfScreenWithOnScreenNavigation(_ size: CGSize) -> Bool { - return (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) - } - private func commitUpdatingLayout() { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil From 869fc25d81210a302aa68c26999a15f91ed160e6 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 5 Sep 2018 13:23:24 +0300 Subject: [PATCH 065/245] no message --- Display/InteractiveTransitionGestureRecognizer.swift | 10 +++++++--- Display/UIViewController+Navigation.h | 2 ++ Display/UIViewController+Navigation.m | 9 +++++++++ 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Display/InteractiveTransitionGestureRecognizer.swift b/Display/InteractiveTransitionGestureRecognizer.swift index 7b5e5601c3..8d08e8c5bf 100644 --- a/Display/InteractiveTransitionGestureRecognizer.swift +++ b/Display/InteractiveTransitionGestureRecognizer.swift @@ -1,11 +1,15 @@ import Foundation import UIKit -private func hasHorizontalGestures(_ view: UIView) -> Bool { +private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool { if view.disablesInteractiveTransitionGestureRecognizer { return true } + if let point = point, let test = view.interactiveTransitionGestureRecognizerTest, test(point) { + return true + } + if let view = view as? ListViewBackingView { let transform = view.transform let angle: Double = Double(atan2f(Float(transform.b), Float(transform.a))) @@ -18,7 +22,7 @@ private func hasHorizontalGestures(_ view: UIView) -> Bool { } if let superview = view.superview { - return hasHorizontalGestures(superview) + return hasHorizontalGestures(superview, point: point != nil ? view.convert(point!, to: superview) : nil) } else { return false } @@ -47,7 +51,7 @@ class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer { self.firstLocation = touch.location(in: self.view) if let target = self.view?.hitTest(self.firstLocation, with: event) { - if hasHorizontalGestures(target) { + if hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target)) { self.state = .cancelled } } diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 3d3ba93667..ff134d36c1 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -23,6 +23,8 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { @property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer; @property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling; +@property (nonatomic, copy) BOOL (^_Nullable interactiveTransitionGestureRecognizerTest)(CGPoint); + - (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block; - (CGFloat)input_getInputAccessoryHeight; diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 4293086dc9..2aed4b00ad 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -38,6 +38,7 @@ static const void *disablesInteractiveTransitionGestureRecognizerKey = &disables static const void *disableAutomaticKeyboardHandlingKey = &disableAutomaticKeyboardHandlingKey; static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey; static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey; +static const void *interactiveTransitionGestureRecognizerTestKey = &interactiveTransitionGestureRecognizerTestKey; static const void *UIViewControllerHintWillBePresentedInPreviewingContextKey = &UIViewControllerHintWillBePresentedInPreviewingContextKey; static bool notyfyingShiftState = false; @@ -231,6 +232,14 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey]; } +- (BOOL (^)(CGPoint))interactiveTransitionGestureRecognizerTest { + return [self associatedObjectForKey:interactiveTransitionGestureRecognizerTestKey]; +} + +- (void)setInteractiveTransitionGestureRecognizerTest:(BOOL (^)(CGPoint))block { + [self setAssociatedObject:[block copy] forKey:interactiveTransitionGestureRecognizerTestKey]; +} + - (UIResponderDisableAutomaticKeyboardHandling)disableAutomaticKeyboardHandling { return (UIResponderDisableAutomaticKeyboardHandling)[[self associatedObjectForKey:disableAutomaticKeyboardHandlingKey] unsignedIntegerValue]; } From 8242714fd44f52ce1aa9b93776714de238f62d02 Mon Sep 17 00:00:00 2001 From: overtake <> Date: Wed, 5 Sep 2018 18:32:05 +0100 Subject: [PATCH 066/245] no message --- Display/GridNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index a32e98134f..05f7f37f2d 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -659,7 +659,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { for (index, itemNode) in self.itemNodes { if stationaryItemIndices.contains(index) { //let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y - selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) + selectedContentOffset = CGPoint(x: 0.0, y: max(self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y, self.scrollView.bounds.minY)) break } } From af504a333ec8478ab081a1d92b58cc3a31a39237 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 7 Sep 2018 00:40:15 +0300 Subject: [PATCH 067/245] no message --- Display/ActionSheetControllerNode.swift | 3 +++ Display/ContextMenuController.swift | 12 +++++++----- Display/ContextMenuNode.swift | 15 ++++++++++++++- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 76a8227cc7..e7c0878f2b 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -84,6 +84,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { insets.left = floor((layout.size.width - containerWidth) / 2.0) insets.right = insets.left + if insets.bottom > 0 { + insets.bottom -= 12.0 + } self.validLayout = layout diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 732968f662..45cd12438c 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -16,14 +16,16 @@ public final class ContextMenuController: ViewController { private let actions: [ContextMenuAction] private let catchTapsOutside: Bool + private let hasHapticFeedback: Bool private var layout: ContainerViewLayout? public var dismissed: (() -> Void)? - public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false) { + public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false) { self.actions = actions self.catchTapsOutside = catchTapsOutside + self.hasHapticFeedback = hasHapticFeedback super.init(navigationBarPresentationData: nil) } @@ -32,13 +34,13 @@ public final class ContextMenuController: ViewController { fatalError("init(coder:) has not been implemented") } - open override func loadDisplayNode() { + override public func loadDisplayNode() { self.displayNode = ContextMenuNode(actions: self.actions, dismiss: { [weak self] in self?.dismissed?() self?.contextMenuNode.animateOut { self?.presentingViewController?.dismiss(animated: false) } - }, catchTapsOutside: self.catchTapsOutside) + }, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback) self.displayNodeDidLoad() } @@ -55,7 +57,7 @@ public final class ContextMenuController: ViewController { } } - override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { super.containerLayoutUpdated(layout, transition: transition) if self.layout != nil && self.layout! != layout { @@ -78,7 +80,7 @@ public final class ContextMenuController: ViewController { } } - open override func viewWillAppear(_ animated: Bool) { + override public func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) self.contextMenuNode.animateIn() diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index 9ec3f64072..b2eae34d50 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -144,7 +144,9 @@ final class ContextMenuNode: ASDisplayNode { private var dismissedByTouchOutside = false private let catchTapsOutside: Bool - init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, catchTapsOutside: Bool) { + private let feedback: HapticFeedback? + + init(actions: [ContextMenuAction], dismiss: @escaping () -> Void, catchTapsOutside: Bool, hasHapticFeedback: Bool = false) { self.actions = actions self.dismiss = dismiss self.catchTapsOutside = catchTapsOutside @@ -156,6 +158,13 @@ final class ContextMenuNode: ASDisplayNode { return ContextMenuActionNode(action: action) } + if hasHapticFeedback { + self.feedback = HapticFeedback() + self.feedback?.prepareImpact() + } else { + self.feedback = nil + } + super.init() self.containerNode.addSubnode(self.scrollNode) @@ -219,6 +228,10 @@ final class ContextMenuNode: ASDisplayNode { self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: containerPosition.x, y: containerPosition.y + (self.arrowOnBottom ? 1.0 : -1.0) * self.containerNode.bounds.size.height / 2.0)), to: NSValue(cgPoint: containerPosition), keyPath: "position", duration: 0.4) self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + + if let feedback = self.feedback { + feedback.impact() + } } func animateOut(completion: @escaping () -> Void) { From 6f9f6e8e88b7221fb620f177eca5ff37875034bc Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 7 Sep 2018 01:30:07 +0300 Subject: [PATCH 068/245] no message --- Display/ViewController.swift | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index e2d883bc35..1f0926119f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -393,4 +393,63 @@ open class ViewControllerPresentationArguments { } return nil } + + public func traceVisibility() -> Bool { + if !self.isViewLoaded { + return false + } + return traceViewVisibility(view: self.view, rect: self.view.bounds) + } +} + +private func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { + if layer.bounds.intersects(rect) { + if layer.isOpaque { + return true + } + if let backgroundColor = layer.backgroundColor { + var alpha: CGFloat = 0.0 + UIColor(cgColor: backgroundColor).getRed(nil, green: nil, blue: nil, alpha: &alpha) + if alpha > 0.95 { + return true + } + } + if let sublayers = layer.sublayers { + for sublayer in sublayers { + let sublayerRect = layer.convert(rect, to: sublayer) + if traceIsOpaque(layer: sublayer, rect: sublayerRect) { + return true + } + } + } + return false + } else { + return false + } +} + +private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool { + if view.window == nil { + return false + } + if view is UIWindow { + return true + } else if let superview = view.superview, let siblings = superview.layer.sublayers { + if let index = siblings.firstIndex(of: view.layer) { + let viewFrame = view.convert(rect, to: superview) + for i in (index + 1) ..< siblings.count { + if siblings[i].frame.contains(viewFrame) { + let siblingSubframe = view.layer.convert(viewFrame, to: siblings[i]) + if traceIsOpaque(layer: siblings[i], rect: siblingSubframe) { + return false + } + } + } + return true + } else { + return false + } + } else { + return false + } } From f89366d354caedfd54428f399da0c30cbd1c1b70 Mon Sep 17 00:00:00 2001 From: overtake <> Date: Thu, 6 Sep 2018 23:40:24 +0100 Subject: [PATCH 069/245] no message --- Display/GridNode.swift | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 05f7f37f2d..16325820b7 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -659,16 +659,22 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { for (index, itemNode) in self.itemNodes { if stationaryItemIndices.contains(index) { //let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y - selectedContentOffset = CGPoint(x: 0.0, y: max(self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y, self.scrollView.bounds.minY)) + selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) break } } + if let _ = selectedContentOffset, self.itemNodes.count > 0, let itemNode = self.itemNodes[0], self.scrollView.contentInset.top + self.scrollView.contentOffset.y <= itemNode.frame.maxY { + selectedContentOffset = self.scrollView.contentOffset + } + if let selectedContentOffset = selectedContentOffset { contentOffset = selectedContentOffset } else { contentOffset = self.scrollView.contentOffset } + + case .all: var selectedContentOffset: CGPoint? for (index, itemNode) in self.itemNodes { From dc78968b28c0681bcb05737573e5b9714ae218a5 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 7 Sep 2018 02:01:02 +0300 Subject: [PATCH 070/245] no message --- Display/ViewController.swift | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 1f0926119f..786152f0c9 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -404,8 +404,11 @@ open class ViewControllerPresentationArguments { private func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { if layer.bounds.intersects(rect) { - if layer.isOpaque { - return true + if layer.isHidden { + return false + } + if layer.opacity < 0.01 { + return false } if let backgroundColor = layer.backgroundColor { var alpha: CGFloat = 0.0 @@ -429,12 +432,15 @@ private func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { } private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool { - if view.window == nil { + if view.isHidden { return false } if view is UIWindow { return true } else if let superview = view.superview, let siblings = superview.layer.sublayers { + if view.window == nil { + return false + } if let index = siblings.firstIndex(of: view.layer) { let viewFrame = view.convert(rect, to: superview) for i in (index + 1) ..< siblings.count { @@ -445,7 +451,7 @@ private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool { } } } - return true + return traceViewVisibility(view: superview, rect: viewFrame) } else { return false } From def73a2713adc746f69036c8a713fc95334b335e Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 7 Sep 2018 02:08:09 +0300 Subject: [PATCH 071/245] no message --- Display/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 786152f0c9..c1a8b3b980 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -441,7 +441,7 @@ private func traceViewVisibility(view: UIView, rect: CGRect) -> Bool { if view.window == nil { return false } - if let index = siblings.firstIndex(of: view.layer) { + if let index = siblings.index(where: { $0 === view.layer }) { let viewFrame = view.convert(rect, to: superview) for i in (index + 1) ..< siblings.count { if siblings[i].frame.contains(viewFrame) { From b4fb56c510d752054cc1c0881b764cbb62d03d8b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 7 Sep 2018 22:33:15 +0300 Subject: [PATCH 072/245] no message --- Display/SwitchNode.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index 8d920ef87a..cdfc498be6 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -84,6 +84,7 @@ open class SwitchNode: ASDisplayNode { } @objc func switchValueChanged(_ view: UISwitch) { + self._isOn = view.isOn self.valueUpdated?(view.isOn) } } From 351e852f71dacc5cfc0da589983995b870ffe6f5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Sep 2018 17:14:04 +0100 Subject: [PATCH 073/245] no message --- Display/WindowContent.swift | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index ed12e1b793..2942773790 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -339,6 +339,8 @@ public class Window1 { self.volumeControlStatusBarNode = VolumeControlStatusBarNode() self.volumeControlStatusBarNode.isHidden = true + let boundsSize = self.hostView.view.bounds.size + self.statusBarHost = statusBarHost let statusBarHeight: CGFloat if let statusBarHost = statusBarHost { @@ -348,11 +350,14 @@ public class Window1 { } else { self.statusBarManager = nil self.keyboardManager = nil - statusBarHeight = 20.0 + + if (isSizeOfScreenWithOnScreenNavigation(boundsSize)) { + statusBarHeight = 44.0 + } else { + statusBarHeight = 20.0 + } } - let boundsSize = self.hostView.view.bounds.size - var onScreenNavigationHeight: CGFloat? if (isSizeOfScreenWithOnScreenNavigation(boundsSize)) { onScreenNavigationHeight = 34.0 @@ -817,7 +822,11 @@ public class Window1 { if let statusBarHost = self.statusBarHost { statusBarHeight = statusBarHost.statusBarFrame.size.height } else { - statusBarHeight = 20.0 + if isSizeOfScreenWithOnScreenNavigation(updatingLayout.layout.size) { + statusBarHeight = 44.0 + } else { + statusBarHeight = 20.0 + } } let statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { From 99bc4d57828774f28222990b803d3260300931fb Mon Sep 17 00:00:00 2001 From: Peter Date: Tue, 11 Sep 2018 22:19:36 +0300 Subject: [PATCH 074/245] no message --- Display/NavigationController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index ea304a4319..40c0848d8c 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -816,6 +816,9 @@ open class NavigationController: UINavigationController, ContainableController, var poppedControllers: [UIViewController] = [] var found = false var controllers = self.viewControllers + if !controllers.contains(where: { $0 === viewController }) { + return nil + } while !controllers.isEmpty { if controllers[controllers.count - 1] === viewController { found = true From bffb8eb8ca39b9be2fcd161ae1c876adbe5b0fec Mon Sep 17 00:00:00 2001 From: Peter Date: Mon, 17 Sep 2018 19:15:29 +0100 Subject: [PATCH 075/245] no message --- Display/ListView.swift | 75 ++++++++++++++++--------- Display/ListViewIntermediateState.swift | 10 ++-- Display/ListViewItem.swift | 2 +- Display/NativeWindowHostView.swift | 16 +++--- 4 files changed, 63 insertions(+), 40 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 5b7ad477cc..999db10275 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -199,7 +199,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var beganInteractiveDragging: () -> Void = { } public final var didEndScrolling: (() -> Void)? - public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in } + public final var reorderItem: (Int, Int, Any?) -> Signal = { _, _, _ in return .single(false) } private final var animations: [ListViewAnimation] = [] private final var actionsForVSync: [() -> ()] = [] @@ -216,16 +216,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var flashNodesDelayTimer: Foundation.Timer? private var highlightedItemIndex: Int? private var reorderNode: ListViewReorderingItemNode? + private var reorderFeedback: HapticFeedback? + private var reorderFeedbackDisposable: MetaDisposable? private let waitingForNodesDisposable = MetaDisposable() - - public func reportDurationInMS(duration: Int, smallDropEvent: Double, largeDropEvent: Double) { - print("reportDurationInMS duration: \(duration), smallDropEvent: \(smallDropEvent), largeDropEvent: \(largeDropEvent)") - } - - public func reportStackTrace(stack: String!, withSlide slide: String!) { - NSLog("reportStackTrace stack: \(stack)\n\nslide: \(slide)") - } override public init() { class DisplayLinkProxy: NSObject { @@ -331,6 +325,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } self.waitingForNodesDisposable.dispose() + self.reorderFeedbackDisposable?.dispose() } private func displayLinkEvent() { @@ -435,7 +430,19 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if reorderNode.currentState?.0 != reorderItemIndex || reorderNode.currentState?.1 != toIndex { reorderNode.currentState = (reorderItemIndex, toIndex) //print("reorder \(reorderItemIndex) to \(toIndex) offset \(offset)") - self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState) + if self.reorderFeedbackDisposable == nil { + self.reorderFeedbackDisposable = MetaDisposable() + } + self.reorderFeedbackDisposable?.set((self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState) + |> deliverOnMainQueue).start(next: { [weak self] value in + guard let strongSelf = self, value else { + return + } + if strongSelf.reorderFeedback == nil { + strongSelf.reorderFeedback = HapticFeedback() + } + strongSelf.reorderFeedback?.tap() + })) } } } @@ -1098,7 +1105,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: ListViewItemNode?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNode, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { + private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -1106,12 +1113,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: previousNode, params: params, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in + }, node: { + assert(Queue.mainQueue().isCurrent()) + return previousNode.syncWith({ $0 })! + }, params: params, previousItem: previousItem, nextItem: nextItem, animation: updateAnimation, completion: { (layout, apply) in if Thread.isMainThread { if synchronous { completion(previousNode, layout, { return (nil, { - previousNode.index = index + assert(Queue.mainQueue().isCurrent()) + previousNode.with({ $0.index = index }) apply() }) }) @@ -1119,7 +1130,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.async { completion(previousNode, layout, { return (nil, { - previousNode.index = index + assert(Queue.mainQueue().isCurrent()) + previousNode.with({ $0.index = index }) apply() }) }) @@ -1128,7 +1140,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { completion(previousNode, layout, { return (nil, { - previousNode.index = index + assert(Queue.mainQueue().isCurrent()) + previousNode.with({ $0.index = index }) apply() }) }) @@ -1142,8 +1155,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.async(f) } }, params: params, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in + //assert(Queue.mainQueue().isCurrent()) itemNode.index = index - completion(itemNode, ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) + completion(QueueLocalObject(queue: Queue.mainQueue(), generate: { return itemNode }), ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) }) } } @@ -1153,7 +1167,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel nodes.reserveCapacity(self.itemNodes.count) for node in self.itemNodes { if let index = node.index { - nodes.append(.Node(index: index, frame: node.apparentFrame, referenceNode: node)) + nodes.append(.Node(index: index, frame: node.apparentFrame, referenceNode: QueueLocalObject(queue: Queue.mainQueue(), generate: { + return node + }))) } else { nodes.append(.Placeholder(frame: node.apparentFrame)) } @@ -1226,13 +1242,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - var previousNodes: [Int: ListViewItemNode] = [:] + var previousNodes: [Int: QueueLocalObject] = [:] for insertedItem in sortedIndicesAndItems { self.items.insert(insertedItem.item, at: insertedItem.index) if let previousIndex = insertedItem.previousIndex { for itemNode in self.itemNodes { if itemNode.index == previousIndex { - previousNodes[insertedItem.index] = itemNode + previousNodes[insertedItem.index] = QueueLocalObject(queue: Queue.mainQueue(), generate: { return itemNode }) } } } @@ -1242,7 +1258,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.items[updatedItem.index] = updatedItem.item for itemNode in self.itemNodes { if itemNode.index == updatedItem.previousIndex { - previousNodes[updatedItem.index] = itemNode + previousNodes[updatedItem.index] = QueueLocalObject(queue: Queue.mainQueue(), generate: { return itemNode }) break } } @@ -1539,7 +1555,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, node: referenceNode, params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in + }, node: { + assert(Queue.mainQueue().isCurrent()) + return referenceNode.syncWith({ $0 })! + }, params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in var updatedState = state var updatedOperations = operations @@ -1579,7 +1598,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { + private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { let animatedInsertIndices = inputAnimatedInsertIndices var state = inputState var previousNodes = inputPreviousNodes @@ -1638,7 +1657,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: ListViewItemNode], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { + private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { var state = inputState var operations = inputOperations var updateIndicesAndItems = updateIndicesAndItems @@ -1938,7 +1957,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var takenPreviousNodes = Set() for operation in operations { if case let .InsertNode(_, _, _, node, _, _) = operation { - takenPreviousNodes.insert(node) + takenPreviousNodes.insert(node.syncWith({ $0 })!) } } @@ -1946,7 +1965,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for operation in operations { switch operation { - case let .InsertNode(index, offsetDirection, nodeAnimated, node, layout, apply): + case let .InsertNode(index, offsetDirection, nodeAnimated, nodeObject, layout, apply): + let node = nodeObject.syncWith({ $0 })! var previousFrame: CGRect? for (previousNode, frame) in previousApparentFrames { if previousNode === node { @@ -1989,10 +2009,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } } - case let .InsertDisappearingPlaceholder(index, referenceNode, offsetDirection): + case let .InsertDisappearingPlaceholder(index, referenceNodeObject, offsetDirection): var height: CGFloat? var previousLayout: ListViewItemNodeLayout? + let referenceNode = referenceNodeObject.syncWith({ $0 })! + for (node, previousFrame) in previousApparentFrames { if node === referenceNode { height = previousFrame.size.height @@ -2004,7 +2026,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { let tempNode = ListViewTempItemNode(layerBacked: true) - //referenceNode.copyHeightAndApparentHeightAnimations(to: tempNode) self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) } else { referenceNode.index = nil diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index b8f48c166f..184160ffcb 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -203,14 +203,14 @@ struct TransactionState { struct PendingNode { let index: Int - let node: ListViewItemNode + let node: QueueLocalObject let apply: () -> (Signal?, () -> Void) let frame: CGRect let apparentHeight: CGFloat } enum ListViewStateNode { - case Node(index: Int, frame: CGRect, referenceNode: ListViewItemNode?) + case Node(index: Int, frame: CGRect, referenceNode: QueueLocalObject?) case Placeholder(frame: CGRect) var index: Int? { @@ -687,7 +687,7 @@ struct ListViewState { return height } - mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (Signal?, () -> Void), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { + mutating func insertNode(_ itemIndex: Int, node: QueueLocalObject, layout: ListViewItemNodeLayout, apply: @escaping () -> (Signal?, () -> Void), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) let nodeOrigin: CGPoint @@ -848,8 +848,8 @@ struct ListViewState { } enum ListViewStateOperation { - case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) - case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, node: QueueLocalObject, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) + case InsertDisappearingPlaceholder(index: Int, referenceNode: QueueLocalObject, offsetDirection: ListViewInsertionOffsetDirection) case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) case Remap([Int: Int]) case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 3e83961725..dead8c13c0 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -34,7 +34,7 @@ public struct ListViewItemConfigureNodeFlags: OptionSet { public protocol ListViewItem { func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 8c4871553d..9ab58e2eeb 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -92,14 +92,20 @@ private final class NativeWindow: UIWindow, WindowHost { var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? var forEachControllerImpl: (((ViewController) -> Void) -> Void)? - private var frameTransition: ContainedViewLayoutTransition? - override var frame: CGRect { get { return super.frame } set(value) { let sizeUpdated = super.frame.size != value.size - if sizeUpdated, let transition = self.frameTransition, case let .animated(duration, curve) = transition { + + var frameTransition: ContainedViewLayoutTransition = .immediate + if #available(iOSApplicationExtension 9.0, *) { + let duration = UIView.inheritedAnimationDuration + if !duration.isZero { + frameTransition = .animated(duration: duration, curve: .easeInOut) + } + } + if sizeUpdated, case let .animated(duration, curve) = frameTransition { let previousFrame = super.frame super.frame = value self.layer.animateFrame(from: previousFrame, to: value, duration: duration, timingFunction: curve.timingFunction) @@ -149,11 +155,7 @@ private final class NativeWindow: UIWindow, WindowHost { override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { self.updateIsUpdatingOrientationLayout?(true) - if !arg2.isZero { - self.frameTransition = .animated(duration: arg2, curve: .easeInOut) - } super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) - self.frameTransition = nil self.updateIsUpdatingOrientationLayout?(false) self.updateToInterfaceOrientation?() From 30c7b8bbd5ccf59886a8d27b3c994e9c9265fec5 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 17 Sep 2018 20:34:21 +0100 Subject: [PATCH 076/245] no message --- Display.xcodeproj/project.pbxproj | 4 + Display/DeviceMetrics.swift | 117 ++++++++++++++++++++++++++++++ Display/WindowContent.swift | 44 +++-------- 3 files changed, 133 insertions(+), 32 deletions(-) create mode 100644 Display/DeviceMetrics.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 67c62e82eb..29738932f0 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,6 +7,7 @@ objects = { /* Begin PBXBuildFile section */ + 09E12476214D0978009FC9C3 /* DeviceMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */; }; D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701972029CAD6006B9E34 /* TooltipController.swift */; }; D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */; }; D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; @@ -191,6 +192,7 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetrics.swift; sourceTree = ""; }; D00701972029CAD6006B9E34 /* TooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipControllerNode.swift; sourceTree = ""; }; D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; @@ -535,6 +537,7 @@ D0FA08C120487A8600DD23FC /* HapticFeedback.swift */, D06B76DA20592A97006E9EEA /* LayoutSizes.swift */, D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */, + 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */, ); name = Utils; sourceTree = ""; @@ -1088,6 +1091,7 @@ D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */, D0C0B59D1EE022CC000F4D2C /* NavigationBarContentNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, + 09E12476214D0978009FC9C3 /* DeviceMetrics.swift in Sources */, D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift new file mode 100644 index 0000000000..01354976ec --- /dev/null +++ b/Display/DeviceMetrics.swift @@ -0,0 +1,117 @@ +import UIKit + +enum DeviceMetrics { + case iPhone4 + case iPhone5 + case iPhone6 + case iPhone6Plus + case iPhoneX + case iPhoneXSMax + case iPad + case iPadPro + + static let allDevices = [iPhone4, iPhone5, iPhone6, iPhone6Plus, iPhoneX, iPhoneXSMax, iPad, iPadPro] + + static func forScreenSize(_ size: CGSize) -> DeviceMetrics? { + for device in allDevices { + let width = device.screenSize.width + let height = device.screenSize.height + + if (size.width.isEqual(to: width) && size.height.isEqual(to: height)) || size.height.isEqual(to: width) && size.width.isEqual(to: height) { + return device + } + } + return nil + } + + var screenSize: CGSize { + switch self { + case .iPhone4: + return CGSize(width: 320.0, height: 480.0) + case .iPhone5: + return CGSize(width: 320.0, height: 568.0) + case .iPhone6: + return CGSize(width: 375.0, height: 667.0) + case .iPhone6Plus: + return CGSize(width: 414.0, height: 736.0) + case .iPhoneX: + return CGSize(width: 375.0, height: 812.0) + case .iPhoneXSMax: + return CGSize(width: 414.0, height: 896.0) + case .iPad: + return CGSize(width: 768.0, height: 1024.0) + case .iPadPro: + return CGSize(width: 1024.0, height: 1366.0) + } + } + + func safeAreaInsets(inLandscape: Bool) -> UIEdgeInsets { + switch self { + case .iPhoneX, .iPhoneXSMax: + return inLandscape ? UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) : UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) + default: + return UIEdgeInsets.zero + } + } + + func onScreenNavigationHeight(inLandscape: Bool) -> CGFloat { + switch self { + case .iPhoneX, .iPhoneXSMax: + return inLandscape ? 21.0 : 34.0 + default: + return 0.0 + } + } + + func standardInputHeight(inLandscape: Bool) -> CGFloat { + if inLandscape { + switch self { + case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus: + return 162.0 + case .iPhoneX, .iPhoneXSMax: + return 171.0 + case .iPad, .iPadPro: + return 264.0 + } + } else { + switch self { + case .iPhone4, .iPhone5, .iPhone6: + return 216.0 + case .iPhone6Plus: + return 226.0 + case .iPhoneX: + return 291.0 + case .iPhoneXSMax: + return 301.0 + case .iPad, .iPadPro: + return 264.0 + } + } + } + + func predictiveInputHeight(inLandscape: Bool) -> CGFloat { + if inLandscape { + switch self { + case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus: + return 38.0 + case .iPhoneX, .iPhoneXSMax: + return 38.0 + case .iPad, .iPadPro: + return 42.0 + } + } else { + switch self { + case .iPhone4, .iPhone5: + return 37.0 + case .iPhone6, .iPhoneX: + return 42.0 + case .iPhone6Plus: + return 42.0 + case .iPhoneXSMax: + return 45.0 + case .iPad, .iPadPro: + return 42.0 + } + } + } +} diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 2942773790..71a59f545a 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -100,32 +100,19 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container } var resolvedSafeInsets = layout.safeInsets - if layout.size.height.isEqual(to: 375.0) && layout.size.width.isEqual(to: 812.0) { - resolvedSafeInsets.left = 44.0 - resolvedSafeInsets.right = 44.0 - } - var standardInputHeight: CGFloat = 216.0 var predictiveHeight: CGFloat = 42.0 - if layout.size.width.isEqual(to: 320.0) { - standardInputHeight = 216.0 - predictiveHeight = 37.0 - } else if layout.size.width.isEqual(to: 375.0) { - standardInputHeight = 291.0 - predictiveHeight = 42.0 - } else if layout.size.width.isEqual(to: 414.0) { - standardInputHeight = 226.0 - predictiveHeight = 42.0 - } else if layout.size.width.isEqual(to: 480.0) || layout.size.width.isEqual(to: 568.0) || layout.size.width.isEqual(to: 667.0) || layout.size.width.isEqual(to: 736.0) { - standardInputHeight = 162.0 - predictiveHeight = 38.0 - } else if layout.size.width.isEqual(to: 812.0) { - standardInputHeight = 171.0 - predictiveHeight = 38.0 - } else if layout.size.width.isEqual(to: 768.0) || layout.size.width.isEqual(to: 1024.0) { - standardInputHeight = 264.0 - predictiveHeight = 42.0 + if let metrics = DeviceMetrics.forScreenSize(layout.size) { + let isLandscape = layout.size.width > layout.size.height + let safeAreaInsets = metrics.safeAreaInsets(inLandscape: isLandscape) + if safeAreaInsets.left > 0.0 { + resolvedSafeInsets.left = safeAreaInsets.left + resolvedSafeInsets.right = safeAreaInsets.right + } + + standardInputHeight = metrics.standardInputHeight(inLandscape: isLandscape) + predictiveHeight = metrics.predictiveInputHeight(inLandscape: isLandscape) } standardInputHeight += predictiveHeight @@ -262,18 +249,11 @@ private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { } private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { - if (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) { - if size.width.isEqual(to: 375.0) { - return UIEdgeInsets(top: 44.0, left: 0.0, bottom: 0.0, right: 0.0) - } else { - return UIEdgeInsets(top: 0.0, left: 44.0, bottom: 0.0, right: 44.0) - } - } - return UIEdgeInsets() + return DeviceMetrics.forScreenSize(size)?.safeAreaInsets(inLandscape: size.width > size.height) ?? UIEdgeInsets.zero } private func isSizeOfScreenWithOnScreenNavigation(_ size: CGSize) -> Bool { - return (size.width.isEqual(to: 375.0) && size.height.isEqual(to: 812.0)) || size.height.isEqual(to: 375.0) && size.width.isEqual(to: 812.0) + return (DeviceMetrics.forScreenSize(size)?.onScreenNavigationHeight(inLandscape: size.width > size.height) ?? 0.0) > 0.0 } public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { From 2487877be332a9aa4307739c6cf256e486b7c64a Mon Sep 17 00:00:00 2001 From: Peter Date: Thu, 20 Sep 2018 00:11:33 +0100 Subject: [PATCH 077/245] no message --- Display/ImmediateTextNode.swift | 14 +++++++++++++- Display/NativeWindowHostView.swift | 4 ++-- Display/UIWindow+OrientationChange.h | 2 +- Display/WindowContent.swift | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index a9ffdf3f4c..7b21b1e79a 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -47,7 +47,19 @@ public class ImmediateTextNode: TextNode { if let point = point { if let (index, attributes) = strongSelf.attributesAtPoint(CGPoint(x: point.x, y: point.y)) { if let selectedAttribute = strongSelf.highlightAttributeAction?(attributes) { - rects = strongSelf.attributeRects(name: selectedAttribute.rawValue, at: index) + let initialRects = strongSelf.lineAndAttributeRects(name: selectedAttribute.rawValue, at: index) + if let initialRects = initialRects, case .center = strongSelf.textAlignment { + var mappedRects: [CGRect] = [] + for i in 0 ..< initialRects.count { + let lineRect = initialRects[i].0 + var itemRect = initialRects[i].1 + itemRect.origin.x = floor((strongSelf.bounds.size.width - lineRect.width) / 2.0) + itemRect.origin.x + mappedRects.append(itemRect) + } + rects = mappedRects + } else { + rects = strongSelf.attributeRects(name: selectedAttribute.rawValue, at: index) + } } } } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 9ab58e2eeb..445e1c498b 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -153,13 +153,13 @@ private final class NativeWindow: UIWindow, WindowHost { self.layoutSubviewsEvent?() } - override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { + /*override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { self.updateIsUpdatingOrientationLayout?(true) super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) self.updateIsUpdatingOrientationLayout?(false) self.updateToInterfaceOrientation?() - } + }*/ func present(_ controller: ViewController, on level: PresentationSurfaceLevel) { self.presentController?(controller, level) diff --git a/Display/UIWindow+OrientationChange.h b/Display/UIWindow+OrientationChange.h index b6f1320618..5ff3094647 100644 --- a/Display/UIWindow+OrientationChange.h +++ b/Display/UIWindow+OrientationChange.h @@ -6,6 +6,6 @@ + (void)addPostDeviceOrientationDidChangeBlock:(void (^)())block; + (bool)isDeviceRotating; -- (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3; +//- (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3; @end diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 71a59f545a..79d7329c35 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -745,7 +745,7 @@ public class Window1 { } if !UIWindow.isDeviceRotating() { - if !self.hostView.isUpdatingOrientationLayout { + if true || !self.hostView.isUpdatingOrientationLayout { self.commitUpdatingLayout() } else { self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in From ded0bc895f1045f3bb1e1f00ac73683152d1587e Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 20 Sep 2018 21:26:29 +0100 Subject: [PATCH 078/245] no message --- Display/NativeWindowHostView.swift | 20 ++++++++++++-------- Display/TabBarContollerNode.swift | 2 +- Display/TabBarController.swift | 11 ++++++++--- Display/TabBarNode.swift | 14 +++++++------- Display/TabBarTapRecognizer.swift | 27 ++++++++++++++++++++++++++- Display/ViewController.swift | 1 + 6 files changed, 55 insertions(+), 20 deletions(-) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 445e1c498b..1be378e317 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -76,6 +76,12 @@ private class WindowRootViewController: UIViewController { self.transitionToSize?(size, coordinator.transitionDuration) } } + + override func loadView() { + self.view = UIView() + self.view.isOpaque = false + self.view.backgroundColor = nil + } } private final class NativeWindow: UIWindow, WindowHost { @@ -210,14 +216,14 @@ private final class NativeWindow: UIWindow, WindowHost { } } -public func nativeWindowHostView() -> WindowHostView { +public func nativeWindowHostView() -> (UIWindow, WindowHostView) { let window = NativeWindow(frame: UIScreen.main.bounds) let rootViewController = WindowRootViewController() window.rootViewController = rootViewController rootViewController.viewWillAppear(false) + rootViewController.view.frame = CGRect(origin: CGPoint(), size: window.bounds.size) rootViewController.viewDidAppear(false) - rootViewController.view.isHidden = true let hostView = WindowHostView(view: window, isRotating: { return window.isRotating() @@ -283,13 +289,11 @@ public func nativeWindowHostView() -> WindowHostView { } rootViewController.presentController = { [weak hostView] controller, level, animated, completion in - if let strongSelf = hostView { - strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) - if let completion = completion { - completion() - } + if let hostView = hostView { + hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) + completion?() } } - return hostView + return (window, hostView) } diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index f4e5d3b420..b34cf744f6 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -14,7 +14,7 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int) -> Void) { + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool) -> Void) { self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) super.init() diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index d3770e25c8..d53db8f195 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -87,7 +87,7 @@ open class TabBarController: ViewController { private var debugTapCounter: (Double, Int) = (0.0, 0) override open func loadDisplayNode() { - self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index in + self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap in if let strongSelf = self { if strongSelf.selectedIndex == index { let timestamp = CACurrentMediaTime() @@ -110,11 +110,16 @@ open class TabBarController: ViewController { if let validLayout = strongSelf.validLayout { strongSelf.controllers[index].containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) } - strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in + strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() + |> deliverOnMainQueue).start(next: { _ in if let strongSelf = self { if strongSelf.selectedIndex == index { if let controller = strongSelf.currentController { - controller.scrollToTopWithTabBar?() + if longTap { + controller.longTapWithTabBar?() + } else { + controller.scrollToTopWithTabBar?() + } } } else { strongSelf.selectedIndex = index diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 463ed3e057..de3fcf85aa 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -167,7 +167,7 @@ class TabBarNode: ASDisplayNode { } } - private let itemSelected: (Int) -> Void + private let itemSelected: (Int, Bool) -> Void private var theme: TabBarControllerTheme private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? @@ -178,7 +178,7 @@ class TabBarNode: ASDisplayNode { let separatorNode: ASDisplayNode private var tabBarNodeContainers: [TabBarNodeContainer] = [] - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int) -> Void) { + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool) -> Void) { self.itemSelected = itemSelected self.theme = theme @@ -201,9 +201,9 @@ class TabBarNode: ASDisplayNode { super.didLoad() self.view.addGestureRecognizer(TabBarTapRecognizer(tap: { [weak self] point in - if let strongSelf = self { - strongSelf.tapped(at: point) - } + self?.tapped(at: point, longTap: false) + }, longTap: { [weak self] point in + self?.tapped(at: point, longTap: true) })) } @@ -364,7 +364,7 @@ class TabBarNode: ASDisplayNode { } } - private func tapped(at location: CGPoint) { + private func tapped(at location: CGPoint, longTap: Bool) { if let bottomInset = self.validLayout?.3 { if location.y > self.bounds.size.height - bottomInset { return @@ -384,7 +384,7 @@ class TabBarNode: ASDisplayNode { } if let closestNode = closestNode { - self.itemSelected(closestNode.0) + self.itemSelected(closestNode.0, longTap) } } } diff --git a/Display/TabBarTapRecognizer.swift b/Display/TabBarTapRecognizer.swift index 4cbe02b777..616ced31e5 100644 --- a/Display/TabBarTapRecognizer.swift +++ b/Display/TabBarTapRecognizer.swift @@ -1,13 +1,17 @@ import Foundation import UIKit +import SwiftSignalKit final class TabBarTapRecognizer: UIGestureRecognizer { private let tap: (CGPoint) -> Void + private let longTap: (CGPoint) -> Void private var initialLocation: CGPoint? + private var longTapTimer: SwiftSignalKit.Timer? - init(tap: @escaping (CGPoint) -> Void) { + init(tap: @escaping (CGPoint) -> Void, longTap: @escaping (CGPoint) -> Void) { self.tap = tap + self.longTap = longTap super.init(target: nil, action: nil) } @@ -16,6 +20,8 @@ final class TabBarTapRecognizer: UIGestureRecognizer { super.reset() self.initialLocation = nil + self.longTapTimer?.invalidate() + self.longTapTimer = nil } override func touchesBegan(_ touches: Set, with event: UIEvent) { @@ -23,6 +29,19 @@ final class TabBarTapRecognizer: UIGestureRecognizer { if self.initialLocation == nil { self.initialLocation = touches.first?.location(in: self.view) + let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in + guard let strongSelf = self else { + return + } + if let initialLocation = strongSelf.initialLocation { + strongSelf.initialLocation = nil + strongSelf.longTap(initialLocation) + strongSelf.state = .ended + } + }, queue: Queue.mainQueue()) + self.longTapTimer?.invalidate() + self.longTapTimer = longTapTimer + longTapTimer.start() } } @@ -31,6 +50,8 @@ final class TabBarTapRecognizer: UIGestureRecognizer { if let initialLocation = self.initialLocation { self.initialLocation = nil + self.longTapTimer?.invalidate() + self.longTapTimer = nil self.tap(initialLocation) self.state = .ended } @@ -43,6 +64,8 @@ final class TabBarTapRecognizer: UIGestureRecognizer { let deltaX = initialLocation.x - location.x let deltaY = initialLocation.y - location.y if deltaX * deltaX + deltaY * deltaY > 4.0 { + self.longTapTimer?.invalidate() + self.longTapTimer = nil self.initialLocation = nil self.state = .failed } @@ -53,6 +76,8 @@ final class TabBarTapRecognizer: UIGestureRecognizer { super.touchesCancelled(touches, with: event) self.initialLocation = nil + self.longTapTimer?.invalidate() + self.longTapTimer = nil self.state = .failed } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index c31a241ae0..962a37eb8b 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -151,6 +151,7 @@ open class ViewControllerPresentationArguments { } } public var scrollToTopWithTabBar: (() -> Void)? + public var longTapWithTabBar: (() -> Void)? public var attemptNavigation: (@escaping () -> Void) -> Bool = { _ in return true From e56e4c2aee0ad4d8a59005a5dedcf763a6e3e321 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 25 Sep 2018 01:34:42 +0100 Subject: [PATCH 079/245] no message --- Display/TextNode.swift | 62 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 51 insertions(+), 11 deletions(-) diff --git a/Display/TextNode.swift b/Display/TextNode.swift index be1b1272cf..666ca1fc79 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -1,5 +1,6 @@ import Foundation import AsyncDisplayKit +import CoreText private let defaultFont = UIFont.systemFont(ofSize: 15.0) @@ -7,17 +8,20 @@ private final class TextNodeLine { let line: CTLine let frame: CGRect let range: NSRange + let isRTL: Bool - init(line: CTLine, frame: CGRect, range: NSRange) { + init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool) { self.line = line self.frame = frame self.range = range + self.isRTL = isRTL } } public enum TextNodeCutoutPosition { case TopLeft case TopRight + case BottomRight } public struct TextNodeCutout: Equatable { @@ -67,6 +71,7 @@ public final class TextNodeLayout: NSObject { public let size: CGSize fileprivate let firstLineOffset: CGFloat fileprivate let lines: [TextNodeLine] + public let hasRTL: Bool fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, firstLineOffset: CGFloat, lines: [TextNodeLine], backgroundColor: UIColor?) { self.attributedString = attributedString @@ -81,6 +86,13 @@ public final class TextNodeLayout: NSObject { self.firstLineOffset = firstLineOffset self.lines = lines self.backgroundColor = backgroundColor + var hasRTL = false + for line in lines { + if line.isRTL { + hasRTL = true + } + } + self.hasRTL = hasRTL } public var numberOfLines: Int { @@ -101,10 +113,14 @@ public final class TextNodeLayout: NSObject { for line in self.lines { var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) switch self.alignment { - case .center: - lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) - default: - break + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + case .natural: + if line.isRTL { + lineFrame.origin.x = floor(self.size.width - lineFrame.size.width) + } + default: + break } if lineFrame.contains(transformedPoint) { var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) @@ -126,10 +142,14 @@ public final class TextNodeLayout: NSObject { for line in self.lines { var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) switch self.alignment { - case .center: - lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) - default: - break + case .center: + lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) + case .natural: + if line.isRTL { + lineFrame.origin.x = floor(self.size.width - lineFrame.size.width) + } + default: + break } if lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.size.height).insetBy(dx: -3.0, dy: -3.0).contains(transformedPoint) { var index = CTLineGetStringIndexForPosition(line.line, CGPoint(x: transformedPoint.x - lineFrame.minX, y: transformedPoint.y - lineFrame.minY)) @@ -358,7 +378,16 @@ public class TextNode: ASDisplayNode { layoutSize.height += fontLineHeight + fontLineSpacing layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length))) + var isRTL = false + let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray + if glyphRuns.count != 0 { + let run = glyphRuns[0] as! CTRun + if CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { + isRTL = true + } + } + + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL)) break } else { @@ -378,7 +407,16 @@ public class TextNode: ASDisplayNode { layoutSize.height += fontLineHeight layoutSize.width = max(layoutSize.width, lineWidth + lineAdditionalWidth) - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length))) + var isRTL = false + let glyphRuns = CTLineGetGlyphRuns(coreTextLine) as NSArray + if glyphRuns.count != 0 { + let run = glyphRuns[0] as! CTRun + if CTRunGetStatus(run).contains(CTRunStatus.rightToLeft) { + isRTL = true + } + } + + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL)) } else { if !lines.isEmpty { layoutSize.height += fontLineSpacing @@ -439,6 +477,8 @@ public class TextNode: ASDisplayNode { let lineOffset: CGFloat if alignment == .center { lineOffset = floor((bounds.size.width - line.frame.size.width) / 2.0) + } else if alignment == .natural, line.isRTL { + lineOffset = floor(bounds.size.width - line.frame.size.width) } else { lineOffset = 0.0 } From 287cfeaab19f8853f2a5704f8c2e3f358353aee3 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 25 Sep 2018 17:57:44 +0100 Subject: [PATCH 080/245] no message --- Display/ChildWindowHostView.swift | 2 +- Display/NativeWindowHostView.swift | 2 +- Display/WindowContent.swift | 68 +++++++++++++++--------------- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/Display/ChildWindowHostView.swift b/Display/ChildWindowHostView.swift index 2581956144..f489eab8de 100644 --- a/Display/ChildWindowHostView.swift +++ b/Display/ChildWindowHostView.swift @@ -29,7 +29,7 @@ public func childWindowHostView(parent: UIView) -> WindowHostView { let view = ChildWindowHostView() view.autoresizingMask = [.flexibleWidth, .flexibleHeight] - let hostView = WindowHostView(view: view, isRotating: { + let hostView = WindowHostView(containerView: view, eventView: view, isRotating: { return false }, updateSupportedInterfaceOrientations: { orientations in }, updateDeferScreenEdgeGestures: { edges in diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 1be378e317..93229123e6 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -225,7 +225,7 @@ public func nativeWindowHostView() -> (UIWindow, WindowHostView) { rootViewController.view.frame = CGRect(origin: CGPoint(), size: window.bounds.size) rootViewController.viewDidAppear(false) - let hostView = WindowHostView(view: window, isRotating: { + let hostView = WindowHostView(containerView: window, eventView: window, isRotating: { return window.isRotating() }, updateSupportedInterfaceOrientations: { orientations in rootViewController.orientations = orientations diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 79d7329c35..8c5d5b6330 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -197,7 +197,8 @@ public func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeigh } public final class WindowHostView { - public let view: UIView + public let containerView: UIView + public let eventView: UIView public let isRotating: () -> Bool let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void @@ -217,8 +218,9 @@ public final class WindowHostView { var cancelInteractiveKeyboardGestures: (() -> Void)? var forEachController: (((ViewController) -> Void) -> Void)? - init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { - self.view = view + init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { + self.containerView = containerView + self.eventView = eventView self.isRotating = isRotating self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures @@ -319,7 +321,7 @@ public class Window1 { self.volumeControlStatusBarNode = VolumeControlStatusBarNode() self.volumeControlStatusBarNode.isHidden = true - let boundsSize = self.hostView.view.bounds.size + let boundsSize = self.hostView.eventView.bounds.size self.statusBarHost = statusBarHost let statusBarHeight: CGFloat @@ -364,7 +366,7 @@ public class Window1 { self?.updateSize(size, duration: duration) } - self.hostView.view.layer.setInvalidateTracingSublayers { [weak self] in + self.hostView.eventView.layer.setInvalidateTracingSublayers { [weak self] in self?.invalidateTracingStatusBars() } @@ -399,7 +401,7 @@ public class Window1 { }) } - self.presentationContext.view = self.hostView.view + self.presentationContext.view = self.hostView.containerView self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) @@ -448,14 +450,14 @@ public class Window1 { if #available(iOSApplicationExtension 11.0, *) { self.keyboardTypeChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UITextInputCurrentInputModeDidChange, object: nil, queue: OperationQueue.main, using: { [weak self] notification in - if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if let strongSelf = self, let initialInputHeight = strongSelf.windowLayout.inputHeight, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.eventView).0 { if firstResponder.textInputMode?.primaryLanguage != nil { return } strongSelf.keyboardTypeChangeTimer?.invalidate() let timer = SwiftSignalKit.Timer(timeout: 0.1, repeat: false, completion: { - if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.view).0 { + if let strongSelf = self, let firstResponder = getFirstResponderAndAccessoryHeight(strongSelf.hostView.eventView).0 { if firstResponder.textInputMode?.primaryLanguage != nil { return } @@ -489,10 +491,10 @@ public class Window1 { self?.panGestureEnded(location: point, velocity: velocity) } self.windowPanRecognizer = recognizer - self.hostView.view.addGestureRecognizer(recognizer) + self.hostView.containerView.addGestureRecognizer(recognizer) - self.hostView.view.addSubview(self.volumeControlStatusBar) - self.hostView.view.addSubview(self.volumeControlStatusBarNode.view) + self.hostView.containerView.addSubview(self.volumeControlStatusBar) + self.hostView.containerView.addSubview(self.volumeControlStatusBarNode.view) } public required init(coder aDecoder: NSCoder) { @@ -523,17 +525,17 @@ public class Window1 { private func invalidateTracingStatusBars() { self.tracingStatusBarsInvalidated = true - self.hostView.view.setNeedsLayout() + self.hostView.eventView.setNeedsLayout() } public func invalidateDeferScreenEdgeGestures() { self.shouldUpdateDeferScreenEdgeGestures = true - self.hostView.view.setNeedsLayout() + self.hostView.eventView.setNeedsLayout() } public func invalidatePreferNavigationUIHidden() { self.shouldInvalidatePreferNavigationUIHidden = true - self.hostView.view.setNeedsLayout() + self.hostView.eventView.setNeedsLayout() } public func cancelInteractiveKeyboardGestures() { @@ -552,7 +554,7 @@ public class Window1 { } public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - for view in self.hostView.view.subviews.reversed() { + for view in self.hostView.eventView.subviews.reversed() { if NSStringFromClass(type(of: view)) == "UITransitionView" { if let result = view.hitTest(point, with: event) { return result @@ -603,9 +605,9 @@ public class Window1 { } if let coveringView = self.coveringView { - self.hostView.view.insertSubview(rootController.view, belowSubview: coveringView) + self.hostView.containerView.insertSubview(rootController.view, belowSubview: coveringView) } else { - self.hostView.view.insertSubview(rootController.view, belowSubview: self.volumeControlStatusBarNode.view) + self.hostView.containerView.insertSubview(rootController.view, belowSubview: self.volumeControlStatusBarNode.view) } } } @@ -626,9 +628,9 @@ public class Window1 { controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) if let coveringView = self.coveringView { - self.hostView.view.insertSubview(controller.view, belowSubview: coveringView) + self.hostView.containerView.insertSubview(controller.view, belowSubview: coveringView) } else { - self.hostView.view.insertSubview(controller.view, belowSubview: self.volumeControlStatusBarNode.view) + self.hostView.containerView.insertSubview(controller.view, belowSubview: self.volumeControlStatusBarNode.view) } } @@ -649,7 +651,7 @@ public class Window1 { coveringView.layer.removeAnimation(forKey: "opacity") coveringView.layer.allowsGroupOpacity = false coveringView.alpha = 1.0 - self.hostView.view.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view) + self.hostView.containerView.insertSubview(coveringView, belowSubview: self.volumeControlStatusBarNode.view) if !self.windowLayout.size.width.isZero { coveringView.frame = CGRect(origin: CGPoint(), size: self.windowLayout.size) coveringView.updateLayout(self.windowLayout.size) @@ -662,7 +664,7 @@ public class Window1 { private func layoutSubviews() { var hasPreview = false var updatedHasPreview = false - for subview in self.hostView.view.subviews { + for subview in self.hostView.eventView.subviews { if checkIsPreviewingView(subview) { applyThemeToPreviewingView(subview, accentColor: self.previewThemeAccentColor, darkBlur: self.previewThemeDarkBlur) hasPreview = true @@ -681,7 +683,7 @@ public class Window1 { statusBarManager.updateState(surfaces: [], withSafeInsets: false, forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false) } else { var statusBarSurfaces: [StatusBarSurface] = [] - for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { + for layers in self.hostView.containerView.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { let surface = StatusBarSurface() for layer in layers { let traceableInfo = layer.traceableInfo() @@ -691,19 +693,19 @@ public class Window1 { } statusBarSurfaces.append(surface) } - self.hostView.view.layer.adjustTraceableLayerTransforms(CGSize()) + self.hostView.containerView.layer.adjustTraceableLayerTransforms(CGSize()) var animatedUpdate = false if let updatingLayout = self.updatingLayout { if case .animated = updatingLayout.transition { animatedUpdate = true } } - self.cachedWindowSubviewCount = self.hostView.view.window?.subviews.count ?? 0 + self.cachedWindowSubviewCount = self.hostView.containerView.window?.subviews.count ?? 0 statusBarManager.updateState(surfaces: statusBarSurfaces, withSafeInsets: !self.windowLayout.safeInsets.top.isZero, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate) } var keyboardSurfaces: [KeyboardSurface] = [] - for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.keyboard) { + for layers in self.hostView.containerView.layer.traceableLayerSurfaces(withTag: WindowTracingTags.keyboard) { for layer in layers { if let view = layer.delegate as? UITracingLayerView { keyboardSurfaces.append(KeyboardSurface(host: view)) @@ -750,14 +752,14 @@ public class Window1 { } else { self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in if let strongSelf = self { - strongSelf.hostView.view.setNeedsLayout() + strongSelf.hostView.eventView.setNeedsLayout() } }) } } else { UIWindow.addPostDeviceOrientationDidChange({ [weak self] in if let strongSelf = self { - strongSelf.hostView.view.setNeedsLayout() + strongSelf.hostView.eventView.setNeedsLayout() } }) } @@ -783,11 +785,11 @@ public class Window1 { update(&updatingLayout) if updatingLayout.layout != self.windowLayout { self.updatingLayout = updatingLayout - self.hostView.view.setNeedsLayout() + self.hostView.eventView.setNeedsLayout() } } else { update(&self.updatingLayout!) - self.hostView.view.setNeedsLayout() + self.hostView.eventView.setNeedsLayout() } } @@ -817,7 +819,7 @@ public class Window1 { } if self.statusBarHidden != statusBarWasHidden { self.tracingStatusBarsInvalidated = true - self.hostView.view.setNeedsLayout() + self.hostView.eventView.setNeedsLayout() } let previousInputOffset = inputHeightOffsetForLayout(self.windowLayout) @@ -852,7 +854,7 @@ public class Window1 { strongSelf.updateLayout { $0.update(upperKeyboardInputPositionBound: nil, transition: .immediate, overrideTransition: false) } - strongSelf.hostView.view.endEditing(true) + strongSelf.hostView.eventView.endEditing(true) } }) } @@ -886,11 +888,11 @@ public class Window1 { } let keyboardGestureBeginLocation = location - let view = self.hostView.view + let view = self.hostView.containerView let (firstResponder, accessoryHeight) = getFirstResponderAndAccessoryHeight(view) if let inputHeight = self.windowLayout.inputHeight, !inputHeight.isZero, keyboardGestureBeginLocation.y < self.windowLayout.size.height - inputHeight - (accessoryHeight ?? 0.0) { var enableGesture = true - if let view = self.hostView.view.hitTest(location, with: nil) { + if let view = self.hostView.containerView.hitTest(location, with: nil) { if doesViewTreeDisableInteractiveTransitionGestureRecognizer(view) { enableGesture = false } From 2e9350d71734f0d12bd653a55fbc8c793b253663 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 27 Sep 2018 21:01:04 +0100 Subject: [PATCH 081/245] no message --- Display/NativeWindowHostView.swift | 4 ++-- Display/NavigationController.swift | 2 +- Display/TextNode.swift | 31 ++++++++++++++++++++++-------- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 93229123e6..918a91fc88 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -216,7 +216,7 @@ private final class NativeWindow: UIWindow, WindowHost { } } -public func nativeWindowHostView() -> (UIWindow, WindowHostView) { +public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { let window = NativeWindow(frame: UIScreen.main.bounds) let rootViewController = WindowRootViewController() @@ -225,7 +225,7 @@ public func nativeWindowHostView() -> (UIWindow, WindowHostView) { rootViewController.view.frame = CGRect(origin: CGPoint(), size: window.bounds.size) rootViewController.viewDidAppear(false) - let hostView = WindowHostView(containerView: window, eventView: window, isRotating: { + let hostView = WindowHostView(containerView: rootViewController.view, eventView: window, isRotating: { return window.isRotating() }, updateSupportedInterfaceOrientations: { orientations in rootViewController.orientations = orientations diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 40c0848d8c..5cbee58463 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -951,7 +951,7 @@ open class NavigationController: UINavigationController, ContainableController, return false } - public final var window: WindowHost? { + public final var currentWindow: WindowHost? { if let window = self.view.window as? WindowHost { return window } else if let superwindow = self.view.window { diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 666ca1fc79..e2efc66444 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -110,7 +110,10 @@ public final class TextNodeLayout: NSObject { public func attributesAtPoint(_ point: CGPoint) -> (Int, [NSAttributedStringKey: Any])? { if let attributedString = self.attributedString { let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top) + var lineIndex = -1 + let lineCount = self.lines.count for line in self.lines { + lineIndex += 1 var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) switch self.alignment { case .center: @@ -118,6 +121,9 @@ public final class TextNodeLayout: NSObject { case .natural: if line.isRTL { lineFrame.origin.x = floor(self.size.width - lineFrame.size.width) + if lineIndex == lineCount - 1, let cutout = self.cutout, case .BottomRight = cutout.position { + lineFrame.origin.x -= cutout.size.width + } } default: break @@ -139,7 +145,9 @@ public final class TextNodeLayout: NSObject { break } } + lineIndex = -1 for line in self.lines { + lineIndex += 1 var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) switch self.alignment { case .center: @@ -147,6 +155,9 @@ public final class TextNodeLayout: NSObject { case .natural: if line.isRTL { lineFrame.origin.x = floor(self.size.width - lineFrame.size.width) + if lineIndex == lineCount - 1, let cutout = self.cutout, case .BottomRight = cutout.position { + lineFrame.origin.x -= cutout.size.width + } } default: break @@ -197,7 +208,10 @@ public final class TextNodeLayout: NSObject { let _ = attributedString.attribute(NSAttributedStringKey(rawValue: name), at: index, effectiveRange: &range) if range.length != 0 { var rects: [(CGRect, CGRect)] = [] + var lineIndex = -1 + let lineCount = self.lines.count for line in self.lines { + lineIndex += 1 let lineRange = NSIntersectionRange(range, line.range) if lineRange.length != 0 { var leftOffset: CGFloat = 0.0 @@ -208,7 +222,10 @@ public final class TextNodeLayout: NSObject { if lineRange.location + lineRange.length != line.range.length { rightOffset = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) } - let lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + if lineIndex == lineCount - 1, let cutout = self.cutout, case .BottomRight = cutout.position { + lineFrame.origin.x -= cutout.size.width + } rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height)))) } } @@ -264,12 +281,6 @@ public class TextNode: ASDisplayNode { if let attributedString = attributedString { let stringLength = attributedString.length - #if DEBUG - if attributedString.string == "مثلاً مثلاً مثلاً" { - assert(true) - } - #endif - let font: CTFont if stringLength != 0 { if let stringFont = attributedString.attribute(NSAttributedStringKey.font, at: 0, effectiveRange: nil) { @@ -472,13 +483,17 @@ public class TextNode: ASDisplayNode { let alignment = layout.alignment let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) + let lineCount = layout.lines.count for i in 0 ..< layout.lines.count { let line = layout.lines[i] - let lineOffset: CGFloat + var lineOffset: CGFloat if alignment == .center { lineOffset = floor((bounds.size.width - line.frame.size.width) / 2.0) } else if alignment == .natural, line.isRTL { lineOffset = floor(bounds.size.width - line.frame.size.width) + if i == lineCount - 1, let cutout = layout.cutout, case .BottomRight = cutout.position { + lineOffset -= cutout.size.width + } } else { lineOffset = 0.0 } From 9a84348e225c08f30e70c0207e276cac733a039b Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 3 Oct 2018 01:30:05 +0400 Subject: [PATCH 082/245] no message --- .../xcschemes/DisplayMac.xcscheme | 82 ------------- .../xcschemes/DisplayTests.xcscheme | 56 --------- .../xcschemes/xcschememanagement.plist | 12 +- Display/NativeWindowHostView.swift | 27 ++++- Display/TextNode.swift | 112 ++++++++++++------ Display/WindowContent.swift | 4 + 6 files changed, 106 insertions(+), 187 deletions(-) delete mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme delete mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme deleted file mode 100644 index 4c933753f7..0000000000 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayMac.xcscheme +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme deleted file mode 100644 index 2ae31f3e32..0000000000 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/DisplayTests.xcscheme +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index 17298f558b..5a5ed076ae 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,17 +7,7 @@ Display.xcscheme orderHint - 6 - - DisplayMac.xcscheme - - orderHint - 25 - - DisplayTests.xcscheme - - orderHint - 8 + 4 SuppressBuildableAutocreation diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 918a91fc88..306b4900d8 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -11,7 +11,20 @@ private let defaultOrientations: UIInterfaceOrientationMask = { } }() -private class WindowRootViewController: UIViewController { +private final class WindowRootViewControllerView: UIView { + override var frame: CGRect { + get { + return super.frame + } set(value) { + var value = value + value.size.height += value.minY + value.origin.y = 0.0 + super.frame = value + } + } +} + +private final class WindowRootViewController: UIViewController { var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)? var transitionToSize: ((CGSize, Double) -> Void)? @@ -62,6 +75,16 @@ private class WindowRootViewController: UIViewController { return orientations } + init() { + super.init(nibName: nil, bundle: nil) + + self.extendedLayoutIncludesOpaqueBars = true + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { return self.gestureEdges } @@ -78,7 +101,7 @@ private class WindowRootViewController: UIViewController { } override func loadView() { - self.view = UIView() + self.view = WindowRootViewControllerView() self.view.isOpaque = false self.view.backgroundColor = nil } diff --git a/Display/TextNode.swift b/Display/TextNode.swift index e2efc66444..ed6a16b36f 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -25,15 +25,43 @@ public enum TextNodeCutoutPosition { } public struct TextNodeCutout: Equatable { - public let position: TextNodeCutoutPosition - public let size: CGSize + public var topLeft: CGSize? + public var topRight: CGSize? + public var bottomRight: CGSize? - public init(position: TextNodeCutoutPosition, size: CGSize) { - self.position = position - self.size = size + public init(topLeft: CGSize? = nil, topRight: CGSize? = nil, bottomRight: CGSize? = nil) { + self.topLeft = topLeft + self.topRight = topRight + self.bottomRight = bottomRight } } +private func displayLineFrame(frame: CGRect, isRTL: Bool, boundingRect: CGRect, cutout: TextNodeCutout?) -> CGRect { + if frame.width.isEqual(to: boundingRect.width) { + return frame + } + var lineFrame = frame + let intersectionFrame = lineFrame.offsetBy(dx: 0.0, dy: -lineFrame.height) + if isRTL { + lineFrame.origin.x = max(0.0, floor(boundingRect.width - lineFrame.size.width)) + if let topRight = cutout?.topRight { + let topRightRect = CGRect(origin: CGPoint(x: boundingRect.width - topRight.width, y: 0.0), size: topRight) + if intersectionFrame.intersects(topRightRect) { + lineFrame.origin.x -= topRight.width + return lineFrame + } + } + if let bottomRight = cutout?.bottomRight { + let bottomRightRect = CGRect(origin: CGPoint(x: boundingRect.width - bottomRight.width, y: boundingRect.height - bottomRight.height), size: bottomRight) + if intersectionFrame.intersects(bottomRightRect) { + lineFrame.origin.x -= bottomRight.width + return lineFrame + } + } + } + return lineFrame +} + public final class TextNodeLayoutArguments { public let attributedString: NSAttributedString? public let backgroundColor: UIColor? @@ -111,7 +139,6 @@ public final class TextNodeLayout: NSObject { if let attributedString = self.attributedString { let transformedPoint = CGPoint(x: point.x - self.insets.left, y: point.y - self.insets.top) var lineIndex = -1 - let lineCount = self.lines.count for line in self.lines { lineIndex += 1 var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) @@ -120,11 +147,9 @@ public final class TextNodeLayout: NSObject { lineFrame.origin.x = floor((self.size.width - lineFrame.size.width) / 2.0) case .natural: if line.isRTL { - lineFrame.origin.x = floor(self.size.width - lineFrame.size.width) - if lineIndex == lineCount - 1, let cutout = self.cutout, case .BottomRight = cutout.position { - lineFrame.origin.x -= cutout.size.width - } + lineFrame.origin.x = self.size.width - lineFrame.size.width } + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) default: break } @@ -155,10 +180,8 @@ public final class TextNodeLayout: NSObject { case .natural: if line.isRTL { lineFrame.origin.x = floor(self.size.width - lineFrame.size.width) - if lineIndex == lineCount - 1, let cutout = self.cutout, case .BottomRight = cutout.position { - lineFrame.origin.x -= cutout.size.width - } } + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) default: break } @@ -208,10 +231,7 @@ public final class TextNodeLayout: NSObject { let _ = attributedString.attribute(NSAttributedStringKey(rawValue: name), at: index, effectiveRange: &range) if range.length != 0 { var rects: [(CGRect, CGRect)] = [] - var lineIndex = -1 - let lineCount = self.lines.count for line in self.lines { - lineIndex += 1 let lineRange = NSIntersectionRange(range, line.range) if lineRange.length != 0 { var leftOffset: CGFloat = 0.0 @@ -220,12 +240,17 @@ public final class TextNodeLayout: NSObject { } var rightOffset: CGFloat = line.frame.width if lineRange.location + lineRange.length != line.range.length { - rightOffset = ceil(CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, nil)) + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } } var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) - if lineIndex == lineCount - 1, let cutout = self.cutout, case .BottomRight = cutout.position { - lineFrame.origin.x -= cutout.size.width - } + + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + rects.append((lineFrame, CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height)))) } } @@ -315,14 +340,26 @@ public class TextNode: ASDisplayNode { var cutoutMaxY: CGFloat = 0.0 var cutoutWidth: CGFloat = 0.0 var cutoutOffset: CGFloat = 0.0 - if let cutout = cutout { + + var bottomCutoutEnabled = false + var bottomCutoutSize = CGSize() + + if let topLeft = cutout?.topLeft { cutoutMinY = -fontLineSpacing - cutoutMaxY = cutout.size.height + fontLineSpacing - cutoutWidth = cutout.size.width - if case .TopLeft = cutout.position { - cutoutOffset = cutoutWidth - } + cutoutMaxY = topLeft.height + fontLineSpacing + cutoutWidth = topLeft.width + cutoutOffset = cutoutWidth cutoutEnabled = true + } else if let topRight = cutout?.topRight { + cutoutMinY = -fontLineSpacing + cutoutMaxY = topRight.height + fontLineSpacing + cutoutWidth = topRight.width + cutoutEnabled = true + } + + if let bottomRight = cutout?.bottomRight { + bottomCutoutSize = bottomRight + bottomCutoutEnabled = true } let firstLineOffset = floorToScreenPixels(fontDescent) @@ -437,6 +474,10 @@ public class TextNode: ASDisplayNode { } } + if !lines.isEmpty && bottomCutoutEnabled { + layoutSize.width = max(layoutSize.width, lines[lines.count - 1].frame.width + bottomCutoutSize.width) + } + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) } else { return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) @@ -483,21 +524,20 @@ public class TextNode: ASDisplayNode { let alignment = layout.alignment let offset = CGPoint(x: layout.insets.left, y: layout.insets.top) - let lineCount = layout.lines.count for i in 0 ..< layout.lines.count { let line = layout.lines[i] - var lineOffset: CGFloat + + var lineFrame = line.frame + lineFrame.origin.y += offset.y + if alignment == .center { - lineOffset = floor((bounds.size.width - line.frame.size.width) / 2.0) + lineFrame.origin.x = offset.x + floor((bounds.size.width - lineFrame.width) / 2.0) } else if alignment == .natural, line.isRTL { - lineOffset = floor(bounds.size.width - line.frame.size.width) - if i == lineCount - 1, let cutout = layout.cutout, case .BottomRight = cutout.position { - lineOffset -= cutout.size.width - } - } else { - lineOffset = 0.0 + lineFrame.origin.x = offset.x + floor(bounds.size.width - lineFrame.width) + + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: bounds.size), cutout: layout.cutout) } - context.textPosition = CGPoint(x: line.frame.origin.x + lineOffset + offset.x, y: line.frame.origin.y + offset.y) + context.textPosition = CGPoint(x: lineFrame.minX, y: lineFrame.minY) CTLineDraw(line.line, context) } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 8c5d5b6330..19ea652fe6 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -554,6 +554,10 @@ public class Window1 { } public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let coveringView = self.coveringView, !coveringView.isHidden, coveringView.superview != nil, coveringView.frame.contains(point) { + return coveringView.hitTest(point, with: event) + } + for view in self.hostView.eventView.subviews.reversed() { if NSStringFromClass(type(of: view)) == "UITransitionView" { if let result = view.hitTest(point, with: event) { From bacdfcc6afa38b395ed5afa3c0f1d45ff0ae81d8 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 3 Oct 2018 02:39:41 +0400 Subject: [PATCH 083/245] TextNode width extension fix --- Display/TextNode.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Display/TextNode.swift b/Display/TextNode.swift index ed6a16b36f..e55480d261 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -475,7 +475,14 @@ public class TextNode: ASDisplayNode { } if !lines.isEmpty && bottomCutoutEnabled { - layoutSize.width = max(layoutSize.width, lines[lines.count - 1].frame.width + bottomCutoutSize.width) + let proposedWidth = lines[lines.count - 1].frame.width + bottomCutoutSize.width + if proposedWidth > layoutSize.width { + if proposedWidth < constrainedSize.width { + layoutSize.width = proposedWidth + } else { + layoutSize.height += bottomCutoutSize.height + } + } } return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) From b3a102782295598d04ff67c74da250d1438db7e3 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 6 Oct 2018 00:49:03 +0300 Subject: [PATCH 084/245] Customizable color for disabled navigation bar buttons --- Display/DeviceMetrics.swift | 2 +- Display/ListViewItemHeader.swift | 2 +- Display/ListViewItemNode.swift | 14 +++++--------- Display/NavigationBackButtonNode.swift | 8 +++++++- Display/NavigationBar.swift | 12 ++++++++++-- Display/NavigationButtonNode.swift | 20 +++++++++++++++++++- 6 files changed, 43 insertions(+), 15 deletions(-) diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index 01354976ec..84d1d66206 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -104,7 +104,7 @@ enum DeviceMetrics { case .iPhone4, .iPhone5: return 37.0 case .iPhone6, .iPhoneX: - return 42.0 + return 44.0 case .iPhone6Plus: return 42.0 case .iPhoneXSMax: diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index db5a74e35f..a11585bc50 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -20,7 +20,7 @@ public protocol ListViewItemHeader: class { open class ListViewItemHeaderNode: ASDisplayNode { private final var spring: ListViewItemSpring? - let wantsScrollDynamics: Bool + open private(set) var wantsScrollDynamics: Bool let isRotated: Bool final private(set) var internalStickLocationDistanceFactor: CGFloat = 0.0 final var internalStickLocationDistance: CGFloat = 0.0 diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 1b41f683a4..05e4eda19b 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -94,7 +94,7 @@ open class ListViewItemNode: ASDisplayNode { private final var spring: ListViewItemSpring? private final var animations: [(String, ListViewAnimation)] = [] - final let wantsScrollDynamics: Bool + open private(set) var wantsScrollDynamics: Bool public final var wantsTrailingItemSpaceUpdates: Bool = false @@ -178,14 +178,10 @@ open class ListViewItemNode: ASDisplayNode { } public init(layerBacked: Bool, dynamicBounce: Bool = true, rotated: Bool = false, seeThrough: Bool = false) { - if true { - if dynamicBounce { - self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) - } - self.wantsScrollDynamics = dynamicBounce - } else { - self.wantsScrollDynamics = false + if dynamicBounce { + self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85) } + self.wantsScrollDynamics = dynamicBounce self.rotated = rotated @@ -198,7 +194,7 @@ open class ListViewItemNode: ASDisplayNode { } else { super.init() - self.setViewBlock({ + self.setViewBlock({ return CASeeThroughTracingView() }) } diff --git a/Display/NavigationBackButtonNode.swift b/Display/NavigationBackButtonNode.swift index 8b81086c1a..393ba98db8 100644 --- a/Display/NavigationBackButtonNode.swift +++ b/Display/NavigationBackButtonNode.swift @@ -9,7 +9,7 @@ public class NavigationBackButtonNode: ASControlNode { private func attributesForCurrentState() -> [NSAttributedStringKey : AnyObject] { return [ NSAttributedStringKey.font: self.fontForCurrentState(), - NSAttributedStringKey.foregroundColor: self.isEnabled ? self.color : UIColor.gray + NSAttributedStringKey.foregroundColor: self.isEnabled ? self.color : self.disabledColor ] } @@ -36,6 +36,12 @@ public class NavigationBackButtonNode: ASControlNode { } } + public var disabledColor: UIColor = UIColor(rgb: 0xd0d0d0) { + didSet { + self.label.attributedText = NSAttributedString(string: self._text, attributes: self.attributesForCurrentState()) + } + } + private var touchCount = 0 var pressed: () -> () = {} diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 905c1de1e5..61931c9b67 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -16,6 +16,7 @@ public final class NavigationBarTheme { } public let buttonColor: UIColor + public let disabledButtonColor: UIColor public let primaryTextColor: UIColor public let backgroundColor: UIColor public let separatorColor: UIColor @@ -23,8 +24,9 @@ public final class NavigationBarTheme { public let badgeStrokeColor: UIColor public let badgeTextColor: UIColor - public init(buttonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor) { + public init(buttonColor: UIColor, disabledButtonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor) { self.buttonColor = buttonColor + self.disabledButtonColor = disabledButtonColor self.primaryTextColor = primaryTextColor self.backgroundColor = backgroundColor self.separatorColor = separatorColor @@ -34,7 +36,7 @@ public final class NavigationBarTheme { } public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme { - return NavigationBarTheme(buttonColor: self.buttonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) + return NavigationBarTheme(buttonColor: self.buttonColor, disabledButtonColor: self.disabledButtonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor) } } @@ -606,8 +608,11 @@ open class NavigationBar: ASDisplayNode { self.clippingNode.clipsToBounds = true self.backButtonNode.color = self.presentationData.theme.buttonColor + self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.leftButtonNode.color = self.presentationData.theme.buttonColor + self.leftButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.rightButtonNode.color = self.presentationData.theme.buttonColor + self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) @@ -676,8 +681,11 @@ open class NavigationBar: ASDisplayNode { self.backgroundColor = self.presentationData.theme.backgroundColor self.backButtonNode.color = self.presentationData.theme.buttonColor + self.backButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.leftButtonNode.color = self.presentationData.theme.buttonColor + self.leftButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.rightButtonNode.color = self.presentationData.theme.buttonColor + self.rightButtonNode.disabledColor = self.presentationData.theme.disabledButtonColor self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index b6a74ce8b6..a56663fcc3 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -9,7 +9,7 @@ private final class NavigationButtonItemNode: ASTextNode { private func attributesForCurrentState() -> [NSAttributedStringKey : AnyObject] { return [ NSAttributedStringKey.font: self.fontForCurrentState(), - NSAttributedStringKey.foregroundColor: self.isEnabled ? self.color : UIColor.gray + NSAttributedStringKey.foregroundColor: self.isEnabled ? self.color : self.disabledColor ] } @@ -93,6 +93,14 @@ private final class NavigationButtonItemNode: ASTextNode { } } + public var disabledColor: UIColor = UIColor(rgb: 0xd0d0d0) { + didSet { + if let text = self._text { + self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + } + } + } + private var _bold: Bool = false public var bold: Bool { get { @@ -229,6 +237,16 @@ final class NavigationButtonNode: ASDisplayNode { } } + public var disabledColor: UIColor = UIColor(rgb: 0xd0d0d0) { + didSet { + if !self.disabledColor.isEqual(oldValue) { + for node in self.nodes { + node.disabledColor = self.disabledColor + } + } + } + } + override init() { super.init() } From 2fe25a6784bc7d613d12a66fc42527a9a739e66e Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 10 Oct 2018 20:11:38 +0300 Subject: [PATCH 085/245] Fixed missing viewWillAppear for TabBarController children --- Display/TabBarController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index d53db8f195..d39859d92d 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -207,6 +207,12 @@ open class TabBarController: ViewController { } } + override open func viewWillAppear(_ animated: Bool) { + if let currentController = self.currentController { + currentController.viewWillAppear(animated) + } + } + override open func viewDidAppear(_ animated: Bool) { if let currentController = self.currentController { currentController.viewDidAppear(animated) From 5bd6ce75fd365f07c3289d94b1e53b763b25dcfc Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 10 Oct 2018 18:13:08 +0100 Subject: [PATCH 086/245] Key Shortcuts --- Display.xcodeproj/project.pbxproj | 16 +++++ Display/ActionSheetControllerNode.swift | 2 +- Display/DeviceMetrics.swift | 27 +++++++- Display/KeyShortcut.swift | 43 +++++++++++++ Display/KeyShortcutsController.swift | 83 +++++++++++++++++++++++++ Display/ListView.swift | 6 +- Display/ListViewItemHeader.swift | 2 +- Display/ListViewItemNode.swift | 2 +- Display/PeekControllerNode.swift | 10 ++- 9 files changed, 183 insertions(+), 8 deletions(-) create mode 100644 Display/KeyShortcut.swift create mode 100644 Display/KeyShortcutsController.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 29738932f0..bd40bc20a4 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 09C147D8216CCEF700390252 /* KeyShortcutsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */; }; + 09C147DA216CD7E500390252 /* KeyShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C147D9216CD7E500390252 /* KeyShortcut.swift */; }; 09E12476214D0978009FC9C3 /* DeviceMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */; }; D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701972029CAD6006B9E34 /* TooltipController.swift */; }; D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */; }; @@ -192,6 +194,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyShortcutsController.swift; sourceTree = ""; }; + 09C147D9216CD7E500390252 /* KeyShortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyShortcut.swift; sourceTree = ""; }; 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetrics.swift; sourceTree = ""; }; D00701972029CAD6006B9E34 /* TooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipControllerNode.swift; sourceTree = ""; }; @@ -387,6 +391,15 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 09C147D6216CCE9700390252 /* Key Shortcuts */ = { + isa = PBXGroup; + children = ( + 09C147D9216CD7E500390252 /* KeyShortcut.swift */, + 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */, + ); + name = "Key Shortcuts"; + sourceTree = ""; + }; D00701962029CAC4006B9E34 /* Tooltip */ = { isa = PBXGroup; children = ( @@ -775,6 +788,7 @@ D0FF9B2E1E7196E2000C66DB /* Keyboard */ = { isa = PBXGroup; children = ( + 09C147D6216CCE9700390252 /* Key Shortcuts */, D0FF9B2F1E7196F6000C66DB /* KeyboardManager.swift */, D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */, ); @@ -1039,6 +1053,7 @@ D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, + 09C147D8216CCEF700390252 /* KeyShortcutsController.swift in Sources */, D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, D0CE8CE91F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift in Sources */, @@ -1101,6 +1116,7 @@ D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */, + 09C147DA216CD7E500390252 /* KeyShortcut.swift in Sources */, D08CAA7B1ED73C990000FDA8 /* ViewControllerTracingNode.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index e7c0878f2b..ecb906f891 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -84,7 +84,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { insets.left = floor((layout.size.width - containerWidth) / 2.0) insets.right = insets.left - if insets.bottom > 0 { + if !insets.bottom.isZero { insets.bottom -= 12.0 } diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index 84d1d66206..ab73db4b92 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -1,6 +1,6 @@ import UIKit -enum DeviceMetrics { +public enum DeviceMetrics { case iPhone4 case iPhone5 case iPhone6 @@ -12,7 +12,7 @@ enum DeviceMetrics { static let allDevices = [iPhone4, iPhone5, iPhone6, iPhone6Plus, iPhoneX, iPhoneXSMax, iPad, iPadPro] - static func forScreenSize(_ size: CGSize) -> DeviceMetrics? { + public static func forScreenSize(_ size: CGSize) -> DeviceMetrics? { for device in allDevices { let width = device.screenSize.width let height = device.screenSize.height @@ -114,4 +114,27 @@ enum DeviceMetrics { } } } + + public func previewingContentSize(inLandscape: Bool) -> CGSize { + let screenSize = self.screenSize + if inLandscape { + switch self { + case .iPhoneX: + return CGSize(width: screenSize.height, height: screenSize.width + 48.0) + case .iPhoneXSMax: + return CGSize(width: screenSize.height, height: screenSize.width - 30.0) + default: + return CGSize(width: screenSize.height, height: screenSize.width - 10.0) + } + } else { + switch self { + case .iPhoneX: + return CGSize(width: screenSize.width, height: screenSize.height - 190.0) + case .iPhoneXSMax: + return CGSize(width: screenSize.width, height: screenSize.height - 90.0) + default: + return CGSize(width: screenSize.width, height: screenSize.height - 50.0) + } + } + } } diff --git a/Display/KeyShortcut.swift b/Display/KeyShortcut.swift new file mode 100644 index 0000000000..ef00181595 --- /dev/null +++ b/Display/KeyShortcut.swift @@ -0,0 +1,43 @@ +import UIKit + +public struct KeyShortcut: Hashable { + let title: String + let input: String + let modifiers: UIKeyModifierFlags + let action: () -> Void + + public init(title: String = "", input: String = "", modifiers: UIKeyModifierFlags = [], action: @escaping () -> Void = {}) { + self.title = title + self.input = input + self.modifiers = modifiers + self.action = action + } + + public var hashValue: Int { + return input.hashValue ^ modifiers.hashValue + } + + public static func ==(lhs: KeyShortcut, rhs: KeyShortcut) -> Bool { + return lhs.hashValue == rhs.hashValue + } +} + +extension UIKeyModifierFlags: Hashable { + public var hashValue: Int { + return self.rawValue + } +} + +extension KeyShortcut { + var uiKeyCommand: UIKeyCommand { + if #available(iOSApplicationExtension 9.0, *), !self.title.isEmpty { + return UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:)), discoverabilityTitle: self.title) + } else { + return UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:))) + } + } + + func isEqual(to command: UIKeyCommand) -> Bool { + return self.input == command.input && self.modifiers == command.modifierFlags + } +} diff --git a/Display/KeyShortcutsController.swift b/Display/KeyShortcutsController.swift new file mode 100644 index 0000000000..d125265d44 --- /dev/null +++ b/Display/KeyShortcutsController.swift @@ -0,0 +1,83 @@ +import UIKit + +public protocol KeyShortcutResponder { + var keyShortcuts: [KeyShortcut] { get }; +} + +public class KeyShortcutsController: UIResponder { + private var effectiveShortcuts: [KeyShortcut]? + private var viewControllerEnumerator: ((ViewController) -> Bool) -> Void + + public static var isAvailable: Bool { + if #available(iOSApplicationExtension 8.0, *), UIDevice.current.userInterfaceIdiom == .pad { + return true + } else { + return false + } + } + + public init(enumerator: @escaping ((ViewController) -> Bool) -> Void) { + self.viewControllerEnumerator = enumerator + super.init() + } + + public override var keyCommands: [UIKeyCommand]? { + var convertedCommands: [UIKeyCommand] = [] + var shortcuts: [KeyShortcut] = [] + + self.viewControllerEnumerator({ viewController -> Bool in + guard let viewController = viewController as? KeyShortcutResponder else { + return true + } + shortcuts.append(contentsOf: viewController.keyShortcuts) + return true + }) + + // iOS 8 fix + convertedCommands.append(KeyShortcut(modifiers:[.command]).uiKeyCommand) + convertedCommands.append(KeyShortcut(modifiers:[.alternate]).uiKeyCommand) + + convertedCommands.append(contentsOf: shortcuts.map { $0.uiKeyCommand }) + + self.effectiveShortcuts = shortcuts + + return convertedCommands + } + + @objc func handleKeyCommand(_ command: UIKeyCommand) { + if let shortcut = findShortcut(for: command) { + shortcut.action() + } + } + + private func findShortcut(for command: UIKeyCommand) -> KeyShortcut? { + if let shortcuts = self.effectiveShortcuts { + for shortcut in shortcuts { + if shortcut.isEqual(to: command) { + return shortcut + } + } + } + return nil + } + + public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { + if sender is UIKeyCommand { + return true + } else { + return super.canPerformAction(action, withSender: sender) + } + } + + public override func target(forAction action: Selector, withSender sender: Any?) -> Any? { + if sender is UIKeyCommand { + return self + } else { + return super.target(forAction: action, withSender: sender) + } + } + + public override var canBecomeFirstResponder: Bool { + return true + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index 999db10275..9475586868 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -124,6 +124,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final var displayLink: CADisplayLink! private final var needsAnimations = false + public final var dynamicBounceEnabled = true + private final var invisibleInset: CGFloat = 500.0 public var preloadPages: Bool = true { didSet { @@ -558,7 +560,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let position = itemNode.position itemNode.position = CGPoint(x: position.x, y: position.y - deltaY) - if itemNode.wantsScrollDynamics { + if self.dynamicBounceEnabled && itemNode.wantsScrollDynamics { useScrollDynamics = true var distance: CGFloat @@ -587,7 +589,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemHeaders(leftInset: self.insets.left, rightInset: self.insets.right) for (_, headerNode) in self.itemHeaderNodes { - if headerNode.wantsScrollDynamics { + if self.dynamicBounceEnabled && headerNode.wantsScrollDynamics { useScrollDynamics = true var distance: CGFloat diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index a11585bc50..db5a74e35f 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -20,7 +20,7 @@ public protocol ListViewItemHeader: class { open class ListViewItemHeaderNode: ASDisplayNode { private final var spring: ListViewItemSpring? - open private(set) var wantsScrollDynamics: Bool + let wantsScrollDynamics: Bool let isRotated: Bool final private(set) var internalStickLocationDistanceFactor: CGFloat = 0.0 final var internalStickLocationDistance: CGFloat = 0.0 diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 05e4eda19b..3128fe8503 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -94,7 +94,7 @@ open class ListViewItemNode: ASDisplayNode { private final var spring: ListViewItemSpring? private final var animations: [(String, ListViewAnimation)] = [] - open private(set) var wantsScrollDynamics: Bool + final let wantsScrollDynamics: Bool public final var wantsTrailingItemSpaceUpdates: Bool = false diff --git a/Display/PeekControllerNode.swift b/Display/PeekControllerNode.swift index 2c4f54ff58..2c6aff61a1 100644 --- a/Display/PeekControllerNode.swift +++ b/Display/PeekControllerNode.swift @@ -103,7 +103,15 @@ final class PeekControllerNode: ViewControllerTracingNode { transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size)) transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: layout.size)) - let layoutInsets = layout.insets(options: []) + var layoutInsets = layout.insets(options: []) + let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + + layoutInsets.left = floor((layout.size.width - containerWidth) / 2.0) + layoutInsets.right = layoutInsets.left + if !layoutInsets.bottom.isZero { + layoutInsets.bottom -= 12.0 + } + let maxContainerSize = CGSize(width: layout.size.width - 14.0 * 2.0, height: layout.size.height - layoutInsets.top - layoutInsets.bottom - 90.0) var menuSize: CGSize? From af872377a36b52fbd0bf2ddc04705ea5fd2d03bb Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 13 Oct 2018 03:27:28 +0300 Subject: [PATCH 087/245] Fixed navigation buttons touch insets --- Display/NavigationBar.swift | 12 ++++++++++++ Display/NavigationButtonNode.swift | 4 ++-- Display/NavigationController.swift | 7 ++++++- Display/UIViewController+Navigation.h | 2 ++ Display/UIViewController+Navigation.m | 9 +++++++++ Display/WindowContent.swift | 3 +++ 6 files changed, 34 insertions(+), 3 deletions(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 61931c9b67..abf0d835e3 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -603,6 +603,7 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.displaysAsynchronously = false self.leftButtonNode = NavigationButtonNode() self.rightButtonNode = NavigationButtonNode() + self.rightButtonNode.hitTestSlop = UIEdgeInsets(top: -4.0, left: -4.0, bottom: -4.0, right: -10.0) self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true @@ -1003,4 +1004,15 @@ open class NavigationBar: ASDisplayNode { } } } + + override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.frame.contains(point) { + if !self.rightButtonNode.isHidden { + if let result = self.rightButtonNode.hitTest(self.view.convert(point, to: self.rightButtonNode.view), with: event) { + return result + } + } + } + return super.hitTest(point, with: event) + } } diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index a56663fcc3..d2db53b9d8 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -149,9 +149,9 @@ private final class NavigationButtonItemNode: ASTextNode { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop apparentBounds.origin.x += hitTestSlop.left - apparentBounds.size.width -= hitTestSlop.left + hitTestSlop.right + apparentBounds.size.width += -hitTestSlop.left - hitTestSlop.right apparentBounds.origin.y += hitTestSlop.top - apparentBounds.size.height -= hitTestSlop.top + hitTestSlop.bottom + apparentBounds.size.height += -hitTestSlop.top - hitTestSlop.bottom return apparentBounds.contains(touch.location(in: self.view)) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 5cbee58463..0994fb007d 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -718,6 +718,10 @@ open class NavigationController: UINavigationController, ContainableController, } public func pushViewController(_ controller: ViewController) { + self.pushViewController(controller, completion: {}) + } + + public func pushViewController(_ controller: ViewController, completion: @escaping () -> Void) { let navigateAction: () -> Void = { [weak self] in guard let strongSelf = self else { return @@ -784,7 +788,7 @@ open class NavigationController: UINavigationController, ContainableController, })) } - public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { + public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { self.view.endEditing(true) if let validLayout = self.validLayout { var (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) @@ -794,6 +798,7 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { ready?.set(true) + completion() var controllers = strongSelf.viewControllers while controllers.count > 1 { controllers.removeLast() diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index ff134d36c1..5c71ced132 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -21,6 +21,8 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) { @interface UIView (Navigation) @property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer; +@property (nonatomic, copy) bool (^ disablesInteractiveTransitionGestureRecognizerNow)(); + @property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling; @property (nonatomic, copy) BOOL (^_Nullable interactiveTransitionGestureRecognizerTest)(CGPoint); diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 2aed4b00ad..cfef8b7103 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -35,6 +35,7 @@ static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNa static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey; static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey; static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey; +static const void *disablesInteractiveTransitionGestureRecognizerNowKey = &disablesInteractiveTransitionGestureRecognizerNowKey; static const void *disableAutomaticKeyboardHandlingKey = &disableAutomaticKeyboardHandlingKey; static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey; static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey; @@ -232,6 +233,14 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey]; } +- (bool (^)())disablesInteractiveTransitionGestureRecognizerNow { + return [self associatedObjectForKey:disablesInteractiveTransitionGestureRecognizerNowKey]; +} + +- (void)setDisablesInteractiveTransitionGestureRecognizerNow:(bool (^)())disablesInteractiveTransitionGestureRecognizerNow { + [self setAssociatedObject:[disablesInteractiveTransitionGestureRecognizerNow copy] forKey:disablesInteractiveTransitionGestureRecognizerNowKey]; +} + - (BOOL (^)(CGPoint))interactiveTransitionGestureRecognizerTest { return [self associatedObjectForKey:interactiveTransitionGestureRecognizerTestKey]; } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 19ea652fe6..bb9f097419 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -132,6 +132,9 @@ public func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UI if view.disablesInteractiveTransitionGestureRecognizer { return true } + if let f = view.disablesInteractiveTransitionGestureRecognizerNow, f() { + return true + } if let superview = view.superview { return doesViewTreeDisableInteractiveTransitionGestureRecognizer(superview) } From eea8a08aef5e8aa72d899f063df98cedbb365d6d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 13 Oct 2018 09:04:24 +0100 Subject: [PATCH 088/245] Added various impact haptic feedback styles support --- Display/DeviceMetrics.swift | 4 ++-- Display/HapticFeedback.swift | 27 ++++++++++++++++++--------- 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index ab73db4b92..15421869a0 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -129,9 +129,9 @@ public enum DeviceMetrics { } else { switch self { case .iPhoneX: - return CGSize(width: screenSize.width, height: screenSize.height - 190.0) + return CGSize(width: screenSize.width, height: screenSize.height - 154.0) case .iPhoneXSMax: - return CGSize(width: screenSize.width, height: screenSize.height - 90.0) + return CGSize(width: screenSize.width, height: screenSize.height - 84.0) default: return CGSize(width: screenSize.width, height: screenSize.height - 50.0) } diff --git a/Display/HapticFeedback.swift b/Display/HapticFeedback.swift index 281b263bba..4883cf21aa 100644 --- a/Display/HapticFeedback.swift +++ b/Display/HapticFeedback.swift @@ -1,9 +1,18 @@ import Foundation import UIKit +public enum ImpactHapticFeedbackStyle: Hashable { + case light + case medium + case heavy +} + @available(iOSApplicationExtension 10.0, *) private final class HapticFeedbackImpl { - private lazy var impactGenerator = { UIImpactFeedbackGenerator(style: .medium) }() + private lazy var impactGenerator: [ImpactHapticFeedbackStyle : UIImpactFeedbackGenerator] = { + [.light: UIImpactFeedbackGenerator(style: .light), + .medium: UIImpactFeedbackGenerator(style: .medium), + .heavy: UIImpactFeedbackGenerator(style: .heavy)] }() private lazy var selectionGenerator = { UISelectionFeedbackGenerator() }() private lazy var notificationGenerator = { UINotificationFeedbackGenerator() }() @@ -15,12 +24,12 @@ private final class HapticFeedbackImpl { self.selectionGenerator.selectionChanged() } - func prepareImpact() { - self.impactGenerator.prepare() + func prepareImpact(_ style: ImpactHapticFeedbackStyle) { + self.impactGenerator[style]?.prepare() } - func impact() { - self.impactGenerator.impactOccurred() + func impact(_ style: ImpactHapticFeedbackStyle) { + self.impactGenerator[style]?.impactOccurred() } func success() { @@ -80,18 +89,18 @@ public final class HapticFeedback { } } - public func prepareImpact() { + public func prepareImpact(style: ImpactHapticFeedbackStyle = .medium) { if #available(iOSApplicationExtension 10.0, *) { self.withImpl { impl in - impl.prepareImpact() + impl.prepareImpact(style) } } } - public func impact() { + public func impact(_ style: ImpactHapticFeedbackStyle = .medium) { if #available(iOSApplicationExtension 10.0, *) { self.withImpl { impl in - impl.impact() + impl.impact(style) } } } From 2086a561f13b3ae52a0816691dc962d1f9fce11b Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 15 Oct 2018 16:46:13 +0300 Subject: [PATCH 089/245] Fixed NavigationBar hit testing --- Display/NavigationBar.swift | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index abf0d835e3..c7a73653c3 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1004,15 +1004,4 @@ open class NavigationBar: ASDisplayNode { } } } - - override open func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.frame.contains(point) { - if !self.rightButtonNode.isHidden { - if let result = self.rightButtonNode.hitTest(self.view.convert(point, to: self.rightButtonNode.view), with: event) { - return result - } - } - } - return super.hitTest(point, with: event) - } } From 9cc4ea00e5061c09f54260a3b921b5fe1ab66b81 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 16 Oct 2018 16:08:04 +0300 Subject: [PATCH 090/245] no message --- Display/ActionSheetButtonItem.swift | 20 ++++++++++++++++++-- Display/ContextMenuNode.swift | 4 ++-- Display/HapticFeedback.swift | 2 +- Display/PeekControllerMenuItemNode.swift | 2 +- 4 files changed, 22 insertions(+), 6 deletions(-) diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index e34e4ec1f2..7c9dc9d804 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -7,15 +7,23 @@ public enum ActionSheetButtonColor { case disabled } + +public enum ActionSheetButtonFont { + case `default` + case bold +} + public class ActionSheetButtonItem: ActionSheetItem { public let title: String public let color: ActionSheetButtonColor + public let font: ActionSheetButtonFont public let enabled: Bool public let action: () -> Void - public init(title: String, color: ActionSheetButtonColor = .accent, enabled: Bool = true, action: @escaping () -> Void) { + public init(title: String, color: ActionSheetButtonColor = .accent, font: ActionSheetButtonFont = .default, enabled: Bool = true, action: @escaping () -> Void) { self.title = title self.color = color + self.font = font self.enabled = enabled self.action = action } @@ -40,6 +48,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { private let theme: ActionSheetControllerTheme public static let defaultFont: UIFont = Font.regular(20.0) + public static let boldFont: UIFont = Font.medium(20.0) private var item: ActionSheetButtonItem? @@ -83,6 +92,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.item = item let textColor: UIColor + let textFont: UIFont switch item.color { case .accent: textColor = self.theme.standardActionTextColor @@ -91,7 +101,13 @@ public class ActionSheetButtonNode: ActionSheetItemNode { case .disabled: textColor = self.theme.disabledActionTextColor } - self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: textColor) + switch item.font { + case .default: + textFont = ActionSheetButtonNode.defaultFont + case .bold: + textFont = ActionSheetButtonNode.boldFont + } + self.label.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor) self.button.isEnabled = item.enabled diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index b2eae34d50..032cbf59bd 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -160,7 +160,7 @@ final class ContextMenuNode: ASDisplayNode { if hasHapticFeedback { self.feedback = HapticFeedback() - self.feedback?.prepareImpact() + self.feedback?.prepareImpact(.light) } else { self.feedback = nil } @@ -230,7 +230,7 @@ final class ContextMenuNode: ASDisplayNode { self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) if let feedback = self.feedback { - feedback.impact() + feedback.impact(.light) } } diff --git a/Display/HapticFeedback.swift b/Display/HapticFeedback.swift index 4883cf21aa..2379f0242d 100644 --- a/Display/HapticFeedback.swift +++ b/Display/HapticFeedback.swift @@ -89,7 +89,7 @@ public final class HapticFeedback { } } - public func prepareImpact(style: ImpactHapticFeedbackStyle = .medium) { + public func prepareImpact(_ style: ImpactHapticFeedbackStyle = .medium) { if #available(iOSApplicationExtension 10.0, *) { self.withImpl { impl in impl.prepareImpact(style) diff --git a/Display/PeekControllerMenuItemNode.swift b/Display/PeekControllerMenuItemNode.swift index 419fdc0bc1..af8e9bd189 100644 --- a/Display/PeekControllerMenuItemNode.swift +++ b/Display/PeekControllerMenuItemNode.swift @@ -62,7 +62,7 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { case .default: textFont = Font.regular(20.0) case .bold: - textFont = Font.semibold(20.0) + textFont = Font.medium(20.0) } self.textNode.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor) From 17c998e07a1362b8057338d11d864471c2d078a8 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 17 Oct 2018 15:55:34 +0300 Subject: [PATCH 091/245] Fix action sheet text item insets --- Display/ActionSheetTextItem.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Display/ActionSheetTextItem.swift b/Display/ActionSheetTextItem.swift index 8d2d9dc63d..52a240ea17 100644 --- a/Display/ActionSheetTextItem.swift +++ b/Display/ActionSheetTextItem.swift @@ -57,7 +57,8 @@ public class ActionSheetTextNode: ActionSheetItemNode { } public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - return CGSize(width: constrainedSize.width, height: 57.0) + let labelSize = self.label.measure(CGSize(width: max(1.0, constrainedSize.width - 20.0), height: constrainedSize.height)) + return CGSize(width: constrainedSize.width, height: max(57.0, labelSize.height + 32.0)) } public override func layout() { From 06b1bc2bdde2006d6dc106528028a44428089e46 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 19 Oct 2018 20:28:23 +0300 Subject: [PATCH 092/245] Improved support for temporary locking device orientation --- Display/ContainableController.swift | 2 +- Display/NavigationController.swift | 12 ++++++++++-- Display/ViewController.swift | 13 +++++++++++-- Display/WindowContent.swift | 9 ++++++++- 4 files changed, 30 insertions(+), 6 deletions(-) diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 6fbbfb8633..d0a6f0f0cb 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit public protocol ContainableController: class { var view: UIView! { get } - func combinedSupportedOrientations() -> ViewControllerSupportedOrientations + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 0994fb007d..c3ed02677e 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -139,11 +139,19 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPresentDisposable.dispose() } - public func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) if let controller = self.viewControllers.last { if let controller = controller as? ViewController { - supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations) + if controller.lockOrientation { + if let lockedOrientation = controller.lockedOrientation { + supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: lockedOrientation, compactSize: lockedOrientation)) + } else { + supportedOrientations = supportedOrientations.intersection(ViewControllerSupportedOrientations(regularSize: currentOrientationToLock, compactSize: currentOrientationToLock)) + } + } else { + supportedOrientations = supportedOrientations.intersection(controller.supportedOrientations) + } } } return supportedOrientations diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 962a37eb8b..7bdc4eb4ac 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -52,9 +52,18 @@ open class ViewControllerPresentationArguments { private let presentationContext: PresentationContext public final var supportedOrientations: ViewControllerSupportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .allButUpsideDown) - public final var lockOrientation: Bool = false + public final var lockedOrientation: UIInterfaceOrientationMask? + public final var lockOrientation: Bool = false { + didSet { + if self.lockOrientation != oldValue { + if !self.lockOrientation { + self.lockedOrientation = nil + } + } + } + } - public func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { return self.supportedOrientations } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index bb9f097419..664fd544d1 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -723,7 +723,14 @@ public class Window1 { var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) if let _rootController = self._rootController { - supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations()) + let orientationToLock: UIInterfaceOrientationMask + if self.windowLayout.size.width < self.windowLayout.size.height { + orientationToLock = .portrait + } else { + orientationToLock = .landscape + } + + supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) } supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations()) supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations()) From b34f17452011b3b2b30ad239b323ff75a0b954cc Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 22 Oct 2018 18:59:00 +0300 Subject: [PATCH 093/245] Fixed overlay presentation when keyboard window is about to be hidden or appears to remain on the screen when it actually doesn't. Added keyboard-awareness to tab bar on iPad. --- Display/GlobalOverlayPresentationContext.swift | 8 ++++++-- Display/TabBarContollerNode.swift | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index 6ae8082f40..75eafc370c 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -2,7 +2,7 @@ import Foundation import AsyncDisplayKit import SwiftSignalKit -private func isViewVisibleInHierarchy(_ view: UIView) -> Bool { +private func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> Bool { guard let window = view.window else { return false } @@ -12,7 +12,11 @@ private func isViewVisibleInHierarchy(_ view: UIView) -> Bool { if view.superview === window { return true } else if let superview = view.superview { - return isViewVisibleInHierarchy(superview) + if initial && view.frame.minY >= superview.frame.height { + return false + } else { + return isViewVisibleInHierarchy(superview, false) + } } else { return false } diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index b34cf744f6..2b240c3b2d 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -36,7 +36,7 @@ final class TabBarControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { - let tabBarHeight: CGFloat + var tabBarHeight: CGFloat let bottomInset: CGFloat = layout.insets(options: []).bottom if !layout.safeInsets.left.isZero { tabBarHeight = 34.0 + bottomInset @@ -44,6 +44,10 @@ final class TabBarControllerNode: ASDisplayNode { tabBarHeight = 49.0 + bottomInset } + if let inputHeight = layout.inputHeight, layout.metrics.widthClass == .regular { + tabBarHeight += inputHeight + } + transition.updateFrame(node: self.tabBarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))) self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition) } From 8763efee72b36a53f7ee7b258c403e818b628184 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 26 Oct 2018 19:31:48 +0300 Subject: [PATCH 094/245] Customizeable ListView animation duration --- Display/ListView.swift | 100 +++++++++++++++++++----- Display/ListViewIntermediateState.swift | 2 +- Display/NativeWindowHostView.swift | 13 ++- Display/TabBarNode.swift | 36 ++++++--- 4 files changed, 118 insertions(+), 33 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 9475586868..3bd62ae38b 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -171,6 +171,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var itemHighlightOverlayBackground: ASDisplayNode? + private var verticalScrollIndicator: ASImageNode? + public var verticalScrollIndicatorColor: UIColor? { + didSet { + if let fillColor = self.verticalScrollIndicatorColor { + if self.verticalScrollIndicator == nil { + let verticalScrollIndicator = ASImageNode() + verticalScrollIndicator.image = generateStretchableFilledCircleImage(diameter: 3.0, color: fillColor) + self.verticalScrollIndicator = verticalScrollIndicator + self.addSubnode(verticalScrollIndicator) + } + } else { + self.verticalScrollIndicator?.removeFromSupernode() + self.verticalScrollIndicator = nil + } + } + } + private var touchesPosition = CGPoint() public private(set) var isTracking = false public private(set) var trackingOffset: CGFloat = 0.0 @@ -709,16 +726,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel switch updateSizeAndInsets.curve { case let .Spring(duration): transition = .animated(duration: duration, curve: .spring) - case .Default: - transition = .animated(duration: updateSizeAndInsets.duration, curve: .easeInOut) + case let .Default(duration): + transition = .animated(duration: max(updateSizeAndInsets.duration, duration ?? 0.3), curve: .easeInOut) } } } else if let scrollToItem = scrollToItem { switch scrollToItem.curve { case let .Spring(duration): transition = .animated(duration: duration, curve: .spring) - case .Default: - transition = .animated(duration: 0.5, curve: .easeInOut) + case let .Default(duration): + transition = .animated(duration: duration ?? 0.3, curve: .easeInOut) } } @@ -1871,6 +1888,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel //return itemNode } var lowestHeaderNode: ASDisplayNode? + lowestHeaderNode = self.verticalScrollIndicator var lowestHeaderNodeIndex: Int? for (_, headerNode) in self.itemHeaderNodes { if let index = self.subnodes?.index(of: headerNode) { @@ -2299,8 +2317,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel springAnimation.isAdditive = true animation = springAnimation - case .Default: - headerNodesTransition = (.animated(duration: updateSizeAndInsets.duration, curve: .easeInOut), false, -completeOffset) + case let .Default(duration): + headerNodesTransition = (.animated(duration: max(duration ?? 0.3, updateSizeAndInsets.duration), curve: .easeInOut), false, -completeOffset) let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) basicAnimation.duration = updateSizeAndInsets.duration * UIView.animationDurationFactor() @@ -2438,8 +2456,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel switch scrollToItem.curve { case let .Spring(duration): headerNodesTransition = (.animated(duration: duration, curve: .spring), headerNodesTransition.1, headerNodesTransition.2 - offsetOrZero) - case .Default: - headerNodesTransition = (.animated(duration: 0.5, curve: .easeInOut), true, headerNodesTransition.2 - offsetOrZero) + case let .Default(duration): + headerNodesTransition = (.animated(duration: duration ?? 0.3, curve: .easeInOut), true, headerNodesTransition.2 - offsetOrZero) } for (_, headerNode) in self.itemHeaderNodes { previousItemHeaderNodes.append(headerNode) @@ -2485,16 +2503,27 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel springAnimation.speed = speed * Float(springAnimation.duration / duration) animation = springAnimation - case .Default: - let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") - basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) - //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) - basicAnimation.duration = 0.5 * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) - basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - basicAnimation.isRemovedOnCompletion = true - basicAnimation.isAdditive = true - animation = basicAnimation + case let .Default(duration): + if let duration = duration { + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + basicAnimation.duration = duration * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true + animation = basicAnimation + } else { + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) + //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + basicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true + animation = basicAnimation + } } animation.completion = { _ in for itemNode in temporaryPreviousNodes { @@ -2749,10 +2778,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { + var totalVisibleHeight: CGFloat = 0.0 var index = -1 let count = self.itemNodes.count for itemNode in self.itemNodes { index += 1 + totalVisibleHeight += itemNode.apparentHeight guard let itemNodeIndex = itemNode.index else { continue @@ -2877,6 +2908,33 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } } + + if let verticalScrollIndicator = self.verticalScrollIndicator { + var totalEstimatedHeight: CGFloat = 0.0 + var estimatedOffset: CGFloat = 0.0 + let visibleHeight = self.visibleSize.height - self.insets.top - self.insets.bottom + if !self.itemNodes.isEmpty { + totalEstimatedHeight = totalVisibleHeight / CGFloat(self.itemNodes.count) * CGFloat(self.items.count) + for i in 0 ..< self.itemNodes.count { + if let index = self.itemNodes[i].index { + break + let topOffset = self.insets.top - self.itemNodes[0].apparentFrame.minY + estimatedOffset = CGFloat(index) + } + } + } + + /* + visibleHeight - indicatorInsets * 2.0 -> totalEstimatedHeight + x -> visibleHeight + */ + + let indicatorInsets: CGFloat = 2.0 + let indicatorMaxHeight: CGFloat = visibleHeight - indicatorInsets * 2.0 + + let indicatorHeight: CGFloat = max(3.0, floor(indicatorMaxHeight * indicatorMaxHeight / totalEstimatedHeight)) + verticalScrollIndicator.frame = CGRect(origin: CGPoint(x: self.visibleSize.width - 3.0 - indicatorInsets, y: self.insets.top + indicatorInsets), size: CGSize(width: 3.0, height: indicatorHeight)) + } } private func enqueueUpdateVisibleItems() { @@ -3275,9 +3333,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func ensureItemNodeVisible(_ node: ListViewItemNode) { if let index = node.index { if node.frame.minY < self.insets.top { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } } @@ -3285,7 +3343,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func ensureItemNodeVisibleAtTopInset(_ node: ListViewItemNode) { if let index = node.index { if node.frame.minY != self.insets.top { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default, directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 184160ffcb..a304e7d847 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -23,7 +23,7 @@ public enum ListViewScrollToItemDirectionHint { public enum ListViewAnimationCurve { case Spring(duration: Double) - case Default + case Default(duration: Double?) } public struct ListViewScrollToItem { diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 306b4900d8..d32c6ca5b5 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -24,7 +24,7 @@ private final class WindowRootViewControllerView: UIView { } } -private final class WindowRootViewController: UIViewController { +private final class WindowRootViewController: UIViewController, UIViewControllerPreviewingDelegate { var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)? var transitionToSize: ((CGSize, Double) -> Void)? @@ -104,6 +104,17 @@ private final class WindowRootViewController: UIViewController { self.view = WindowRootViewControllerView() self.view.isOpaque = false self.view.backgroundColor = nil + + if #available(iOSApplicationExtension 9.0, *) { + self.registerForPreviewing(with: self, sourceView: self.view) + } + } + + public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { + return nil + } + + public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { } } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index de3fcf85aa..194a1760cc 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale -private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool) -> UIImage? { +private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool) -> (UIImage, CGFloat) { let font = horizontal ? Font.regular(13.0) : Font.medium(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size @@ -22,10 +22,13 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: let horizontalSpacing: CGFloat = 4.0 let size: CGSize + let contentWidth: CGFloat if horizontal { size = CGSize(width: ceil(titleSize.width) + horizontalSpacing + imageSize.width, height: 34.0) + contentWidth = size.width } else { size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + contentWidth = imageSize.width } UIGraphicsBeginImageContextWithOptions(size, true, 0.0) @@ -67,11 +70,15 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: let image = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return image + return (image!, contentWidth) } private let badgeFont = Font.regular(13.0) +private final class TabBarItemNode: ASImageNode { + var contentWidth: CGFloat? +} + private final class TabBarNodeContainer { let item: UITabBarItem let updateBadgeListenerIndex: Int @@ -79,7 +86,7 @@ private final class TabBarNodeContainer { let updateImageListenerIndex: Int let updateSelectedImageListenerIndex: Int - let imageNode: ASImageNode + let imageNode: TabBarItemNode let badgeContainerNode: ASDisplayNode let badgeBackgroundNode: ASImageNode let badgeTextNode: ASTextNode @@ -96,7 +103,7 @@ private final class TabBarNodeContainer { var selectedImageValue: UIImage? var appliedSelectedImageValue: UIImage? - init(item: UITabBarItem, imageNode: ASImageNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void) { + init(item: UITabBarItem, imageNode: TabBarItemNode, updateBadge: @escaping (String) -> Void, updateTitle: @escaping (String, Bool) -> Void, updateImage: @escaping (UIImage?) -> Void, updateSelectedImage: @escaping (UIImage?) -> Void) { self.item = item self.imageNode = imageNode @@ -238,7 +245,7 @@ class TabBarNode: ASDisplayNode { var tabBarNodeContainers: [TabBarNodeContainer] = [] for i in 0 ..< self.tabBarItems.count { let item = self.tabBarItems[i] - let node = ASImageNode() + let node = TabBarItemNode() node.displaysAsynchronously = false node.displayWithoutProcessing = true node.isLayerBacked = true @@ -252,9 +259,13 @@ class TabBarNode: ASDisplayNode { self?.updateNodeImage(i, layout: true) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) + node.image = image + node.contentWidth = contentWidth } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) + node.image = image + node.contentWidth = contentWidth } container.badgeBackgroundNode.image = self.badgeImage tabBarNodeContainers.append(container) @@ -277,9 +288,13 @@ class TabBarNode: ASDisplayNode { let previousImage = node.image if let selectedIndex = self.selectedIndex, selectedIndex == index { - node.image = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) + node.image = image + node.contentWidth = contentWidth } else { - node.image = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) + node.image = image + node.contentWidth = contentWidth } if previousImage?.size != node.image?.size { if let validLayout = self.validLayout, layout { @@ -351,7 +366,8 @@ class TabBarNode: ASDisplayNode { if horizontal { backgroundFrame = CGRect(origin: CGPoint(x: originX, y: 2.0), size: backgroundSize) } else { - backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 1.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) + let contentWidth = node.contentWidth ?? node.frame.width + backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 1.0 + contentWidth - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) } transition.updateFrame(node: container.badgeContainerNode, frame: backgroundFrame) container.badgeBackgroundNode.frame = CGRect(origin: CGPoint(), size: backgroundFrame.size) From 43047c73304e93fa2c59652568fdc96f69675c83 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 31 Oct 2018 21:29:35 +0400 Subject: [PATCH 095/245] Added support for 3rd gen iPad Pro --- Display/DeviceMetrics.swift | 97 +++++++++++++++++++++---------- Display/TabBarContollerNode.swift | 10 ++-- Display/WindowContent.swift | 69 ++++++++++------------ 3 files changed, 103 insertions(+), 73 deletions(-) diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index 15421869a0..ae233602f2 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -1,6 +1,6 @@ import UIKit -public enum DeviceMetrics { +public enum DeviceMetrics: CaseIterable { case iPhone4 case iPhone5 case iPhone6 @@ -8,16 +8,20 @@ public enum DeviceMetrics { case iPhoneX case iPhoneXSMax case iPad + case iPadPro10Inch + case iPadPro11Inch case iPadPro - - static let allDevices = [iPhone4, iPhone5, iPhone6, iPhone6Plus, iPhoneX, iPhoneXSMax, iPad, iPadPro] - - public static func forScreenSize(_ size: CGSize) -> DeviceMetrics? { - for device in allDevices { + case iPadPro3rdGen + + public static func forScreenSize(_ size: CGSize, hintHasOnScreenNavigation: Bool = false) -> DeviceMetrics? { + for device in DeviceMetrics.allCases { let width = device.screenSize.width let height = device.screenSize.height if (size.width.isEqual(to: width) && size.height.isEqual(to: height)) || size.height.isEqual(to: width) && size.width.isEqual(to: height) { + if hintHasOnScreenNavigation && device.onScreenNavigationHeight(inLandscape: false) == nil { + continue + } return device } } @@ -40,7 +44,11 @@ public enum DeviceMetrics { return CGSize(width: 414.0, height: 896.0) case .iPad: return CGSize(width: 768.0, height: 1024.0) - case .iPadPro: + case .iPadPro10Inch: + return CGSize(width: 834.0, height: 1112.0) + case .iPadPro11Inch: + return CGSize(width: 834.0, height: 1194.0) + case .iPadPro, .iPadPro3rdGen: return CGSize(width: 1024.0, height: 1366.0) } } @@ -54,37 +62,64 @@ public enum DeviceMetrics { } } - func onScreenNavigationHeight(inLandscape: Bool) -> CGFloat { + func onScreenNavigationHeight(inLandscape: Bool) -> CGFloat? { switch self { case .iPhoneX, .iPhoneXSMax: return inLandscape ? 21.0 : 34.0 + case .iPadPro3rdGen, .iPadPro11Inch: + return 21.0 default: - return 0.0 + return nil + } + } + + var statusBarHeight: CGFloat { + switch self { + case .iPhoneX, .iPhoneXSMax: + return 44.0 + case .iPadPro11Inch, .iPadPro3rdGen: + return 24.0 + default: + return 20.0 } } func standardInputHeight(inLandscape: Bool) -> CGFloat { if inLandscape { switch self { - case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus: + case .iPhone4, .iPhone5: return 162.0 + case .iPhone6, .iPhone6Plus: + return 163.0 case .iPhoneX, .iPhoneXSMax: - return 171.0 - case .iPad, .iPadPro: - return 264.0 + return 172.0 + case .iPad, .iPadPro10Inch: + return 348.0 + case .iPadPro11Inch: + return 368.0 + case .iPadPro: + return 421.0 + case .iPadPro3rdGen: + return 441.0 } } else { switch self { case .iPhone4, .iPhone5, .iPhone6: return 216.0 case .iPhone6Plus: - return 226.0 + return 227.0 case .iPhoneX: return 291.0 case .iPhoneXSMax: - return 301.0 - case .iPad, .iPadPro: - return 264.0 + return 302.0 + case .iPad, .iPadPro10Inch: + return 263.0 + case .iPadPro11Inch: + return 283.0 + case .iPadPro: + return 328.0 + case .iPadPro3rdGen: + return 348.0 } } } @@ -92,25 +127,19 @@ public enum DeviceMetrics { func predictiveInputHeight(inLandscape: Bool) -> CGFloat { if inLandscape { switch self { - case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus: - return 38.0 - case .iPhoneX, .iPhoneXSMax: - return 38.0 - case .iPad, .iPadPro: - return 42.0 + case .iPhone4, .iPhone5, .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax: + return 37.0 + case .iPad, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen: + return 50.0 } } else { switch self { case .iPhone4, .iPhone5: return 37.0 - case .iPhone6, .iPhoneX: + case .iPhone6, .iPhone6Plus, .iPhoneX, .iPhoneXSMax: return 44.0 - case .iPhone6Plus: - return 42.0 - case .iPhoneXSMax: - return 45.0 - case .iPad, .iPadPro: - return 42.0 + case .iPad, .iPadPro10Inch, .iPadPro11Inch, .iPadPro, .iPadPro3rdGen: + return 50.0 } } } @@ -119,6 +148,10 @@ public enum DeviceMetrics { let screenSize = self.screenSize if inLandscape { switch self { + case .iPhone5: + return CGSize(width: screenSize.height, height: screenSize.width - 10.0) + case .iPhone6: + return CGSize(width: screenSize.height, height: screenSize.width - 22.0) case .iPhoneX: return CGSize(width: screenSize.height, height: screenSize.width + 48.0) case .iPhoneXSMax: @@ -128,6 +161,10 @@ public enum DeviceMetrics { } } else { switch self { + case .iPhone5: + return CGSize(width: screenSize.width, height: screenSize.height - 50.0) + case .iPhone6: + return CGSize(width: screenSize.width, height: screenSize.height - 97.0) case .iPhoneX: return CGSize(width: screenSize.width, height: screenSize.height - 154.0) case .iPhoneXSMax: diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 2b240c3b2d..19c0005142 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -37,17 +37,17 @@ final class TabBarControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { let update = { var tabBarHeight: CGFloat - let bottomInset: CGFloat = layout.insets(options: []).bottom + var options: ContainerViewLayoutInsetOptions = [] + if layout.metrics.widthClass == .regular { + options.insert(.input) + } + let bottomInset: CGFloat = layout.insets(options: options).bottom if !layout.safeInsets.left.isZero { tabBarHeight = 34.0 + bottomInset } else { tabBarHeight = 49.0 + bottomInset } - if let inputHeight = layout.inputHeight, layout.metrics.widthClass == .regular { - tabBarHeight += inputHeight - } - transition.updateFrame(node: self.tabBarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))) self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition) } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 664fd544d1..7d74d88bf4 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -82,7 +82,7 @@ private func inputHeightOffsetForLayout(_ layout: WindowLayout) -> CGFloat { return 0.0 } -private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout { +private func containedLayoutForWindowLayout(_ layout: WindowLayout, hasOnScreenNavigation: Bool) -> ContainerViewLayout { let resolvedStatusBarHeight: CGFloat? if let statusBarHeight = layout.statusBarHeight { if layout.forceInCallStatusBarText != nil { @@ -103,10 +103,10 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container var standardInputHeight: CGFloat = 216.0 var predictiveHeight: CGFloat = 42.0 - if let metrics = DeviceMetrics.forScreenSize(layout.size) { + if let metrics = DeviceMetrics.forScreenSize(layout.size, hintHasOnScreenNavigation: hasOnScreenNavigation) { let isLandscape = layout.size.width > layout.size.height let safeAreaInsets = metrics.safeAreaInsets(inLandscape: isLandscape) - if safeAreaInsets.left > 0.0 { + if !safeAreaInsets.left.isZero { resolvedSafeInsets.left = safeAreaInsets.left resolvedSafeInsets.right = safeAreaInsets.right } @@ -229,6 +229,14 @@ public final class WindowHostView { self.updateDeferScreenEdgeGestures = updateDeferScreenEdgeGestures self.updatePreferNavigationUIHidden = updatePreferNavigationUIHidden } + + var hasOnScreenNavigation: Bool { + if #available(iOSApplicationExtension 11.0, *) { + return !self.eventView.safeAreaInsets.bottom.isZero + } else { + return false + } + } } public struct WindowTracingTags { @@ -253,12 +261,8 @@ private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { } } -private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { - return DeviceMetrics.forScreenSize(size)?.safeAreaInsets(inLandscape: size.width > size.height) ?? UIEdgeInsets.zero -} - -private func isSizeOfScreenWithOnScreenNavigation(_ size: CGSize) -> Bool { - return (DeviceMetrics.forScreenSize(size)?.onScreenNavigationHeight(inLandscape: size.width > size.height) ?? 0.0) > 0.0 +private func safeInsetsForScreenSize(_ size: CGSize, hasOnScreenNavigation: Bool) -> UIEdgeInsets { + return DeviceMetrics.forScreenSize(size, hintHasOnScreenNavigation: hasOnScreenNavigation)?.safeAreaInsets(inLandscape: size.width > size.height) ?? UIEdgeInsets.zero } public final class WindowKeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate { @@ -325,30 +329,23 @@ public class Window1 { self.volumeControlStatusBarNode.isHidden = true let boundsSize = self.hostView.eventView.bounds.size + let deviceMetrics = DeviceMetrics.forScreenSize(boundsSize, hintHasOnScreenNavigation: self.hostView.hasOnScreenNavigation) self.statusBarHost = statusBarHost let statusBarHeight: CGFloat if let statusBarHost = statusBarHost { - self.statusBarManager = StatusBarManager(host: statusBarHost, volumeControlStatusBar: self.volumeControlStatusBar, volumeControlStatusBarNode: self.volumeControlStatusBarNode) statusBarHeight = statusBarHost.statusBarFrame.size.height + self.statusBarManager = StatusBarManager(host: statusBarHost, volumeControlStatusBar: self.volumeControlStatusBar, volumeControlStatusBarNode: self.volumeControlStatusBarNode) self.keyboardManager = KeyboardManager(host: statusBarHost) } else { + statusBarHeight = deviceMetrics?.statusBarHeight ?? 20.0 self.statusBarManager = nil self.keyboardManager = nil - - if (isSizeOfScreenWithOnScreenNavigation(boundsSize)) { - statusBarHeight = 44.0 - } else { - statusBarHeight = 20.0 - } } - var onScreenNavigationHeight: CGFloat? - if (isSizeOfScreenWithOnScreenNavigation(boundsSize)) { - onScreenNavigationHeight = 34.0 - } + let onScreenNavigationHeight = deviceMetrics?.onScreenNavigationHeight(inLandscape: boundsSize.width > boundsSize.height) - self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) + self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) self.presentationContext = PresentationContext() self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost) @@ -405,8 +402,8 @@ public class Window1 { } self.presentationContext.view = self.hostView.containerView - self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) + self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { @@ -592,7 +589,7 @@ public class Window1 { } else { transition = .immediate } - self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), safeInsets: safeInsetsForScreenSize(value), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } + self.updateLayout { $0.update(size: value, metrics: layoutMetricsForScreenSize(value), safeInsets: safeInsetsForScreenSize(value, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), forceInCallStatusBarText: self.forceInCallStatusBarText, transition: transition, overrideTransition: true) } } private var _rootController: ContainableController? @@ -608,7 +605,7 @@ public class Window1 { if let rootController = self._rootController { if !self.windowLayout.size.width.isZero && !self.windowLayout.size.height.isZero { - rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) } if let coveringView = self.coveringView { @@ -631,8 +628,9 @@ public class Window1 { } self._topLevelOverlayControllers = value + let layout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation) for controller in self._topLevelOverlayControllers { - controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + controller.containerLayoutUpdated(layout, transition: .immediate) if let coveringView = self.coveringView { self.hostView.containerView.insertSubview(controller.view, belowSubview: coveringView) @@ -814,15 +812,15 @@ public class Window1 { self.updatingLayout = nil if updatingLayout.layout != self.windowLayout || self.isFirstLayout { self.isFirstLayout = false + + let boundsSize = updatingLayout.layout.size + let deviceMetrics = DeviceMetrics.forScreenSize(boundsSize, hintHasOnScreenNavigation: self.hostView.hasOnScreenNavigation) + var statusBarHeight: CGFloat? if let statusBarHost = self.statusBarHost { statusBarHeight = statusBarHost.statusBarFrame.size.height } else { - if isSizeOfScreenWithOnScreenNavigation(updatingLayout.layout.size) { - statusBarHeight = 44.0 - } else { - statusBarHeight = 20.0 - } + statusBarHeight = deviceMetrics?.statusBarHeight ?? 20.0 } let statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { @@ -837,16 +835,11 @@ public class Window1 { } let previousInputOffset = inputHeightOffsetForLayout(self.windowLayout) - var onScreenNavigationHeight: CGFloat? - if let height = updatingLayout.layout.onScreenNavigationHeight { - onScreenNavigationHeight = height - } else if isSizeOfScreenWithOnScreenNavigation(updatingLayout.layout.size) { - onScreenNavigationHeight = 34.0 - } + let onScreenNavigationHeight = deviceMetrics?.onScreenNavigationHeight(inLandscape: boundsSize.width > boundsSize.height) self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound) - let childLayout = containedLayoutForWindowLayout(self.windowLayout) + let childLayout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation) let childLayoutUpdated = self.updatedContainerLayout != childLayout self.updatedContainerLayout = childLayout From 4dd5c5fef16da9fc2d2df3a132a84845b4590036 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 31 Oct 2018 23:39:19 +0400 Subject: [PATCH 096/245] Updated ListView animations Added global 3d touch handling (workaround for 3d touch controller leak) --- Display/ListView.swift | 11 +++++++++- Display/NativeWindowHostView.swift | 33 ++++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 3bd62ae38b..366c2ef17b 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2506,7 +2506,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel case let .Default(duration): if let duration = duration { let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) basicAnimation.duration = duration * UIView.animationDurationFactor() basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) @@ -3354,6 +3354,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } return nil } + + public func itemNodeVisibleInsideInsets(_ node: ListViewItemNode) -> Bool { + if let _ = node.index { + if node.frame.maxY > self.insets.top && node.frame.minY < self.visibleSize.height - self.insets.bottom { + return true + } + } + return false + } override open func touchesMoved(_ touches: Set, with event: UIEvent?) { if let selectionTouchLocation = self.selectionTouchLocation { diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index d32c6ca5b5..d3afd54b12 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -11,6 +11,25 @@ private let defaultOrientations: UIInterfaceOrientationMask = { } }() +public protocol PreviewingHostView { + @available(iOSApplicationExtension 9.0, *) + var previewingDelegate: UIViewControllerPreviewingDelegate? { get } +} + +private func tracePreviewingHostView(view: UIView, point: CGPoint) -> (UIView & PreviewingHostView, CGPoint)? { + if let view = view as? UIView & PreviewingHostView { + return (view, point) + } + for subview in view.subviews { + if subview.frame.contains(point) && !subview.isHidden && subview.isUserInteractionEnabled { + if let result = tracePreviewingHostView(view: subview, point: view.convert(point, to: subview)) { + return result + } + } + } + return nil +} + private final class WindowRootViewControllerView: UIView { override var frame: CGRect { get { @@ -110,11 +129,25 @@ private final class WindowRootViewController: UIViewController, UIViewController } } + private weak var previousPreviewingHostView: (UIView & PreviewingHostView)? + public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { + if #available(iOSApplicationExtension 9.0, *) { + if let (result, resultPoint) = tracePreviewingHostView(view: self.view, point: location), let delegate = result.previewingDelegate { + self.previousPreviewingHostView = result + return delegate.previewingContext(previewingContext, viewControllerForLocation: resultPoint) + } + } return nil } public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { + if #available(iOSApplicationExtension 9.0, *) { + if let previousPreviewingHostView = self.previousPreviewingHostView, let delegate = previousPreviewingHostView.previewingDelegate { + delegate.previewingContext(previewingContext, commit: viewControllerToCommit) + } + self.previousPreviewingHostView = nil + } } } From d3f62da37545681a04624ec63aa7aa43b0a63ed7 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 1 Nov 2018 19:08:21 +0400 Subject: [PATCH 097/245] Additional fixes for global 3d touch workaround --- Display/NativeWindowHostView.swift | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index d3afd54b12..15dfe2c5eb 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -20,11 +20,9 @@ private func tracePreviewingHostView(view: UIView, point: CGPoint) -> (UIView & if let view = view as? UIView & PreviewingHostView { return (view, point) } - for subview in view.subviews { - if subview.frame.contains(point) && !subview.isHidden && subview.isUserInteractionEnabled { - if let result = tracePreviewingHostView(view: subview, point: view.convert(point, to: subview)) { - return result - } + if let superview = view.superview { + if let result = tracePreviewingHostView(view: superview, point: superview.convert(point, from: view)) { + return result } } return nil @@ -133,7 +131,10 @@ private final class WindowRootViewController: UIViewController, UIViewController public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { if #available(iOSApplicationExtension 9.0, *) { - if let (result, resultPoint) = tracePreviewingHostView(view: self.view, point: location), let delegate = result.previewingDelegate { + guard let result = self.view.hitTest(location, with: nil) else { + return nil + } + if let (result, resultPoint) = tracePreviewingHostView(view: result, point: self.view.convert(location, to: result)), let delegate = result.previewingDelegate { self.previousPreviewingHostView = result return delegate.previewingContext(previewingContext, viewControllerForLocation: resultPoint) } From ea3a8f541481a0788aaad410ccdeae33774e4e06 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 2 Nov 2018 11:50:44 +0400 Subject: [PATCH 098/245] Fixed context menu leaving screen bounds when source is beyond the left edge of the screen --- Display/ContextMenuNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index 032cbf59bd..447e1fadcf 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -209,7 +209,7 @@ final class ContextMenuNode: ASDisplayNode { } self.arrowOnBottom = arrowOnBottom - let horizontalOrigin: CGFloat = floor(min(max(sourceRect.minX + 8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) + let horizontalOrigin: CGFloat = floor(max(8.0, min(max(sourceRect.minX + 8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0))) self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0)) self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) From e56e8f1511ae7be409ab3e1c239912514681aa07 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 2 Nov 2018 22:40:24 +0400 Subject: [PATCH 099/245] Added ability to "block" interaction until controller presentation is completed --- Display/NativeWindowHostView.swift | 31 +++++++++++++++------- Display/PresentationContext.swift | 42 +++++++++++++++++++++++++++--- Display/ViewController.swift | 4 +-- Display/WindowContent.swift | 21 ++++++++++----- 4 files changed, 78 insertions(+), 20 deletions(-) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 15dfe2c5eb..d5c8620d5c 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -11,9 +11,19 @@ private let defaultOrientations: UIInterfaceOrientationMask = { } }() +public final class PreviewingHostViewDelegate { + public let controllerForLocation: (UIView, CGPoint) -> (UIViewController, CGRect)? + public let commitController: (UIViewController) -> Void + + public init(controllerForLocation: @escaping (UIView, CGPoint) -> (UIViewController, CGRect)?, commitController: @escaping (UIViewController) -> Void) { + self.controllerForLocation = controllerForLocation + self.commitController = commitController + } +} + public protocol PreviewingHostView { @available(iOSApplicationExtension 9.0, *) - var previewingDelegate: UIViewControllerPreviewingDelegate? { get } + var previewingDelegate: PreviewingHostViewDelegate? { get } } private func tracePreviewingHostView(view: UIView, point: CGPoint) -> (UIView & PreviewingHostView, CGPoint)? { @@ -136,7 +146,10 @@ private final class WindowRootViewController: UIViewController, UIViewController } if let (result, resultPoint) = tracePreviewingHostView(view: result, point: self.view.convert(location, to: result)), let delegate = result.previewingDelegate { self.previousPreviewingHostView = result - return delegate.previewingContext(previewingContext, viewControllerForLocation: resultPoint) + if let (controller, rect) = delegate.controllerForLocation(previewingContext.sourceView, resultPoint) { + previewingContext.sourceRect = rect + return controller + } } } return nil @@ -145,7 +158,7 @@ private final class WindowRootViewController: UIViewController, UIViewController public func previewingContext(_ previewingContext: UIViewControllerPreviewing, commit viewControllerToCommit: UIViewController) { if #available(iOSApplicationExtension 9.0, *) { if let previousPreviewingHostView = self.previousPreviewingHostView, let delegate = previousPreviewingHostView.previewingDelegate { - delegate.previewingContext(previewingContext, commit: viewControllerToCommit) + delegate.commitController(viewControllerToCommit) } self.previousPreviewingHostView = nil } @@ -157,7 +170,7 @@ private final class NativeWindow: UIWindow, WindowHost { var layoutSubviewsEvent: (() -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateToInterfaceOrientation: (() -> Void)? - var presentController: ((ViewController, PresentationSurfaceLevel) -> Void)? + var presentController: ((ViewController, PresentationSurfaceLevel, Bool) -> Void)? var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? @@ -235,8 +248,8 @@ private final class NativeWindow: UIWindow, WindowHost { self.updateToInterfaceOrientation?() }*/ - func present(_ controller: ViewController, on level: PresentationSurfaceLevel) { - self.presentController?(controller, level) + func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool) { + self.presentController?(controller, level, blockInteraction) } func presentInGlobalOverlay(_ controller: ViewController) { @@ -324,8 +337,8 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { hostView?.updateToInterfaceOrientation?() } - window.presentController = { [weak hostView] controller, level in - hostView?.present?(controller, level) + window.presentController = { [weak hostView] controller, level, blockInteraction in + hostView?.present?(controller, level, blockInteraction) } window.presentControllerInGlobalOverlay = { [weak hostView] controller in @@ -358,7 +371,7 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let hostView = hostView { - hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) + hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level, false) completion?() } } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 9262ea5546..15790e94c5 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -31,6 +31,8 @@ final class PresentationContext { } } + var updateIsInteractionBlocked: ((Bool) -> Void)? + private var layout: ContainerViewLayout? private var ready: Bool { @@ -43,7 +45,29 @@ final class PresentationContext { var topLevelSubview: UIView? - public func present(_ controller: ViewController, on: PresentationSurfaceLevel) { + private var nextBlockInteractionToken = 0 + private var blockInteractionTokens = Set() + + private func addBlockInteraction() -> Int { + let token = self.nextBlockInteractionToken + self.nextBlockInteractionToken += 1 + let wasEmpty = self.blockInteractionTokens.isEmpty + self.blockInteractionTokens.insert(token) + if wasEmpty { + self.updateIsInteractionBlocked?(true) + } + return token + } + + private func removeBlockInteraction(_ token: Int) { + let wasEmpty = self.blockInteractionTokens.isEmpty + self.blockInteractionTokens.remove(token) + if !wasEmpty && self.blockInteractionTokens.isEmpty { + self.updateIsInteractionBlocked?(false) + } + } + + public func present(_ controller: ViewController, on: PresentationSurfaceLevel, blockInteraction: Bool = false) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -63,9 +87,21 @@ final class PresentationContext { } controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) - - self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in + var blockInteractionToken: Int? + if blockInteraction { + blockInteractionToken = self.addBlockInteraction() + } + self.presentationDisposables.add((controllerReady |> afterDisposed { [weak self] in + Queue.mainQueue().async { + if let blockInteractionToken = blockInteractionToken { + self?.removeBlockInteraction(blockInteractionToken) + } + } + }).start(next: { [weak self] _ in if let strongSelf = self { + if let blockInteractionToken = blockInteractionToken { + strongSelf.removeBlockInteraction(blockInteractionToken) + } if strongSelf.controllers.contains(where: { $0 === controller }) { return } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 7bdc4eb4ac..2f11d1b6fe 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -334,13 +334,13 @@ open class ViewControllerPresentationArguments { return nil } - public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil) { + public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false) { controller.presentationArguments = arguments switch context { case .current: self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0)) case let .window(level): - self.window?.present(controller, on: level) + self.window?.present(controller, on: level, blockInteraction: blockInteraction) } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 7d74d88bf4..98f8edbaf0 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -208,7 +208,7 @@ public final class WindowHostView { let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void let updatePreferNavigationUIHidden: (Bool) -> Void - var present: ((ViewController, PresentationSurfaceLevel) -> Void)? + var present: ((ViewController, PresentationSurfaceLevel, Bool) -> Void)? var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize, Double) -> Void)? @@ -246,7 +246,7 @@ public struct WindowTracingTags { public protocol WindowHost { func forEachController(_ f: (ViewController) -> Void) - func present(_ controller: ViewController, on level: PresentationSurfaceLevel) + func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool) func presentInGlobalOverlay(_ controller: ViewController) func invalidateDeferScreenEdgeGestures() func invalidatePreferNavigationUIHidden() @@ -321,6 +321,8 @@ public class Window1 { private let volumeControlStatusBar: VolumeControlStatusBar private let volumeControlStatusBarNode: VolumeControlStatusBarNode + private var isInteractionBlocked = false + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -350,8 +352,12 @@ public class Window1 { self.presentationContext = PresentationContext() self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost) - self.hostView.present = { [weak self] controller, level in - self?.present(controller, on: level) + self.presentationContext.updateIsInteractionBlocked = { [weak self] value in + self?.isInteractionBlocked = value + } + + self.hostView.present = { [weak self] controller, level, blockInteraction in + self?.present(controller, on: level, blockInteraction: blockInteraction) } self.hostView.presentInGlobalOverlay = { [weak self] controller in @@ -554,6 +560,9 @@ public class Window1 { } public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.isInteractionBlocked { + return nil + } if let coveringView = self.coveringView, !coveringView.isHidden, coveringView.superview != nil, coveringView.frame.contains(point) { return coveringView.hitTest(point, with: event) } @@ -877,8 +886,8 @@ public class Window1 { } } - public func present(_ controller: ViewController, on level: PresentationSurfaceLevel) { - self.presentationContext.present(controller, on: level) + public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false) { + self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction) } public func presentInGlobalOverlay(_ controller: ViewController) { From 44585b49aeb3133fea3f2d72d7406e2b5edb3412 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Tue, 6 Nov 2018 13:04:37 +0400 Subject: [PATCH 100/245] Cleanup --- Display/NativeWindowHostView.swift | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index d5c8620d5c..6f1799076d 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -264,22 +264,6 @@ private final class NativeWindow: UIWindow, WindowHost { return self.hitTestImpl?(point, event) } - override func insertSubview(_ view: UIView, at index: Int) { - super.insertSubview(view, at: index) - } - - override func addSubview(_ view: UIView) { - super.addSubview(view) - } - - override func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView) { - if let transitionClass = NSClassFromString("UITransitionView"), view.isKind(of: transitionClass) { - super.insertSubview(view, aboveSubview: self.subviews.last!) - } else { - super.insertSubview(view, aboveSubview: siblingSubview) - } - } - func invalidateDeferScreenEdgeGestures() { self.invalidateDeferScreenEdgeGestureImpl?() } From 0a5786e03e1d647cb28281e722ff9719174eab31 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 6 Nov 2018 19:54:44 +0400 Subject: [PATCH 101/245] no message --- Display/DeviceMetrics.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index ae233602f2..8916934fff 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -152,6 +152,8 @@ public enum DeviceMetrics: CaseIterable { return CGSize(width: screenSize.height, height: screenSize.width - 10.0) case .iPhone6: return CGSize(width: screenSize.height, height: screenSize.width - 22.0) + case .iPhone6Plus: + return CGSize(width: screenSize.height, height: screenSize.width - 22.0) case .iPhoneX: return CGSize(width: screenSize.height, height: screenSize.width + 48.0) case .iPhoneXSMax: @@ -165,6 +167,8 @@ public enum DeviceMetrics: CaseIterable { return CGSize(width: screenSize.width, height: screenSize.height - 50.0) case .iPhone6: return CGSize(width: screenSize.width, height: screenSize.height - 97.0) + case .iPhone6Plus: + return CGSize(width: screenSize.width, height: screenSize.height - 95.0) case .iPhoneX: return CGSize(width: screenSize.width, height: screenSize.height - 154.0) case .iPhoneXSMax: From 37c9ed8829bfa81016f0e1fe53f8e83d7c0e03d2 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 11 Nov 2018 17:43:33 +0400 Subject: [PATCH 102/245] CAAnimationUtils improvements --- Display/CAAnimationUtils.swift | 8 ++++++++ Display/ContainedViewLayoutTransition.swift | 18 ++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 297b7b9a04..607d2299d6 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -237,10 +237,18 @@ public extension CALayer { self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } + public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + } + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } + public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true) + } + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) { self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true) } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index 1a9d824924..7d2d8d9ee4 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -215,6 +215,24 @@ public extension ContainedViewLayoutTransition { } } + func animateHorizontalOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { completed in + print("completed \(completed)") + }) + } + } + func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { switch self { case .immediate: From 6b29e5897915a5fda752bacd9e4399d096444f32 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Nov 2018 17:46:36 +0400 Subject: [PATCH 103/245] Added ability to disable dismiss by tap outside for alerts Fixed input language switch on external keyboard --- Display/AlertContentNode.swift | 4 ++++ Display/AlertController.swift | 2 +- Display/KeyShortcutsController.swift | 6 +++--- Display/TextAlertController.swift | 15 ++++++++++----- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/Display/AlertContentNode.swift b/Display/AlertContentNode.swift index c95d14ac95..1aa5e4f686 100644 --- a/Display/AlertContentNode.swift +++ b/Display/AlertContentNode.swift @@ -2,6 +2,10 @@ import Foundation import AsyncDisplayKit open class AlertContentNode: ASDisplayNode { + open var dismissOnOutsideTap: Bool { + return true + } + open func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { assertionFailure() diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 93c70a428d..aa33dfcd87 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -49,7 +49,7 @@ open class AlertController: ViewController { self.displayNodeDidLoad() self.controllerNode.dismiss = { [weak self] in - if let strongSelf = self { + if let strongSelf = self, strongSelf.contentNode.dismissOnOutsideTap { strongSelf.controllerNode.animateOut { self?.dismiss() } diff --git a/Display/KeyShortcutsController.swift b/Display/KeyShortcutsController.swift index d125265d44..9e9d321269 100644 --- a/Display/KeyShortcutsController.swift +++ b/Display/KeyShortcutsController.swift @@ -36,7 +36,7 @@ public class KeyShortcutsController: UIResponder { // iOS 8 fix convertedCommands.append(KeyShortcut(modifiers:[.command]).uiKeyCommand) convertedCommands.append(KeyShortcut(modifiers:[.alternate]).uiKeyCommand) - + convertedCommands.append(contentsOf: shortcuts.map { $0.uiKeyCommand }) self.effectiveShortcuts = shortcuts @@ -62,7 +62,7 @@ public class KeyShortcutsController: UIResponder { } public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool { - if sender is UIKeyCommand { + if let keyCommand = sender as? UIKeyCommand, let _ = findShortcut(for: keyCommand) { return true } else { return super.canPerformAction(action, withSender: sender) @@ -70,7 +70,7 @@ public class KeyShortcutsController: UIResponder { } public override func target(forAction action: Selector, withSender sender: Any?) -> Any? { - if sender is UIKeyCommand { + if let keyCommand = sender as? UIKeyCommand, let _ = findShortcut(for: keyCommand) { return self } else { return super.target(forAction: action, withSender: sender) diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index bd456c204a..04eb795723 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -200,9 +200,14 @@ public final class TextAlertContentNode: AlertContentNode { var minActionsWidth: CGFloat = 0.0 let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count)) let actionTitleInsets: CGFloat = 8.0 + + var effectiveActionLayout = self.actionLayout for actionNode in self.actionNodes { let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionButtonHeight)) - switch self.actionLayout { + if case .horizontal = effectiveActionLayout, actionTitleSize.height > actionButtonHeight * 0.6667 { + effectiveActionLayout = .vertical + } + switch effectiveActionLayout { case .horizontal: minActionsWidth += actionTitleSize.width + actionTitleInsets case .vertical: @@ -213,7 +218,7 @@ public final class TextAlertContentNode: AlertContentNode { let resultSize: CGSize var actionsHeight: CGFloat = 0.0 - switch self.actionLayout { + switch effectiveActionLayout { case .horizontal: actionsHeight = actionButtonHeight case .vertical: @@ -251,7 +256,7 @@ public final class TextAlertContentNode: AlertContentNode { for actionNode in self.actionNodes { if separatorIndex >= 0 { let separatorNode = self.actionVerticalSeparators[separatorIndex] - switch self.actionLayout { + switch effectiveActionLayout { case .horizontal: transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel))) case .vertical: @@ -261,7 +266,7 @@ public final class TextAlertContentNode: AlertContentNode { separatorIndex += 1 let currentActionWidth: CGFloat - switch self.actionLayout { + switch effectiveActionLayout { case .horizontal: if nodeIndex == self.actionNodes.count - 1 { currentActionWidth = resultSize.width - actionOffset @@ -273,7 +278,7 @@ public final class TextAlertContentNode: AlertContentNode { } let actionNodeFrame: CGRect - switch self.actionLayout { + switch effectiveActionLayout { case .horizontal: actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionButtonHeight)) actionOffset += currentActionWidth From df55d0269d0670cef6da64a9fae9003aae91f87f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 11 Nov 2018 22:21:44 +0400 Subject: [PATCH 104/245] Fixed context menu buttons not firing events while in landscape with keyboard on screen --- Display/ContextMenuActionNode.swift | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift index 9325ce0db9..9b7f81bdf9 100644 --- a/Display/ContextMenuActionNode.swift +++ b/Display/ContextMenuActionNode.swift @@ -1,11 +1,21 @@ import Foundation import AsyncDisplayKit +final private class ContextMenuActionButton: HighlightTrackingButton { + override func convert(_ point: CGPoint, from view: UIView?) -> CGPoint { + if view is UIWindow { + return super.convert(point, from: nil) + } else { + return super.convert(point, from: view) + } + } +} + final class ContextMenuActionNode: ASDisplayNode { private let textNode: ASTextNode? private let iconNode: ASImageNode? private let action: () -> Void - private let button: HighlightTrackingButton + private let button: ContextMenuActionButton var dismiss: (() -> Void)? @@ -30,7 +40,7 @@ final class ContextMenuActionNode: ASDisplayNode { } self.action = action.action - self.button = HighlightTrackingButton() + self.button = ContextMenuActionButton() super.init() From f27bc011dc4275dd102b5fad479ed738f5f6a67d Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 13 Nov 2018 00:27:27 +0400 Subject: [PATCH 105/245] Added UIColor.mixedWith --- Display/UIKitUtils.swift | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 9af84b77ef..7135da899a 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -67,6 +67,30 @@ public extension UIColor { return UIColor(hue: hue, saturation: saturation, brightness: max(0.0, min(1.0, brightness * factor)), alpha: alpha) } + + func mixedWith(_ other: UIColor, alpha: CGFloat) -> UIColor { + let alpha = min(1.0, max(0.0, alpha)) + let oneMinusAlpha = 1.0 - alpha + + var r1: CGFloat = 0.0 + var r2: CGFloat = 0.0 + var g1: CGFloat = 0.0 + var g2: CGFloat = 0.0 + var b1: CGFloat = 0.0 + var b2: CGFloat = 0.0 + var a1: CGFloat = 0.0 + var a2: CGFloat = 0.0 + if self.getRed(&r1, green: &g1, blue: &b1, alpha: &a1) && + other.getRed(&r2, green: &g2, blue: &b2, alpha: &a2) + { + let r = r1 * oneMinusAlpha + r2 * alpha + let g = g1 * oneMinusAlpha + g2 * alpha + let b = b1 * oneMinusAlpha + b2 * alpha + let a = a1 * oneMinusAlpha + a2 * alpha + return UIColor(red: r, green: g, blue: b, alpha: a) + } + return self + } } public extension CGSize { From 9cb0610c4b4d828681c764c4ad12f46952a7c7a2 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Tue, 13 Nov 2018 18:09:06 +0400 Subject: [PATCH 106/245] Cleanup --- Display/ContainedViewLayoutTransition.swift | 4 +--- Display/ViewController.swift | 12 ++++++++++++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index 7d2d8d9ee4..b277b8df4b 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -227,9 +227,7 @@ public extension ContainedViewLayoutTransition { case .spring: timingFunction = kCAMediaTimingFunctionSpring } - node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { completed in - print("completed \(completed)") - }) + node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 2f11d1b6fe..ba369b2a78 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -384,6 +384,18 @@ open class ViewControllerPresentationArguments { } } + @available(iOSApplicationExtension 9.0, *) + public func registerForPreviewingNonNative(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme) { + if self.traitCollection.forceTouchCapability != .available { + if self.previewingContext == nil { + let previewingContext = SimulatedViewControllerPreviewing(theme: theme, delegate: delegate, sourceView: sourceView, node: self.displayNode, present: { [weak self] c, a in + self?.presentInGlobalOverlay(c, with: a) + }) + self.previewingContext = previewingContext + } + } + } + @available(iOSApplicationExtension 9.0, *) open override func unregisterForPreviewing(withContext previewing: UIViewControllerPreviewing) { if self.previewingContext != nil { From e7712fcbe81b1df34c553da02571bd5907767f1a Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Wed, 14 Nov 2018 22:54:59 +0400 Subject: [PATCH 107/245] Added .gitignore --- .gitignore | 24 ++++++++++++++ .../xcschemes/xcschememanagement.plist | 32 ------------------- 2 files changed, 24 insertions(+), 32 deletions(-) create mode 100644 .gitignore delete mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..249d3670f3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +fastlane/README.md +fastlane/report.xml +fastlane/test_output/* +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata +*.xccheckout +*.xcscmblueprint +*.moved-aside +DerivedData +*.hmap +*.ipa +*.xcuserstate +.DS_Store +*.dSYM +*.dSYM.zip +*.ipa +*/xcuserdata/* diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist deleted file mode 100644 index 5a5ed076ae..0000000000 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ /dev/null @@ -1,32 +0,0 @@ - - - - - SchemeUserState - - Display.xcscheme - - orderHint - 4 - - - SuppressBuildableAutocreation - - D01159B61F40E96B0039383E - - primary - - - D05CC2621B69316F00E235A3 - - primary - - - D05CC26C1B69316F00E235A3 - - primary - - - - - From 99bc2d538b0d47c26a95a6b38064b4302a4e9384 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Wed, 14 Nov 2018 23:54:23 +0400 Subject: [PATCH 108/245] Updated project --- Display.xcodeproj/project.pbxproj | 157 ++++++++++++++++++++++++++++++ 1 file changed, 157 insertions(+) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index bd40bc20a4..c646d032cb 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -1388,6 +1388,159 @@ }; name = "Release AppStore"; }; + D021D4F4219CB1AD0064BEBA /* Debug Fork */ = { + 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; + CURRENT_PROJECT_VERSION = 1; + 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.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DMINIMAL_ASDK=1"; + SDKROOT = iphoneos; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug Fork"; + }; + D021D4F5219CB1AD0064BEBA /* Debug Fork */ = { + isa = XCBuildConfiguration; + buildSettings = { + APPLICATION_EXTENSION_API_ONLY = YES; + CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_BITCODE = YES; + INFOPLIST_FILE = Display/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + OTHER_SWIFT_FLAGS = ""; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; + PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_REFLECTION_METADATA_LEVEL = none; + SWIFT_VERSION = 4.0; + }; + name = "Debug Fork"; + }; + D021D4F6219CB1AD0064BEBA /* Debug Fork */ = { + isa = XCBuildConfiguration; + buildSettings = { + INFOPLIST_FILE = DisplayTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.telegram.DisplayTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_VERSION = 3.0; + }; + name = "Debug Fork"; + }; + D021D4F7219CB1AD0064BEBA /* Debug Fork */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + CODE_SIGN_IDENTITY = "-"; + COMBINE_HIDPI_IMAGES = YES; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = X834Q8SBVP; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + INFOPLIST_FILE = DisplayMac/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 10.12; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 4.0; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = "Debug Fork"; + }; D05CC2751B69316F00E235A3 /* Debug Hockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2160,6 +2313,7 @@ isa = XCConfigurationList; buildConfigurations = ( D01159BC1F40E96C0039383E /* Debug Hockeyapp */, + D021D4F7219CB1AD0064BEBA /* Debug Fork */, D01159BD1F40E96C0039383E /* Debug AppStore */, D0ADF923212B3ABC00310BBC /* Debug AppStore LLC */, D01159BE1F40E96C0039383E /* Release Hockeyapp */, @@ -2174,6 +2328,7 @@ isa = XCConfigurationList; buildConfigurations = ( D05CC2751B69316F00E235A3 /* Debug Hockeyapp */, + D021D4F4219CB1AD0064BEBA /* Debug Fork */, D079FD091F06BD9C0038FADE /* Debug AppStore */, D0ADF920212B3ABC00310BBC /* Debug AppStore LLC */, D05CC2761B69316F00E235A3 /* Release Hockeyapp */, @@ -2188,6 +2343,7 @@ isa = XCConfigurationList; buildConfigurations = ( D05CC2781B69316F00E235A3 /* Debug Hockeyapp */, + D021D4F5219CB1AD0064BEBA /* Debug Fork */, D079FD0A1F06BD9C0038FADE /* Debug AppStore */, D0ADF921212B3ABC00310BBC /* Debug AppStore LLC */, D05CC2791B69316F00E235A3 /* Release Hockeyapp */, @@ -2202,6 +2358,7 @@ isa = XCConfigurationList; buildConfigurations = ( D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */, + D021D4F6219CB1AD0064BEBA /* Debug Fork */, D079FD0B1F06BD9C0038FADE /* Debug AppStore */, D0ADF922212B3ABC00310BBC /* Debug AppStore LLC */, D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, From 2e301e099823aec0b0d91d318c929d2e745bb7ca Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 16 Nov 2018 15:54:39 +0400 Subject: [PATCH 109/245] Reduce layerBacked usage --- Display/ActionSheetButtonItem.swift | 2 +- Display/ActionSheetTextItem.swift | 2 +- Display/ContextMenuActionNode.swift | 2 +- Display/LinkHighlightingNode.swift | 2 +- Display/NavigationBarBadge.swift | 2 +- Display/PageControlNode.swift | 2 +- Display/PeekControllerMenuItemNode.swift | 2 +- Display/StatusBar.swift | 4 ++-- Display/StatusBarProxyNode.swift | 2 +- Display/TabBarNode.swift | 8 ++++---- Display/TextAlertController.swift | 2 +- Display/TooltipControllerNode.swift | 2 +- 12 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index 7c9dc9d804..bdbe6a6aed 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -61,7 +61,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode { self.button = HighlightTrackingButton() self.label = ASTextNode() - self.label.isLayerBacked = true + self.label.isUserInteractionEnabled = false self.label.maximumNumberOfLines = 1 self.label.displaysAsynchronously = false self.label.truncationMode = .byTruncatingTail diff --git a/Display/ActionSheetTextItem.swift b/Display/ActionSheetTextItem.swift index 52a240ea17..80f8d53678 100644 --- a/Display/ActionSheetTextItem.swift +++ b/Display/ActionSheetTextItem.swift @@ -37,7 +37,7 @@ public class ActionSheetTextNode: ActionSheetItemNode { self.theme = theme self.label = ASTextNode() - self.label.isLayerBacked = true + self.label.isUserInteractionEnabled = false self.label.maximumNumberOfLines = 0 self.label.displaysAsynchronously = false self.label.truncationMode = .byTruncatingTail diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift index 9b7f81bdf9..b5616cfb22 100644 --- a/Display/ContextMenuActionNode.swift +++ b/Display/ContextMenuActionNode.swift @@ -23,7 +23,7 @@ final class ContextMenuActionNode: ASDisplayNode { switch action.content { case let .text(title): let textNode = ASTextNode() - textNode.isLayerBacked = true + textNode.isUserInteractionEnabled = false textNode.displaysAsynchronously = false textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white) diff --git a/Display/LinkHighlightingNode.swift b/Display/LinkHighlightingNode.swift index 3517fd4f66..bed1008336 100644 --- a/Display/LinkHighlightingNode.swift +++ b/Display/LinkHighlightingNode.swift @@ -175,7 +175,7 @@ public final class LinkHighlightingNode: ASDisplayNode { self._color = color self.imageNode = ASImageNode() - self.imageNode.isLayerBacked = true + self.imageNode.isUserInteractionEnabled = false self.imageNode.displaysAsynchronously = false self.imageNode.displayWithoutProcessing = true diff --git a/Display/NavigationBarBadge.swift b/Display/NavigationBarBadge.swift index d55b1c7924..c3a03fb19e 100644 --- a/Display/NavigationBarBadge.swift +++ b/Display/NavigationBarBadge.swift @@ -24,7 +24,7 @@ final class NavigationBarBadgeNode: ASDisplayNode { self.textColor = textColor self.textNode = ASTextNode() - self.textNode.isLayerBacked = true + self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false self.backgroundNode = ASImageNode() diff --git a/Display/PageControlNode.swift b/Display/PageControlNode.swift index 22bddea020..83f16de106 100644 --- a/Display/PageControlNode.swift +++ b/Display/PageControlNode.swift @@ -30,7 +30,7 @@ public final class PageControlNode: ASDisplayNode { dotNode.image = self.normalDotImage dotNode.displaysAsynchronously = false dotNode.displayWithoutProcessing = true - dotNode.isLayerBacked = true + dotNode.isUserInteractionEnabled = false self.dotNodes.append(dotNode) self.addSubnode(dotNode) } diff --git a/Display/PeekControllerMenuItemNode.swift b/Display/PeekControllerMenuItemNode.swift index af8e9bd189..fb51bcb4b7 100644 --- a/Display/PeekControllerMenuItemNode.swift +++ b/Display/PeekControllerMenuItemNode.swift @@ -47,7 +47,7 @@ final class PeekControllerMenuItemNode: HighlightTrackingButtonNode { self.highlightedBackgroundNode.alpha = 0.0 self.textNode = ASTextNode() - self.textNode.isLayerBacked = true + self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false let textColor: UIColor diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index ed325bacbd..faa8be4f8d 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -95,9 +95,9 @@ public final class StatusBar: ASDisplayNode { public override init() { self.inCallLabel = StatusBarLabelNode() - self.inCallLabel.isLayerBacked = true + self.inCallLabel.isUserInteractionEnabled = false - self.offsetNode.isLayerBacked = true + self.offsetNode.isUserInteractionEnabled = false let labelSize = self.inCallLabel.measure(CGSize(width: 300.0, height: 300.0)) self.inCallLabel.frame = CGRect(origin: CGPoint(x: 10.0, y: 20.0 + 4.0), size: labelSize) diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 95df717884..be2f0f3795 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -62,7 +62,7 @@ private class StatusBarItemNode: ASDisplayNode { self.targetView = targetView self.rootView = rootView self.contentNode = ASDisplayNode() - self.contentNode.isLayerBacked = true + self.contentNode.isUserInteractionEnabled = false super.init() diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 194a1760cc..dfd72fd8fe 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -109,16 +109,16 @@ private final class TabBarNodeContainer { self.imageNode = imageNode self.badgeContainerNode = ASDisplayNode() - self.badgeContainerNode.isLayerBacked = true + self.badgeContainerNode.isUserInteractionEnabled = false self.badgeBackgroundNode = ASImageNode() - self.badgeBackgroundNode.isLayerBacked = true + self.badgeBackgroundNode.isUserInteractionEnabled = false self.badgeBackgroundNode.displayWithoutProcessing = true self.badgeBackgroundNode.displaysAsynchronously = false self.badgeTextNode = ASTextNode() self.badgeTextNode.maximumNumberOfLines = 1 - self.badgeTextNode.isLayerBacked = true + self.badgeTextNode.isUserInteractionEnabled = false self.badgeTextNode.displaysAsynchronously = false self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) @@ -248,7 +248,7 @@ class TabBarNode: ASDisplayNode { let node = TabBarItemNode() node.displaysAsynchronously = false node.displayWithoutProcessing = true - node.isLayerBacked = true + node.isUserInteractionEnabled = false let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) }, updateTitle: { [weak self] _, _ in diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 04eb795723..f3d223c4fc 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -130,7 +130,7 @@ public final class TextAlertContentNode: AlertContentNode { let titleNode = ASTextNode() titleNode.attributedText = title titleNode.displaysAsynchronously = false - titleNode.isLayerBacked = true + titleNode.isUserInteractionEnabled = false titleNode.maximumNumberOfLines = 1 titleNode.truncationMode = .byTruncatingTail self.titleNode = titleNode diff --git a/Display/TooltipControllerNode.swift b/Display/TooltipControllerNode.swift index a9e194a316..a1c8d3c99c 100644 --- a/Display/TooltipControllerNode.swift +++ b/Display/TooltipControllerNode.swift @@ -25,7 +25,7 @@ final class TooltipControllerNode: ASDisplayNode { self.textNode = ImmediateTextNode() self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) - self.textNode.isLayerBacked = true + self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false self.textNode.maximumNumberOfLines = 0 From 32a425b111d3869d3c1de7ccf42ab27d12544fcf Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 20 Nov 2018 04:02:29 +0300 Subject: [PATCH 110/245] Cleanup --- Display/NavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index c3ed02677e..f52cc03f3a 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -622,7 +622,7 @@ open class NavigationController: UINavigationController, ContainableController, if let topController = topController as? ViewController { if !topController.attemptNavigation({ [weak self] in - self?.popViewController(animated: true) + let _ = self?.popViewController(animated: true) }) { return } From f1b535b111c5a11adf81a9672b9e45f6f3f33503 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 20 Nov 2018 15:17:39 +0300 Subject: [PATCH 111/245] Fixed GridNode transition when simultaneously changing bounds and scrolling --- Display/GridNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 16325820b7..25fe8f2f2f 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -919,7 +919,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { self.removeItemNodeWithIndex(index, removeNode: false) let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) - itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in + itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y + boundsOffset), to: CGPoint(x: position.x, y: position.y + contentOffset.y + boundsOffset - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in itemNode?.removeFromSupernode() }) } else { From 33eb6fae00f6b3413013bff612c2bad7d7e90330 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 21 Nov 2018 00:28:12 +0300 Subject: [PATCH 112/245] GridNode: additiona boundsOffset fix --- Display/GridNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 25fe8f2f2f..4653e1c954 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -919,7 +919,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] { self.removeItemNodeWithIndex(index, removeNode: false) let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY) - itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y + boundsOffset), to: CGPoint(x: position.x, y: position.y + contentOffset.y + boundsOffset - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in + itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y - boundsOffset), to: CGPoint(x: position.x, y: position.y + contentOffset.y - boundsOffset - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in itemNode?.removeFromSupernode() }) } else { From 7b089c0659313b52cf873ccbc59197cc1f85bad6 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 23 Nov 2018 04:19:11 +0300 Subject: [PATCH 113/245] Fix ListView element ordering --- Display/ListView.swift | 15 +++++++++------ Display/NativeWindowHostView.swift | 12 ++++++------ Display/PresentationContext.swift | 3 ++- Display/ViewController.swift | 7 ++++--- Display/WindowContent.swift | 12 ++++++------ 5 files changed, 27 insertions(+), 22 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 366c2ef17b..9c1f1b2449 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1027,13 +1027,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel transition.updateAlpha(node: itemHighlightOverlayBackground, alpha: 0.0, completion: { [weak itemHighlightOverlayBackground] _ in itemHighlightOverlayBackground?.removeFromSupernode() }) + for (_, headerNode) in self.itemHeaderNodes { + self.view.bringSubview(toFront: headerNode.view) + } } - - /*if let ensureInset = self.ensureTopInsetForOverlayHighlightedItems { - transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint(x: 0.0, y: -ensureInset)) - } else { - transition.updateSublayerTransformOffset(layer: self.layer, offset: CGPoint()) - }*/ } private func updateScroller(transition: ContainedViewLayoutTransition) { @@ -1982,6 +1979,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() + var hadInserts = false for operation in operations { switch operation { @@ -2004,6 +2002,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets) + hadInserts = true if let _ = updatedPreviousFrame { if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self { self.insertSubnode(node, belowSubnode: itemNode) @@ -2177,6 +2176,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + if hadInserts, let reorderNode = self.reorderNode, reorderNode.supernode != nil { + self.view.bringSubview(toFront: reorderNode.view) + } + if self.debugInfo { //print("replay after \(self.itemNodes.map({"\($0.index) \(unsafeAddressOf($0))"}))") } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 6f1799076d..c052a206c3 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -170,7 +170,7 @@ private final class NativeWindow: UIWindow, WindowHost { var layoutSubviewsEvent: (() -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateToInterfaceOrientation: (() -> Void)? - var presentController: ((ViewController, PresentationSurfaceLevel, Bool) -> Void)? + var presentController: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? @@ -248,8 +248,8 @@ private final class NativeWindow: UIWindow, WindowHost { self.updateToInterfaceOrientation?() }*/ - func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool) { - self.presentController?(controller, level, blockInteraction) + func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { + self.presentController?(controller, level, blockInteraction, completion) } func presentInGlobalOverlay(_ controller: ViewController) { @@ -321,8 +321,8 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { hostView?.updateToInterfaceOrientation?() } - window.presentController = { [weak hostView] controller, level, blockInteraction in - hostView?.present?(controller, level, blockInteraction) + window.presentController = { [weak hostView] controller, level, blockInteraction, completion in + hostView?.present?(controller, level, blockInteraction, completion) } window.presentControllerInGlobalOverlay = { [weak hostView] controller in @@ -355,7 +355,7 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let hostView = hostView { - hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level, false) + hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level, false, completion ?? {}) completion?() } } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 15790e94c5..75e878cebf 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -67,7 +67,7 @@ final class PresentationContext { } } - public func present(_ controller: ViewController, on: PresentationSurfaceLevel, blockInteraction: Bool = false) { + public func present(_ controller: ViewController, on: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -96,6 +96,7 @@ final class PresentationContext { if let blockInteractionToken = blockInteractionToken { self?.removeBlockInteraction(blockInteractionToken) } + completion() } }).start(next: { [weak self] _ in if let strongSelf = self { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index ba369b2a78..c57d49cfaa 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -334,13 +334,14 @@ open class ViewControllerPresentationArguments { return nil } - public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false) { + public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { controller.presentationArguments = arguments switch context { case .current: - self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0)) + self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0), completion: completion) + completion() case let .window(level): - self.window?.present(controller, on: level, blockInteraction: blockInteraction) + self.window?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 98f8edbaf0..edb9dd5dae 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -208,7 +208,7 @@ public final class WindowHostView { let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void let updatePreferNavigationUIHidden: (Bool) -> Void - var present: ((ViewController, PresentationSurfaceLevel, Bool) -> Void)? + var present: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize, Double) -> Void)? @@ -246,7 +246,7 @@ public struct WindowTracingTags { public protocol WindowHost { func forEachController(_ f: (ViewController) -> Void) - func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool) + func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) func presentInGlobalOverlay(_ controller: ViewController) func invalidateDeferScreenEdgeGestures() func invalidatePreferNavigationUIHidden() @@ -356,8 +356,8 @@ public class Window1 { self?.isInteractionBlocked = value } - self.hostView.present = { [weak self] controller, level, blockInteraction in - self?.present(controller, on: level, blockInteraction: blockInteraction) + self.hostView.present = { [weak self] controller, level, blockInteraction, completion in + self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } self.hostView.presentInGlobalOverlay = { [weak self] controller in @@ -886,8 +886,8 @@ public class Window1 { } } - public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false) { - self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction) + public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { + self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } public func presentInGlobalOverlay(_ controller: ViewController) { From c0fec6a466d47a6538bd506d1e5e6ae247e0d652 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 23 Nov 2018 18:14:13 +0300 Subject: [PATCH 114/245] Fix ListView ordering --- Display/ListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 9c1f1b2449..e64978de52 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1888,7 +1888,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel lowestHeaderNode = self.verticalScrollIndicator var lowestHeaderNodeIndex: Int? for (_, headerNode) in self.itemHeaderNodes { - if let index = self.subnodes?.index(of: headerNode) { + if let index = self.view.subviews.index(of: headerNode.view) { if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! { lowestHeaderNodeIndex = index lowestHeaderNode = headerNode From 3afb9200cfa3bb2b7197acbe55088fa906c2e680 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 27 Nov 2018 17:26:47 +0300 Subject: [PATCH 115/245] Toolbar support --- Display.xcodeproj/project.pbxproj | 16 ++--- Display/ListView.swift | 42 ++++++------ Display/ListViewItemNode.swift | 3 + Display/TabBarContollerNode.swift | 71 ++++++++++++------- Display/TabBarController.swift | 6 +- Display/Toolbar.swift | 21 ++++++ Display/ToolbarNode.swift | 109 ++++++++++++++++++++++++++++++ Display/ViewController.swift | 17 ++++- 8 files changed, 229 insertions(+), 56 deletions(-) create mode 100644 Display/Toolbar.swift create mode 100644 Display/ToolbarNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index c646d032cb..7a46bba81a 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -12,6 +12,8 @@ 09E12476214D0978009FC9C3 /* DeviceMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */; }; D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701972029CAD6006B9E34 /* TooltipController.swift */; }; D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */; }; + D0076F2021AC9C930059500A /* ToolbarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0076F1F21AC9C930059500A /* ToolbarNode.swift */; }; + D0076F2221ACA5020059500A /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0076F2121ACA5020059500A /* Toolbar.swift */; }; D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D01159B91F40E96C0039383E /* DisplayMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -199,6 +201,8 @@ 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetrics.swift; sourceTree = ""; }; D00701972029CAD6006B9E34 /* TooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipControllerNode.swift; sourceTree = ""; }; + D0076F1F21AC9C930059500A /* ToolbarNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolbarNode.swift; sourceTree = ""; }; + D0076F2121ACA5020059500A /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = ""; }; D01159B71F40E96B0039383E /* DisplayMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DisplayMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -448,6 +452,8 @@ D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */, D0DC48551BF945DD00F672FD /* TabBarNode.swift */, D0A134632034DE580059716A /* TabBarTapRecognizer.swift */, + D0076F2121ACA5020059500A /* Toolbar.swift */, + D0076F1F21AC9C930059500A /* ToolbarNode.swift */, ); name = "Tab Bar"; sourceTree = ""; @@ -586,7 +592,6 @@ D07921AA1B6FC911005C23D9 /* Status Bar */, D0FF9B2E1E7196E2000C66DB /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, - D0DC48521BF93D7C00F672FD /* Tabs */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, D0E49C861B83A1680099E553 /* Image Cache */, D05CC2E11B69534100E235A3 /* Supporting Files */, @@ -770,13 +775,6 @@ name = Alert; sourceTree = ""; }; - D0DC48521BF93D7C00F672FD /* Tabs */ = { - isa = PBXGroup; - children = ( - ); - name = Tabs; - sourceTree = ""; - }; D0E49C861B83A1680099E553 /* Image Cache */ = { isa = PBXGroup; children = ( @@ -1017,6 +1015,7 @@ D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */, D03AA4D9202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift in Sources */, + D0076F2221ACA5020059500A /* Toolbar.swift in Sources */, D0F8C3932014FB7C00236FC5 /* ListView.swift in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, @@ -1113,6 +1112,7 @@ D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, + D0076F2021AC9C930059500A /* ToolbarNode.swift in Sources */, D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */, diff --git a/Display/ListView.swift b/Display/ListView.swift index e64978de52..96b0bf2bce 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2912,31 +2912,28 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + let indicatorInsets: CGFloat = 3.0 + if let verticalScrollIndicator = self.verticalScrollIndicator { - var totalEstimatedHeight: CGFloat = 0.0 - var estimatedOffset: CGFloat = 0.0 - let visibleHeight = self.visibleSize.height - self.insets.top - self.insets.bottom - if !self.itemNodes.isEmpty { - totalEstimatedHeight = totalVisibleHeight / CGFloat(self.itemNodes.count) * CGFloat(self.items.count) - for i in 0 ..< self.itemNodes.count { - if let index = self.itemNodes[i].index { - break - let topOffset = self.insets.top - self.itemNodes[0].apparentFrame.minY - estimatedOffset = CGFloat(index) - } - } + /*let size = self.visibleSize.height + let range = computeVerticalScrollRange() + let extent = computeVerticalScrollExtent() + let mOffset = computeVerticalScrollOffset() + + let thickness: CGFloat = 3.0 + var length = round(size * extent / range) + var offset = round((size - length) * mOffset / (range - extent)) + + let minLength = thickness * 2.0 + if length < minLength { + length = minLength + } + if offset + length > size { + offset = size - length } - /* - visibleHeight - indicatorInsets * 2.0 -> totalEstimatedHeight - x -> visibleHeight - */ - - let indicatorInsets: CGFloat = 2.0 - let indicatorMaxHeight: CGFloat = visibleHeight - indicatorInsets * 2.0 - - let indicatorHeight: CGFloat = max(3.0, floor(indicatorMaxHeight * indicatorMaxHeight / totalEstimatedHeight)) - verticalScrollIndicator.frame = CGRect(origin: CGPoint(x: self.visibleSize.width - 3.0 - indicatorInsets, y: self.insets.top + indicatorInsets), size: CGSize(width: 3.0, height: indicatorHeight)) + let indicatorHeight: CGFloat = max(3.0, 30.0) + verticalScrollIndicator.frame = CGRect(origin: CGPoint(x: self.visibleSize.width - 3.0 - indicatorInsets, y: self.insets.top + offset), size: CGSize(width: 3.0, height: length))*/ } } @@ -3408,6 +3405,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel itemNode.setHighlighted(true, at: selectionTouchLocation.offsetBy(dx: -itemNodeFrame.minX, dy: -itemNodeFrame.minY), animated: false) } else { self.highlightedItemIndex = nil + itemNode.tapped() } break } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 3128fe8503..b4399d3f54 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -120,6 +120,9 @@ open class ListViewItemNode: ASDisplayNode { } + open func tapped() { + } + open func longTapped() { } diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 19c0005142..1eae4a433b 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -2,8 +2,11 @@ import Foundation import AsyncDisplayKit final class TabBarControllerNode: ASDisplayNode { + private var theme: TabBarControllerTheme let tabBarNode: TabBarNode - + private var toolbarNode: ToolbarNode? + private let toolbarActionSelected: (Bool) -> Void + var currentControllerView: UIView? { didSet { oldValue?.removeFromSuperview() @@ -14,8 +17,10 @@ final class TabBarControllerNode: ASDisplayNode { } } - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool) -> Void) { + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool) -> Void, toolbarActionSelected: @escaping (Bool) -> Void) { + self.theme = theme self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) + self.toolbarActionSelected = toolbarActionSelected super.init() @@ -29,34 +34,54 @@ final class TabBarControllerNode: ASDisplayNode { } func updateTheme(_ theme: TabBarControllerTheme) { + self.theme = theme self.backgroundColor = theme.backgroundColor self.tabBarNode.updateTheme(theme) + self.toolbarNode?.updateTheme(theme) } - func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { - let update = { - var tabBarHeight: CGFloat - var options: ContainerViewLayoutInsetOptions = [] - if layout.metrics.widthClass == .regular { - options.insert(.input) - } - let bottomInset: CGFloat = layout.insets(options: options).bottom - if !layout.safeInsets.left.isZero { - tabBarHeight = 34.0 + bottomInset - } else { - tabBarHeight = 49.0 + bottomInset - } - - transition.updateFrame(node: self.tabBarNode, frame: CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))) - self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition) + func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { + var tabBarHeight: CGFloat + var options: ContainerViewLayoutInsetOptions = [] + if layout.metrics.widthClass == .regular { + options.insert(.input) + } + let bottomInset: CGFloat = layout.insets(options: options).bottom + if !layout.safeInsets.left.isZero { + tabBarHeight = 34.0 + bottomInset + } else { + tabBarHeight = 49.0 + bottomInset } - switch transition { - case .immediate: - update() - case .animated: - update() + let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight)) + + transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame) + self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition) + + if let toolbar = toolbar { + if let toolbarNode = self.toolbarNode { + transition.updateFrame(node: toolbarNode, frame: tabBarFrame) + toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: transition) + } else { + let toolbarNode = ToolbarNode(theme: self.theme, left: { [weak self] in + self?.toolbarActionSelected(true) + }, right: { [weak self] in + self?.toolbarActionSelected(false) + }) + toolbarNode.frame = tabBarFrame + toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) + self.addSubnode(toolbarNode) + self.toolbarNode = toolbarNode + if transition.isAnimated { + toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + } + } else if let toolbarNode = self.toolbarNode { + self.toolbarNode = nil + transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in + toolbarNode?.removeFromSupernode() + }) } } } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index d39859d92d..83ece14c42 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -127,6 +127,8 @@ open class TabBarController: ViewController { } })) } + }, toolbarActionSelected: { [weak self] left in + self?.currentController?.toolbarActionSelected(left: left) }) self.updateSelectedIndex() @@ -182,7 +184,7 @@ open class TabBarController: ViewController { } if let validLayout = self.validLayout { - self.tabBarControllerNode.containerLayoutUpdated(validLayout, transition: .immediate) + self.tabBarControllerNode.containerLayoutUpdated(validLayout, toolbar: self.currentController?.toolbar, transition: .immediate) } } @@ -191,7 +193,7 @@ open class TabBarController: ViewController { self.validLayout = layout - self.tabBarControllerNode.containerLayoutUpdated(layout, transition: transition) + self.tabBarControllerNode.containerLayoutUpdated(layout, toolbar: self.currentController?.toolbar, transition: transition) if let currentController = self.currentController { currentController.view.frame = CGRect(origin: CGPoint(), size: layout.size) diff --git a/Display/Toolbar.swift b/Display/Toolbar.swift new file mode 100644 index 0000000000..b9c103f024 --- /dev/null +++ b/Display/Toolbar.swift @@ -0,0 +1,21 @@ +import Foundation + +public struct ToolbarAction: Equatable { + public let title: String + public let isEnabled: Bool + + public init(title: String, isEnabled: Bool) { + self.title = title + self.isEnabled = isEnabled + } +} + +public struct Toolbar: Equatable { + public let leftAction: ToolbarAction? + public let rightAction: ToolbarAction? + + public init(leftAction: ToolbarAction?, rightAction: ToolbarAction?) { + self.leftAction = leftAction + self.rightAction = rightAction + } +} diff --git a/Display/ToolbarNode.swift b/Display/ToolbarNode.swift new file mode 100644 index 0000000000..8adf7d6107 --- /dev/null +++ b/Display/ToolbarNode.swift @@ -0,0 +1,109 @@ +import Foundation +import AsyncDisplayKit + +final class ToolbarNode: ASDisplayNode { + private var theme: TabBarControllerTheme + private let left: () -> Void + private let right: () -> Void + + private let separatorNode: ASDisplayNode + private let leftTitle: ImmediateTextNode + private let leftButton: HighlightableButtonNode + private let rightTitle: ImmediateTextNode + private let rightButton: HighlightableButtonNode + + init(theme: TabBarControllerTheme, left: @escaping () -> Void, right: @escaping () -> Void) { + self.theme = theme + self.left = left + self.right = right + + self.separatorNode = ASDisplayNode() + self.separatorNode.isLayerBacked = true + + self.leftTitle = ImmediateTextNode() + self.leftTitle.displaysAsynchronously = false + self.leftButton = HighlightableButtonNode() + self.rightTitle = ImmediateTextNode() + self.rightTitle.displaysAsynchronously = false + self.rightButton = HighlightableButtonNode() + + super.init() + + self.addSubnode(self.leftTitle) + self.addSubnode(self.leftButton) + self.addSubnode(self.rightTitle) + self.addSubnode(self.rightButton) + + self.updateTheme(theme) + + self.leftButton.addTarget(self, action: #selector(self.leftPressed), forControlEvents: .touchUpInside) + self.leftButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.leftTitle.layer.removeAnimation(forKey: "opacity") + strongSelf.leftTitle.alpha = 0.4 + } else { + strongSelf.leftTitle.alpha = 1.0 + strongSelf.leftTitle.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + self.rightButton.addTarget(self, action: #selector(self.rightPressed), forControlEvents: .touchUpInside) + self.rightButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.rightTitle.layer.removeAnimation(forKey: "opacity") + strongSelf.rightTitle.alpha = 0.4 + } else { + strongSelf.rightTitle.alpha = 1.0 + strongSelf.rightTitle.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + func updateTheme(_ theme: TabBarControllerTheme) { + self.separatorNode.backgroundColor = theme.tabBarSeparatorColor + self.backgroundColor = theme.tabBarBackgroundColor + } + + func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, toolbar: Toolbar, transition: ContainedViewLayoutTransition) { + transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))) + + let sideInset: CGFloat = 16.0 + + self.leftTitle.attributedText = NSAttributedString(string: toolbar.leftAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.leftAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor) + self.rightTitle.attributedText = NSAttributedString(string: toolbar.rightAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.rightAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor) + let leftSize = self.leftTitle.updateLayout(size) + let rightSize = self.rightTitle.updateLayout(size) + + let leftFrame = CGRect(origin: CGPoint(x: leftInset + sideInset, y: floor((size.height - bottomInset - leftSize.height) / 2.0)), size: leftSize) + let rightFrame = CGRect(origin: CGPoint(x: size.width - rightInset - sideInset - rightSize.width, y: floor((size.height - bottomInset - rightSize.height) / 2.0)), size: rightSize) + + if leftFrame.size == self.leftTitle.frame.size { + transition.updateFrame(node: self.leftTitle, frame: leftFrame) + } else { + self.leftTitle.frame = leftFrame + } + + if rightFrame.size == self.rightTitle.frame.size { + transition.updateFrame(node: self.rightTitle, frame: rightFrame) + } else { + self.rightTitle.frame = rightFrame + } + + self.leftButton.isEnabled = toolbar.leftAction?.isEnabled ?? false + self.rightButton.isEnabled = toolbar.rightAction?.isEnabled ?? false + + self.leftButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: leftSize.width + sideInset * 2.0, height: size.height - bottomInset)) + self.rightButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - sideInset * 2.0 - rightSize.width, y: 0.0), size: CGSize(width: rightSize.width + sideInset * 2.0, height: size.height - bottomInset)) + } + + @objc private func leftPressed() { + self.left() + } + + @objc private func rightPressed() { + self.right() + } +} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index c57d49cfaa..aa6cb74757 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -116,6 +116,7 @@ open class ViewControllerPresentationArguments { public let statusBar: StatusBar public let navigationBar: NavigationBar? + private(set) var toolbar: Toolbar? private var previewingContext: Any? @@ -423,10 +424,24 @@ open class ViewControllerPresentationArguments { } return traceViewVisibility(view: self.view, rect: self.view.bounds) } + + public func setToolbar(_ toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { + if self.toolbar != toolbar { + self.toolbar = toolbar + if let parent = self.parent as? TabBarController { + if parent.currentController === self { + parent.requestLayout(transition: transition) + } + } + } + } + + open func toolbarActionSelected(left: Bool) { + } } private func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { - if layer.bounds.intersects(rect) { + if layer.bounds.contains(rect) { if layer.isHidden { return false } From bb444545000da26bb34251c3cb868ddab215de16 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 27 Nov 2018 19:45:33 +0300 Subject: [PATCH 116/245] Fixed ListView reordering snapshot --- Display/ListViewReorderingItemNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ListViewReorderingItemNode.swift b/Display/ListViewReorderingItemNode.swift index 536e73f84c..49d3dd97e5 100644 --- a/Display/ListViewReorderingItemNode.swift +++ b/Display/ListViewReorderingItemNode.swift @@ -11,7 +11,7 @@ final class ListViewReorderingItemNode: ASDisplayNode { init(itemNode: ListViewItemNode, initialLocation: CGPoint) { self.itemNode = itemNode - self.copyView = itemNode.view.snapshotContentTree() + self.copyView = itemNode.view.snapshotView(afterScreenUpdates: false) self.initialLocation = initialLocation super.init() From 7a58ac1bfca9247fd4cccfa3b43e4ddea06cb4e3 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 30 Nov 2018 14:26:48 +0400 Subject: [PATCH 117/245] Various fixes --- Display/GridItem.swift | 2 +- Display/GridNode.swift | 12 +- Display/ListView.swift | 823 ++++++++++++++---------- Display/ListViewIntermediateState.swift | 4 +- Display/ListViewItem.swift | 2 +- Display/UIKitUtils.swift | 3 + 6 files changed, 494 insertions(+), 352 deletions(-) diff --git a/Display/GridItem.swift b/Display/GridItem.swift index ec201d2edd..d0a54461e8 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -10,7 +10,7 @@ public protocol GridSection { public protocol GridItem { var section: GridSection? { get } - func node(layout: GridNodeLayout) -> GridItemNode + func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode func update(node: GridItemNode) var aspectRatio: CGFloat { get } var fillsRowWithHeight: CGFloat? { get } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 4653e1c954..921450bce7 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -97,8 +97,9 @@ public struct GridNodeTransaction { public let stationaryItems: GridNodeStationaryItems public let updateFirstIndexInSectionOffset: Int? public let updateOpaqueState: Any? + public let synchronousLoads: Bool - public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?, updateOpaqueState: Any? = nil) { + public init(deleteItems: [Int], insertItems: [GridNodeInsertItem], updateItems: [GridNodeUpdateItem], scrollToItem: GridNodeScrollToItem?, updateLayout: GridNodeUpdateLayout?, itemTransition: ContainedViewLayoutTransition, stationaryItems: GridNodeStationaryItems, updateFirstIndexInSectionOffset: Int?, updateOpaqueState: Any? = nil, synchronousLoads: Bool = false) { self.deleteItems = deleteItems self.insertItems = insertItems self.updateItems = updateItems @@ -108,6 +109,7 @@ public struct GridNodeTransaction { self.stationaryItems = stationaryItems self.updateFirstIndexInSectionOffset = updateFirstIndexInSectionOffset self.updateOpaqueState = updateOpaqueState + self.synchronousLoads = synchronousLoads } } @@ -346,7 +348,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, updatingLayout: transaction.updateLayout != nil, completion: completion) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, synchronousLoads: transaction.synchronousLoads, updatingLayout: transaction.updateLayout != nil, completion: completion) } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { @@ -368,7 +370,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, updatingLayout: false, completion: { _ in }) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, synchronousLoads: false, updatingLayout: false, completion: { _ in }) } } @@ -744,7 +746,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { return lowestHeaderNode } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) { + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, synchronousLoads: Bool, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) { let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate var previousItemFrames: [WrappedGridItemNode: CGRect]? @@ -806,7 +808,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { itemNode.frame = item.frame } } else { - let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout) + let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, synchronousLoad: synchronousLoads) itemNode.frame = item.frame self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) } diff --git a/Display/ListView.swift b/Display/ListView.swift index 96b0bf2bce..8878e6a18b 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -116,6 +116,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() public private(set) final var insets = UIEdgeInsets() + public private(set) final var scrollIndicatorInsets = UIEdgeInsets() private final var ensureTopInsetForOverlayHighlightedItems: CGFloat? private final var lastContentOffset: CGPoint = CGPoint() private final var lastContentOffsetTimestamp: CFAbsoluteTime = 0.0 @@ -125,6 +126,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final var needsAnimations = false public final var dynamicBounceEnabled = true + public final var rotated = false private final var invisibleInset: CGFloat = 500.0 public var preloadPages: Bool = true { @@ -177,6 +179,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let fillColor = self.verticalScrollIndicatorColor { if self.verticalScrollIndicator == nil { let verticalScrollIndicator = ASImageNode() + verticalScrollIndicator.isUserInteractionEnabled = false + verticalScrollIndicator.alpha = 0.0 verticalScrollIndicator.image = generateStretchableFilledCircleImage(diameter: 3.0, color: fillColor) self.verticalScrollIndicator = verticalScrollIndicator self.addSubnode(verticalScrollIndicator) @@ -233,6 +237,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var selectionTouchDelayTimer: Foundation.Timer? private var selectionLongTapDelayTimer: Foundation.Timer? private var flashNodesDelayTimer: Foundation.Timer? + private var flashScrollIndicatorTimer: Foundation.Timer? private var highlightedItemIndex: Int? private var reorderNode: ListViewReorderingItemNode? private var reorderFeedback: HapticFeedback? @@ -383,7 +388,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin) self.reorderNode = reorderNode - self.addSubnode(reorderNode) + if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(reorderNode, belowSubnode: verticalScrollIndicator) + } else { + self.addSubnode(reorderNode) + } itemNode.isHidden = true } @@ -492,6 +501,31 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func resetScrollIndicatorFlashTimer(start: Bool) { + if let flashScrollIndicatorTimer = self.flashScrollIndicatorTimer { + flashScrollIndicatorTimer.invalidate() + self.flashScrollIndicatorTimer = nil + } + + if start { + let timer = Timer(timeInterval: 0.1, target: ListViewTimerProxy { [weak self] in + if let strongSelf = self { + if let flashScrollIndicatorTimer = strongSelf.flashScrollIndicatorTimer { + flashScrollIndicatorTimer.invalidate() + strongSelf.flashScrollIndicatorTimer = nil + strongSelf.verticalScrollIndicator?.alpha = 0.0 + strongSelf.verticalScrollIndicator?.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3) + } + } + }, selector: #selector(ListViewTimerProxy.timerEvent), userInfo: nil, repeats: false) + self.flashScrollIndicatorTimer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + } else { + self.verticalScrollIndicator?.layer.removeAnimation(forKey: "opacity") + self.verticalScrollIndicator?.alpha = 1.0 + } + } + private func headerItemsAreFlashing() -> Bool { //print("\(self.scroller.isDragging) || (\(self.scroller.isDecelerating) && \(self.isDeceleratingAfterTracking)) || \(self.flashNodesDelayTimer != nil)") return self.scroller.isDragging || (self.isDeceleratingAfterTracking) || self.flashNodesDelayTimer != nil @@ -508,6 +542,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.lastContentOffsetTimestamp = 0.0 self.resetHeaderItemsFlashTimer(start: false) self.updateHeaderItemsFlashing(animated: true) + self.resetScrollIndicatorFlashTimer(start: false) if self.snapToBottomInsetUntilFirstInteraction { self.snapToBottomInsetUntilFirstInteraction = false @@ -521,10 +556,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.lastContentOffsetTimestamp = CACurrentMediaTime() self.isDeceleratingAfterTracking = true self.updateHeaderItemsFlashing(animated: true) + self.resetScrollIndicatorFlashTimer(start: false) } else { self.isDeceleratingAfterTracking = false self.resetHeaderItemsFlashTimer(start: true) self.updateHeaderItemsFlashing(animated: true) + self.resetScrollIndicatorFlashTimer(start: true) self.lastContentOffsetTimestamp = 0.0 self.didEndScrolling?() @@ -536,6 +573,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.isDeceleratingAfterTracking = false self.resetHeaderItemsFlashTimer(start: true) self.updateHeaderItemsFlashing(animated: true) + self.resetScrollIndicatorFlashTimer(start: true) self.didEndScrolling?() } @@ -1006,6 +1044,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if itemNode.isHighlightedInOverlay { lowestOverlayNode = itemNode itemNode.view.superview?.bringSubview(toFront: itemNode.view) + if let verticalScrollIndicator = self.verticalScrollIndicator { + verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) + } } } @@ -1030,6 +1071,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel for (_, headerNode) in self.itemHeaderNodes { self.view.bringSubview(toFront: headerNode.view) } + if let verticalScrollIndicator = self.verticalScrollIndicator { + verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) + } } } @@ -1121,7 +1165,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { + private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -1170,8 +1214,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { self.async(f) } - }, params: params, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in - //assert(Queue.mainQueue().isCurrent()) + }, params: params, synchronousLoads: synchronousLoads, previousItem: previousItem, nextItem: nextItem, completion: { itemNode, apply in itemNode.index = index completion(QueueLocalObject(queue: Queue.mainQueue(), generate: { return itemNode }), ListViewItemNodeLayout(contentSize: itemNode.contentSize, insets: itemNode.insets), apply) }) @@ -1216,6 +1259,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets + self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems let wasIgnoringScrollingEvents = self.ignoreScrollingEvents @@ -1438,7 +1482,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel print("deleteAndInsertItemsTransaction prepare \((CACurrentMediaTime() - startTime) * 1000.0) ms") } - self.fillMissingNodes(synchronous: options.contains(.Synchronous), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in + self.fillMissingNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animated: animated, inputAnimatedInsertIndices: animated ? insertedIndexSet : Set(), insertDirectionHints: insertDirectionHints, inputState: state, inputPreviousNodes: previousNodes, inputOperations: operations, inputCompletion: { updatedState, operations in if self.debugInfo { print("fillMissingNodes completion \((CACurrentMediaTime() - startTime) * 1000.0) ms") @@ -1461,7 +1505,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel updateIndices.subtract(explicitelyUpdateIndices) - self.updateNodes(synchronous: options.contains(.Synchronous), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in + self.updateNodes(synchronous: options.contains(.Synchronous), synchronousLoads: options.contains(.PreferSynchronousResourceLoading), animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: updatedState, previousNodes: previousNodes, inputOperations: operations, completion: { updatedState, operations in self.updateAdjacent(synchronous: options.contains(.Synchronous), animated: animated, state: updatedState, updateAdjacentItemsIndices: updateIndices, operations: operations, completion: { state, operations in var updatedState = state var updatedOperations = operations @@ -1614,7 +1658,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func fillMissingNodes(synchronous: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { + private func fillMissingNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, inputAnimatedInsertIndices: Set, insertDirectionHints: [Int: ListViewItemOperationDirectionHint], inputState: ListViewState, inputPreviousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], inputCompletion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { let animatedInsertIndices = inputAnimatedInsertIndices var state = inputState var previousNodes = inputPreviousNodes @@ -1649,7 +1693,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let index = insertionItemIndexAndDirection.0 let threadId = pthread_self() var tailRecurse = false - self.nodeForItem(synchronous: synchronous, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: updateAnimation, completion: { (node, layout, apply) in + self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: self.items[index], previousNode: previousNodes[index], index: index, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: self.items.count == index + 1 ? nil : self.items[index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: updateAnimation, completion: { (node, layout, apply) in if pthread_equal(pthread_self(), threadId) != 0 && !tailRecurse { tailRecurse = true @@ -1658,7 +1702,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var updatedState = state var updatedOperations = operations updatedState.insertNode(index, node: node, layout: layout, apply: apply, offsetDirection: insertionItemIndexAndDirection.1, animated: animated && animatedInsertIndices.contains(index), operations: &updatedOperations, itemCount: self.items.count) - self.fillMissingNodes(synchronous: synchronous, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion) + self.fillMissingNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, animated: animated, inputAnimatedInsertIndices: animatedInsertIndices, insertDirectionHints: insertDirectionHints, inputState: updatedState, inputPreviousNodes: previousNodes, inputOperations: updatedOperations, inputCompletion: completion) } }) if !tailRecurse { @@ -1673,7 +1717,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateNodes(synchronous: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { + private func updateNodes(synchronous: Bool, synchronousLoads: Bool, animated: Bool, updateIndicesAndItems: [ListViewUpdateItem], inputState: ListViewState, previousNodes: [Int: QueueLocalObject], inputOperations: [ListViewStateOperation], completion: @escaping (ListViewState, [ListViewStateOperation]) -> Void) { var state = inputState var operations = inputOperations var updateIndicesAndItems = updateIndicesAndItems @@ -1685,11 +1729,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { let updateItem = updateIndicesAndItems[0] if let previousNode = previousNodes[updateItem.index] { - self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in + self.nodeForItem(synchronous: synchronous, synchronousLoads: synchronousLoads, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], params: ListViewItemLayoutParams(width: state.visibleSize.width, leftInset: state.insets.left, rightInset: state.insets.right), updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations) updateIndicesAndItems.remove(at: 0) - self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) + self.updateNodes(synchronous: synchronous, synchronousLoads: synchronousLoads, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion) }) break } else { @@ -2008,6 +2052,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.insertSubnode(node, belowSubnode: itemNode) } else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow) + } else if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(node, belowSubnode: verticalScrollIndicator) } else { self.addSubnode(node) } @@ -2023,6 +2069,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.insertSubnode(node, belowSubnode: itemNode) } else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow) + } else if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(node, belowSubnode: verticalScrollIndicator) } else { self.addSubnode(node) } @@ -2049,7 +2097,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else { referenceNode.index = nil self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) - self.addSubnode(referenceNode) + if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(referenceNode, belowSubnode: verticalScrollIndicator) + } else { + self.addSubnode(referenceNode) + } } } else { assertionFailure() @@ -2178,6 +2230,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if hadInserts, let reorderNode = self.reorderNode, reorderNode.supernode != nil { self.view.bringSubview(toFront: reorderNode.view) + if let verticalScrollIndicator = self.verticalScrollIndicator { + verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) + } } if self.debugInfo { @@ -2249,149 +2304,42 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.stopScrolling() } - self.insertNodesInBatches(nodes: [], completion: { - self.debugCheckMonotonity() - - var sizeAndInsetsOffset: CGFloat = 0.0 - - var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) - - if let updateSizeAndInsets = updateSizeAndInsets { - if self.insets != updateSizeAndInsets.insets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { - let previousVisibleSize = self.visibleSize - self.visibleSize = updateSizeAndInsets.size - - var offsetFix: CGFloat - if self.isTracking { - offsetFix = 0.0 - } else if self.snapToBottomInsetUntilFirstInteraction { - offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom - } else { - offsetFix = updateSizeAndInsets.insets.top - self.insets.top - } - - offsetFix += additionalScrollDistance - - self.insets = updateSizeAndInsets.insets - self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems - self.visibleSize = updateSizeAndInsets.size - - for itemNode in self.itemNodes { - let position = itemNode.position - itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) - } - - let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets) - - if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) { - offsetFix += snappedTopInset - - for itemNode in self.itemNodes { - let position = itemNode.position - itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) - } - } - - var completeOffset = offsetFix - - if !snapToBoundsOffset.isZero { - self.updateVisibleContentOffset() - } - - sizeAndInsetsOffset = offsetFix - completeOffset += snapToBoundsOffset - - if !updateSizeAndInsets.duration.isZero { - let animation: CABasicAnimation - switch updateSizeAndInsets.curve { - case let .Spring(duration): - headerNodesTransition = (.animated(duration: duration, curve: .spring), false, -completeOffset) - let springAnimation = makeSpringAnimation("sublayerTransform") - springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - springAnimation.isRemovedOnCompletion = true - - let k = Float(UIView.animationDurationFactor()) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - springAnimation.speed = speed * Float(springAnimation.duration / duration) - - springAnimation.isAdditive = true - animation = springAnimation - case let .Default(duration): - headerNodesTransition = (.animated(duration: max(duration ?? 0.3, updateSizeAndInsets.duration), curve: .easeInOut), false, -completeOffset) - let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - basicAnimation.duration = updateSizeAndInsets.duration * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - basicAnimation.isRemovedOnCompletion = true - basicAnimation.isAdditive = true - animation = basicAnimation - } - - self.layer.add(animation, forKey: nil) - } + self.debugCheckMonotonity() + + var sizeAndInsetsOffset: CGFloat = 0.0 + + var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) + + if let updateSizeAndInsets = updateSizeAndInsets { + if self.insets != updateSizeAndInsets.insets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { + let previousVisibleSize = self.visibleSize + self.visibleSize = updateSizeAndInsets.size + + var offsetFix: CGFloat + if self.isTracking { + offsetFix = 0.0 + } else if self.snapToBottomInsetUntilFirstInteraction { + offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom } else { - self.visibleSize = updateSizeAndInsets.size - - if !self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom).offset.isZero { - self.updateVisibleContentOffset() - } + offsetFix = updateSizeAndInsets.insets.top - self.insets.top } - if let updatedTopItemVerticalOrigin = self.topItemVerticalOrigin(), let previousTopItemVerticalOrigin = previousTopItemVerticalOrigin, animateTopItemVerticalOrigin, !updatedTopItemVerticalOrigin.isEqual(to: previousTopItemVerticalOrigin) { - self.stopScrolling() - - let completeOffset = updatedTopItemVerticalOrigin - previousTopItemVerticalOrigin - let duration: Double = 0.4 - - if let snapshotView = snapshotView { - snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: completeOffset), size: snapshotView.frame.size) - self.view.addSubview(snapshotView) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } - - let springAnimation = makeSpringAnimation("sublayerTransform") - springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) - springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - springAnimation.isRemovedOnCompletion = true - - let k = Float(UIView.animationDurationFactor()) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - springAnimation.speed = speed * Float(springAnimation.duration / duration) - - springAnimation.isAdditive = true - self.layer.add(springAnimation, forKey: nil) - } else { - if let snapshotView = snapshotView { - snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: snapshotView.frame.size) - self.view.addSubview(snapshotView) - snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in - snapshotView?.removeFromSuperview() - }) - } + offsetFix += additionalScrollDistance + + self.insets = updateSizeAndInsets.insets + self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets + self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems + self.visibleSize = updateSizeAndInsets.size + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) } - let wasIgnoringScrollingEvents = self.ignoreScrollingEvents - self.ignoreScrollingEvents = true - self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) - self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) - self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) - self.scroller.contentOffset = self.lastContentOffset - self.ignoreScrollingEvents = wasIgnoringScrollingEvents - } else { - let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem) + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets) - if !snappedTopInset.isZero && previousApparentFrames.isEmpty { - let offsetFix = snappedTopInset + if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) { + offsetFix += snappedTopInset for itemNode in self.itemNodes { let position = itemNode.position @@ -2399,10 +2347,85 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + var completeOffset = offsetFix + if !snapToBoundsOffset.isZero { self.updateVisibleContentOffset() } + sizeAndInsetsOffset = offsetFix + completeOffset += snapToBoundsOffset + + if !updateSizeAndInsets.duration.isZero { + let animation: CABasicAnimation + switch updateSizeAndInsets.curve { + case let .Spring(duration): + headerNodesTransition = (.animated(duration: duration, curve: .spring), false, -completeOffset) + let springAnimation = makeSpringAnimation("sublayerTransform") + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + + springAnimation.isAdditive = true + animation = springAnimation + case let .Default(duration): + headerNodesTransition = (.animated(duration: max(duration ?? 0.3, updateSizeAndInsets.duration), curve: .easeInOut), false, -completeOffset) + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + basicAnimation.duration = updateSizeAndInsets.duration * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true + animation = basicAnimation + } + + self.layer.add(animation, forKey: nil) + } + } else { + self.visibleSize = updateSizeAndInsets.size + + if !self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } + } + + if let updatedTopItemVerticalOrigin = self.topItemVerticalOrigin(), let previousTopItemVerticalOrigin = previousTopItemVerticalOrigin, animateTopItemVerticalOrigin, !updatedTopItemVerticalOrigin.isEqual(to: previousTopItemVerticalOrigin) { + self.stopScrolling() + + let completeOffset = updatedTopItemVerticalOrigin - previousTopItemVerticalOrigin + let duration: Double = 0.4 + + if let snapshotView = snapshotView { + snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: completeOffset), size: snapshotView.frame.size) + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + + let springAnimation = makeSpringAnimation("sublayerTransform") + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + + springAnimation.isAdditive = true + self.layer.add(springAnimation, forKey: nil) + } else { if let snapshotView = snapshotView { snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: snapshotView.frame.size) self.view.addSubview(snapshotView) @@ -2412,219 +2435,245 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents + self.ignoreScrollingEvents = true + self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) + self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) + self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) + self.scroller.contentOffset = self.lastContentOffset + self.ignoreScrollingEvents = wasIgnoringScrollingEvents + } else { + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom, updateSizeAndInsets: updateSizeAndInsets, scrollToItem: scrollToItem) - if let scrollToItem = scrollToItem, scrollToItem.animated { - if self.itemNodes.count != 0 { - var offset: CGFloat? + if !snappedTopInset.isZero && previousApparentFrames.isEmpty { + let offsetFix = snappedTopInset + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) + } + } + + if !snapToBoundsOffset.isZero { + self.updateVisibleContentOffset() + } + + if let snapshotView = snapshotView { + snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: snapshotView.frame.size) + self.view.addSubview(snapshotView) + snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in + snapshotView?.removeFromSuperview() + }) + } + } + + self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) + + if let scrollToItem = scrollToItem, scrollToItem.animated { + if self.itemNodes.count != 0 { + var offset: CGFloat? + + var temporaryPreviousNodes: [ListViewItemNode] = [] + var previousUpperBound: CGFloat? + var previousLowerBound: CGFloat? + for (previousNode, previousFrame) in previousApparentFrames { + if previousNode.supernode == nil { + temporaryPreviousNodes.append(previousNode) + previousNode.frame = previousFrame + if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { + previousUpperBound = previousFrame.minY + } + if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { + previousLowerBound = previousFrame.maxY + } + } else { + if previousNode.canBeUsedAsScrollToItemAnchor { + offset = previousNode.apparentFrame.minY - previousFrame.minY + } + } + } + + if offset == nil { + let updatedUpperBound = self.itemNodes[0].apparentFrame.minY + let updatedLowerBound = max(self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY, self.visibleSize.height) - var temporaryPreviousNodes: [ListViewItemNode] = [] - var previousUpperBound: CGFloat? - var previousLowerBound: CGFloat? - for (previousNode, previousFrame) in previousApparentFrames { - if previousNode.supernode == nil { - temporaryPreviousNodes.append(previousNode) - previousNode.frame = previousFrame - if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { - previousUpperBound = previousFrame.minY - } - if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { - previousLowerBound = previousFrame.maxY - } + switch scrollToItem.directionHint { + case .Up: + offset = updatedLowerBound - (previousUpperBound ?? 0.0) + case .Down: + offset = updatedUpperBound - (previousLowerBound ?? self.visibleSize.height) + } + } + + if let offsetValue = offset { + offset = offsetValue - sizeAndInsetsOffset + } + + var previousItemHeaderNodes: [ListViewItemHeaderNode] = [] + let offsetOrZero: CGFloat = offset ?? 0.0 + switch scrollToItem.curve { + case let .Spring(duration): + headerNodesTransition = (.animated(duration: duration, curve: .spring), headerNodesTransition.1, headerNodesTransition.2 - offsetOrZero) + case let .Default(duration): + headerNodesTransition = (.animated(duration: duration ?? 0.3, curve: .easeInOut), true, headerNodesTransition.2 - offsetOrZero) + } + for (_, headerNode) in self.itemHeaderNodes { + previousItemHeaderNodes.append(headerNode) + } + + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + + if let offset = offset , abs(offset) > CGFloat.ulpOfOne { + let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() + for itemNode in temporaryPreviousNodes { + itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) + if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { + self.insertSubnode(itemNode, belowSubnode: lowestNodeToInsertBelow) + } else if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(itemNode, belowSubnode: verticalScrollIndicator) } else { - if previousNode.canBeUsedAsScrollToItemAnchor { - offset = previousNode.apparentFrame.minY - previousFrame.minY - } + self.addSubnode(itemNode) } } - if offset == nil { - let updatedUpperBound = self.itemNodes[0].apparentFrame.minY - let updatedLowerBound = max(self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY, self.visibleSize.height) - - switch scrollToItem.directionHint { - case .Up: - offset = updatedLowerBound - (previousUpperBound ?? 0.0) - case .Down: - offset = updatedUpperBound - (previousLowerBound ?? self.visibleSize.height) - } - } - - if let offsetValue = offset { - offset = offsetValue - sizeAndInsetsOffset - } - - var previousItemHeaderNodes: [ListViewItemHeaderNode] = [] - let offsetOrZero: CGFloat = offset ?? 0.0 - switch scrollToItem.curve { - case let .Spring(duration): - headerNodesTransition = (.animated(duration: duration, curve: .spring), headerNodesTransition.1, headerNodesTransition.2 - offsetOrZero) - case let .Default(duration): - headerNodesTransition = (.animated(duration: duration ?? 0.3, curve: .easeInOut), true, headerNodesTransition.2 - offsetOrZero) - } - for (_, headerNode) in self.itemHeaderNodes { - previousItemHeaderNodes.append(headerNode) - } - - self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) - - if let offset = offset , abs(offset) > CGFloat.ulpOfOne { - let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() - for itemNode in temporaryPreviousNodes { - itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) - if let lowestNodeToInsertBelow = lowestNodeToInsertBelow { - self.insertSubnode(itemNode, belowSubnode: lowestNodeToInsertBelow) + var temporaryHeaderNodes: [ListViewItemHeaderNode] = [] + for headerNode in previousItemHeaderNodes { + if headerNode.supernode == nil { + headerNode.frame = headerNode.frame.offsetBy(dx: 0.0, dy: offset) + temporaryHeaderNodes.append(headerNode) + if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator) } else { - self.addSubnode(itemNode) - } - } - - var temporaryHeaderNodes: [ListViewItemHeaderNode] = [] - for headerNode in previousItemHeaderNodes { - if headerNode.supernode == nil { - headerNode.frame = headerNode.frame.offsetBy(dx: 0.0, dy: offset) - temporaryHeaderNodes.append(headerNode) self.addSubnode(headerNode) } } - - let animation: CABasicAnimation - switch scrollToItem.curve { - case let .Spring(duration): - let springAnimation = makeSpringAnimation("sublayerTransform") - springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) - springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - springAnimation.isRemovedOnCompletion = true - springAnimation.isAdditive = true - springAnimation.fillMode = kCAFillModeForwards - - let k = Float(UIView.animationDurationFactor()) - var speed: Float = 1.0 - if k != 0 && k != 1 { - speed = Float(1.0) / k - } - springAnimation.speed = speed * Float(springAnimation.duration / duration) - - animation = springAnimation - case let .Default(duration): - if let duration = duration { - let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") - basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) - basicAnimation.duration = duration * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) - basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - basicAnimation.isRemovedOnCompletion = true - basicAnimation.isAdditive = true - animation = basicAnimation - } else { - let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") - basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) - //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) - basicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor() - basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) - basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) - basicAnimation.isRemovedOnCompletion = true - basicAnimation.isAdditive = true - animation = basicAnimation - } - } - animation.completion = { _ in - for itemNode in temporaryPreviousNodes { - itemNode.removeFromSupernode() - if useBackgroundDeallocation { - assertionFailure() - //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) - } else { - //ASPerformMainThreadDeallocation(itemNode) - } + } + + let animation: CABasicAnimation + switch scrollToItem.curve { + case let .Spring(duration): + let springAnimation = makeSpringAnimation("sublayerTransform") + springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + springAnimation.isRemovedOnCompletion = true + springAnimation.isAdditive = true + springAnimation.fillMode = kCAFillModeForwards + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k } - for headerNode in temporaryHeaderNodes { - headerNode.removeFromSupernode() - if useBackgroundDeallocation { - assertionFailure() - //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) - } else { - //ASPerformMainThreadDeallocation(headerNode) - } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + + animation = springAnimation + case let .Default(duration): + if let duration = duration { + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + basicAnimation.duration = duration * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true + animation = basicAnimation + } else { + let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) + //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) + basicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor() + basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) + basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + basicAnimation.isRemovedOnCompletion = true + basicAnimation.isAdditive = true + animation = basicAnimation } - } - self.layer.add(animation, forKey: nil) - } else { - if useBackgroundDeallocation { - assertionFailure() - /*for itemNode in temporaryPreviousNodes { - ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) - }*/ - } else { - for itemNode in temporaryPreviousNodes { + } + animation.completion = { _ in + for itemNode in temporaryPreviousNodes { + itemNode.removeFromSupernode() + if useBackgroundDeallocation { + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + } else { //ASPerformMainThreadDeallocation(itemNode) } } + for headerNode in temporaryHeaderNodes { + headerNode.removeFromSupernode() + if useBackgroundDeallocation { + assertionFailure() + //ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: headerNode) + } else { + //ASPerformMainThreadDeallocation(headerNode) + } + } } - } - - self.updateItemNodesVisibilities() - - self.updateScroller(transition: headerNodesTransition.0) - - if let topItemOverscrollBackground = self.topItemOverscrollBackground { - headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) - } - - self.setNeedsAnimations() - - self.updateVisibleContentOffset() - - if self.debugInfo { - //let delta = CACurrentMediaTime() - timestamp - //print("replayOperations \(delta * 1000.0) ms") - } - - completion() - } else { - self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) - self.updateItemNodesVisibilities() - - if animated { - self.setNeedsAnimations() - } - - self.updateScroller(transition: headerNodesTransition.0) - - if let topItemOverscrollBackground = self.topItemOverscrollBackground { - headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) - } - - self.updateVisibleContentOffset() - - if self.debugInfo { - //let delta = CACurrentMediaTime() - timestamp - //print("replayOperations \(delta * 1000.0) ms") - } - - for (previousNode, _) in previousApparentFrames { - if previousNode.supernode == nil { - if useBackgroundDeallocation { - assertionFailure() - //ASDeallocQueue.sharedDeallocatio.releaseObject(inBackground: previousNode) - } else { - //ASPerformMainThreadDeallocation(previousNode) + self.layer.add(animation, forKey: nil) + } else { + if useBackgroundDeallocation { + assertionFailure() + /*for itemNode in temporaryPreviousNodes { + ASDeallocQueue.sharedDeallocation().releaseObject(inBackground: itemNode) + }*/ + } else { + for itemNode in temporaryPreviousNodes { + //ASPerformMainThreadDeallocation(itemNode) } } } - - completion() } - }) - } - - private func insertNodesInBatches(nodes: [ASDisplayNode], completion: () -> Void) { - if nodes.count == 0 { + + self.updateItemNodesVisibilities() + + self.updateScroller(transition: headerNodesTransition.0) + + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) + } + + self.setNeedsAnimations() + + self.updateVisibleContentOffset() + + if self.debugInfo { + //let delta = CACurrentMediaTime() - timestamp + //print("replayOperations \(delta * 1000.0) ms") + } + completion() } else { - for node in nodes { - self.addSubnode(node) + self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemNodesVisibilities() + + if animated { + self.setNeedsAnimations() } + + self.updateScroller(transition: headerNodesTransition.0) + + if let topItemOverscrollBackground = self.topItemOverscrollBackground { + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) + } + + self.updateVisibleContentOffset() + + if self.debugInfo { + //let delta = CACurrentMediaTime() - timestamp + //print("replayOperations \(delta * 1000.0) ms") + } + + for (previousNode, _) in previousApparentFrames { + if previousNode.supernode == nil { + if useBackgroundDeallocation { + assertionFailure() + //ASDeallocQueue.sharedDeallocatio.releaseObject(inBackground: previousNode) + } else { + //ASPerformMainThreadDeallocation(previousNode) + } + } + } + completion() } } @@ -2722,7 +2771,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel headerNode.updateLayoutInternal(size: headerFrame.size, leftInset: leftInset, rightInset: rightInset) headerNode.updateInternalStickLocationDistanceFactor(stickLocationDistanceFactor, animated: false) self.itemHeaderNodes[id] = headerNode - self.addSubnode(headerNode) + if let verticalScrollIndicator = self.verticalScrollIndicator { + self.insertSubnode(headerNode, belowSubnode: verticalScrollIndicator) + } else { + self.addSubnode(headerNode) + } if animateInsertion { headerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) headerNode.layer.animateScale(from: 0.2, to: 1.0, duration: 0.3) @@ -2912,9 +2965,85 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - let indicatorInsets: CGFloat = 3.0 - if let verticalScrollIndicator = self.verticalScrollIndicator { + var topIndexAndBoundary: (Int, CGFloat, CGFloat)? + var bottomIndexAndBoundary: (Int, CGFloat, CGFloat)? + for itemNode in self.itemNodes { + if itemNode.apparentFrame.maxY > 0.0, let index = itemNode.index { + topIndexAndBoundary = (index, itemNode.apparentFrame.minY, itemNode.apparentFrame.height) + break + } + } + for itemNode in self.itemNodes.reversed() { + if itemNode.apparentFrame.minY <= self.visibleSize.height, let index = itemNode.index { + bottomIndexAndBoundary = (index, itemNode.apparentFrame.maxY, itemNode.apparentFrame.height) + break + } + } + if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary { + let rangeItemCount = max(1, bottomIndexAndBoundary.0 - topIndexAndBoundary.0 + 1) + //let visibleRangeHeight = max(0.0, bottomIndexAndBoundary.1 - topIndexAndBoundary.1) + //let averageRangeItemHeight = visibleRangeHeight / CGFloat(rangeItemCount) + let averageRangeItemHeight: CGFloat = 44.0 + + let visibleRangeHeight = CGFloat(rangeItemCount) * averageRangeItemHeight + let upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0)) + let lowerItemsHeight = floor(averageRangeItemHeight * CGFloat(self.items.count - bottomIndexAndBoundary.0)) + let approximateContentHeight = upperItemsHeight + visibleRangeHeight + lowerItemsHeight + + let convertedTopBoundary: CGFloat + if topIndexAndBoundary.1 < 0.0 { + convertedTopBoundary = topIndexAndBoundary.1 * averageRangeItemHeight / topIndexAndBoundary.2 + } else { + convertedTopBoundary = topIndexAndBoundary.1 + } + + var convertedBottomBoundary: CGFloat = 0.0 + if bottomIndexAndBoundary.1 > self.visibleSize.height { + convertedBottomBoundary = (bottomIndexAndBoundary.1 - self.visibleSize.height) * averageRangeItemHeight / bottomIndexAndBoundary.2 + } + convertedBottomBoundary += convertedTopBoundary + CGFloat(rangeItemCount) * averageRangeItemHeight + + let approximateFirstItemOffset = convertedTopBoundary - upperItemsHeight + let approximateLastItemOffset = convertedBottomBoundary + + let approximateOffset = -approximateFirstItemOffset + self.insets.top + let approximateBottomOffset = -approximateLastItemOffset + self.insets.top + let indicatorInsets: CGFloat = 3.0 + + //print("convertedTopBoundary = \(convertedTopBoundary), topIndexAndBoundary.1 = \(topIndexAndBoundary.1), upperItemsHeight = \(upperItemsHeight), approximateOffset = \(approximateOffset), approximateBottomOffset = \(approximateBottomOffset)") + + let visibleHeightWithoutInsets = self.visibleSize.height - self.insets.top - self.insets.bottom + let visibleHeightWithoutIndicatorInsets = visibleHeightWithoutInsets - indicatorInsets * 2.0 + + // visibleHeightWithoutIndicatorInsets -> approximateContentHeight + // x -> approximateOffset + // x = visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight + + // visibleHeightWithoutIndicatorInsets -> approximateContentHeight + // x -> visibleHeightWithoutInsets + + let indicatorOffset = ceilToScreenPixels(visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight) + let approximateIndicatorHeight = ceilToScreenPixels(visibleHeightWithoutIndicatorInsets * visibleHeightWithoutInsets / approximateContentHeight) + + let minHeight: CGFloat = 6.0 + let indicatorHeight = max(minHeight, approximateIndicatorHeight) + + var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorInsets : (self.visibleSize.width - 3.0 - indicatorInsets), y: self.scrollIndicatorInsets.top + indicatorInsets + indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) + if indicatorFrame.minY < self.scrollIndicatorInsets.top + indicatorInsets { + indicatorFrame.size.height -= self.scrollIndicatorInsets.top + indicatorInsets - indicatorFrame.minY + indicatorFrame.origin.y = self.scrollIndicatorInsets.top + indicatorInsets + indicatorFrame.size.height = max(minHeight, indicatorFrame.height) + } + if verticalScrollIndicator.isHidden { + verticalScrollIndicator.isHidden = false + verticalScrollIndicator.frame = indicatorFrame + } else { + verticalScrollIndicator.frame = indicatorFrame + } + } else { + verticalScrollIndicator.isHidden = true + } /*let size = self.visibleSize.height let range = computeVerticalScrollRange() let extent = computeVerticalScrollExtent() @@ -2986,7 +3115,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let state = self.currentState() self.async { - self.fillMissingNodes(synchronous: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in + self.fillMissingNodes(synchronous: false, synchronousLoads: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in var updatedState = state var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) @@ -3489,6 +3618,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { itemHighlightOverlayBackground.view.superview?.bringSubview(toFront: itemHighlightOverlayBackground.view) } + if let verticalScrollIndicator = self.verticalScrollIndicator { + verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) + } } private func reorderHeaderNodeToFront(_ headerNode: ListViewItemHeaderNode) { @@ -3496,5 +3628,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { itemHighlightOverlayBackground.view.superview?.bringSubview(toFront: itemHighlightOverlayBackground.view) } + if let verticalScrollIndicator = self.verticalScrollIndicator { + verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) + } } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index a304e7d847..8e91b0c723 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -108,13 +108,15 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public struct ListViewUpdateSizeAndInsets { public let size: CGSize public let insets: UIEdgeInsets + public let scrollIndicatorInsets: UIEdgeInsets? public let duration: Double public let curve: ListViewAnimationCurve public let ensureTopInsetForOverlayHighlightedItems: CGFloat? - public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) { + public init(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) { self.size = size self.insets = insets + self.scrollIndicatorInsets = scrollIndicatorInsets self.duration = duration self.curve = curve self.ensureTopInsetForOverlayHighlightedItems = ensureTopInsetForOverlayHighlightedItems diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index dead8c13c0..7072657ee6 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -33,7 +33,7 @@ public struct ListViewItemConfigureNodeFlags: OptionSet { } public protocol ListViewItem { - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 7135da899a..40a8ef11db 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -32,6 +32,9 @@ public let UIScreenScale = UIScreen.main.scale public func floorToScreenPixels(_ value: CGFloat) -> CGFloat { return floor(value * UIScreenScale) / UIScreenScale } +public func ceilToScreenPixels(_ value: CGFloat) -> CGFloat { + return ceil(value * UIScreenScale) / UIScreenScale +} public let UIScreenPixel = 1.0 / UIScreenScale From b3419d2af51d00546bf34f40bc1af69a8559feee Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 30 Nov 2018 20:04:03 +0400 Subject: [PATCH 118/245] Added volume control icon --- Display/VolumeControlStatusBar.swift | 80 ++++++++++++++++++++++++++-- Display/WindowContent.swift | 4 ++ 2 files changed, 79 insertions(+), 5 deletions(-) diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index c7cdea2f5f..7c397fee96 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -76,7 +76,22 @@ final class VolumeControlStatusBar: UIView { } final class VolumeControlStatusBarNode: ASDisplayNode { + var innerGraphics: (UIImage, UIImage, UIImage, Bool)? + var graphics: (UIImage, UIImage, UIImage)? = nil { + didSet { + if self.isDark { + self.innerGraphics = generateDarkGraphics(self.graphics) + } else { + if let graphics = self.graphics { + self.innerGraphics = (graphics.0, graphics.1, graphics.2, false) + } else { + self.innerGraphics = nil + } + } + } + } private let backgroundNode: ASImageNode + private let iconNode: ASImageNode private let foregroundNode: ASImageNode private let foregroundClippingNode: ASDisplayNode @@ -88,9 +103,15 @@ final class VolumeControlStatusBarNode: ASDisplayNode { if self.isDark { self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .white) + + self.innerGraphics = generateDarkGraphics(self.graphics) } else { - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(rgb: 0xc5c5c5)) self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) + + if let graphics = self.graphics { + self.innerGraphics = (graphics.0, graphics.1, graphics.2, false) + } } } } @@ -102,7 +123,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false self.backgroundNode.displayWithoutProcessing = true - self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .gray) + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(rgb: 0xc5c5c5)) self.foregroundNode = ASImageNode() self.foregroundNode.isLayerBacked = true @@ -114,22 +135,49 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.foregroundClippingNode.clipsToBounds = true self.foregroundClippingNode.addSubnode(self.foregroundNode) + self.iconNode = ASImageNode() + self.iconNode.isLayerBacked = true + self.iconNode.displaysAsynchronously = false + self.iconNode.displayWithoutProcessing = true + super.init() self.isUserInteractionEnabled = false self.addSubnode(self.backgroundNode) self.addSubnode(self.foregroundClippingNode) + self.addSubnode(self.iconNode) + } + + func generateDarkGraphics(_ graphics: (UIImage, UIImage, UIImage)?) -> (UIImage, UIImage, UIImage, Bool)? { + if var (offImage, halfImage, onImage) = graphics { + offImage = generateTintedImage(image: offImage, color: UIColor.black)! + halfImage = generateTintedImage(image: halfImage, color: UIColor.black)! + onImage = generateTintedImage(image: onImage, color: UIColor.black)! + return (offImage, halfImage, onImage, true) + } else { + return nil + } + } + + func updateGraphics() { + if self.isDark { + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) + self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .white) + } else { + self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) + self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) + } } func updateLayout(layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.validLayout = layout let barHeight: CGFloat = 4.0 - let barWidth: CGFloat + var barWidth: CGFloat let statusBarHeight: CGFloat - let sideInset: CGFloat + var sideInset: CGFloat if let actual = layout.statusBarHeight { statusBarHeight = actual } else { @@ -141,14 +189,26 @@ final class VolumeControlStatusBarNode: ASDisplayNode { sideInset = 12.0 } + let iconRect = CGRect(x: sideInset + 4.0, y: 14.0, width: 21.0, height: 16.0) if !layout.intrinsicInsets.bottom.isZero { - barWidth = 92.0 - sideInset * 2.0 + if layout.size.width > 375.0 { + barWidth = 88.0 - sideInset * 2.0 + } else { + barWidth = 80.0 - sideInset * 2.0 + } + if self.graphics != nil { + self.iconNode.isHidden = false + barWidth -= iconRect.width - 8.0 + sideInset += iconRect.width + 8.0 + } } else { + self.iconNode.isHidden = true barWidth = layout.size.width - sideInset * 2.0 } let boundingRect = CGRect(origin: CGPoint(x: sideInset, y: floor((statusBarHeight - barHeight) / 2.0)), size: CGSize(width: barWidth, height: barHeight)) + transition.updateFrame(node: self.iconNode, frame: iconRect) transition.updateFrame(node: self.backgroundNode, frame: boundingRect) transition.updateFrame(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: boundingRect.size)) transition.updateFrame(node: self.foregroundClippingNode, frame: CGRect(origin: boundingRect.origin, size: CGSize(width: self.value * boundingRect.width, height: boundingRect.height))) @@ -162,6 +222,16 @@ final class VolumeControlStatusBarNode: ASDisplayNode { } self.value = toValue self.updateLayout(layout: layout, transition: .animated(duration: 0.25, curve: .spring)) + + if let graphics = self.graphics { + if self.value > 0.5 { + self.iconNode.image = graphics.2 + } else if self.value > 0.0 { + self.iconNode.image = graphics.1 + } else { + self.iconNode.image = graphics.0 + } + } } else { self.value = toValue } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index edb9dd5dae..055b59a3cd 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -519,6 +519,10 @@ public class Window1 { } } + public func setupVolumeControlStatusBarGraphics(_ graphics: (UIImage, UIImage, UIImage)) { + self.volumeControlStatusBarNode.graphics = graphics + } + public func setForceInCallStatusBar(_ forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition = .animated(duration: 0.3, curve: .easeInOut)) { if self.forceInCallStatusBarText != forceInCallStatusBarText { self.forceInCallStatusBarText = forceInCallStatusBarText From 44a6e284e4934adf0c1331a40304dbdd54ffdba8 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 1 Dec 2018 02:42:21 +0400 Subject: [PATCH 119/245] Improved ListView scroll indicator Fixed PresentationContext ordering --- Display/ListView.swift | 141 +++++++++++++++++------------- Display/PresentationContext.swift | 53 ++++++++--- Display/WindowContent.swift | 2 +- 3 files changed, 120 insertions(+), 76 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 8878e6a18b..42971a3df8 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2523,7 +2523,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) - if let offset = offset , abs(offset) > CGFloat.ulpOfOne { + if let offset = offset, !offset.isZero { let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() for itemNode in temporaryPreviousNodes { itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset) @@ -2550,6 +2550,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } let animation: CABasicAnimation + let reverseAnimation: CABasicAnimation switch scrollToItem.curve { case let .Spring(duration): let springAnimation = makeSpringAnimation("sublayerTransform") @@ -2566,7 +2567,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } springAnimation.speed = speed * Float(springAnimation.duration / duration) + let reverseSpringAnimation = makeSpringAnimation("sublayerTransform") + reverseSpringAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0)) + reverseSpringAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + reverseSpringAnimation.isRemovedOnCompletion = true + reverseSpringAnimation.isAdditive = true + reverseSpringAnimation.fillMode = kCAFillModeForwards + + reverseSpringAnimation.speed = speed * Float(reverseSpringAnimation.duration / duration) + animation = springAnimation + reverseAnimation = reverseSpringAnimation case let .Default(duration): if let duration = duration { let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") @@ -2576,17 +2587,36 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) basicAnimation.isRemovedOnCompletion = true basicAnimation.isAdditive = true + + let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + reverseBasicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut) + reverseBasicAnimation.duration = duration * UIView.animationDurationFactor() + reverseBasicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0)) + reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + reverseBasicAnimation.isRemovedOnCompletion = true + reverseBasicAnimation.isAdditive = true + animation = basicAnimation + reverseAnimation = reverseBasicAnimation } else { let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") basicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) - //basicAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut) basicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor() basicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) basicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) basicAnimation.isRemovedOnCompletion = true basicAnimation.isAdditive = true + + let reverseBasicAnimation = CABasicAnimation(keyPath: "sublayerTransform") + reverseBasicAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99) + reverseBasicAnimation.duration = (duration ?? 0.3) * UIView.animationDurationFactor() + reverseBasicAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0)) + reverseBasicAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) + reverseBasicAnimation.isRemovedOnCompletion = true + reverseBasicAnimation.isAdditive = true + animation = basicAnimation + reverseAnimation = reverseBasicAnimation } } animation.completion = { _ in @@ -2610,6 +2640,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } self.layer.add(animation, forKey: nil) + if let verticalScrollIndicator = self.verticalScrollIndicator { + verticalScrollIndicator.layer.add(reverseAnimation, forKey: nil) + } } else { if useBackgroundDeallocation { assertionFailure() @@ -2969,100 +3002,86 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var topIndexAndBoundary: (Int, CGFloat, CGFloat)? var bottomIndexAndBoundary: (Int, CGFloat, CGFloat)? for itemNode in self.itemNodes { - if itemNode.apparentFrame.maxY > 0.0, let index = itemNode.index { + if itemNode.apparentFrame.maxY > self.insets.top, let index = itemNode.index { topIndexAndBoundary = (index, itemNode.apparentFrame.minY, itemNode.apparentFrame.height) break } } for itemNode in self.itemNodes.reversed() { - if itemNode.apparentFrame.minY <= self.visibleSize.height, let index = itemNode.index { + if itemNode.apparentFrame.minY <= self.visibleSize.height - self.insets.bottom, let index = itemNode.index { bottomIndexAndBoundary = (index, itemNode.apparentFrame.maxY, itemNode.apparentFrame.height) break } } if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary { - let rangeItemCount = max(1, bottomIndexAndBoundary.0 - topIndexAndBoundary.0 + 1) - //let visibleRangeHeight = max(0.0, bottomIndexAndBoundary.1 - topIndexAndBoundary.1) - //let averageRangeItemHeight = visibleRangeHeight / CGFloat(rangeItemCount) - let averageRangeItemHeight: CGFloat = 44.0 + let averageRangeItemHeight: CGFloat = 44.0 //(bottomIndexAndBoundary.1 - topIndexAndBoundary.1) / CGFloat(bottomIndexAndBoundary.0 - topIndexAndBoundary.0 + 1) - let visibleRangeHeight = CGFloat(rangeItemCount) * averageRangeItemHeight let upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0)) - let lowerItemsHeight = floor(averageRangeItemHeight * CGFloat(self.items.count - bottomIndexAndBoundary.0)) - let approximateContentHeight = upperItemsHeight + visibleRangeHeight + lowerItemsHeight + let approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight - let convertedTopBoundary: CGFloat - if topIndexAndBoundary.1 < 0.0 { - convertedTopBoundary = topIndexAndBoundary.1 * averageRangeItemHeight / topIndexAndBoundary.2 + var convertedTopBoundary: CGFloat + if topIndexAndBoundary.1 < self.insets.top { + convertedTopBoundary = (topIndexAndBoundary.1 - self.insets.top) * averageRangeItemHeight / topIndexAndBoundary.2 } else { - convertedTopBoundary = topIndexAndBoundary.1 + convertedTopBoundary = topIndexAndBoundary.1 - self.insets.top } + convertedTopBoundary -= upperItemsHeight + + let approximateOffset = -convertedTopBoundary var convertedBottomBoundary: CGFloat = 0.0 - if bottomIndexAndBoundary.1 > self.visibleSize.height { - convertedBottomBoundary = (bottomIndexAndBoundary.1 - self.visibleSize.height) * averageRangeItemHeight / bottomIndexAndBoundary.2 + if bottomIndexAndBoundary.1 > self.visibleSize.height - self.insets.bottom { + convertedBottomBoundary = ((self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1) * averageRangeItemHeight / bottomIndexAndBoundary.2 + } else { + convertedBottomBoundary = (self.visibleSize.height - self.insets.bottom) - bottomIndexAndBoundary.1 } - convertedBottomBoundary += convertedTopBoundary + CGFloat(rangeItemCount) * averageRangeItemHeight + convertedBottomBoundary += CGFloat(bottomIndexAndBoundary.0 + 1) * averageRangeItemHeight - let approximateFirstItemOffset = convertedTopBoundary - upperItemsHeight - let approximateLastItemOffset = convertedBottomBoundary + let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset) + + let approximateScrollingProgress = approximateOffset / (approximateContentHeight - approximateVisibleHeight) + /*#if targetEnvironment(simulator) + print("approximateOffset = \(approximateOffset), convertedBottomBoundary = \(convertedBottomBoundary) / \(vanillaBoundary), approximateScrollingProgress = \(approximateScrollingProgress)") + #endif*/ - let approximateOffset = -approximateFirstItemOffset + self.insets.top - let approximateBottomOffset = -approximateLastItemOffset + self.insets.top let indicatorInsets: CGFloat = 3.0 + let minIndicatorContentHeight: CGFloat = 12.0 + let minIndicatorHeight: CGFloat = 6.0 - //print("convertedTopBoundary = \(convertedTopBoundary), topIndexAndBoundary.1 = \(topIndexAndBoundary.1), upperItemsHeight = \(upperItemsHeight), approximateOffset = \(approximateOffset), approximateBottomOffset = \(approximateBottomOffset)") + let visibleHeightWithoutIndicatorInsets = self.visibleSize.height - self.scrollIndicatorInsets.top - self.scrollIndicatorInsets.bottom - indicatorInsets * 2.0 + let indicatorHeight: CGFloat = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight)) - let visibleHeightWithoutInsets = self.visibleSize.height - self.insets.top - self.insets.bottom - let visibleHeightWithoutIndicatorInsets = visibleHeightWithoutInsets - indicatorInsets * 2.0 + let upperBound = self.scrollIndicatorInsets.top + indicatorInsets + let lowerBound = self.visibleSize.height - self.scrollIndicatorInsets.bottom - indicatorInsets - indicatorHeight - // visibleHeightWithoutIndicatorInsets -> approximateContentHeight - // x -> approximateOffset - // x = visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight + let indicatorOffset = ceilToScreenPixels(upperBound * (1.0 - approximateScrollingProgress) + lowerBound * approximateScrollingProgress) - // visibleHeightWithoutIndicatorInsets -> approximateContentHeight - // x -> visibleHeightWithoutInsets - - let indicatorOffset = ceilToScreenPixels(visibleHeightWithoutIndicatorInsets * approximateOffset / approximateContentHeight) - let approximateIndicatorHeight = ceilToScreenPixels(visibleHeightWithoutIndicatorInsets * visibleHeightWithoutInsets / approximateContentHeight) - - let minHeight: CGFloat = 6.0 - let indicatorHeight = max(minHeight, approximateIndicatorHeight) - - var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorInsets : (self.visibleSize.width - 3.0 - indicatorInsets), y: self.scrollIndicatorInsets.top + indicatorInsets + indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) + var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorInsets : (self.visibleSize.width - 3.0 - indicatorInsets), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) if indicatorFrame.minY < self.scrollIndicatorInsets.top + indicatorInsets { indicatorFrame.size.height -= self.scrollIndicatorInsets.top + indicatorInsets - indicatorFrame.minY indicatorFrame.origin.y = self.scrollIndicatorInsets.top + indicatorInsets - indicatorFrame.size.height = max(minHeight, indicatorFrame.height) + indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height) } - if verticalScrollIndicator.isHidden { - verticalScrollIndicator.isHidden = false + if indicatorFrame.maxY > self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets) { + indicatorFrame.size.height -= indicatorFrame.maxY - (self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets)) + indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height) + indicatorFrame.origin.y = self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets) - indicatorFrame.height + } + + if indicatorHeight >= visibleHeightWithoutIndicatorInsets { + verticalScrollIndicator.isHidden = true verticalScrollIndicator.frame = indicatorFrame } else { - verticalScrollIndicator.frame = indicatorFrame + if verticalScrollIndicator.isHidden { + verticalScrollIndicator.isHidden = false + verticalScrollIndicator.frame = indicatorFrame + } else { + verticalScrollIndicator.frame = indicatorFrame + } } } else { verticalScrollIndicator.isHidden = true } - /*let size = self.visibleSize.height - let range = computeVerticalScrollRange() - let extent = computeVerticalScrollExtent() - let mOffset = computeVerticalScrollOffset() - - let thickness: CGFloat = 3.0 - var length = round(size * extent / range) - var offset = round((size - length) * mOffset / (range - extent)) - - let minLength = thickness * 2.0 - if length < minLength { - length = minLength - } - if offset + length > size { - offset = size - length - } - - let indicatorHeight: CGFloat = max(3.0, 30.0) - verticalScrollIndicator.frame = CGRect(origin: CGPoint(x: self.visibleSize.width - 3.0 - indicatorInsets, y: self.insets.top + offset), size: CGSize(width: 3.0, height: length))*/ } } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 75e878cebf..9b7da9bb95 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -39,12 +39,31 @@ final class PresentationContext { return self.view != nil && self.layout != nil } - private(set) var controllers: [ViewController] = [] + private(set) var controllers: [(ViewController, PresentationSurfaceLevel)] = [] private var presentationDisposables = DisposableSet() var topLevelSubview: UIView? + private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? { + var topController: ViewController? + for (controller, controllerLevel) in self.controllers.reversed() { + if !controller.isViewLoaded || controller.view.superview == nil { + continue + } + if controllerLevel.rawValue > level.rawValue { + topController = controller + } else { + break + } + } + if let topController = topController { + return topController.view + } else { + return topLevelSubview + } + } + private var nextBlockInteractionToken = 0 private var blockInteractionTokens = Set() @@ -67,7 +86,7 @@ final class PresentationContext { } } - public func present(_ controller: ViewController, on: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { + public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -103,11 +122,17 @@ final class PresentationContext { if let blockInteractionToken = blockInteractionToken { strongSelf.removeBlockInteraction(blockInteractionToken) } - if strongSelf.controllers.contains(where: { $0 === controller }) { + if strongSelf.controllers.contains(where: { $0.0 === controller }) { return } - strongSelf.controllers.append(controller) + var insertIndex: Int? + for i in (0 ..< strongSelf.controllers.count).reversed() { + if strongSelf.controllers[i].1.rawValue > level.rawValue { + insertIndex = i + } + } + strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count) if let view = strongSelf.view, let layout = strongSelf.layout { controller.navigation_setDismiss({ [weak controller] in if let strongSelf = self, let controller = controller { @@ -117,14 +142,14 @@ final class PresentationContext { controller.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) - if let topLevelSubview = strongSelf.topLevelSubview { + if let topLevelSubview = strongSelf.topLevelSubview(for: level) { view.insertSubview(controller.view, belowSubview: topLevelSubview) } else { view.addSubview(controller.view) } controller.containerLayoutUpdated(layout, transition: .immediate) } else { - if let topLevelSubview = strongSelf.topLevelSubview { + if let topLevelSubview = strongSelf.topLevelSubview(for: level) { view.insertSubview(controller.view, belowSubview: topLevelSubview) } else { view.addSubview(controller.view) @@ -138,7 +163,7 @@ final class PresentationContext { } })) } else { - self.controllers.append(controller) + self.controllers.append((controller, level)) } } @@ -147,7 +172,7 @@ final class PresentationContext { } private func dismiss(_ controller: ViewController) { - if let index = self.controllers.index(where: { $0 === controller }) { + if let index = self.controllers.index(where: { $0.0 === controller }) { self.controllers.remove(at: index) controller.viewWillDisappear(false) controller.view.removeFromSuperview() @@ -162,7 +187,7 @@ final class PresentationContext { if wasReady != self.ready { self.readyChanged(wasReady: wasReady) } else if self.ready { - for controller in self.controllers { + for (controller, _) in self.controllers { controller.containerLayoutUpdated(layout, transition: transition) } } @@ -178,7 +203,7 @@ final class PresentationContext { private func addViews() { if let view = self.view, let layout = self.layout { - for controller in self.controllers { + for (controller, _) in self.controllers { controller.viewWillAppear(false) if let topLevelSubview = self.topLevelSubview { view.insertSubview(controller.view, belowSubview: topLevelSubview) @@ -193,7 +218,7 @@ final class PresentationContext { } private func removeViews() { - for controller in self.controllers { + for (controller, _) in self.controllers { controller.viewWillDisappear(false) controller.view.removeFromSuperview() controller.viewDidDisappear(false) @@ -201,7 +226,7 @@ final class PresentationContext { } func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - for controller in self.controllers.reversed() { + for (controller, _) in self.controllers.reversed() { if controller.isViewLoaded { if let result = controller.view.hitTest(point, with: event) { return result @@ -214,7 +239,7 @@ final class PresentationContext { func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) - for controller in self.controllers { + for (controller, _) in self.controllers { mask = mask.intersection(controller.supportedOrientations) } @@ -224,7 +249,7 @@ final class PresentationContext { func combinedDeferScreenEdgeGestures() -> UIRectEdge { var edges: UIRectEdge = [] - for controller in self.controllers { + for (controller, _) in self.controllers { edges = edges.union(controller.deferScreenEdgeGestures) } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index edb9dd5dae..0a63d4e86f 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -992,7 +992,7 @@ public class Window1 { } public func forEachViewController(_ f: (ViewController) -> Bool) { - for controller in self.presentationContext.controllers { + for (controller, _) in self.presentationContext.controllers { if !f(controller) { break } From 6c2b24912ec21d0eea83ecdc0ce2894d403b209e Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Sun, 2 Dec 2018 04:41:31 +0400 Subject: [PATCH 120/245] Added aspectFittedOrSmaller --- Display/UIKitUtils.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 40a8ef11db..214d1cef72 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -132,6 +132,11 @@ public extension CGSize { return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) } + public func aspectFittedOrSmaller(_ size: CGSize) -> CGSize { + let scale = min(1.0, min(size.width / max(1.0, self.width), size.height / max(1.0, self.height))) + return CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) + } + public func aspectFittedWithOverflow(_ size: CGSize, leeway: CGFloat) -> CGSize { let scale = min(size.width / max(1.0, self.width), size.height / max(1.0, self.height)) var result = CGSize(width: floor(self.width * scale), height: floor(self.height * scale)) From 7dbc5b2e78119a24f26c2888030d861e6ff07d45 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Mon, 3 Dec 2018 05:04:45 +0400 Subject: [PATCH 121/245] Collection API updates --- Display/GridItemNode.swift | 3 +++ Display/GridNode.swift | 3 +++ Display/ListView.swift | 32 +++++++++++++++---------- Display/ListViewIntermediateState.swift | 8 +++---- Display/ListViewItem.swift | 12 ++++++++-- 5 files changed, 39 insertions(+), 19 deletions(-) diff --git a/Display/GridItemNode.swift b/Display/GridItemNode.swift index 5acb7a1aa4..613a6b168c 100644 --- a/Display/GridItemNode.swift +++ b/Display/GridItemNode.swift @@ -14,4 +14,7 @@ open class GridItemNode: ASDisplayNode { super.frame = value } } + + open func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) { + } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 921450bce7..f967cde388 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -799,6 +799,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let lowestSectionNode: ASDisplayNode? = self.lowestSectionNode() + let bounds = self.bounds var existingItemIndices = Set() for item in presentationLayoutTransition.layout.items { existingItemIndices.insert(item.index) @@ -807,10 +808,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if itemNode.frame != item.frame { itemNode.frame = item.frame } + itemNode.updateLayout(item: self.items[item.index], size: item.frame.size, isVisible: bounds.intersects(item.frame), synchronousLoads: synchronousLoads) } else { let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, synchronousLoad: synchronousLoads) itemNode.frame = item.frame self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) + itemNode.updateLayout(item: self.items[item.index], size: item.frame.size, isVisible: bounds.intersects(item.frame), synchronousLoads: synchronousLoads) } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 42971a3df8..95874b094f 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1165,7 +1165,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel DispatchQueue.global().async(execute: f) } - private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, () -> Void)) -> Void) { + private func nodeForItem(synchronous: Bool, synchronousLoads: Bool, item: ListViewItem, previousNode: QueueLocalObject?, index: Int, previousItem: ListViewItem?, nextItem: ListViewItem?, params: ListViewItemLayoutParams, updateAnimation: ListViewItemUpdateAnimation, completion: @escaping (QueueLocalObject, ListViewItemNodeLayout, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) { if let previousNode = previousNode { item.updateNode(async: { f in if synchronous { @@ -1180,29 +1180,29 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if Thread.isMainThread { if synchronous { completion(previousNode, layout, { - return (nil, { + return (nil, { info in assert(Queue.mainQueue().isCurrent()) previousNode.with({ $0.index = index }) - apply() + apply(info) }) }) } else { self.async { completion(previousNode, layout, { - return (nil, { + return (nil, { info in assert(Queue.mainQueue().isCurrent()) previousNode.with({ $0.index = index }) - apply() + apply(info) }) }) } } } else { completion(previousNode, layout, { - return (nil, { + return (nil, { info in assert(Queue.mainQueue().isCurrent()) previousNode.with({ $0.index = index }) - apply() + apply(info) }) }) } @@ -1759,7 +1759,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void), timestamp: Double, listInsets: UIEdgeInsets) { + private func insertNodeAtIndex(animated: Bool, animateAlpha: Bool, forceAnimateInsertion: Bool, previousFrame: CGRect?, nodeIndex: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> (Signal?, (ListViewItemApply) -> Void), timestamp: Double, listInsets: UIEdgeInsets, visibleBounds: CGRect) { let insertionOrigin = self.referencePointForInsertionAtIndex(nodeIndex) let nodeOrigin: CGPoint @@ -1782,7 +1782,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let accessoryItemNode = node.accessoryItemNode { node.layoutAccessoryItemNode(accessoryItemNode, leftInset: listInsets.left, rightInset: listInsets.right) } - apply().1() + apply().1(ListViewItemApply(isOnScreen: visibleBounds.intersects(nodeFrame))) self.itemNodes.insert(node, at: nodeIndex) var offsetHeight = node.apparentHeight @@ -2025,6 +2025,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow() var hadInserts = false + let visibleBounds = CGRect(origin: CGPoint(), size: self.visibleSize) + for operation in operations { switch operation { case let .InsertNode(index, offsetDirection, nodeAnimated, nodeObject, layout, apply): @@ -2045,7 +2047,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel updatedPreviousFrame = nil } - self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets) + self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) hadInserts = true if let _ = updatedPreviousFrame { if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self { @@ -2093,10 +2095,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let height = height, let previousLayout = previousLayout { if takenPreviousNodes.contains(referenceNode) { let tempNode = ListViewTempItemNode(layerBacked: true) - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, { _ in }) }, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) } else { referenceNode.index = nil - self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets) + self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, { _ in }) }, timestamp: timestamp, listInsets: listInsets, visibleBounds: visibleBounds) if let verticalScrollIndicator = self.verticalScrollIndicator { self.insertSubnode(referenceNode, belowSubnode: verticalScrollIndicator) } else { @@ -2151,11 +2153,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel node.contentSize = layout.contentSize node.insets = layout.insets - apply().1() let updatedApparentHeight = node.bounds.size.height let updatedInsets = node.insets + var apparentFrame = node.apparentFrame + apparentFrame.size.height = updatedApparentHeight + + apply().1(ListViewItemApply(isOnScreen: visibleBounds.intersects(apparentFrame))) + var offsetRanges = OffsetRanges() if animated { diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 8e91b0c723..4fc0f49409 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -689,7 +689,7 @@ struct ListViewState { return height } - mutating func insertNode(_ itemIndex: Int, node: QueueLocalObject, layout: ListViewItemNodeLayout, apply: @escaping () -> (Signal?, () -> Void), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { + mutating func insertNode(_ itemIndex: Int, node: QueueLocalObject, layout: ListViewItemNodeLayout, apply: @escaping () -> (Signal?, (ListViewItemApply) -> Void), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) let nodeOrigin: CGPoint @@ -794,7 +794,7 @@ struct ListViewState { } } - mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> (Signal?, () -> Void), operations: inout [ListViewStateOperation]) { + mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> (Signal?, (ListViewItemApply) -> Void), operations: inout [ListViewStateOperation]) { var i = -1 for node in self.nodes { i += 1 @@ -850,9 +850,9 @@ struct ListViewState { } enum ListViewStateOperation { - case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, node: QueueLocalObject, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, node: QueueLocalObject, layout: ListViewItemNodeLayout, apply: () -> (Signal?, (ListViewItemApply) -> Void)) case InsertDisappearingPlaceholder(index: Int, referenceNode: QueueLocalObject, offsetDirection: ListViewInsertionOffsetDirection) case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) case Remap([Int: Int]) - case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> (Signal?, () -> Void)) + case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> (Signal?, (ListViewItemApply) -> Void)) } diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index 7072657ee6..bc8e15dc55 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -32,9 +32,17 @@ public struct ListViewItemConfigureNodeFlags: OptionSet { public static let preferSynchronousResourceLoading = ListViewItemConfigureNodeFlags(rawValue: 1 << 0) } +public struct ListViewItemApply { + public let isOnScreen: Bool + + public init(isOnScreen: Bool) { + self.isOnScreen = isOnScreen + } +} + public protocol ListViewItem { - func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, () -> Void)) -> Void) - func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping () -> Void) -> Void) + func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void) + func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void) var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } From 1644dd0b24b6315f677b294083f7cb80ee17fb61 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 3 Dec 2018 19:40:56 +0400 Subject: [PATCH 122/245] Updated grid node to support borderless layout with provided item spacing --- Display/GridNode.swift | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 921450bce7..07bd23389d 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -51,7 +51,7 @@ public struct GridNodeScrollToItem { } public enum GridNodeLayoutType: Equatable { - case fixed(itemSize: CGSize, lineSpacing: CGFloat) + case fixed(itemSize: CGSize, fillWidth: Bool?, lineSpacing: CGFloat, itemSpacing: CGFloat?) case balanced(idealHeight: CGFloat) } @@ -203,7 +203,7 @@ private struct WrappedGridItemNode: Hashable { } open class GridNode: GridNodeScroller, UIScrollViewDelegate { - private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), lineSpacing: 0.0)) + private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize(), fillWidth: nil, lineSpacing: 0.0, itemSpacing: nil)) private var firstIndexInSectionOffset: Int = 0 public private(set) var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] @@ -400,7 +400,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var sections: [GridNodePresentationSection] = [] switch gridLayout.type { - case let .fixed(defaultItemSize, lineSpacing): + case let .fixed(defaultItemSize, fillWidth, lineSpacing, defaultItemSpacing): let itemInsets = gridLayout.insets let effectiveWidth = gridLayout.size.width - itemInsets.left - itemInsets.right @@ -409,10 +409,11 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let itemsInRowWidth = CGFloat(itemsInRow) * defaultItemSize.width let remainingWidth = max(0.0, effectiveWidth - itemsInRowWidth) - let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) + let itemSpacing = defaultItemSpacing ?? floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) + let initialSpacing: CGFloat = (fillWidth ?? false) ? 0.0 : itemSpacing var incrementedCurrentRow = false - var nextItemOrigin = CGPoint(x: itemInsets.left + itemSpacing, y: 0.0) + var nextItemOrigin = CGPoint(x: itemInsets.left + initialSpacing, y: 0.0) var index = 0 var previousSection: GridSection? for item in self.items { @@ -454,6 +455,11 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let itemsInRow = max(1, Int(effectiveWidth) / Int(itemSize.width)) let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) + } else if let fillWidth = fillWidth, fillWidth { + let nextItemOriginX = nextItemOrigin.x + itemSize.width + itemSpacing + if nextItemOriginX + itemSize.width > gridLayout.size.width && remainingWidth > 0.0 { + itemSize.width += remainingWidth + } } if !incrementedCurrentRow { @@ -466,7 +472,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { nextItemOrigin.x += itemSize.width + itemSpacing if nextItemOrigin.x + itemSize.width > gridLayout.size.width { - nextItemOrigin.x = itemSpacing + itemInsets.left + nextItemOrigin.x = initialSpacing + itemInsets.left nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false } From 8299c988a781baa9e97533729b8882fbf38f2302 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 4 Dec 2018 21:55:28 +0400 Subject: [PATCH 123/245] Don't perform inline navigation bar transition if there is no Back button --- Display/NavigationTransitionCoordinator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index d13b498d3d..9b35cb2f79 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -56,7 +56,7 @@ class NavigationTransitionCoordinator { self.dimView.backgroundColor = UIColor.black self.shadowView = UIImageView(image: shadowImage) - if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.contentNode == nil, bottomNavigationBar.contentNode == nil { + if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.contentNode == nil, bottomNavigationBar.contentNode == nil, topNavigationBar.item?.leftBarButtonItem == nil { var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container) var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container) topFrame.origin.x = 0.0 From 74099618ef703e40f79c5264c6e8591d1c159f69 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 4 Dec 2018 22:23:45 +0400 Subject: [PATCH 124/245] Grid layout fix --- Display/GridNode.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 1d9d71c172..d4e63aa7c4 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -413,7 +413,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let initialSpacing: CGFloat = (fillWidth ?? false) ? 0.0 : itemSpacing var incrementedCurrentRow = false - var nextItemOrigin = CGPoint(x: itemInsets.left + initialSpacing, y: 0.0) + var nextItemOrigin = CGPoint(x: initialSpacing + itemInsets.left, y: 0.0) var index = 0 var previousSection: GridSection? for item in self.items { @@ -429,7 +429,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if !keepSection { if incrementedCurrentRow { - nextItemOrigin.x = itemSpacing + itemInsets.left + nextItemOrigin.x = initialSpacing + itemInsets.left nextItemOrigin.y += itemSize.height + lineSpacing incrementedCurrentRow = false } From d71707c31b1395bffe54699c02a238a9010f8d0c Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Tue, 11 Dec 2018 17:51:48 +0400 Subject: [PATCH 125/245] Added CollectionIndexNode --- Display.xcodeproj/project.pbxproj | 4 + Display/CollectionIndexNode.swift | 141 ++++++++++++++++++++ Display/ContainedViewLayoutTransition.swift | 16 +-- Display/GridNode.swift | 19 ++- Display/ImmediateTextNode.swift | 12 ++ Display/TextNode.swift | 12 +- 6 files changed, 191 insertions(+), 13 deletions(-) create mode 100644 Display/CollectionIndexNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 7a46bba81a..114bc5c179 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -76,6 +76,7 @@ D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DF71C96C5F200C07816 /* NSWeakReference.m */; }; D03E7DFF1C96F7B400C07816 /* StatusBarManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */; }; D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */; }; + D04554AA21BDB93E007A6DD9 /* CollectionIndexNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04554A921BDB93E007A6DD9 /* CollectionIndexNode.swift */; }; D04C468E1F4C97BE00D30FE1 /* PageControlNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */; }; D05174B31EAA833200A1BF36 /* CASeeThroughTracingLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */; settings = {ATTRIBUTES = (Public, ); }; }; D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */ = {isa = PBXBuildFile; fileRef = D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */; }; @@ -254,6 +255,7 @@ D03E7DF71C96C5F200C07816 /* NSWeakReference.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NSWeakReference.m; sourceTree = ""; }; D03E7DFE1C96F7B400C07816 /* StatusBarManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarManager.swift; sourceTree = ""; }; D03E7E001C974AB300C07816 /* DisplayLinkDispatcher.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayLinkDispatcher.swift; sourceTree = ""; }; + D04554A921BDB93E007A6DD9 /* CollectionIndexNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionIndexNode.swift; sourceTree = ""; }; D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PageControlNode.swift; sourceTree = ""; }; D05174B11EAA833200A1BF36 /* CASeeThroughTracingLayer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CASeeThroughTracingLayer.h; sourceTree = ""; }; D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CASeeThroughTracingLayer.m; sourceTree = ""; }; @@ -508,6 +510,7 @@ D0FA08C32048803C00DD23FC /* TextNode.swift */, D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */, D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */, + D04554A921BDB93E007A6DD9 /* CollectionIndexNode.swift */, ); name = Nodes; sourceTree = ""; @@ -1012,6 +1015,7 @@ D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, + D04554AA21BDB93E007A6DD9 /* CollectionIndexNode.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D05174B41EAA833200A1BF36 /* CASeeThroughTracingLayer.m in Sources */, D03AA4D9202D8E5E0056C405 /* GlobalOverlayPresentationContext.swift in Sources */, diff --git a/Display/CollectionIndexNode.swift b/Display/CollectionIndexNode.swift new file mode 100644 index 0000000000..d00d1abd6b --- /dev/null +++ b/Display/CollectionIndexNode.swift @@ -0,0 +1,141 @@ +import Foundation +import AsyncDisplayKit + +private let titleFont = Font.bold(11.0) + +public final class CollectionIndexNode: ASDisplayNode { + public static let searchIndex: String = "_$search$_" + + private var currentSize: CGSize? + private var currentSections: [String] = [] + private var currentColor: UIColor? + private var titleNodes: [String: (node: ImmediateTextNode, size: CGSize)] = [:] + + private var currentSelectedIndex: String? + public var indexSelected: ((String) -> Void)? + + override public init() { + super.init() + } + + override public func didLoad() { + super.didLoad() + + self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))) + } + + public func update(size: CGSize, color: UIColor, sections: [String], transition: ContainedViewLayoutTransition) { + if self.currentColor == nil || !color.isEqual(self.currentColor) { + self.currentColor = color + for (title, nodeAndSize) in self.titleNodes { + nodeAndSize.node.attributedText = NSAttributedString(string: title, font: titleFont, textColor: color) + let _ = nodeAndSize.node.updateLayout(CGSize(width: 100.0, height: 100.0)) + } + } + + if self.currentSize == size && self.currentSections == sections { + return + } + + self.currentSize = size + self.currentSections = sections + + let itemHeight: CGFloat = 15.0 + let verticalInset: CGFloat = 10.0 + let maxHeight = size.height - verticalInset * 2.0 + + let maxItemCount = min(sections.count, Int(floor(maxHeight / itemHeight))) + let skipCount = Int(ceil(CGFloat(sections.count) / CGFloat(maxItemCount))) + let actualCount: CGFloat = ceil(CGFloat(sections.count) / CGFloat(skipCount)) + + let totalHeight = actualCount * itemHeight + let verticalOrigin = verticalInset + floor((maxHeight - totalHeight) / 2.0) + + var validTitles = Set() + + var index = 0 + var displayIndex = 0 + while index < sections.count { + let title = sections[index] + let nodeAndSize: (node: ImmediateTextNode, size: CGSize) + var animate = false + if let current = self.titleNodes[title] { + animate = true + nodeAndSize = current + } else { + let node = ImmediateTextNode() + node.attributedText = NSAttributedString(string: title, font: titleFont, textColor: color) + let nodeSize = node.updateLayout(CGSize(width: 100.0, height: 100.0)) + nodeAndSize = (node, nodeSize) + self.addSubnode(node) + self.titleNodes[title] = nodeAndSize + } + validTitles.insert(title) + let previousPosition = nodeAndSize.node.position + nodeAndSize.node.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeAndSize.size.width) / 2.0), y: verticalOrigin + itemHeight * CGFloat(displayIndex) + floor((itemHeight - nodeAndSize.size.height) / 2.0)), size: nodeAndSize.size) + if animate { + transition.animatePosition(node: nodeAndSize.node, from: previousPosition) + } + + index += skipCount + displayIndex += 1 + } + + var removeTitles: [String] = [] + for title in self.titleNodes.keys { + if !validTitles.contains(title) { + removeTitles.append(title) + } + } + + for title in removeTitles { + self.titleNodes.removeValue(forKey: title)?.node.removeFromSupernode() + } + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if self.bounds.insetBy(dx: -5.0, dy: 0.0).contains(point) { + return self.view + } else { + return nil + } + } + + @objc private func panGesture(_ recognizer: UIPanGestureRecognizer) { + var locationTitleAndPosition: (String, CGFloat)? + let location = recognizer.location(in: self.view) + for (title, nodeAndSize) in self.titleNodes { + let nodeFrame = nodeAndSize.node.frame + if location.y >= nodeFrame.minY - 5.0 && location.y <= nodeFrame.maxY + 5.0 { + if let currentTitleAndPosition = locationTitleAndPosition { + let distance = abs(nodeFrame.midY - location.y) + let previousDistance = abs(currentTitleAndPosition.1 - location.y) + if distance < previousDistance { + locationTitleAndPosition = (title, nodeFrame.midY) + } + } else { + locationTitleAndPosition = (title, nodeFrame.midY) + } + } + } + let locationTitle = locationTitleAndPosition?.0 + switch recognizer.state { + case .began: + self.currentSelectedIndex = locationTitle + if let locationTitle = locationTitle { + self.indexSelected?(locationTitle) + } + case .changed: + if locationTitle != self.currentSelectedIndex { + self.currentSelectedIndex = locationTitle + if let locationTitle = locationTitle { + self.indexSelected?(locationTitle) + } + } + case .cancelled, .ended: + self.currentSelectedIndex = nil + default: + break + } + } +} diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index b277b8df4b..ab5301fb29 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -137,16 +137,16 @@ public extension ContainedViewLayoutTransition { func animatePosition(node: ASDisplayNode, from position: CGPoint, completion: ((Bool) -> Void)? = nil) { switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + case .immediate: if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index d4e63aa7c4..9da4dd68eb 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -227,6 +227,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + public var indicatorStyle: UIScrollViewIndicatorStyle = .default { + didSet { + self.scrollView.indicatorStyle = self.indicatorStyle + } + } + public private(set) var opaqueState: Any? public override init() { @@ -755,6 +761,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, synchronousLoads: Bool, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) { let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate + var addedNodes = false + let verticalIndicator = self.scrollView.subviews.last as? UIImageView + var previousItemFrames: [WrappedGridItemNode: CGRect]? var saveItemFrames = false switch presentationLayoutTransition.transition { @@ -782,7 +791,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { previousItemFrames = itemFrames } - applyingContentOffset = true + self.applyingContentOffset = true let previousBounds = self.bounds self.scrollView.contentSize = presentationLayoutTransition.layout.contentSize @@ -819,6 +828,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, synchronousLoad: synchronousLoads) itemNode.frame = item.frame self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) + addedNodes = true itemNode.updateLayout(item: self.items[item.index], size: item.frame.size, isVisible: bounds.intersects(item.frame), synchronousLoads: synchronousLoads) } } @@ -845,6 +855,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let sectionNode = section.section.node() sectionNode.frame = sectionFrame self.addSectionNode(section: wrappedSection, sectionNode: sectionNode) + addedNodes = true } } @@ -1100,6 +1111,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + if addedNodes { + if let verticalIndicator = verticalIndicator, self.scrollView.subviews.last !== verticalIndicator { + verticalIndicator.superview?.bringSubview(toFront: verticalIndicator) + } + } + if let presentationLayoutUpdated = self.presentationLayoutUpdated { presentationLayoutUpdated(GridNodeCurrentPresentationLayout(layout: presentationLayoutTransition.layout.layout, contentOffset: presentationLayoutTransition.layout.contentOffset, contentSize: presentationLayoutTransition.layout.contentSize), updateLayoutTransition ?? presentationLayoutTransition.transition) } diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index 7b21b1e79a..65094245e4 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -1,5 +1,10 @@ import Foundation +public struct ImmediateTextNodeLayoutInfo { + public let size: CGSize + public let truncated: Bool +} + public class ImmediateTextNode: TextNode { public var attributedText: NSAttributedString? public var textAlignment: NSTextAlignment = .natural @@ -31,6 +36,13 @@ public class ImmediateTextNode: TextNode { return layout.size } + public func updateLayoutInfo(_ constrainedSize: CGSize) -> ImmediateTextNodeLayoutInfo { + let makeLayout = TextNode.asyncLayout(self) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) + let _ = apply() + return ImmediateTextNodeLayoutInfo(size: layout.size, truncated: layout.truncated) + } + override public func didLoad() { super.didLoad() diff --git a/Display/TextNode.swift b/Display/TextNode.swift index e55480d261..96a34bbae9 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -97,11 +97,12 @@ public final class TextNodeLayout: NSObject { fileprivate let cutout: TextNodeCutout? fileprivate let insets: UIEdgeInsets public let size: CGSize + public let truncated: Bool fileprivate let firstLineOffset: CGFloat fileprivate let lines: [TextNodeLine] public let hasRTL: Bool - fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, firstLineOffset: CGFloat, lines: [TextNodeLine], backgroundColor: UIColor?) { + fileprivate init(attributedString: NSAttributedString?, maximumNumberOfLines: Int, truncationType: CTLineTruncationType, constrainedSize: CGSize, alignment: NSTextAlignment, lineSpacing: CGFloat, cutout: TextNodeCutout?, insets: UIEdgeInsets, size: CGSize, truncated: Bool, firstLineOffset: CGFloat, lines: [TextNodeLine], backgroundColor: UIColor?) { self.attributedString = attributedString self.maximumNumberOfLines = maximumNumberOfLines self.truncationType = truncationType @@ -111,6 +112,7 @@ public final class TextNodeLayout: NSObject { self.cutout = cutout self.insets = insets self.size = size + self.truncated = truncated self.firstLineOffset = firstLineOffset self.lines = lines self.backgroundColor = backgroundColor @@ -327,7 +329,7 @@ public class TextNode: ASDisplayNode { var maybeTypesetter: CTTypesetter? maybeTypesetter = CTTypesetterCreateWithAttributedString(attributedString as CFAttributedString) if maybeTypesetter == nil { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) } let typesetter = maybeTypesetter! @@ -364,6 +366,7 @@ public class TextNode: ASDisplayNode { let firstLineOffset = floorToScreenPixels(fontDescent) + var truncated = false var first = true while true { var lineConstrainedWidth = constrainedSize.width @@ -419,6 +422,7 @@ public class TextNode: ASDisplayNode { let truncationToken = CTLineCreateWithAttributedString(truncatedTokenString) coreTextLine = CTLineCreateTruncatedLine(originalLine, Double(constrainedSize.width), truncationType, truncationToken) ?? truncationToken + truncated = true } let lineWidth = min(constrainedSize.width, ceil(CGFloat(CTLineGetTypographicBounds(coreTextLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(coreTextLine)))) @@ -485,9 +489,9 @@ public class TextNode: ASDisplayNode { } } - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(width: ceil(layoutSize.width) + insets.left + insets.right, height: ceil(layoutSize.height) + insets.top + insets.bottom), truncated: truncated, firstLineOffset: firstLineOffset, lines: lines, backgroundColor: backgroundColor) } else { - return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) + return TextNodeLayout(attributedString: attributedString, maximumNumberOfLines: maximumNumberOfLines, truncationType: truncationType, constrainedSize: constrainedSize, alignment: alignment, lineSpacing: lineSpacingFactor, cutout: cutout, insets: insets, size: CGSize(), truncated: false, firstLineOffset: 0.0, lines: [], backgroundColor: backgroundColor) } } From 95fd1ed77b14e10441596929b65557884bdcf9c3 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 11 Dec 2018 21:54:07 +0400 Subject: [PATCH 126/245] Update project --- Display.xcodeproj/project.pbxproj | 200 +++++++++++++++--------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 114bc5c179..c9ccb5d57c 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -1144,7 +1144,7 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - D01159BC1F40E96C0039383E /* Debug Hockeyapp */ = { + D01159BC1F40E96C0039383E /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1208,9 +1208,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D01159BD1F40E96C0039383E /* Debug AppStore */ = { + D01159BD1F40E96C0039383E /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1274,9 +1274,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D01159BE1F40E96C0039383E /* Release Hockeyapp */ = { + D01159BE1F40E96C0039383E /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1332,9 +1332,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D01159BF1F40E96C0039383E /* Release AppStore */ = { + D01159BF1F40E96C0039383E /* ReleaseAppStore */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1390,9 +1390,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D021D4F4219CB1AD0064BEBA /* Debug Fork */ = { + D021D4F4219CB1AD0064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1439,9 +1439,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug Fork"; + name = DebugFork; }; - D021D4F5219CB1AD0064BEBA /* Debug Fork */ = { + D021D4F5219CB1AD0064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1466,9 +1466,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Debug Fork"; + name = DebugFork; }; - D021D4F6219CB1AD0064BEBA /* Debug Fork */ = { + D021D4F6219CB1AD0064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1477,9 +1477,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = "Debug Fork"; + name = DebugFork; }; - D021D4F7219CB1AD0064BEBA /* Debug Fork */ = { + D021D4F7219CB1AD0064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1543,9 +1543,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug Fork"; + name = DebugFork; }; - D05CC2751B69316F00E235A3 /* Debug Hockeyapp */ = { + D05CC2751B69316F00E235A3 /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1592,9 +1592,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D05CC2761B69316F00E235A3 /* Release Hockeyapp */ = { + D05CC2761B69316F00E235A3 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1634,9 +1634,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D05CC2781B69316F00E235A3 /* Debug Hockeyapp */ = { + D05CC2781B69316F00E235A3 /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1661,9 +1661,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D05CC2791B69316F00E235A3 /* Release Hockeyapp */ = { + D05CC2791B69316F00E235A3 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1688,9 +1688,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */ = { + D05CC27B1B69316F00E235A3 /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1699,9 +1699,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = "Debug Hockeyapp"; + name = DebugHockeyapp; }; - D05CC27C1B69316F00E235A3 /* Release Hockeyapp */ = { + D05CC27C1B69316F00E235A3 /* ReleaseHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1711,9 +1711,9 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 3.0; }; - name = "Release Hockeyapp"; + name = ReleaseHockeyapp; }; - D079FD091F06BD9C0038FADE /* Debug AppStore */ = { + D079FD091F06BD9C0038FADE /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1760,9 +1760,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D079FD0A1F06BD9C0038FADE /* Debug AppStore */ = { + D079FD0A1F06BD9C0038FADE /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1787,9 +1787,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D079FD0B1F06BD9C0038FADE /* Debug AppStore */ = { + D079FD0B1F06BD9C0038FADE /* DebugAppStore */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1798,9 +1798,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = "Debug AppStore"; + name = DebugAppStore; }; - D086A56E1CC0115D00F08284 /* Release AppStore */ = { + D086A56E1CC0115D00F08284 /* ReleaseAppStore */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1840,9 +1840,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D086A56F1CC0115D00F08284 /* Release AppStore */ = { + D086A56F1CC0115D00F08284 /* ReleaseAppStore */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1867,9 +1867,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D086A5701CC0115D00F08284 /* Release AppStore */ = { + D086A5701CC0115D00F08284 /* ReleaseAppStore */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1878,9 +1878,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = "Release AppStore"; + name = ReleaseAppStore; }; - D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */ = { + D0924FD41FE52BE9003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -1920,9 +1920,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */ = { + D0924FD51FE52BE9003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -1947,9 +1947,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */ = { + D0924FD61FE52BE9003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -1959,9 +1959,9 @@ SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_VERSION = 3.0; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */ = { + D0924FD71FE52BE9003F693F /* ReleaseHockeyappInternal */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2017,9 +2017,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release Hockeyapp Internal"; + name = ReleaseHockeyappInternal; }; - D0ADF920212B3ABC00310BBC /* Debug AppStore LLC */ = { + D0ADF920212B3ABC00310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2066,9 +2066,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0ADF921212B3ABC00310BBC /* Debug AppStore LLC */ = { + D0ADF921212B3ABC00310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -2093,9 +2093,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0ADF922212B3ABC00310BBC /* Debug AppStore LLC */ = { + D0ADF922212B3ABC00310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -2104,9 +2104,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0ADF923212B3ABC00310BBC /* Debug AppStore LLC */ = { + D0ADF923212B3ABC00310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2170,9 +2170,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Debug AppStore LLC"; + name = DebugAppStoreLLC; }; - D0CE6EE1213DB54B00BCD44B /* Release AppStore LLC */ = { + D0CE6EE1213DB54B00BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2212,9 +2212,9 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; - D0CE6EE2213DB54B00BCD44B /* Release AppStore LLC */ = { + D0CE6EE2213DB54B00BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; @@ -2239,9 +2239,9 @@ SWIFT_REFLECTION_METADATA_LEVEL = none; SWIFT_VERSION = 4.0; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; - D0CE6EE3213DB54B00BCD44B /* Release AppStore LLC */ = { + D0CE6EE3213DB54B00BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { INFOPLIST_FILE = DisplayTests/Info.plist; @@ -2250,9 +2250,9 @@ PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_VERSION = 3.0; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; - D0CE6EE4213DB54B00BCD44B /* Release AppStore LLC */ = { + D0CE6EE4213DB54B00BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; @@ -2308,7 +2308,7 @@ VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; - name = "Release AppStore LLC"; + name = ReleaseAppStoreLLC; }; /* End XCBuildConfiguration section */ @@ -2316,62 +2316,62 @@ D01159C01F40E96C0039383E /* Build configuration list for PBXNativeTarget "DisplayMac" */ = { isa = XCConfigurationList; buildConfigurations = ( - D01159BC1F40E96C0039383E /* Debug Hockeyapp */, - D021D4F7219CB1AD0064BEBA /* Debug Fork */, - D01159BD1F40E96C0039383E /* Debug AppStore */, - D0ADF923212B3ABC00310BBC /* Debug AppStore LLC */, - D01159BE1F40E96C0039383E /* Release Hockeyapp */, - D0924FD71FE52BE9003F693F /* Release Hockeyapp Internal */, - D01159BF1F40E96C0039383E /* Release AppStore */, - D0CE6EE4213DB54B00BCD44B /* Release AppStore LLC */, + D01159BC1F40E96C0039383E /* DebugHockeyapp */, + D021D4F7219CB1AD0064BEBA /* DebugFork */, + D01159BD1F40E96C0039383E /* DebugAppStore */, + D0ADF923212B3ABC00310BBC /* DebugAppStoreLLC */, + D01159BE1F40E96C0039383E /* ReleaseHockeyapp */, + D0924FD71FE52BE9003F693F /* ReleaseHockeyappInternal */, + D01159BF1F40E96C0039383E /* ReleaseAppStore */, + D0CE6EE4213DB54B00BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { isa = XCConfigurationList; buildConfigurations = ( - D05CC2751B69316F00E235A3 /* Debug Hockeyapp */, - D021D4F4219CB1AD0064BEBA /* Debug Fork */, - D079FD091F06BD9C0038FADE /* Debug AppStore */, - D0ADF920212B3ABC00310BBC /* Debug AppStore LLC */, - D05CC2761B69316F00E235A3 /* Release Hockeyapp */, - D0924FD41FE52BE9003F693F /* Release Hockeyapp Internal */, - D086A56E1CC0115D00F08284 /* Release AppStore */, - D0CE6EE1213DB54B00BCD44B /* Release AppStore LLC */, + D05CC2751B69316F00E235A3 /* DebugHockeyapp */, + D021D4F4219CB1AD0064BEBA /* DebugFork */, + D079FD091F06BD9C0038FADE /* DebugAppStore */, + D0ADF920212B3ABC00310BBC /* DebugAppStoreLLC */, + D05CC2761B69316F00E235A3 /* ReleaseHockeyapp */, + D0924FD41FE52BE9003F693F /* ReleaseHockeyappInternal */, + D086A56E1CC0115D00F08284 /* ReleaseAppStore */, + D0CE6EE1213DB54B00BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; D05CC2771B69316F00E235A3 /* Build configuration list for PBXNativeTarget "Display" */ = { isa = XCConfigurationList; buildConfigurations = ( - D05CC2781B69316F00E235A3 /* Debug Hockeyapp */, - D021D4F5219CB1AD0064BEBA /* Debug Fork */, - D079FD0A1F06BD9C0038FADE /* Debug AppStore */, - D0ADF921212B3ABC00310BBC /* Debug AppStore LLC */, - D05CC2791B69316F00E235A3 /* Release Hockeyapp */, - D0924FD51FE52BE9003F693F /* Release Hockeyapp Internal */, - D086A56F1CC0115D00F08284 /* Release AppStore */, - D0CE6EE2213DB54B00BCD44B /* Release AppStore LLC */, + D05CC2781B69316F00E235A3 /* DebugHockeyapp */, + D021D4F5219CB1AD0064BEBA /* DebugFork */, + D079FD0A1F06BD9C0038FADE /* DebugAppStore */, + D0ADF921212B3ABC00310BBC /* DebugAppStoreLLC */, + D05CC2791B69316F00E235A3 /* ReleaseHockeyapp */, + D0924FD51FE52BE9003F693F /* ReleaseHockeyappInternal */, + D086A56F1CC0115D00F08284 /* ReleaseAppStore */, + D0CE6EE2213DB54B00BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; D05CC27A1B69316F00E235A3 /* Build configuration list for PBXNativeTarget "DisplayTests" */ = { isa = XCConfigurationList; buildConfigurations = ( - D05CC27B1B69316F00E235A3 /* Debug Hockeyapp */, - D021D4F6219CB1AD0064BEBA /* Debug Fork */, - D079FD0B1F06BD9C0038FADE /* Debug AppStore */, - D0ADF922212B3ABC00310BBC /* Debug AppStore LLC */, - D05CC27C1B69316F00E235A3 /* Release Hockeyapp */, - D0924FD61FE52BE9003F693F /* Release Hockeyapp Internal */, - D086A5701CC0115D00F08284 /* Release AppStore */, - D0CE6EE3213DB54B00BCD44B /* Release AppStore LLC */, + D05CC27B1B69316F00E235A3 /* DebugHockeyapp */, + D021D4F6219CB1AD0064BEBA /* DebugFork */, + D079FD0B1F06BD9C0038FADE /* DebugAppStore */, + D0ADF922212B3ABC00310BBC /* DebugAppStoreLLC */, + D05CC27C1B69316F00E235A3 /* ReleaseHockeyapp */, + D0924FD61FE52BE9003F693F /* ReleaseHockeyappInternal */, + D086A5701CC0115D00F08284 /* ReleaseAppStore */, + D0CE6EE3213DB54B00BCD44B /* ReleaseAppStoreLLC */, ); defaultConfigurationIsVisible = 0; - defaultConfigurationName = "Release Hockeyapp"; + defaultConfigurationName = ReleaseHockeyapp; }; /* End XCConfigurationList section */ }; From 20ceeb7071234b4171d011fe8e7a14613281762a Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Dec 2018 23:29:30 +0400 Subject: [PATCH 127/245] Added variable theme support for action sheet and alert controller --- Display.xcodeproj/project.pbxproj | 4 ++ Display/ActionSheetController.swift | 8 ++- Display/ActionSheetControllerNode.swift | 20 ++++-- .../ActionSheetItemGroupsContainerNode.swift | 7 +- Display/ActionSheetTheme.swift | 36 +++++++++- Display/AlertContentNode.swift | 6 ++ Display/AlertController.swift | 35 +++++++++- Display/AlertControllerNode.swift | 15 ++++ Display/EditableTextNode.swift | 20 ++++++ Display/PeekControllerContent.swift | 2 + Display/PeekControllerNode.swift | 36 +++++++++- Display/TabBarNode.swift | 2 +- Display/TextAlertController.swift | 70 ++++++++++++++----- Display/TextFieldNode.swift | 16 +++++ Display/ViewControllerPreviewing.swift | 4 ++ 15 files changed, 251 insertions(+), 30 deletions(-) create mode 100644 Display/EditableTextNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 7a46bba81a..8afed5212d 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -9,6 +9,7 @@ /* Begin PBXBuildFile section */ 09C147D8216CCEF700390252 /* KeyShortcutsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */; }; 09C147DA216CD7E500390252 /* KeyShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C147D9216CD7E500390252 /* KeyShortcut.swift */; }; + 09DD88EB21BCA5E0000766BC /* EditableTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88EA21BCA5E0000766BC /* EditableTextNode.swift */; }; 09E12476214D0978009FC9C3 /* DeviceMetrics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */; }; D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701972029CAD6006B9E34 /* TooltipController.swift */; }; D007019A2029CAE2006B9E34 /* TooltipControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */; }; @@ -198,6 +199,7 @@ /* Begin PBXFileReference section */ 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyShortcutsController.swift; sourceTree = ""; }; 09C147D9216CD7E500390252 /* KeyShortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyShortcut.swift; sourceTree = ""; }; + 09DD88EA21BCA5E0000766BC /* EditableTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextNode.swift; sourceTree = ""; }; 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceMetrics.swift; sourceTree = ""; }; D00701972029CAD6006B9E34 /* TooltipController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipController.swift; sourceTree = ""; }; D00701992029CAE2006B9E34 /* TooltipControllerNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TooltipControllerNode.swift; sourceTree = ""; }; @@ -508,6 +510,7 @@ D0FA08C32048803C00DD23FC /* TextNode.swift */, D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */, D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */, + 09DD88EA21BCA5E0000766BC /* EditableTextNode.swift */, ); name = Nodes; sourceTree = ""; @@ -1109,6 +1112,7 @@ D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, + 09DD88EB21BCA5E0000766BC /* EditableTextNode.swift in Sources */, D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 3c5177032a..6acc6fdcfe 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -5,7 +5,13 @@ open class ActionSheetController: ViewController { return self.displayNode as! ActionSheetControllerNode } - private let theme: ActionSheetControllerTheme + public var theme: ActionSheetControllerTheme { + didSet { + if oldValue != self.theme { + self.actionSheetNode.theme = self.theme + } + } + } private var groups: [ActionSheetItemGroup] = [] diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index ecb906f891..ec2f67c6a5 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -10,7 +10,12 @@ private class ActionSheetControllerNodeScrollView: UIScrollView { } final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { - private let theme: ActionSheetControllerTheme + var theme: ActionSheetControllerTheme { + didSet { + self.itemGroupsContainerNode.theme = self.theme + self.updateTheme() + } + } private let dismissTapView: UIView @@ -42,19 +47,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.dismissTapView = UIView() self.leftDimView = UIView() - self.leftDimView.backgroundColor = self.theme.dimColor self.leftDimView.isUserInteractionEnabled = false self.rightDimView = UIView() - self.rightDimView.backgroundColor = self.theme.dimColor self.rightDimView.isUserInteractionEnabled = false self.topDimView = UIView() - self.topDimView.backgroundColor = self.theme.dimColor self.topDimView.isUserInteractionEnabled = false self.bottomDimView = UIView() - self.bottomDimView.backgroundColor = self.theme.dimColor self.bottomDimView.isUserInteractionEnabled = false self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode(theme: self.theme) @@ -75,6 +76,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.dismissTapView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:)))) self.scrollView.addSubnode(self.itemGroupsContainerNode) + + self.updateTheme() + } + + func updateTheme() { + self.leftDimView.backgroundColor = self.theme.dimColor + self.rightDimView.backgroundColor = self.theme.dimColor + self.topDimView.backgroundColor = self.theme.dimColor + self.bottomDimView.backgroundColor = self.theme.dimColor } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { diff --git a/Display/ActionSheetItemGroupsContainerNode.swift b/Display/ActionSheetItemGroupsContainerNode.swift index 080024b0f9..0e3b9a5d08 100644 --- a/Display/ActionSheetItemGroupsContainerNode.swift +++ b/Display/ActionSheetItemGroupsContainerNode.swift @@ -4,7 +4,12 @@ import AsyncDisplayKit private let groupSpacing: CGFloat = 8.0 final class ActionSheetItemGroupsContainerNode: ASDisplayNode { - private let theme: ActionSheetControllerTheme + var theme: ActionSheetControllerTheme { + didSet { + self.setGroups(self.groups) + self.setNeedsLayout() + } + } private var groups: [ActionSheetItemGroup] = [] private var groupNodes: [ActionSheetItemGroupNode] = [] diff --git a/Display/ActionSheetTheme.swift b/Display/ActionSheetTheme.swift index 934485d135..932de4569e 100644 --- a/Display/ActionSheetTheme.swift +++ b/Display/ActionSheetTheme.swift @@ -6,7 +6,7 @@ public enum ActionSheetControllerThemeBackgroundType { case dark } -public final class ActionSheetControllerTheme { +public final class ActionSheetControllerTheme: Equatable { public let dimColor: UIColor public let backgroundType: ActionSheetControllerThemeBackgroundType public let itemBackgroundColor: UIColor @@ -30,4 +30,38 @@ public final class ActionSheetControllerTheme { self.secondaryTextColor = secondaryTextColor self.controlAccentColor = controlAccentColor } + + public static func ==(lhs: ActionSheetControllerTheme, rhs: ActionSheetControllerTheme) -> Bool { + if lhs.dimColor != rhs.dimColor { + return false + } + if lhs.backgroundType != rhs.backgroundType { + return false + } + if lhs.itemBackgroundColor != rhs.itemBackgroundColor { + return false + } + if lhs.itemHighlightedBackgroundColor != rhs.itemHighlightedBackgroundColor { + return false + } + if lhs.standardActionTextColor != rhs.standardActionTextColor { + return false + } + if lhs.destructiveActionTextColor != rhs.destructiveActionTextColor { + return false + } + if lhs.disabledActionTextColor != rhs.disabledActionTextColor { + return false + } + if lhs.primaryTextColor != rhs.primaryTextColor { + return false + } + if lhs.secondaryTextColor != rhs.secondaryTextColor { + return false + } + if lhs.controlAccentColor != rhs.controlAccentColor { + return false + } + return true + } } diff --git a/Display/AlertContentNode.swift b/Display/AlertContentNode.swift index 1aa5e4f686..72a52412c2 100644 --- a/Display/AlertContentNode.swift +++ b/Display/AlertContentNode.swift @@ -2,6 +2,8 @@ import Foundation import AsyncDisplayKit open class AlertContentNode: ASDisplayNode { + open var requestLayout: ((ContainedViewLayoutTransition) -> Void)? + open var dismissOnOutsideTap: Bool { return true } @@ -11,4 +13,8 @@ open class AlertContentNode: ASDisplayNode { return CGSize() } + + open func updateTheme(_ theme: AlertControllerTheme) { + + } } diff --git a/Display/AlertController.swift b/Display/AlertController.swift index aa33dfcd87..f829250ba5 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -1,7 +1,7 @@ import Foundation import AsyncDisplayKit -public final class AlertControllerTheme { +public final class AlertControllerTheme: Equatable { public let backgroundColor: UIColor public let separatorColor: UIColor public let highlightedItemColor: UIColor @@ -19,6 +19,31 @@ public final class AlertControllerTheme { self.accentColor = accentColor self.destructiveColor = destructiveColor } + + public static func ==(lhs: AlertControllerTheme, rhs: AlertControllerTheme) -> Bool { + if lhs.backgroundColor != rhs.backgroundColor { + return false + } + if lhs.separatorColor != rhs.separatorColor { + return false + } + if lhs.highlightedItemColor != rhs.highlightedItemColor { + return false + } + if lhs.primaryColor != rhs.primaryColor { + return false + } + if lhs.secondaryColor != rhs.secondaryColor { + return false + } + if lhs.accentColor != rhs.accentColor { + return false + } + if lhs.destructiveColor != rhs.destructiveColor { + return false + } + return true + } } open class AlertController: ViewController { @@ -26,7 +51,13 @@ open class AlertController: ViewController { return self.displayNode as! AlertControllerNode } - private let theme: AlertControllerTheme + public var theme: AlertControllerTheme { + didSet { + if oldValue != self.theme { + self.controllerNode.updateTheme(self.theme) + } + } + } private let contentNode: AlertContentNode private let allowInputInset: Bool diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 387c815d0c..72bf2308c1 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -8,6 +8,8 @@ final class AlertControllerNode: ASDisplayNode { private let contentNode: AlertContentNode private let allowInputInset: Bool + private var containerLayout: ContainerViewLayout? + var dismiss: (() -> Void)? init(contentNode: AlertContentNode, theme: AlertControllerTheme, allowInputInset: Bool) { @@ -35,6 +37,12 @@ final class AlertControllerNode: ASDisplayNode { self.containerNode.addSubnode(self.contentNode) self.addSubnode(self.containerNode) + + self.contentNode.requestLayout = { [weak self] transition in + if let strongSelf = self, let containerLayout = self?.containerLayout { + strongSelf.containerLayoutUpdated(containerLayout, transition: transition) + } + } } override func didLoad() { @@ -43,6 +51,11 @@ final class AlertControllerNode: ASDisplayNode { self.dimmingNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) } + func updateTheme(_ theme: AlertControllerTheme) { + self.containerNode.backgroundColor = theme.backgroundColor + self.contentNode.updateTheme(theme) + } + func animateIn() { self.dimmingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) @@ -58,6 +71,8 @@ final class AlertControllerNode: ASDisplayNode { } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + self.containerLayout = layout + transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) var insetOptions: ContainerViewLayoutInsetOptions = [.statusBar] diff --git a/Display/EditableTextNode.swift b/Display/EditableTextNode.swift new file mode 100644 index 0000000000..947b4fb817 --- /dev/null +++ b/Display/EditableTextNode.swift @@ -0,0 +1,20 @@ +import Foundation +import AsyncDisplayKit + +public class EditableTextNode : ASEditableTextNode { + override public var keyboardAppearance: UIKeyboardAppearance { + get { + return super.keyboardAppearance + } + set { + let resigning = self.isFirstResponder() + if resigning { + self.resignFirstResponder() + } + super.keyboardAppearance = newValue + if resigning { + self.becomeFirstResponder() + } + } + } +} diff --git a/Display/PeekControllerContent.swift b/Display/PeekControllerContent.swift index 68a6d546eb..16054793cd 100644 --- a/Display/PeekControllerContent.swift +++ b/Display/PeekControllerContent.swift @@ -17,6 +17,8 @@ public protocol PeekControllerContent { func menuItems() -> [PeekControllerMenuItem] func node() -> PeekControllerContentNode & ASDisplayNode + func topAccessoryNode() -> ASDisplayNode? + func isEqual(to: PeekControllerContent) -> Bool } diff --git a/Display/PeekControllerNode.swift b/Display/PeekControllerNode.swift index 2c6aff61a1..f7c9190cc6 100644 --- a/Display/PeekControllerNode.swift +++ b/Display/PeekControllerNode.swift @@ -19,6 +19,8 @@ final class PeekControllerNode: ViewControllerTracingNode { private var contentNode: PeekControllerContentNode & ASDisplayNode private var contentNodeHasValidLayout = false + private var topAccessoryNode: ASDisplayNode? + private var menuNode: PeekControllerMenuNode? private var displayingMenu = false @@ -50,6 +52,7 @@ final class PeekControllerNode: ViewControllerTracingNode { self.content = content self.contentNode = content.node() + self.topAccessoryNode = content.topAccessoryNode() var activatedActionImpl: (() -> Void)? let menuItems = content.menuItems() @@ -75,6 +78,10 @@ final class PeekControllerNode: ViewControllerTracingNode { self.containerNode.addSubnode(self.contentNode) self.addSubnode(self.containerNode) + if let topAccessoryNode = self.topAccessoryNode { + self.addSubnode(topAccessoryNode) + } + if let menuNode = self.menuNode { self.addSubnode(menuNode) } @@ -158,6 +165,13 @@ final class PeekControllerNode: ViewControllerTracingNode { transition.updateFrame(node: self.containerNode, frame: containerFrame) + if let topAccessoryNode = self.topAccessoryNode { + let accessorySize = topAccessoryNode.frame.size + let accessoryFrame = CGRect(origin: CGPoint(x: floorToScreenPixels(containerFrame.midX - accessorySize.width / 2.0), y: containerFrame.minY - accessorySize.height - 16.0), size: accessorySize) + transition.updateFrame(node: topAccessoryNode, frame: accessoryFrame) + transition.updateAlpha(node: topAccessoryNode, alpha: self.displayingMenu ? 0.0 : 1.0) + } + if let menuNode = self.menuNode, let menuSize = menuSize { let menuY: CGFloat if self.displayingMenu { @@ -182,10 +196,17 @@ final class PeekControllerNode: ViewControllerTracingNode { self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3) - self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) + let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y) + self.containerNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + if let topAccessoryNode = self.topAccessoryNode { + topAccessoryNode.layer.animateSpring(from: NSValue(cgPoint: offset), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true) + topAccessoryNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0) + topAccessoryNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15) + } + if case .press = self.content.menuActivation() { self.hapticFeedback?.tap() } else { @@ -196,11 +217,22 @@ final class PeekControllerNode: ViewControllerTracingNode { func animateOut(to rect: CGRect, completion: @escaping () -> Void) { self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.blurView.layer.animateAlpha(from: self.blurView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false) - self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, additive: true, force: true, completion: { _ in + + let offset = CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y) + self.containerNode.layer.animatePosition(from: CGPoint(), to: offset, duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, additive: true, force: true, completion: { _ in completion() }) self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false) self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false) + + if let topAccessoryNode = self.topAccessoryNode { + topAccessoryNode.layer.animatePosition(from: CGPoint(), to: offset, duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, additive: true, force: true, completion: { _ in + completion() + }) + topAccessoryNode.layer.animateAlpha(from: topAccessoryNode.alpha, to: 0.0, duration: 0.2, removeOnCompletion: false) + topAccessoryNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false) + } + if let menuNode = self.menuNode { menuNode.layer.animatePosition(from: menuNode.position, to: CGPoint(x: menuNode.position.x, y: self.bounds.size.height + menuNode.bounds.size.height / 2.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false) } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index dfd72fd8fe..9ac68cc438 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -364,7 +364,7 @@ class TabBarNode: ASDisplayNode { let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame: CGRect if horizontal { - backgroundFrame = CGRect(origin: CGPoint(x: originX, y: 2.0), size: backgroundSize) + backgroundFrame = CGRect(origin: CGPoint(x: originX + 8.0, y: 2.0), size: backgroundSize) } else { let contentWidth = node.contentWidth ?? node.frame.width backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 1.0 + contentWidth - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize) diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index f3d223c4fc..bcf000fd46 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -27,7 +27,6 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { init(theme: AlertControllerTheme, action: TextAlertAction) { self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true - self.backgroundNode.backgroundColor = theme.highlightedItemColor self.backgroundNode.alpha = 0.0 self.action = action @@ -35,21 +34,6 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { super.init() self.titleNode.maximumNumberOfLines = 2 - var font = Font.regular(17.0) - var color = theme.accentColor - switch action.type { - case .defaultAction, .genericAction: - break - case .destructiveAction: - color = theme.destructiveColor - } - switch action.type { - case .defaultAction: - font = Font.semibold(17.0) - case .destructiveAction, .genericAction: - break - } - self.setAttributedTitle(NSAttributedString(string: action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) self.highligthedChanged = { [weak self] value in if let strongSelf = self { @@ -65,6 +49,28 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { } } } + + self.updateTheme(theme) + } + + func updateTheme(_ theme: AlertControllerTheme) { + self.backgroundNode.backgroundColor = theme.highlightedItemColor + + var font = Font.regular(17.0) + var color = theme.accentColor + switch self.action.type { + case .defaultAction, .genericAction: + break + case .destructiveAction: + color = theme.destructiveColor + } + switch self.action.type { + case .defaultAction: + font = Font.semibold(17.0) + case .destructiveAction, .genericAction: + break + } + self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) } override func didLoad() { @@ -90,7 +96,7 @@ public enum TextAlertContentActionLayout { } public final class TextAlertContentNode: AlertContentNode { - private let theme: AlertControllerTheme + private var theme: AlertControllerTheme private let actionLayout: TextAlertContentActionLayout private let titleNode: ASTextNode? @@ -100,6 +106,8 @@ public final class TextAlertContentNode: AlertContentNode { private let actionNodes: [TextAlertContentActionNode] private let actionVerticalSeparators: [ASDisplayNode] + private var validLayout: CGSize? + public var textAttributeAction: (NSAttributedStringKey, (Any) -> Void)? { didSet { if let (attribute, textAttributeAction) = self.textAttributeAction { @@ -186,7 +194,35 @@ public final class TextAlertContentNode: AlertContentNode { } } + override public func updateTheme(_ theme: AlertControllerTheme) { + self.theme = theme + + let textFont: UIFont + if let titleNode = self.titleNode { + titleNode.attributedText = NSAttributedString(string: titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) + textFont = Font.regular(13.0) + } else { + textFont = Font.semibold(17.0) + } + + self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: theme.primaryColor, paragraphAlignment: .center) + + self.actionNodesSeparator.backgroundColor = theme.separatorColor + for actionNode in self.actionNodes { + actionNode.updateTheme(theme) + } + for separatorNode in self.actionVerticalSeparators { + separatorNode.backgroundColor = theme.separatorColor + } + + if let size = self.validLayout { + _ = self.updateLayout(size: size, transition: .immediate) + } + } + override public func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize { + self.validLayout = size + let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) var titleSize: CGSize? diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index 97eceaa129..6a6c7dfcb3 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -24,6 +24,22 @@ public final class TextFieldNodeView: UITextField { } super.deleteBackward() } + + override public var keyboardAppearance: UIKeyboardAppearance { + get { + return super.keyboardAppearance + } + set { + let resigning = self.isFirstResponder + if resigning { + self.resignFirstResponder() + } + super.keyboardAppearance = newValue + if resigning { + self.becomeFirstResponder() + } + } + } } public class TextFieldNode: ASDisplayNode { diff --git a/Display/ViewControllerPreviewing.swift b/Display/ViewControllerPreviewing.swift index 1ad481ae40..82b1356ff7 100644 --- a/Display/ViewControllerPreviewing.swift +++ b/Display/ViewControllerPreviewing.swift @@ -36,6 +36,10 @@ private final class ViewControllerPeekContent: PeekControllerContent { return ViewControllerPeekContentNode(controller: self.controller) } + func topAccessoryNode() -> ASDisplayNode? { + return nil + } + func isEqual(to: PeekControllerContent) -> Bool { if let to = to as? ViewControllerPeekContent { return self.controller === to.controller From ccabf7008cbf877a44148caab481a268d6390a3c Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 12 Dec 2018 16:31:52 +0400 Subject: [PATCH 128/245] Keyboard appearance fixes --- Display/EditableTextNode.swift | 3 +++ Display/TextFieldNode.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/Display/EditableTextNode.swift b/Display/EditableTextNode.swift index 947b4fb817..c5df13e60e 100644 --- a/Display/EditableTextNode.swift +++ b/Display/EditableTextNode.swift @@ -7,6 +7,9 @@ public class EditableTextNode : ASEditableTextNode { return super.keyboardAppearance } set { + guard newValue != self.keyboardAppearance else { + return + } let resigning = self.isFirstResponder() if resigning { self.resignFirstResponder() diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index 6a6c7dfcb3..e755684b2b 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -30,6 +30,9 @@ public final class TextFieldNodeView: UITextField { return super.keyboardAppearance } set { + guard newValue != self.keyboardAppearance else { + return + } let resigning = self.isFirstResponder if resigning { self.resignFirstResponder() From 8cd1f0fc784709445a8234a8df6d6a831bb4b056 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 18 Dec 2018 09:09:15 +0300 Subject: [PATCH 129/245] Fix empty empty section list case --- Display/CollectionIndexNode.swift | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/Display/CollectionIndexNode.swift b/Display/CollectionIndexNode.swift index d00d1abd6b..2a9f996fc7 100644 --- a/Display/CollectionIndexNode.swift +++ b/Display/CollectionIndexNode.swift @@ -45,7 +45,12 @@ public final class CollectionIndexNode: ASDisplayNode { let maxHeight = size.height - verticalInset * 2.0 let maxItemCount = min(sections.count, Int(floor(maxHeight / itemHeight))) - let skipCount = Int(ceil(CGFloat(sections.count) / CGFloat(maxItemCount))) + let skipCount: Int + if sections.isEmpty { + skipCount = 1 + } else { + skipCount = Int(ceil(CGFloat(sections.count) / CGFloat(maxItemCount))) + } let actualCount: CGFloat = ceil(CGFloat(sections.count) / CGFloat(skipCount)) let totalHeight = actualCount * itemHeight From 0e941897f3425ec232c5de7c05623a4f467edbfa Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 18 Dec 2018 10:21:02 +0400 Subject: [PATCH 130/245] ListView: do not hit test when hidden Added option to inherit transformation on snapshotted views --- Display/ListView.swift | 2 +- Display/UIKitUtils.swift | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 95874b094f..acac658342 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -61,7 +61,7 @@ final class ListViewBackingView: UIView { } override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if let target = self.target { + if !self.isHidden, let target = self.target { if target.limitHitTestToNodes, !target.internalHitTest(point, with: event) { return nil } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 214d1cef72..085ad56823 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -188,7 +188,7 @@ public extension UIImage { } } -private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { +private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) -> UIView? { let view = UIView() view.layer.isHidden = layer.isHidden view.layer.opacity = layer.opacity @@ -202,8 +202,11 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { view.layer.backgroundColor = layer.backgroundColor if let sublayers = layer.sublayers { for sublayer in sublayers { - let subtree = makeSubtreeSnapshot(layer: sublayer) + let subtree = makeSubtreeSnapshot(layer: sublayer, keepTransform: keepTransform) if let subtree = subtree { + if keepTransform { + subtree.layer.transform = sublayer.transform + } subtree.frame = sublayer.frame subtree.bounds = sublayer.bounds view.addSubview(subtree) @@ -243,12 +246,12 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? { } public extension UIView { - public func snapshotContentTree(unhide: Bool = false) -> UIView? { + public func snapshotContentTree(unhide: Bool = false, keepTransform: Bool = false) -> UIView? { let wasHidden = self.isHidden if unhide && wasHidden { self.isHidden = false } - let snapshot = makeSubtreeSnapshot(layer: self.layer) + let snapshot = makeSubtreeSnapshot(layer: self.layer, keepTransform: keepTransform) if unhide && wasHidden { self.isHidden = true } From 6295a3778913a03ccba1680e466070d4efeeb467 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 18 Dec 2018 11:19:31 +0400 Subject: [PATCH 131/245] Fixed volume indicator graphics --- Display/VolumeControlStatusBar.swift | 29 ++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 7c397fee96..0e590f21c8 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -113,6 +113,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.innerGraphics = (graphics.0, graphics.1, graphics.2, false) } } + self.updateIcon() } } } @@ -151,9 +152,9 @@ final class VolumeControlStatusBarNode: ASDisplayNode { func generateDarkGraphics(_ graphics: (UIImage, UIImage, UIImage)?) -> (UIImage, UIImage, UIImage, Bool)? { if var (offImage, halfImage, onImage) = graphics { - offImage = generateTintedImage(image: offImage, color: UIColor.black)! - halfImage = generateTintedImage(image: halfImage, color: UIColor.black)! - onImage = generateTintedImage(image: onImage, color: UIColor.black)! + offImage = generateTintedImage(image: offImage, color: UIColor.white)! + halfImage = generateTintedImage(image: halfImage, color: UIColor.white)! + onImage = generateTintedImage(image: onImage, color: UIColor.white)! return (offImage, halfImage, onImage, true) } else { return nil @@ -223,17 +224,21 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.value = toValue self.updateLayout(layout: layout, transition: .animated(duration: 0.25, curve: .spring)) - if let graphics = self.graphics { - if self.value > 0.5 { - self.iconNode.image = graphics.2 - } else if self.value > 0.0 { - self.iconNode.image = graphics.1 - } else { - self.iconNode.image = graphics.0 - } - } + self.updateIcon() } else { self.value = toValue } } + + private func updateIcon() { + if let graphics = self.innerGraphics { + if self.value > 0.5 { + self.iconNode.image = graphics.2 + } else if self.value > 0.0 { + self.iconNode.image = graphics.1 + } else { + self.iconNode.image = graphics.0 + } + } + } } From f34984c4ea91fce9e2903cf52e149838b17fc399 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 18 Dec 2018 16:13:39 +0400 Subject: [PATCH 132/245] Fixed width fulfilment in grid layout --- Display/GridNode.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 9da4dd68eb..7bdec74274 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -463,7 +463,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) } else if let fillWidth = fillWidth, fillWidth { let nextItemOriginX = nextItemOrigin.x + itemSize.width + itemSpacing - if nextItemOriginX + itemSize.width > gridLayout.size.width && remainingWidth > 0.0 { + let remainingWidth = remainingWidth - CGFloat(itemsInRow - 1) * itemSpacing + if nextItemOriginX + itemSize.width > self.gridLayout.size.width && remainingWidth > 0.0 { itemSize.width += remainingWidth } } From a493e4f08101ee56807da7addd1afa8b927cbb13 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 18 Dec 2018 18:58:15 +0300 Subject: [PATCH 133/245] Cleanup --- Display/EditableTextNode.swift | 2 +- Display/ListView.swift | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/Display/EditableTextNode.swift b/Display/EditableTextNode.swift index c5df13e60e..954238778c 100644 --- a/Display/EditableTextNode.swift +++ b/Display/EditableTextNode.swift @@ -1,7 +1,7 @@ import Foundation import AsyncDisplayKit -public class EditableTextNode : ASEditableTextNode { +public class EditableTextNode: ASEditableTextNode { override public var keyboardAppearance: UIKeyboardAppearance { get { return super.keyboardAppearance diff --git a/Display/ListView.swift b/Display/ListView.swift index 95874b094f..2291e0e5e9 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -3486,10 +3486,18 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func ensureItemNodeVisible(_ node: ListViewItemNode) { if let index = node.index { - if node.frame.minY < self.insets.top { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + if node.apparentHeight > self.visibleSize.height - self.insets.top - self.insets.bottom { + if node.frame.maxY > self.visibleSize.height - self.insets.bottom { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + }/* else if node.frame.minY < self.insets.top { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + }*/ + } else { + if node.frame.minY < self.insets.top { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } } } } From b8744db09d3a0f3f4c40259727fd61cf118888aa Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 18 Dec 2018 21:56:59 +0300 Subject: [PATCH 134/245] Added experimental snapping to ListView --- Display/ListView.swift | 36 +++++++++++++++++++++---- Display/ListViewIntermediateState.swift | 11 +++++++- Display/NotificationCenterUtils.m | 15 ++++++----- 3 files changed, 49 insertions(+), 13 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 9844b187cf..d97ee13f06 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -127,6 +127,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public final var dynamicBounceEnabled = true public final var rotated = false + public final var experimentalSnapScrollToItem = false private final var invisibleInset: CGFloat = 500.0 public var preloadPages: Bool = true { @@ -239,6 +240,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var flashNodesDelayTimer: Foundation.Timer? private var flashScrollIndicatorTimer: Foundation.Timer? private var highlightedItemIndex: Int? + private var scrolledToItem: (Int, ListViewScrollPosition)? private var reorderNode: ListViewReorderingItemNode? private var reorderFeedback: HapticFeedback? private var reorderFeedbackDisposable: MetaDisposable? @@ -547,6 +549,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if self.snapToBottomInsetUntilFirstInteraction { self.snapToBottomInsetUntilFirstInteraction = false } + self.scrolledToItem = nil self.beganInteractiveDragging() } @@ -1277,6 +1280,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.scrolledToItem = nil + let startTime = CACurrentMediaTime() var state = self.currentState() @@ -1980,7 +1985,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + var scrollToItem: ListViewScrollToItem? + if let originalScrollToItem = originalScrollToItem { + scrollToItem = originalScrollToItem + if self.experimentalSnapScrollToItem { + self.scrolledToItem = (originalScrollToItem.index, originalScrollToItem.position) + } + } else if let scrolledToItem = self.scrolledToItem { + scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: false, curve: .Default(duration: nil), directionHint: .Down) + } /*if true { print("----------") for itemNode in self.itemNodes { @@ -2268,6 +2282,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY } } + case .visible: + if itemNode.apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { + offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + } else if itemNode.apparentFrame.minY < self.insets.top { + offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + } else { + offset = 0.0 + } } for itemNode in self.itemNodes { @@ -3493,10 +3515,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) }*/ } else { - if node.frame.minY < self.insets.top { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) - } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + if self.experimentalSnapScrollToItem { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: true, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } else { + if node.frame.minY < self.insets.top { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + } } } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 4fc0f49409..9987aa1a92 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -14,6 +14,7 @@ public enum ListViewScrollPosition: Equatable { case top(CGFloat) case bottom(CGFloat) case center(ListViewCenterScrollPositionOverflow) + case visible } public enum ListViewScrollToItemDirectionHint { @@ -284,7 +285,7 @@ struct ListViewState { mutating func fixScrollPosition(_ itemCount: Int) { if let (fixedIndex, fixedPosition) = self.scrollPosition { for node in self.nodes { - if let index = node.index , index == fixedIndex { + if let index = node.index, index == fixedIndex { let offset: CGFloat switch fixedPosition { case let .bottom(additionalOffset): @@ -303,6 +304,14 @@ struct ListViewState { offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY } } + case .visible: + if node.frame.maxY > self.visibleSize.height - self.insets.bottom { + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + } else if node.frame.minY < self.insets.top { + offset = self.insets.top - node.frame.minY + } else { + offset = 0.0 + } } var minY: CGFloat = CGFloat.greatestFiniteMagnitude diff --git a/Display/NotificationCenterUtils.m b/Display/NotificationCenterUtils.m index a99f1b4b15..1c26229623 100644 --- a/Display/NotificationCenterUtils.m +++ b/Display/NotificationCenterUtils.m @@ -20,16 +20,17 @@ static NSMutableArray *notificationHandlers() { - (void)_a65afc19_postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo { - for (NotificationHandlerBlock handler in notificationHandlers()) - { - if (handler(aName, anObject, aUserInfo, ^{ - [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; - })) { - return; + if ([NSThread isMainThread]) { + for (NotificationHandlerBlock handler in notificationHandlers()) + { + if (handler(aName, anObject, aUserInfo, ^{ + [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; + })) { + return; + } } } - //printf("***** %s\n", [aName cStringUsingEncoding:NSUTF8StringEncoding]); [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo]; } From bd9502501881b9195d0ecfa8f2186976df08f86e Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 19 Dec 2018 00:32:03 +0300 Subject: [PATCH 135/245] Fix scroll to visible --- Display/ListView.swift | 67 ++++++++++++++++++++++++------------------ 1 file changed, 39 insertions(+), 28 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index d97ee13f06..6b4dfeaa87 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2283,12 +2283,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } case .visible: - if itemNode.apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { - offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom - } else if itemNode.apparentFrame.minY < self.insets.top { - offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + if itemNode.apparentFrame.size.height > self.visibleSize.height - self.insets.top - self.insets.bottom { + if itemNode.apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { + offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + } else { + offset = 0.0 + } } else { - offset = 0.0 + if itemNode.apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { + offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + } else if itemNode.apparentFrame.minY < self.insets.top { + offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + } else { + offset = 0.0 + } } } @@ -2504,32 +2512,35 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var temporaryPreviousNodes: [ListViewItemNode] = [] var previousUpperBound: CGFloat? var previousLowerBound: CGFloat? - for (previousNode, previousFrame) in previousApparentFrames { - if previousNode.supernode == nil { - temporaryPreviousNodes.append(previousNode) - previousNode.frame = previousFrame - if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { - previousUpperBound = previousFrame.minY - } - if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { - previousLowerBound = previousFrame.maxY - } - } else { - if previousNode.canBeUsedAsScrollToItemAnchor { - offset = previousNode.apparentFrame.minY - previousFrame.minY + if case .visible = scrollToItem.position { + } else { + for (previousNode, previousFrame) in previousApparentFrames { + if previousNode.supernode == nil { + temporaryPreviousNodes.append(previousNode) + previousNode.frame = previousFrame + if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { + previousUpperBound = previousFrame.minY + } + if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { + previousLowerBound = previousFrame.maxY + } + } else { + if previousNode.canBeUsedAsScrollToItemAnchor { + offset = previousNode.apparentFrame.minY - previousFrame.minY + } } } - } - if offset == nil { - let updatedUpperBound = self.itemNodes[0].apparentFrame.minY - let updatedLowerBound = max(self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY, self.visibleSize.height) - - switch scrollToItem.directionHint { - case .Up: - offset = updatedLowerBound - (previousUpperBound ?? 0.0) - case .Down: - offset = updatedUpperBound - (previousLowerBound ?? self.visibleSize.height) + if offset == nil { + let updatedUpperBound = self.itemNodes[0].apparentFrame.minY + let updatedLowerBound = max(self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY, self.visibleSize.height) + + switch scrollToItem.directionHint { + case .Up: + offset = updatedLowerBound - (previousUpperBound ?? 0.0) + case .Down: + offset = updatedUpperBound - (previousLowerBound ?? self.visibleSize.height) + } } } From ba017e7c339b7a7e3ba131151061c5033fdca375 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 19 Dec 2018 19:40:35 +0400 Subject: [PATCH 136/245] Alert title fix --- Display/TextAlertController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index bcf000fd46..b1e8fbe167 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -139,7 +139,7 @@ public final class TextAlertContentNode: AlertContentNode { titleNode.attributedText = title titleNode.displaysAsynchronously = false titleNode.isUserInteractionEnabled = false - titleNode.maximumNumberOfLines = 1 + titleNode.maximumNumberOfLines = 2 titleNode.truncationMode = .byTruncatingTail self.titleNode = titleNode } else { From 54fc9b751c0d8fc98c7a4c3f93570fcf215ef717 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Thu, 20 Dec 2018 15:53:10 +0400 Subject: [PATCH 137/245] Fix ListView scroll to visible --- Display/ListView.swift | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/Display/ListView.swift b/Display/ListView.swift index 6b4dfeaa87..219ba1f303 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2513,6 +2513,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var previousUpperBound: CGFloat? var previousLowerBound: CGFloat? if case .visible = scrollToItem.position { + for (previousNode, previousFrame) in previousApparentFrames { + if previousNode.supernode == nil { + temporaryPreviousNodes.append(previousNode) + previousNode.frame = previousFrame + if previousUpperBound == nil || previousUpperBound! > previousFrame.minY { + previousUpperBound = previousFrame.minY + } + if previousLowerBound == nil || previousLowerBound! < previousFrame.maxY { + previousLowerBound = previousFrame.maxY + } + } else { + if previousNode.canBeUsedAsScrollToItemAnchor { + offset = previousNode.apparentFrame.minY - previousFrame.minY + break + } + } + } } else { for (previousNode, previousFrame) in previousApparentFrames { if previousNode.supernode == nil { From 883b92dc8e9ebd7588c911e2553bfb0a5bfacd1b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 20 Dec 2018 18:11:53 +0400 Subject: [PATCH 138/245] Added protocol to control higlightability of navigation buttons --- Display/NavigationButtonNode.swift | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index d2db53b9d8..15a6ade0db 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -1,6 +1,10 @@ import UIKit import AsyncDisplayKit +public protocol NavigationButtonCustomDisplayNode { + var isHighlightable: Bool { get } +} + private final class NavigationButtonItemNode: ASTextNode { private func fontForCurrentState() -> UIFont { return self.bold ? UIFont.boldSystemFont(ofSize: 17.0) : UIFont.systemFont(ofSize: 17.0) @@ -192,17 +196,15 @@ private final class NavigationButtonItemNode: ASTextNode { if _highlighted != highlighted { _highlighted = highlighted - let alpha: CGFloat = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) + var shouldChangeHighlight = true + if let node = self.node as? NavigationButtonCustomDisplayNode { + shouldChangeHighlight = node.isHighlightable + } - /*if animated { - UIView.animate(withDuration: 0.3, delay: 0.0, options: UIViewAnimationOptions.beginFromCurrentState, animations: { () -> Void in - self.alpha = alpha - }, completion: nil) - } - else {*/ - self.alpha = alpha - self.highlightChanged(highlighted) - //} + if shouldChangeHighlight { + self.alpha = !self.isEnabled ? 1.0 : (highlighted ? 0.4 : 1.0) + self.highlightChanged(highlighted) + } } } From 8cf1aff13fc1d2e6086a96272c0c9c23400f9fcd Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Tue, 25 Dec 2018 17:56:00 +0400 Subject: [PATCH 139/245] Added animated to pushViewController --- Display/ListView.swift | 8 ++++++-- Display/NavigationController.swift | 4 ++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 219ba1f303..ed62ac151d 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2407,7 +2407,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if k != 0 && k != 1 { speed = Float(1.0) / k } - springAnimation.speed = speed * Float(springAnimation.duration / duration) + if !duration.isZero { + springAnimation.speed = speed * Float(springAnimation.duration / duration) + } springAnimation.isAdditive = true animation = springAnimation @@ -2621,7 +2623,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if k != 0 && k != 1 { speed = Float(1.0) / k } - springAnimation.speed = speed * Float(springAnimation.duration / duration) + if !duration.isZero { + springAnimation.speed = speed * Float(springAnimation.duration / duration) + } let reverseSpringAnimation = makeSpringAnimation("sublayerTransform") reverseSpringAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, offset, 0.0)) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index f52cc03f3a..b4aef72342 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -729,7 +729,7 @@ open class NavigationController: UINavigationController, ContainableController, self.pushViewController(controller, completion: {}) } - public func pushViewController(_ controller: ViewController, completion: @escaping () -> Void) { + public func pushViewController(_ controller: ViewController, animated: Bool = true, completion: @escaping () -> Void) { let navigateAction: () -> Void = { [weak self] in guard let strongSelf = self else { return @@ -756,7 +756,7 @@ open class NavigationController: UINavigationController, ContainableController, if containerLayout != appliedLayout { controller.containerLayoutUpdated(containerLayout, transition: .immediate) } - strongSelf.pushViewController(controller, animated: true) + strongSelf.pushViewController(controller, animated: animated) } })) } else { From ebd8cc351d107575e50cb2afb66de2531fc87a16 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 25 Dec 2018 19:44:55 +0400 Subject: [PATCH 140/245] Added rotation animation method for layers --- Display/CAAnimationUtils.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 607d2299d6..53d9a31690 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -217,6 +217,10 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) } + public func animateRotation(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.rotation.z", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) + } + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { From c4da6a5233c87592be97056c6ae7edf4a6726990 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 28 Dec 2018 20:53:31 +0400 Subject: [PATCH 141/245] Added alpha value getter in UIColor extension --- Display/UIKitUtils.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index 085ad56823..ef2b94fb27 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -51,6 +51,17 @@ public extension UIColor { self.init(red: CGFloat((argb >> 16) & 0xff) / 255.0, green: CGFloat((argb >> 8) & 0xff) / 255.0, blue: CGFloat(argb & 0xff) / 255.0, alpha: CGFloat((argb >> 24) & 0xff) / 255.0) } + var alpha: CGFloat { + var alpha: CGFloat = 0.0 + if self.getRed(nil, green: nil, blue: nil, alpha: &alpha) { + return alpha + } else if self.getWhite(nil, alpha: &alpha) { + return alpha + } else { + return 0.0 + } + } + var argb: UInt32 { var red: CGFloat = 0.0 var green: CGFloat = 0.0 From 525e04981ce2a3c579673f2daa1f6bfc59305e27 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Sun, 30 Dec 2018 16:27:51 +0400 Subject: [PATCH 142/245] Add input inset workaround --- Display/PresentationContext.swift | 11 +++++++++++ Display/ViewController.swift | 4 +++- Display/WindowContent.swift | 7 ++++++- 3 files changed, 20 insertions(+), 2 deletions(-) diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 9b7da9bb95..360bbb5c62 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -45,6 +45,17 @@ final class PresentationContext { var topLevelSubview: UIView? + var isCurrentlyOpaque: Bool { + for (controller, _) in self.controllers { + if controller.isOpaqueWhenInOverlay && controller.isNodeLoaded { + if traceIsOpaque(layer: controller.displayNode.layer, rect: controller.displayNode.bounds) { + return true + } + } + } + return false + } + private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? { var topController: ViewController? for (controller, controllerLevel) in self.controllers.reversed() { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index aa6cb74757..dc1ff78d3d 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -63,6 +63,8 @@ open class ViewControllerPresentationArguments { } } + public final var isOpaqueWhenInOverlay: Bool = false + public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { return self.supportedOrientations } @@ -440,7 +442,7 @@ open class ViewControllerPresentationArguments { } } -private func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { +func traceIsOpaque(layer: CALayer, rect: CGRect) -> Bool { if layer.bounds.contains(rect) { if layer.isHidden { return false diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 1511a6e542..396515f705 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -857,7 +857,12 @@ public class Window1 { self.updatedContainerLayout = childLayout if childLayoutUpdated { - self._rootController?.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) + var rootLayout = childLayout + let rootTransition = updatingLayout.transition + if self.presentationContext.isCurrentlyOpaque { + rootLayout.inputHeight = nil + } + self._rootController?.containerLayoutUpdated(rootLayout, transition: rootTransition) self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition) From 839a4477001317e42c083afda63ab898354f72ac Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 30 Dec 2018 22:52:24 +0400 Subject: [PATCH 143/245] Expose NavigationBar.backPressed --- Display/NavigationBar.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index c7a73653c3..cd4600cc01 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -108,7 +108,7 @@ open class NavigationBar: ASDisplayNode { private var validLayout: (CGSize, CGFloat, CGFloat)? private var requestedLayout: Bool = false - var backPressed: () -> () = { } + public var backPressed: () -> () = { } private var collapsed: Bool { get { From c91d10ec4b27b5242ff4683481279095d705a5c7 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 3 Jan 2019 10:31:01 +0100 Subject: [PATCH 144/245] Move push completion callback --- Display/NavigationController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index b4aef72342..66ff2830ed 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -806,13 +806,13 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { ready?.set(true) - completion() var controllers = strongSelf.viewControllers while controllers.count > 1 { controllers.removeLast() } controllers.append(controller) strongSelf.setViewControllers(controllers, animated: animated) + completion() } })) } From ae7e54565babe4c734e83713e3ced9231f06be02 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 3 Jan 2019 21:41:03 +0400 Subject: [PATCH 145/245] Various changes to NavigationBarContentNode to support putting search bar into navigation bar. --- Display/NavigationBar.swift | 50 +++++++++++++++++-- Display/NavigationBarContentNode.swift | 21 +++++++- Display/NavigationTransitionCoordinator.swift | 4 +- Display/TabBarController.swift | 7 ++- Display/ViewController.swift | 19 +++++-- 5 files changed, 86 insertions(+), 15 deletions(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index cd4600cc01..6a8a523d3e 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -107,6 +107,7 @@ open class NavigationBar: ASDisplayNode { private var validLayout: (CGSize, CGFloat, CGFloat)? private var requestedLayout: Bool = false + var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in } public var backPressed: () -> () = { } @@ -519,6 +520,7 @@ open class NavigationBar: ASDisplayNode { private let leftButtonNode: NavigationButtonNode private let rightButtonNode: NavigationButtonNode + private var _transitionState: NavigationBarTransitionState? var transitionState: NavigationBarTransitionState? { get { @@ -722,14 +724,23 @@ open class NavigationBar: ASDisplayNode { let backButtonInset: CGFloat = leftInset + 27.0 transition.updateFrame(node: self.clippingNode, frame: CGRect(origin: CGPoint(), size: size)) + var expansionHeight: CGFloat = 0.0 if let contentNode = self.contentNode { - transition.updateFrame(node: contentNode, frame: CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height))) + let contentNodeFrame: CGRect + switch contentNode.mode { + case .replacement: + contentNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height)) + case .expansion: + expansionHeight = contentNode.height + contentNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: size.height - expansionHeight), size: CGSize(width: size.width - leftInset - rightInset, height: expansionHeight)) + } + transition.updateFrame(node: contentNode, frame: contentNodeFrame) } transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel)) let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 - let contentVerticalOrigin = size.height - nominalHeight + let contentVerticalOrigin = size.height - nominalHeight - expansionHeight var leftTitleInset: CGFloat = leftInset + 1.0 var rightTitleInset: CGFloat = rightInset + 1.0 @@ -960,6 +971,22 @@ open class NavigationBar: ASDisplayNode { } } + public var canTransitionInline: Bool { + if let contentNode = self.contentNode, case .replacement = contentNode.mode { + return false + } else { + return true + } + } + + public var contentHeight: CGFloat { + if let contentNode = self.contentNode, case .expansion = contentNode.mode { + return 44.0 + contentNode.height + } else { + return 44.0 + } + } + public func setContentNode(_ contentNode: NavigationBarContentNode?, animated: Bool) { if self.contentNode !== contentNode { if let previous = self.contentNode { @@ -976,6 +1003,9 @@ open class NavigationBar: ASDisplayNode { } } self.contentNode = contentNode + self.contentNode?.requestContainerLayout = { [weak self] transition in + self?.requestContainerLayout(transition) + } if let contentNode = contentNode { contentNode.layer.removeAnimation(forKey: "opacity") self.addSubnode(contentNode) @@ -983,7 +1013,7 @@ open class NavigationBar: ASDisplayNode { contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } - if !self.clippingNode.alpha.isZero { + if case .replacement = contentNode.mode, !self.clippingNode.alpha.isZero { self.clippingNode.alpha = 0.0 if animated { self.clippingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2) @@ -1004,4 +1034,18 @@ open class NavigationBar: ASDisplayNode { } } } + + public func setHidden(_ hidden: Bool, animated: Bool) { + if let contentNode = self.contentNode, case .replacement = contentNode.mode { + } else { + let targetAlpha: CGFloat = hidden ? 0.0 : 1.0 + let previousAlpha = self.clippingNode.alpha + if previousAlpha != targetAlpha { + self.clippingNode.alpha = targetAlpha + if animated { + self.clippingNode.layer.animateAlpha(from: previousAlpha, to: targetAlpha, duration: 0.2) + } + } + } + } } diff --git a/Display/NavigationBarContentNode.swift b/Display/NavigationBarContentNode.swift index db12a52e5b..97414a2167 100644 --- a/Display/NavigationBarContentNode.swift +++ b/Display/NavigationBarContentNode.swift @@ -1,6 +1,23 @@ import Foundation import AsyncDisplayKit -open class NavigationBarContentNode: ASDisplayNode { - +public enum NavigationBarContentMode { + case replacement + case expansion +} + +open class NavigationBarContentNode: ASDisplayNode { + open var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in } + + open var height: CGFloat { + return self.nominalHeight + } + + open var nominalHeight: CGFloat { + return 0.0 + } + + open var mode: NavigationBarContentMode { + return .replacement + } } diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 9b35cb2f79..41ab61d274 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -56,12 +56,12 @@ class NavigationTransitionCoordinator { self.dimView.backgroundColor = UIColor.black self.shadowView = UIImageView(image: shadowImage) - if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.contentNode == nil, bottomNavigationBar.contentNode == nil, topNavigationBar.item?.leftBarButtonItem == nil { + if let topNavigationBar = topNavigationBar, let bottomNavigationBar = bottomNavigationBar, !topNavigationBar.isHidden, !bottomNavigationBar.isHidden, topNavigationBar.canTransitionInline, bottomNavigationBar.canTransitionInline, topNavigationBar.item?.leftBarButtonItem == nil { var topFrame = topNavigationBar.view.convert(topNavigationBar.bounds, to: container) var bottomFrame = bottomNavigationBar.view.convert(bottomNavigationBar.bounds, to: container) topFrame.origin.x = 0.0 bottomFrame.origin.x = 0.0 - self.inlineNavigationBarTransition = topFrame.equalTo(bottomFrame) + self.inlineNavigationBarTransition = true// topFrame.equalTo(bottomFrame) } else { self.inlineNavigationBarTransition = false } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 83ece14c42..123b9076e5 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -158,9 +158,6 @@ open class TabBarController: ViewController { var displayNavigationBar = false if let currentController = self.currentController { currentController.willMove(toParentViewController: self) - if let validLayout = self.validLayout { - currentController.containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) - } self.tabBarControllerNode.currentControllerView = currentController.view currentController.navigationBar?.isHidden = true self.addChildViewController(currentController) @@ -169,6 +166,7 @@ open class TabBarController: ViewController { currentController.navigationBar?.layoutSuspended = true currentController.navigationItem.setTarget(self.navigationItem) displayNavigationBar = currentController.displayNavigationBar + self.navigationBar?.setContentNode(currentController.navigationBar?.contentNode, animated: false) currentController.displayNode.recursivelyEnsureDisplaySynchronously(true) self.statusBar.statusBarStyle = currentController.statusBar.statusBarStyle } else { @@ -177,6 +175,7 @@ open class TabBarController: ViewController { self.navigationItem.rightBarButtonItem = nil self.navigationItem.titleView = nil self.navigationItem.backBarButtonItem = nil + self.navigationBar?.setContentNode(nil, animated: false) displayNavigationBar = false } if self.displayNavigationBar != displayNavigationBar { @@ -184,7 +183,7 @@ open class TabBarController: ViewController { } if let validLayout = self.validLayout { - self.tabBarControllerNode.containerLayoutUpdated(validLayout, toolbar: self.currentController?.toolbar, transition: .immediate) + self.containerLayoutUpdated(validLayout, transition: .immediate) } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index dc1ff78d3d..8a76bea24d 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -143,7 +143,11 @@ open class ViewControllerPresentationArguments { open var navigationHeight: CGFloat { if let navigationBar = self.navigationBar { - return navigationBar.frame.maxY + var height = navigationBar.frame.maxY + if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode { + height += contentNode.nominalHeight - contentNode.height + } + return height } else { return 0.0 } @@ -205,6 +209,9 @@ open class ViewControllerPresentationArguments { strongSelf.navigationController?.popViewController(animated: true) } } + self.navigationBar?.requestContainerLayout = { [weak self] transition in + self?.requestLayout(transition: transition) + } self.navigationBar?.item = self.navigationItem self.automaticallyAdjustsScrollViewInsets = false @@ -233,7 +240,7 @@ open class ViewControllerPresentationArguments { } let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 - let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + 44.0 + let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + (self.navigationBar?.contentHeight ?? 44.0) let navigationBarOffset: CGFloat if statusBarHeight.isZero { navigationBarOffset = -20.0 @@ -242,19 +249,23 @@ open class ViewControllerPresentationArguments { } var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight)) if layout.statusBarHeight == nil { - navigationBarFrame.size.height = 64.0 + navigationBarFrame.size.height = (self.navigationBar?.contentHeight ?? 44.0) + 20.0 } if !self.displayNavigationBar { navigationBarFrame.origin.y = -navigationBarFrame.size.height } - navigationBarOrigin = navigationBarFrame.origin.y + self.navigationBarOrigin = navigationBarFrame.origin.y navigationBarFrame.origin.y += self.navigationOffset if let navigationBar = self.navigationBar { + if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar { + navigationBarFrame.origin.y += contentNode.height + statusBarHeight + } transition.updateFrame(node: navigationBar, frame: navigationBarFrame) navigationBar.updateLayout(size: navigationBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) + navigationBar.setHidden(!self.displayNavigationBar, animated: transition.isAnimated) } self.presentationContext.containerLayoutUpdated(layout, transition: transition) From e1c998ca9d64e3227f45a874b1d198ccea0d7ae9 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 8 Jan 2019 13:41:18 +0400 Subject: [PATCH 146/245] Navigation bar content fixes --- Display/ContainedViewLayoutTransition.swift | 25 +++++++++++++++++++++ Display/NavigationBar.swift | 16 +++++++++---- Display/NavigationBarContentNode.swift | 5 ++++- Display/ViewController.swift | 8 +++++++ 4 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index ab5301fb29..4464f2b10e 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -442,6 +442,31 @@ public extension ContainedViewLayoutTransition { } } + func updateCornerRadius(node: ASDisplayNode, cornerRadius: CGFloat, completion: ((Bool) -> Void)? = nil) { + if node.cornerRadius.isEqual(to: cornerRadius) { + if let completion = completion { + completion(true) + } + return + } + + switch self { + case .immediate: + node.cornerRadius = cornerRadius + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousCornerRadius = node.cornerRadius + node.cornerRadius = cornerRadius + node.layer.animate(from: NSNumber(value: Float(previousCornerRadius)), to: NSNumber(value: Float(cornerRadius)), keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + func animateTransformScale(node: ASDisplayNode, from fromScale: CGFloat, completion: ((Bool) -> Void)? = nil) { let t = node.layer.transform let currentScale = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 6a8a523d3e..ee6aba5c0d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -729,12 +729,14 @@ open class NavigationBar: ASDisplayNode { let contentNodeFrame: CGRect switch contentNode.mode { case .replacement: - contentNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: size.width - leftInset - rightInset, height: size.height)) + expansionHeight = contentNode.height - 44.0 + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: size.height)) case .expansion: expansionHeight = contentNode.height - contentNodeFrame = CGRect(origin: CGPoint(x: leftInset, y: size.height - expansionHeight), size: CGSize(width: size.width - leftInset - rightInset, height: expansionHeight)) + contentNodeFrame = CGRect(origin: CGPoint(x: 0.0, y: size.height - expansionHeight), size: CGSize(width: size.width, height: expansionHeight)) } transition.updateFrame(node: contentNode, frame: contentNodeFrame) + contentNode.updateLayout(size: contentNodeFrame.size, leftInset: leftInset, rightInset: rightInset, transition: transition) } transition.updateFrame(node: self.stripeNode, frame: CGRect(x: 0.0, y: size.height, width: size.width, height: UIScreenPixel)) @@ -980,8 +982,13 @@ open class NavigationBar: ASDisplayNode { } public var contentHeight: CGFloat { - if let contentNode = self.contentNode, case .expansion = contentNode.mode { - return 44.0 + contentNode.height + if let contentNode = self.contentNode { + switch contentNode.mode { + case .expansion: + return 44.0 + contentNode.height + case .replacement: + return contentNode.height + } } else { return 44.0 } @@ -1006,6 +1013,7 @@ open class NavigationBar: ASDisplayNode { self.contentNode?.requestContainerLayout = { [weak self] transition in self?.requestContainerLayout(transition) } + //let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate if let contentNode = contentNode { contentNode.layer.removeAnimation(forKey: "opacity") self.addSubnode(contentNode) diff --git a/Display/NavigationBarContentNode.swift b/Display/NavigationBarContentNode.swift index 97414a2167..12a193f20e 100644 --- a/Display/NavigationBarContentNode.swift +++ b/Display/NavigationBarContentNode.swift @@ -14,10 +14,13 @@ open class NavigationBarContentNode: ASDisplayNode { } open var nominalHeight: CGFloat { - return 0.0 + return 44.0 } open var mode: NavigationBarContentMode { return .replacement } + + open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) { + } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 8a76bea24d..beaf00f3e2 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -142,6 +142,14 @@ open class ViewControllerPresentationArguments { } open var navigationHeight: CGFloat { + if let navigationBar = self.navigationBar { + return navigationBar.frame.maxY + } else { + return 0.0 + } + } + + open var navigationInsetHeight: CGFloat { if let navigationBar = self.navigationBar { var height = navigationBar.frame.maxY if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode { From 447d0c6e30d0035d20e27f9f3625d63faea70b9d Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 10 Jan 2019 18:09:31 +0300 Subject: [PATCH 147/245] Pass viewWillDisappear to tab bar controller children --- Display/TabBarController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 123b9076e5..eb6fc43e92 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -208,6 +208,12 @@ open class TabBarController: ViewController { } } + override open func viewWillDisappear(_ animated: Bool) { + if let currentController = self.currentController { + currentController.viewWillDisappear(animated) + } + } + override open func viewWillAppear(_ animated: Bool) { if let currentController = self.currentController { currentController.viewWillAppear(animated) From 97e5f8b5312ce325264a9b077efb319554c43fe0 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 10 Jan 2019 23:13:38 +0400 Subject: [PATCH 148/245] Support for separate insets for item headers --- Display/ListView.swift | 4 +++- Display/ListViewIntermediateState.swift | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index ed62ac151d..43519f2f00 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -116,6 +116,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() public private(set) final var insets = UIEdgeInsets() + public private(set) final var headerInsets = UIEdgeInsets() public private(set) final var scrollIndicatorInsets = UIEdgeInsets() private final var ensureTopInsetForOverlayHighlightedItems: CGFloat? private final var lastContentOffset: CGPoint = CGPoint() @@ -1262,6 +1263,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets + self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems @@ -2796,7 +2798,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } private func updateItemHeaders(leftInset: CGFloat, rightInset: CGFloat, transition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0), animateInsertion: Bool = false) { - let upperDisplayBound = self.insets.top + let upperDisplayBound = self.headerInsets.top let lowerDisplayBound = self.visibleSize.height - self.insets.bottom var visibleHeaderNodes = Set() diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 9987aa1a92..d8e1ef4d93 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -109,14 +109,16 @@ public struct ListViewDeleteAndInsertOptions: OptionSet { public struct ListViewUpdateSizeAndInsets { public let size: CGSize public let insets: UIEdgeInsets + public let headerInsets: UIEdgeInsets? public let scrollIndicatorInsets: UIEdgeInsets? public let duration: Double public let curve: ListViewAnimationCurve public let ensureTopInsetForOverlayHighlightedItems: CGFloat? - public init(size: CGSize, insets: UIEdgeInsets, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) { + public init(size: CGSize, insets: UIEdgeInsets, headerInsets: UIEdgeInsets? = nil, scrollIndicatorInsets: UIEdgeInsets? = nil, duration: Double, curve: ListViewAnimationCurve, ensureTopInsetForOverlayHighlightedItems: CGFloat? = nil) { self.size = size self.insets = insets + self.headerInsets = headerInsets self.scrollIndicatorInsets = scrollIndicatorInsets self.duration = duration self.curve = curve From b5aa5c4308a30bcec0f38646315394ee0be4c7b8 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 12 Jan 2019 17:33:19 +0400 Subject: [PATCH 149/245] Ensure that the last index section is always visible --- Display/CollectionIndexNode.swift | 21 +++++++++++++++---- Display/ContainedViewLayoutTransition.swift | 8 +++---- Display/GridNode.swift | 23 +++++---------------- Display/ListView.swift | 1 + 4 files changed, 27 insertions(+), 26 deletions(-) diff --git a/Display/CollectionIndexNode.swift b/Display/CollectionIndexNode.swift index 2a9f996fc7..81faf5befb 100644 --- a/Display/CollectionIndexNode.swift +++ b/Display/CollectionIndexNode.swift @@ -58,9 +58,11 @@ public final class CollectionIndexNode: ASDisplayNode { var validTitles = Set() - var index = 0 + var currentIndex = 0 var displayIndex = 0 - while index < sections.count { + var addedLastTitle = false + + let addTitle: (Int) -> Void = { index in let title = sections[index] let nodeAndSize: (node: ImmediateTextNode, size: CGSize) var animate = false @@ -81,11 +83,22 @@ public final class CollectionIndexNode: ASDisplayNode { if animate { transition.animatePosition(node: nodeAndSize.node, from: previousPosition) } - - index += skipCount + + currentIndex += skipCount displayIndex += 1 } + while currentIndex < sections.count { + if currentIndex == sections.count - 1 { + addedLastTitle = true + } + addTitle(currentIndex) + } + + if !addedLastTitle && sections.count > 0 { + addTitle(sections.count - 1) + } + var removeTitles: [String] = [] for title in self.titleNodes.keys { if !validTitles.contains(title) { diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index 4464f2b10e..436e0c82e6 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -14,10 +14,10 @@ public enum ContainedViewLayoutTransitionCurve { public extension ContainedViewLayoutTransitionCurve { var timingFunction: String { switch self { - case .easeInOut: - return kCAMediaTimingFunctionEaseInEaseOut - case .spring: - return kCAMediaTimingFunctionSpring + case .easeInOut: + return kCAMediaTimingFunctionEaseInEaseOut + case .spring: + return kCAMediaTimingFunctionSpring } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 7bdec74274..8bf002e720 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -354,7 +354,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, synchronousLoads: transaction.synchronousLoads, updatingLayout: transaction.updateLayout != nil, completion: completion) + self.applyPresentationLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, synchronousLoads: transaction.synchronousLoads, updatingLayout: transaction.updateLayout != nil, completion: completion) } public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { @@ -376,7 +376,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, synchronousLoads: false, updatingLayout: false, completion: { _ in }) + self.applyPresentationLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, synchronousLoads: false, updatingLayout: false, completion: { _ in }) } } @@ -759,7 +759,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { return lowestHeaderNode } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, synchronousLoads: Bool, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) { + private func applyPresentationLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, synchronousLoads: Bool, updatingLayout: Bool, completion: (GridNodeDisplayedItemRange) -> Void) { let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate var addedNodes = false @@ -921,13 +921,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } if let offset = offset { - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } + let timingFunction = curve.timingFunction for (index, itemNode) in self.itemNodes where existingItemIndices.contains(index) { itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) @@ -994,14 +988,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } } else if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = itemTransition { - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - + let timingFunction = curve.timingFunction let contentOffset = self.scrollView.contentOffset for index in self.itemNodes.keys { diff --git a/Display/ListView.swift b/Display/ListView.swift index 43519f2f00..96a3ed2bcf 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2365,6 +2365,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel offsetFix += additionalScrollDistance self.insets = updateSizeAndInsets.insets + self.headerInsets = updateSizeAndInsets.headerInsets ?? self.insets self.scrollIndicatorInsets = updateSizeAndInsets.scrollIndicatorInsets ?? self.insets self.ensureTopInsetForOverlayHighlightedItems = updateSizeAndInsets.ensureTopInsetForOverlayHighlightedItems self.visibleSize = updateSizeAndInsets.size From a533f7dd716dd8e059b0e365e72867a9b6fb77a9 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 14 Jan 2019 01:45:39 +0400 Subject: [PATCH 150/245] Added hex string initializer for UIColor --- Display/ListView.swift | 2 +- Display/UIKitUtils.swift | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 96a3ed2bcf..1a4de39219 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2349,7 +2349,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) if let updateSizeAndInsets = updateSizeAndInsets { - if self.insets != updateSizeAndInsets.insets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { + if self.insets != updateSizeAndInsets.insets || self.headerInsets != updateSizeAndInsets.headerInsets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { let previousVisibleSize = self.visibleSize self.visibleSize = updateSizeAndInsets.size diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index ef2b94fb27..ea820e5d37 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -51,6 +51,19 @@ public extension UIColor { self.init(red: CGFloat((argb >> 16) & 0xff) / 255.0, green: CGFloat((argb >> 8) & 0xff) / 255.0, blue: CGFloat(argb & 0xff) / 255.0, alpha: CGFloat((argb >> 24) & 0xff) / 255.0) } + convenience init?(hexString: String) { + let scanner = Scanner(string: hexString) + if hexString.hasPrefix("#") { + scanner.scanLocation = 1 + } + var num: UInt32 = 0 + if scanner.scanHexInt32(&num) { + self.init(rgb: num) + } else { + return nil + } + } + var alpha: CGFloat { var alpha: CGFloat = 0.0 if self.getRed(nil, green: nil, blue: nil, alpha: &alpha) { @@ -62,6 +75,15 @@ public extension UIColor { } } + var rgb: UInt32 { + var red: CGFloat = 0.0 + var green: CGFloat = 0.0 + var blue: CGFloat = 0.0 + self.getRed(&red, green: &green, blue: &blue, alpha: nil) + + return (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) + } + var argb: UInt32 { var red: CGFloat = 0.0 var green: CGFloat = 0.0 From 1df1405bd234c7498df5f42c1bd180244afb3285 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 15 Jan 2019 19:19:28 +0400 Subject: [PATCH 151/245] Fix generateScaledImage --- Display/GenerateImage.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index f3a4bf6414..d69e2cc4ed 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -222,9 +222,9 @@ public func generateScaledImage(image: UIImage?, size: CGSize, scale: CGFloat? = return nil } - return generateImage(size, opaque: true, scale: scale, rotatedContext: { size, context in + return generateImage(size, contextGenerator: { size, context in context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - }) + }, opaque: true, scale: scale) } private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { From 2e4333212c6ecdcb3ea01b3b74b9b6a94e741ece Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 15 Jan 2019 19:57:00 +0400 Subject: [PATCH 152/245] Expose pre-collected keyboard height --- Display/DeviceMetrics.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index 8916934fff..e74cd1b80d 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -84,7 +84,7 @@ public enum DeviceMetrics: CaseIterable { } } - func standardInputHeight(inLandscape: Bool) -> CGFloat { + public func standardInputHeight(inLandscape: Bool) -> CGFloat { if inLandscape { switch self { case .iPhone4, .iPhone5: From 02bca94d9da397c147c14d61acb94cbfc9e58e75 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 16 Jan 2019 21:29:45 +0400 Subject: [PATCH 153/245] Navigation bar content clipping fix --- Display/NavigationBar.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index ee6aba5c0d..d5b6624ce2 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1013,10 +1013,10 @@ open class NavigationBar: ASDisplayNode { self.contentNode?.requestContainerLayout = { [weak self] transition in self?.requestContainerLayout(transition) } - //let transition: ContainedViewLayoutTransition = animated ? .animated(duration: 0.2, curve: .easeInOut) : .immediate if let contentNode = contentNode { + contentNode.clipsToBounds = true contentNode.layer.removeAnimation(forKey: "opacity") - self.addSubnode(contentNode) + self.insertSubnode(contentNode, belowSubnode: self.stripeNode) if animated { contentNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) } From 921e2549e0e6a946c5f3e296e7732f185a7e7b93 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 18 Jan 2019 01:44:03 +0400 Subject: [PATCH 154/245] Initial voiceover navigation support --- Display/NavigationBar.swift | 75 ++++++++++++++++++++++++++++++ Display/NavigationButtonNode.swift | 26 +++++++++++ 2 files changed, 101 insertions(+) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index d5b6624ce2..f1f87d359d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -247,6 +247,7 @@ open class NavigationBar: ASDisplayNode { didSet { if let title = self.title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor) + self.titleNode.accessibilityLabel = title if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -254,6 +255,7 @@ open class NavigationBar: ASDisplayNode { self.titleNode.removeFromSupernode() } + self.updateAccessibilityElements() self.invalidateCalculatedLayout() self.requestLayout() } @@ -281,6 +283,72 @@ open class NavigationBar: ASDisplayNode { var previousItemListenerKey: Int? var previousItemBackListenerKey: Int? + private func updateAccessibilityElements() { + /*if !self.isNodeLoaded { + return + } + var accessibilityElements: [AnyObject] = [] + + if self.leftButtonNode.supernode != nil { + accessibilityElements.append(self.leftButtonNode) + } + if self.titleNode.supernode != nil { + accessibilityElements.append(self.titleNode) + } + if let titleView = self.titleView, titleView.superview != nil { + accessibilityElements.append(titleView) + } + if self.rightButtonNode.supernode != nil { + accessibilityElements.append(self.rightButtonNode) + } + + var updated = false + if let currentAccessibilityElements = self.accessibilityElements { + if currentAccessibilityElements.count != accessibilityElements.count { + updated = true + } else { + for i in 0 ..< accessibilityElements.count { + let element = currentAccessibilityElements[i] as AnyObject + if element !== accessibilityElements[i] { + updated = true + } + } + } + } + if updated { + self.accessibilityElements = accessibilityElements + }*/ + } + + override open var accessibilityElements: [Any]? { + get { + var accessibilityElements: [Any] = [] + if self.backButtonNode.supernode != nil { + accessibilityElements.append(self.backButtonNode) + } + if self.leftButtonNode.supernode != nil { + accessibilityElements.append(self.leftButtonNode) + } + if self.titleNode.supernode != nil { + accessibilityElements.append(self.titleNode) + } + if let titleView = self.titleView, titleView.superview != nil { + accessibilityElements.append(titleView) + } + if self.rightButtonNode.supernode != nil { + accessibilityElements.append(self.rightButtonNode) + } + return accessibilityElements + } set(value) { + } + } + + override open func didLoad() { + super.didLoad() + + self.updateAccessibilityElements() + } + var _previousItem: NavigationPreviousAction? var previousItem: NavigationPreviousAction? { get { @@ -471,6 +539,7 @@ open class NavigationBar: ASDisplayNode { self.badgeNode.removeFromSupernode() } + self.updateAccessibilityElements() if animated { self.hintAnimateTitleNodeOnNextLayout = true } @@ -512,6 +581,7 @@ open class NavigationBar: ASDisplayNode { if animated { self.hintAnimateTitleNodeOnNextLayout = true } + self.updateAccessibilityElements() } private let backButtonNode: NavigationButtonNode @@ -596,6 +666,9 @@ open class NavigationBar: ASDisplayNode { self.stripeNode = ASDisplayNode() self.titleNode = ASTextNode() + self.titleNode.isAccessibilityElement = true + self.titleNode.accessibilityTraits = UIAccessibilityTraitHeader + self.backButtonNode = NavigationButtonNode() self.badgeNode = NavigationBarBadgeNode(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) self.badgeNode.isUserInteractionEnabled = false @@ -619,6 +692,7 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) + self.titleNode.accessibilityLabel = title } self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor @@ -692,6 +766,7 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) + self.titleNode.accessibilityLabel = title } self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 15a6ade0db..00647a78af 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -31,6 +31,7 @@ private final class NavigationButtonItemNode: ASTextNode { self.setEnabledListener = item.addSetEnabledListener { [weak self] value in self?.isEnabled = value } + self.accessibilityHint = item.accessibilityHint } } } @@ -123,6 +124,20 @@ private final class NavigationButtonItemNode: ASTextNode { public var pressed: () -> () = { } public var highlightChanged: (Bool) -> () = { _ in } + override public var isAccessibilityElement: Bool { + get { + return true + } set(value) { + } + } + + override public var accessibilityLabel: String? { + get { + return self.item?.accessibilityLabel + } set(value) { + } + } + override public init() { super.init() @@ -130,6 +145,8 @@ private final class NavigationButtonItemNode: ASTextNode { self.isExclusiveTouch = true self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) self.displaysAsynchronously = false + + self.accessibilityTraits = UIAccessibilityTraitButton } func updateLayout(_ constrainedSize: CGSize) -> CGSize { @@ -249,8 +266,17 @@ final class NavigationButtonNode: ASDisplayNode { } } + override public var accessibilityElements: [Any]? { + get { + return self.nodes + } set(value) { + } + } + override init() { super.init() + + self.isAccessibilityElement = false } var manualText: String { From 89114c721f462f456cc4614e91a5596f6b860f1f Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 18 Jan 2019 17:34:22 +0400 Subject: [PATCH 155/245] Voiceover updates --- Display.xcodeproj/project.pbxproj | 4 ++++ Display/Accessibility.swift | 13 +++++++++++++ Display/ListView.swift | 8 ++++++++ Display/NativeWindowHostView.swift | 16 +++++++++++++--- Display/NavigationBar.swift | 8 +++++--- Display/NavigationController.swift | 19 +++++++++++++++++-- Display/TabBarContollerNode.swift | 26 +++++++++++++++++++++----- Display/TabBarController.swift | 6 +++--- Display/WindowContent.swift | 9 +++++++++ 9 files changed, 93 insertions(+), 16 deletions(-) create mode 100644 Display/Accessibility.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 7cd9aa250a..0c4a107e9e 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -49,6 +49,7 @@ D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE31D904A000066BF65 /* GridItem.swift */; }; D01EA41B203227BA00B4B0B5 /* ViewControllerPreviewing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01EA41A203227BA00B4B0B5 /* ViewControllerPreviewing.swift */; }; + D01F728221F13891006AB634 /* Accessibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01F728121F13891006AB634 /* Accessibility.swift */; }; D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */; }; D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */; }; D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; @@ -229,6 +230,7 @@ D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; D01E2BE31D904A000066BF65 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; D01EA41A203227BA00B4B0B5 /* ViewControllerPreviewing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewControllerPreviewing.swift; sourceTree = ""; }; + D01F728121F13891006AB634 /* Accessibility.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Accessibility.swift; sourceTree = ""; }; D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedController.swift; sourceTree = ""; }; D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedControllerNode.swift; sourceTree = ""; }; D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; @@ -670,6 +672,7 @@ D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */, D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */, D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */, + D01F728121F13891006AB634 /* Accessibility.swift */, ); name = Utils; sourceTree = ""; @@ -1086,6 +1089,7 @@ D03AA4E9202E02070056C405 /* ListViewReorderingItemNode.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, + D01F728221F13891006AB634 /* Accessibility.swift in Sources */, D0FA08C42048803C00DD23FC /* TextNode.swift in Sources */, D0CA3F8C2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, diff --git a/Display/Accessibility.swift b/Display/Accessibility.swift new file mode 100644 index 0000000000..8c6f3a600a --- /dev/null +++ b/Display/Accessibility.swift @@ -0,0 +1,13 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +public func addAccessibilityChildren(of node: ASDisplayNode, to list: inout [Any]) { + if node.isAccessibilityElement { + node.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(node.bounds, node.view) + list.append(node) + } else if let accessibilityElements = node.accessibilityElements { + list.append(contentsOf: accessibilityElements) + } +} + diff --git a/Display/ListView.swift b/Display/ListView.swift index 1a4de39219..98edada894 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -3527,6 +3527,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public func forEachVisibleItemNode(_ f: (ASDisplayNode) -> Void) { + for itemNode in self.itemNodes { + if itemNode.index != nil && itemNode.frame.maxY > self.insets.top && itemNode.frame.minY < self.visibleSize.height - self.insets.bottom { + f(itemNode) + } + } + } + public func forEachItemHeaderNode(_ f: (ListViewItemHeaderNode) -> Void) { for (_, itemNode) in self.itemHeaderNodes { f(itemNode) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index c052a206c3..6cd86d8d61 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -178,6 +178,14 @@ private final class NativeWindow: UIWindow, WindowHost { var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? var forEachControllerImpl: (((ViewController) -> Void) -> Void)? + var getAccessibilityElementsImpl: (() -> [Any]?)? + + override var accessibilityElements: [Any]? { + get { + return self.getAccessibilityElementsImpl?() + } set(value) { + } + } override var frame: CGRect { get { @@ -304,9 +312,7 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { hostView?.updateSize?(size, duration) } - window.updateSize = { [weak hostView] size in - //hostView?.updateSize?(size) - assert(true) + window.updateSize = { _ in } window.layoutSubviewsEvent = { [weak hostView] in @@ -353,6 +359,10 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { hostView?.forEachController?(f) } + window.getAccessibilityElementsImpl = { [weak hostView] in + return hostView?.getAccessibilityElements?() + } + rootViewController.presentController = { [weak hostView] controller, level, animated, completion in if let hostView = hostView { hostView.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level, false, completion ?? {}) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index f1f87d359d..c80693309e 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -324,19 +324,21 @@ open class NavigationBar: ASDisplayNode { get { var accessibilityElements: [Any] = [] if self.backButtonNode.supernode != nil { - accessibilityElements.append(self.backButtonNode) + addAccessibilityChildren(of: self.backButtonNode, to: &accessibilityElements) } if self.leftButtonNode.supernode != nil { - accessibilityElements.append(self.leftButtonNode) + addAccessibilityChildren(of: self.leftButtonNode, to: &accessibilityElements) } if self.titleNode.supernode != nil { + addAccessibilityChildren(of: self.titleNode, to: &accessibilityElements) accessibilityElements.append(self.titleNode) } if let titleView = self.titleView, titleView.superview != nil { + titleView.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(titleView.bounds, titleView) accessibilityElements.append(titleView) } if self.rightButtonNode.supernode != nil { - accessibilityElements.append(self.rightButtonNode) + addAccessibilityChildren(of: self.rightButtonNode, to: &accessibilityElements) } return accessibilityElements } set(value) { diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 66ff2830ed..c7cf2f9748 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -31,6 +31,19 @@ private final class NavigationControllerView: UITracingLayerView { var navigationSeparatorView: UIView? var emptyDetailView: UIImageView? + var topControllerNode: ASDisplayNode? + + override var accessibilityElements: [Any]? { + get { + var accessibilityElements: [Any] = [] + if let topControllerNode = self.topControllerNode { + addAccessibilityChildren(of: topControllerNode, to: &accessibilityElements) + } + return accessibilityElements + } set(value) { + } + } + override init(frame: CGRect) { self.containerView = NavigationControllerContainerView() self.separatorView = UIView() @@ -107,7 +120,7 @@ open class NavigationController: UINavigationController, ContainableController, } private var _viewControllers: [ControllerRecord] = [] - open override var viewControllers: [UIViewController] { + override open var viewControllers: [UIViewController] { get { return self._viewControllers.map { $0.controller } } set(value) { @@ -115,7 +128,7 @@ open class NavigationController: UINavigationController, ContainableController, } } - open override var topViewController: UIViewController? { + override open var topViewController: UIViewController? { return self._viewControllers.last?.controller } @@ -522,6 +535,8 @@ open class NavigationController: UINavigationController, ContainableController, } } + (self.view as! NavigationControllerView).topControllerNode = (self._viewControllers.last?.controller as? ViewController)?.displayNode + for i in 0 ..< self._viewControllers.count { var currentNext: UIViewController? = (i == (self._viewControllers.count - 1)) ? nil : self._viewControllers[i + 1].controller if case .single = layoutConfiguration { diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 1eae4a433b..34100f7f5e 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -4,21 +4,37 @@ import AsyncDisplayKit final class TabBarControllerNode: ASDisplayNode { private var theme: TabBarControllerTheme let tabBarNode: TabBarNode + private let navigationBar: NavigationBar? private var toolbarNode: ToolbarNode? private let toolbarActionSelected: (Bool) -> Void - var currentControllerView: UIView? { + var currentControllerNode: ASDisplayNode? { didSet { - oldValue?.removeFromSuperview() + oldValue?.removeFromSupernode() - if let currentControllerView = self.currentControllerView { - self.view.insertSubview(currentControllerView, at: 0) + if let currentControllerNode = self.currentControllerNode { + self.insertSubnode(currentControllerNode, at: 0) } } } - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool) -> Void, toolbarActionSelected: @escaping (Bool) -> Void) { + override var accessibilityElements: [Any]? { + get { + var accessibilityElements: [Any] = [] + if let navigationBar = self.navigationBar { + addAccessibilityChildren(of: navigationBar, to: &accessibilityElements) + } + if let currentControllerNode = self.currentControllerNode { + addAccessibilityChildren(of: currentControllerNode, to: &accessibilityElements) + } + return accessibilityElements + } set(value) { + } + } + + init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool) -> Void, toolbarActionSelected: @escaping (Bool) -> Void) { self.theme = theme + self.navigationBar = navigationBar self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) self.toolbarActionSelected = toolbarActionSelected diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index eb6fc43e92..1a73baf619 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -87,7 +87,7 @@ open class TabBarController: ViewController { private var debugTapCounter: (Double, Int) = (0.0, 0) override open func loadDisplayNode() { - self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index, longTap in + self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap in if let strongSelf = self { if strongSelf.selectedIndex == index { let timestamp = CACurrentMediaTime() @@ -144,7 +144,7 @@ open class TabBarController: ViewController { if let currentController = self.currentController { currentController.willMove(toParentViewController: nil) - self.tabBarControllerNode.currentControllerView = nil + self.tabBarControllerNode.currentControllerNode = nil currentController.removeFromParentViewController() currentController.didMove(toParentViewController: nil) @@ -158,7 +158,7 @@ open class TabBarController: ViewController { var displayNavigationBar = false if let currentController = self.currentController { currentController.willMove(toParentViewController: self) - self.tabBarControllerNode.currentControllerView = currentController.view + self.tabBarControllerNode.currentControllerNode = currentController.displayNode currentController.navigationBar?.isHidden = true self.addChildViewController(currentController) currentController.didMove(toParentViewController: self) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 396515f705..083de9d572 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -220,6 +220,7 @@ public final class WindowHostView { var invalidatePreferNavigationUIHidden: (() -> Void)? var cancelInteractiveKeyboardGestures: (() -> Void)? var forEachController: (((ViewController) -> Void) -> Void)? + var getAccessibilityElements: (() -> [Any]?)? init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { self.containerView = containerView @@ -323,6 +324,10 @@ public class Window1 { private var isInteractionBlocked = false + private var accessibilityElements: [Any]? { + return self.viewController?.view.accessibilityElements + } + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -407,6 +412,10 @@ public class Window1 { }) } + self.hostView.getAccessibilityElements = { [weak self] in + return self?.accessibilityElements + } + self.presentationContext.view = self.hostView.containerView self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) From 7a7f4bb5864e3bc5023ef1048b4286d674593716 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 22 Jan 2019 21:05:44 +0300 Subject: [PATCH 156/245] GridNode: exposed visible content offset --- Display/GridNode.swift | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 8bf002e720..196bc1a4d2 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -1,6 +1,12 @@ import Foundation import AsyncDisplayKit +public enum GridNodeVisibleContentOffset { + case known(CGFloat) + case unknown + case none +} + public struct GridNodeInsertItem { public let index: Int public let item: GridItem @@ -216,6 +222,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? public var scrollingInitiated: (() -> Void)? public var scrollingCompleted: (() -> Void)? + public var visibleContentOffsetChanged: (GridNodeVisibleContentOffset) -> Void = { _ in } public final var floatingSections = false @@ -359,24 +366,28 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { self.updateItemNodeVisibilititesAndScrolling() + self.updateVisibleContentOffset() self.scrollingInitiated?() } public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { if !decelerate { self.updateItemNodeVisibilititesAndScrolling() + self.updateVisibleContentOffset() self.scrollingCompleted?() } } public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { self.updateItemNodeVisibilititesAndScrolling() + self.updateVisibleContentOffset() self.scrollingCompleted?() } public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { self.applyPresentationLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, synchronousLoads: false, updatingLayout: false, completion: { _ in }) + self.updateVisibleContentOffset() } } @@ -1064,6 +1075,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { completion(self.displayedItemRange()) self.updateItemNodeVisibilititesAndScrolling() + self.updateVisibleContentOffset() if let visibleItemsUpdated = self.visibleItemsUpdated { if presentationLayoutTransition.layout.items.count != 0 { @@ -1160,6 +1172,29 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + public func visibleContentOffset() -> GridNodeVisibleContentOffset { + var offset: GridNodeVisibleContentOffset = .unknown + + if let supernode = self.supernode { + var topItemIndexAndFrame: (Int, CGRect) = (-1, CGRect()) + for index in self.itemNodes.keys.sorted() { + let itemNode = self.itemNodes[index]! + topItemIndexAndFrame = (index, supernode.convert(itemNode.bounds, from: itemNode)) + break + } + if topItemIndexAndFrame.0 == 0 { + offset = .known(self.scrollView.contentOffset.y + self.scrollView.contentInset.top) + } else if topItemIndexAndFrame.0 == -1 { + offset = .none + } + } + return offset + } + + private func updateVisibleContentOffset() { + self.visibleContentOffsetChanged(self.visibleContentOffset()) + } + public func forEachItemNode(_ f: (ASDisplayNode) -> Void) { for (_, node) in self.itemNodes { f(node) From 4f7d5ef987e1947a50c52ae2010484a60768111b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 27 Jan 2019 16:39:18 +0300 Subject: [PATCH 157/245] Added HSV getter on UIColor --- Display/UIKitUtils.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index ea820e5d37..b08054a641 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -94,6 +94,17 @@ public extension UIColor { return (UInt32(alpha * 255.0) << 24) | (UInt32(red * 255.0) << 16) | (UInt32(green * 255.0) << 8) | (UInt32(blue * 255.0)) } + var hsv: (CGFloat, CGFloat, CGFloat) { + var hue: CGFloat = 0.0 + var saturation: CGFloat = 0.0 + var value: CGFloat = 0.0 + if self.getHue(&hue, saturation: &saturation, brightness: &value, alpha: nil) { + return (hue, saturation, value) + } else { + return (0.0, 0.0, 0.0) + } + } + func withMultipliedBrightnessBy(_ factor: CGFloat) -> UIColor { var hue: CGFloat = 0.0 var saturation: CGFloat = 0.0 From 331889247469b2f560c0854421f445c395e49a7b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 28 Jan 2019 17:57:58 +0300 Subject: [PATCH 158/245] GridNode: Added option to provide additional offset when scrolling to item --- Display/GridNode.swift | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 196bc1a4d2..96f200f009 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -32,9 +32,9 @@ public struct GridNodeUpdateItem { } public enum GridNodeScrollToItemPosition { - case top - case bottom - case center + case top(CGFloat) + case bottom(CGFloat) + case center(CGFloat) case visible } @@ -356,7 +356,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { updateLayoutTransition = scrollToItem.transition } } else if previousLayoutWasEmpty { - generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .immediate, directionHint: .up, adjustForSection: true, adjustForTopInset: true) + generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top(0.0), transition: .immediate, directionHint: .up, adjustForSection: true, adjustForTopInset: true) } else { generatedScrollToItem = nil } @@ -640,12 +640,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var verticalOffset: CGFloat = self.scrollView.contentOffset.y switch scrollToItem.position { - case .top: - verticalOffset = itemFrame.frame.minY + additionalOffset - case .center: - verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + additionalOffset - case .bottom: - verticalOffset = itemFrame.frame.maxY - displayHeight + additionalOffset + case let .top(offset): + verticalOffset = itemFrame.frame.minY + additionalOffset + offset + case let .center(offset): + verticalOffset = floor(itemFrame.frame.minY + itemFrame.frame.size.height / 2.0 - displayHeight / 2.0 - self.gridLayout.insets.top) + additionalOffset + offset + case let .bottom(offset): + verticalOffset = itemFrame.frame.maxY - displayHeight + additionalOffset + offset case .visible: if verticalOffset + self.gridLayout.insets.top > itemFrame.frame.minY { //verticalOffset = -self.gridLayout.insets.top + itemFrame.frame.minY From 709df1faeffdd6fefc72bb8a1853b65a5e803bdc Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Tue, 29 Jan 2019 14:00:58 +0400 Subject: [PATCH 159/245] Fix replaceAllButRootController when not initialized --- Display/NavigationController.swift | 32 ++++++++++++++++++++---------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index c7cf2f9748..0d99faae43 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -764,7 +764,11 @@ open class NavigationController: UINavigationController, ContainableController, let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) controller.containerLayoutUpdated(appliedLayout, transition: .immediate) strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in - if let strongSelf = self, let validLayout = strongSelf.validLayout { + guard let strongSelf = self else { + return + } + + if let validLayout = strongSelf.validLayout { let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) @@ -813,13 +817,19 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { self.view.endEditing(true) - if let validLayout = self.validLayout { - var (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) - controllerLayout.inputHeight = nil - controller.containerLayoutUpdated(controllerLayout, transition: .immediate) - } - self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in - if let strongSelf = self { + self.scheduleAfterLayout { [weak self] in + guard let strongSelf = self else { + return + } + if let validLayout = strongSelf.validLayout { + var (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + controllerLayout.inputHeight = nil + controller.containerLayoutUpdated(controllerLayout, transition: .immediate) + } + strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + guard let strongSelf = self else { + return + } ready?.set(true) var controllers = strongSelf.viewControllers while controllers.count > 1 { @@ -828,8 +838,8 @@ open class NavigationController: UINavigationController, ContainableController, controllers.append(controller) strongSelf.setViewControllers(controllers, animated: animated) completion() - } - })) + })) + } } public func popToRoot(animated: Bool) { @@ -993,7 +1003,7 @@ open class NavigationController: UINavigationController, ContainableController, } private func scheduleAfterLayout(_ f: @escaping () -> Void) { - (self.view as? UITracingLayerView)?.schedule(layout: { [weak self] in + (self.view as? UITracingLayerView)?.schedule(layout: { f() }) self.view.setNeedsLayout() From 076af3926c4a5d55bcad4ffece41464059c891d9 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 1 Feb 2019 14:40:39 +0400 Subject: [PATCH 160/245] Universal presentation --- Display/ContainableController.swift | 10 +++++ .../GlobalOverlayPresentationContext.swift | 16 ++++---- Display/KeyShortcutsController.swift | 4 +- Display/NativeWindowHostView.swift | 12 +++--- Display/NavigationController.swift | 8 ++++ Display/PresentationContext.swift | 40 ++++++++++--------- Display/WindowContent.swift | 35 ++++++++-------- 7 files changed, 72 insertions(+), 53 deletions(-) diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index d0a6f0f0cb..055fa46254 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -1,10 +1,20 @@ import UIKit import AsyncDisplayKit +import SwiftSignalKit public protocol ContainableController: class { var view: UIView! { get } + var isViewLoaded: Bool { get } + var isOpaqueWhenInOverlay: Bool { get } + var ready: Promise { get } func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations + var deferScreenEdgeGestures: UIRectEdge { get } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) + + func viewWillAppear(_ animated: Bool) + func viewWillDisappear(_ animated: Bool) + func viewDidAppear(_ animated: Bool) + func viewDidDisappear(_ animated: Bool) } diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index 75eafc370c..c033cccae8 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -25,7 +25,7 @@ private func isViewVisibleInHierarchy(_ view: UIView, _ initial: Bool = true) -> final class GlobalOverlayPresentationContext { private let statusBarHost: StatusBarHost? - private var controllers: [ViewController] = [] + private var controllers: [ContainableController] = [] private var presentationDisposables = DisposableSet() private var layout: ContainerViewLayout? @@ -49,7 +49,7 @@ final class GlobalOverlayPresentationContext { return nil } - func present(_ controller: ViewController) { + func present(_ controller: ContainableController) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -68,12 +68,12 @@ final class GlobalOverlayPresentationContext { strongSelf.controllers.append(controller) if let view = strongSelf.currentPresentationView(), let layout = strongSelf.layout { - controller.navigation_setDismiss({ [weak controller] in + (controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in if let strongSelf = self, let controller = controller { strongSelf.dismiss(controller) } }, rootController: nil) - controller.setIgnoreAppearanceMethodInvocations(true) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) view.addSubview(controller.view) @@ -81,7 +81,7 @@ final class GlobalOverlayPresentationContext { } else { view.addSubview(controller.view) } - controller.setIgnoreAppearanceMethodInvocations(false) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) @@ -97,7 +97,7 @@ final class GlobalOverlayPresentationContext { self.presentationDisposables.dispose() } - private func dismiss(_ controller: ViewController) { + private func dismiss(_ controller: ContainableController) { if let index = self.controllers.index(where: { $0 === controller }) { self.controllers.remove(at: index) controller.viewWillDisappear(false) @@ -158,11 +158,11 @@ final class GlobalOverlayPresentationContext { return nil } - func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) for controller in self.controllers { - mask = mask.intersection(controller.supportedOrientations) + mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock)) } return mask diff --git a/Display/KeyShortcutsController.swift b/Display/KeyShortcutsController.swift index 9e9d321269..84ce20c38d 100644 --- a/Display/KeyShortcutsController.swift +++ b/Display/KeyShortcutsController.swift @@ -6,7 +6,7 @@ public protocol KeyShortcutResponder { public class KeyShortcutsController: UIResponder { private var effectiveShortcuts: [KeyShortcut]? - private var viewControllerEnumerator: ((ViewController) -> Bool) -> Void + private var viewControllerEnumerator: ((ContainableController) -> Bool) -> Void public static var isAvailable: Bool { if #available(iOSApplicationExtension 8.0, *), UIDevice.current.userInterfaceIdiom == .pad { @@ -16,7 +16,7 @@ public class KeyShortcutsController: UIResponder { } } - public init(enumerator: @escaping ((ViewController) -> Bool) -> Void) { + public init(enumerator: @escaping ((ContainableController) -> Bool) -> Void) { self.viewControllerEnumerator = enumerator super.init() } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 6cd86d8d61..4e853a0bb1 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -170,14 +170,14 @@ private final class NativeWindow: UIWindow, WindowHost { var layoutSubviewsEvent: (() -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? var updateToInterfaceOrientation: (() -> Void)? - var presentController: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? - var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)? + var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? + var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? var presentNativeImpl: ((UIViewController) -> Void)? var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? - var forEachControllerImpl: (((ViewController) -> Void) -> Void)? + var forEachControllerImpl: (((ContainableController) -> Void) -> Void)? var getAccessibilityElementsImpl: (() -> [Any]?)? override var accessibilityElements: [Any]? { @@ -256,11 +256,11 @@ private final class NativeWindow: UIWindow, WindowHost { self.updateToInterfaceOrientation?() }*/ - func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { + func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { self.presentController?(controller, level, blockInteraction, completion) } - func presentInGlobalOverlay(_ controller: ViewController) { + func presentInGlobalOverlay(_ controller: ContainableController) { self.presentControllerInGlobalOverlay?(controller) } @@ -284,7 +284,7 @@ private final class NativeWindow: UIWindow, WindowHost { self.cancelInteractiveKeyboardGesturesImpl?() } - func forEachController(_ f: (ViewController) -> Void) { + func forEachController(_ f: (ContainableController) -> Void) { self.forEachControllerImpl?(f) } } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 0d99faae43..b40e5656fa 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -95,6 +95,14 @@ public enum NavigationControllerMode { } open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { + public var isOpaqueWhenInOverlay: Bool = true + + public var ready: Promise = Promise(true) + + public var lockOrientation: Bool = false + + public var deferScreenEdgeGestures: UIRectEdge = UIRectEdge() + private let mode: NavigationControllerMode private var theme: NavigationControllerTheme diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 360bbb5c62..39a8922f40 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -39,7 +39,7 @@ final class PresentationContext { return self.view != nil && self.layout != nil } - private(set) var controllers: [(ViewController, PresentationSurfaceLevel)] = [] + private(set) var controllers: [(ContainableController, PresentationSurfaceLevel)] = [] private var presentationDisposables = DisposableSet() @@ -47,8 +47,8 @@ final class PresentationContext { var isCurrentlyOpaque: Bool { for (controller, _) in self.controllers { - if controller.isOpaqueWhenInOverlay && controller.isNodeLoaded { - if traceIsOpaque(layer: controller.displayNode.layer, rect: controller.displayNode.bounds) { + if controller.isOpaqueWhenInOverlay && controller.isViewLoaded { + if traceIsOpaque(layer: controller.view.layer, rect: controller.view.bounds) { return true } } @@ -57,7 +57,7 @@ final class PresentationContext { } private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? { - var topController: ViewController? + var topController: ContainableController? for (controller, controllerLevel) in self.controllers.reversed() { if !controller.isViewLoaded || controller.view.superview == nil { continue @@ -97,7 +97,7 @@ final class PresentationContext { } } - public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { + public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void) { let controllerReady = controller.ready.get() |> filter({ $0 }) |> take(1) @@ -105,15 +105,17 @@ final class PresentationContext { |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) if let _ = self.view, let initialLayout = self.layout { - if controller.lockOrientation { - let orientations: UIInterfaceOrientationMask - if initialLayout.size.width < initialLayout.size.height { - orientations = .portrait - } else { - orientations = .landscape + if let controller = controller as? ViewController { + if controller.lockOrientation { + let orientations: UIInterfaceOrientationMask + if initialLayout.size.width < initialLayout.size.height { + orientations = .portrait + } else { + orientations = .landscape + } + + controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations) } - - controller.supportedOrientations = ViewControllerSupportedOrientations(regularSize: orientations, compactSize: orientations) } controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) @@ -145,12 +147,12 @@ final class PresentationContext { } strongSelf.controllers.insert((controller, level), at: insertIndex ?? strongSelf.controllers.count) if let view = strongSelf.view, let layout = strongSelf.layout { - controller.navigation_setDismiss({ [weak controller] in + (controller as? UIViewController)?.navigation_setDismiss({ [weak controller] in if let strongSelf = self, let controller = controller { strongSelf.dismiss(controller) } }, rootController: nil) - controller.setIgnoreAppearanceMethodInvocations(true) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) if let topLevelSubview = strongSelf.topLevelSubview(for: level) { @@ -166,7 +168,7 @@ final class PresentationContext { view.addSubview(controller.view) } } - controller.setIgnoreAppearanceMethodInvocations(false) + (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) @@ -182,7 +184,7 @@ final class PresentationContext { self.presentationDisposables.dispose() } - private func dismiss(_ controller: ViewController) { + private func dismiss(_ controller: ContainableController) { if let index = self.controllers.index(where: { $0.0 === controller }) { self.controllers.remove(at: index) controller.viewWillDisappear(false) @@ -247,11 +249,11 @@ final class PresentationContext { return nil } - func combinedSupportedOrientations() -> ViewControllerSupportedOrientations { + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) for (controller, _) in self.controllers { - mask = mask.intersection(controller.supportedOrientations) + mask = mask.intersection(controller.combinedSupportedOrientations(currentOrientationToLock: currentOrientationToLock)) } return mask diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 083de9d572..dfdf9b6899 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -208,8 +208,8 @@ public final class WindowHostView { let updateDeferScreenEdgeGestures: (UIRectEdge) -> Void let updatePreferNavigationUIHidden: (Bool) -> Void - var present: ((ViewController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? - var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)? + var present: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? + var presentInGlobalOverlay: ((_ controller: ContainableController) -> Void)? var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize, Double) -> Void)? var layoutSubviews: (() -> Void)? @@ -219,7 +219,7 @@ public final class WindowHostView { var invalidateDeferScreenEdgeGesture: (() -> Void)? var invalidatePreferNavigationUIHidden: (() -> Void)? var cancelInteractiveKeyboardGestures: (() -> Void)? - var forEachController: (((ViewController) -> Void) -> Void)? + var forEachController: (((ContainableController) -> Void) -> Void)? var getAccessibilityElements: (() -> [Any]?)? init(containerView: UIView, eventView: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void, updatePreferNavigationUIHidden: @escaping (Bool) -> Void) { @@ -246,9 +246,9 @@ public struct WindowTracingTags { } public protocol WindowHost { - func forEachController(_ f: (ViewController) -> Void) - func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) - func presentInGlobalOverlay(_ controller: ViewController) + func forEachController(_ f: (ContainableController) -> Void) + func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) + func presentInGlobalOverlay(_ controller: ContainableController) func invalidateDeferScreenEdgeGestures() func invalidatePreferNavigationUIHidden() func cancelInteractiveKeyboardGestures() @@ -742,18 +742,17 @@ public class Window1 { keyboardManager.surfaces = keyboardSurfaces var supportedOrientations = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) + let orientationToLock: UIInterfaceOrientationMask + if self.windowLayout.size.width < self.windowLayout.size.height { + orientationToLock = .portrait + } else { + orientationToLock = .landscape + } if let _rootController = self._rootController { - let orientationToLock: UIInterfaceOrientationMask - if self.windowLayout.size.width < self.windowLayout.size.height { - orientationToLock = .portrait - } else { - orientationToLock = .landscape - } - supportedOrientations = supportedOrientations.intersection(_rootController.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) } - supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations()) - supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations()) + supportedOrientations = supportedOrientations.intersection(self.presentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) + supportedOrientations = supportedOrientations.intersection(self.overlayPresentationContext.combinedSupportedOrientations(currentOrientationToLock: orientationToLock)) var resolvedOrientations: UIInterfaceOrientationMask switch self.windowLayout.metrics.widthClass { @@ -904,11 +903,11 @@ public class Window1 { } } - public func present(_ controller: ViewController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { + public func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { self.presentationContext.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } - public func presentInGlobalOverlay(_ controller: ViewController) { + public func presentInGlobalOverlay(_ controller: ContainableController) { self.overlayPresentationContext.present(controller) } @@ -1009,7 +1008,7 @@ public class Window1 { return false } - public func forEachViewController(_ f: (ViewController) -> Bool) { + public func forEachViewController(_ f: (ContainableController) -> Bool) { for (controller, _) in self.presentationContext.controllers { if !f(controller) { break From 275fd019647a2c1e7940d97458bde38a82f34137 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 8 Feb 2019 17:55:06 +0400 Subject: [PATCH 161/245] Accessibility tests --- Display/Accessibility.swift | 14 ++++-- Display/ChildWindowHostView.swift | 68 ++++++++++++++++++++++++------ Display/ListView.swift | 21 ++++++++- Display/NativeWindowHostView.swift | 4 +- Display/NavigationBar.swift | 8 ++-- Display/NavigationController.swift | 6 +-- Display/TabBarContollerNode.swift | 10 ++--- Display/TabBarController.swift | 39 ++++++++++++++++- Display/TabBarNode.swift | 46 ++++++++++++-------- Display/UINavigationItem+Proxy.h | 3 ++ Display/UINavigationItem+Proxy.m | 9 ++++ Display/ViewController.swift | 22 +++++++--- Display/WindowContent.swift | 8 ++-- 13 files changed, 195 insertions(+), 63 deletions(-) diff --git a/Display/Accessibility.swift b/Display/Accessibility.swift index 8c6f3a600a..77a8835a7f 100644 --- a/Display/Accessibility.swift +++ b/Display/Accessibility.swift @@ -2,10 +2,18 @@ import Foundation import UIKit import AsyncDisplayKit -public func addAccessibilityChildren(of node: ASDisplayNode, to list: inout [Any]) { +public func addAccessibilityChildren(of node: ASDisplayNode, container: Any, to list: inout [Any]) { if node.isAccessibilityElement { - node.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(node.bounds, node.view) - list.append(node) + let element = UIAccessibilityElement(accessibilityContainer: container) + element.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(node.bounds, node.view) + element.accessibilityLabel = node.accessibilityLabel + element.accessibilityValue = node.accessibilityValue + element.accessibilityTraits = node.accessibilityTraits + element.accessibilityHint = node.accessibilityHint + element.accessibilityIdentifier = node.accessibilityIdentifier + + //node.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(node.bounds, node.view) + list.append(element) } else if let accessibilityElements = node.accessibilityElements { list.append(contentsOf: accessibilityElements) } diff --git a/Display/ChildWindowHostView.swift b/Display/ChildWindowHostView.swift index f489eab8de..3789c0dd71 100644 --- a/Display/ChildWindowHostView.swift +++ b/Display/ChildWindowHostView.swift @@ -1,10 +1,16 @@ import Foundation import UIKit -private final class ChildWindowHostView: UIView { +private final class ChildWindowHostView: UIView, WindowHost { var updateSize: ((CGSize) -> Void)? var layoutSubviewsEvent: (() -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? + var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? + var invalidateDeferScreenEdgeGestureImpl: (() -> Void)? + var invalidatePreferNavigationUIHiddenImpl: (() -> Void)? + var cancelInteractiveKeyboardGesturesImpl: (() -> Void)? + var forEachControllerImpl: (((ContainableController) -> Void) -> Void)? + var getAccessibilityElementsImpl: (() -> [Any]?)? override var frame: CGRect { didSet { @@ -23,6 +29,29 @@ private final class ChildWindowHostView: UIView { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { return self.hitTestImpl?(point, event) } + + func invalidateDeferScreenEdgeGestures() { + self.invalidateDeferScreenEdgeGestureImpl?() + } + + func invalidatePreferNavigationUIHidden() { + self.invalidatePreferNavigationUIHiddenImpl?() + } + + func cancelInteractiveKeyboardGestures() { + self.cancelInteractiveKeyboardGesturesImpl?() + } + + func forEachController(_ f: (ContainableController) -> Void) { + self.forEachControllerImpl?(f) + } + + func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { + self.presentController?(controller, level, blockInteraction, completion) + } + + func presentInGlobalOverlay(_ controller: ContainableController) { + } } public func childWindowHostView(parent: UIView) -> WindowHostView { @@ -50,13 +79,13 @@ public func childWindowHostView(parent: UIView) -> WindowHostView { window.updateToInterfaceOrientation = { [weak hostView] in hostView?.updateToInterfaceOrientation?() + }*/ + + view.presentController = { [weak hostView] controller, level, block, f in + hostView?.present?(controller, level, block, f) } - window.presentController = { [weak hostView] controller, level in - hostView?.present?(controller, level) - } - - window.presentNativeImpl = { [weak hostView] controller in + /*view.presentNativeImpl = { [weak hostView] controller in hostView?.presentNative?(controller) }*/ @@ -64,14 +93,25 @@ public func childWindowHostView(parent: UIView) -> WindowHostView { return hostView?.hitTest?(point, event) } - /*rootViewController.presentController = { [weak hostView] controller, level, animated, completion in - if let strongSelf = hostView { - strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level) - if let completion = completion { - completion() - } - } - }*/ + view.invalidateDeferScreenEdgeGestureImpl = { [weak hostView] in + return hostView?.invalidateDeferScreenEdgeGesture?() + } + + view.invalidatePreferNavigationUIHiddenImpl = { [weak hostView] in + return hostView?.invalidatePreferNavigationUIHidden?() + } + + view.cancelInteractiveKeyboardGesturesImpl = { [weak hostView] in + hostView?.cancelInteractiveKeyboardGestures?() + } + + view.forEachControllerImpl = { [weak hostView] f in + hostView?.forEachController?(f) + } + + view.getAccessibilityElementsImpl = { [weak hostView] in + return hostView?.getAccessibilityElements?() + } return hostView } diff --git a/Display/ListView.swift b/Display/ListView.swift index 98edada894..a55ce03e3e 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -247,6 +247,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private var reorderFeedbackDisposable: MetaDisposable? private let waitingForNodesDisposable = MetaDisposable() + + override open var accessibilityElements: [Any]? { + get { + var accessibilityElements: [Any] = [] + self.forEachItemNode({ itemNode in + addAccessibilityChildren(of: itemNode, container: self, to: &accessibilityElements) + }) + return accessibilityElements + } set(value) { + } + } override public init() { class DisplayLinkProxy: NSObject { @@ -266,6 +277,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel super.init() + self.isAccessibilityContainer = true + self.setViewBlock({ () -> UIView in return ListViewBackingView() }) @@ -1552,7 +1565,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let startTime = CACurrentMediaTime() self?.recursivelyEnsureDisplaySynchronously(true) let deltaTime = CACurrentMediaTime() - startTime - print("ListView: waited \(deltaTime * 1000.0) ms for nodes to display") + if false { + print("ListView: waited \(deltaTime * 1000.0) ms for nodes to display") + } } completion() }) @@ -1566,7 +1581,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel let startTime = CACurrentMediaTime() self.waitingForNodesDisposable.set(readyWithTimeout.start(completed: { let deltaTime = CACurrentMediaTime() - startTime - print("ListView: waited \(deltaTime * 1000.0) ms for nodes to load") + if false { + print("ListView: waited \(deltaTime * 1000.0) ms for nodes to load") + } beginReplay() })) } else { diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 4e853a0bb1..d6c15c4efe 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -180,12 +180,12 @@ private final class NativeWindow: UIWindow, WindowHost { var forEachControllerImpl: (((ContainableController) -> Void) -> Void)? var getAccessibilityElementsImpl: (() -> [Any]?)? - override var accessibilityElements: [Any]? { + /*override var accessibilityElements: [Any]? { get { return self.getAccessibilityElementsImpl?() } set(value) { } - } + }*/ override var frame: CGRect { get { diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index c80693309e..546f231b3f 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -324,13 +324,13 @@ open class NavigationBar: ASDisplayNode { get { var accessibilityElements: [Any] = [] if self.backButtonNode.supernode != nil { - addAccessibilityChildren(of: self.backButtonNode, to: &accessibilityElements) + addAccessibilityChildren(of: self.backButtonNode, container: self, to: &accessibilityElements) } if self.leftButtonNode.supernode != nil { - addAccessibilityChildren(of: self.leftButtonNode, to: &accessibilityElements) + addAccessibilityChildren(of: self.leftButtonNode, container: self, to: &accessibilityElements) } if self.titleNode.supernode != nil { - addAccessibilityChildren(of: self.titleNode, to: &accessibilityElements) + addAccessibilityChildren(of: self.titleNode, container: self, to: &accessibilityElements) accessibilityElements.append(self.titleNode) } if let titleView = self.titleView, titleView.superview != nil { @@ -338,7 +338,7 @@ open class NavigationBar: ASDisplayNode { accessibilityElements.append(titleView) } if self.rightButtonNode.supernode != nil { - addAccessibilityChildren(of: self.rightButtonNode, to: &accessibilityElements) + addAccessibilityChildren(of: self.rightButtonNode, container: self, to: &accessibilityElements) } return accessibilityElements } set(value) { diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index b40e5656fa..666d7e1aa4 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -33,16 +33,16 @@ private final class NavigationControllerView: UITracingLayerView { var topControllerNode: ASDisplayNode? - override var accessibilityElements: [Any]? { + /*override var accessibilityElements: [Any]? { get { var accessibilityElements: [Any] = [] if let topControllerNode = self.topControllerNode { - addAccessibilityChildren(of: topControllerNode, to: &accessibilityElements) + addAccessibilityChildren(of: topControllerNode, container: self, to: &accessibilityElements) } return accessibilityElements } set(value) { } - } + }*/ override init(frame: CGRect) { self.containerView = NavigationControllerContainerView() diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 34100f7f5e..f3ba582203 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -18,21 +18,21 @@ final class TabBarControllerNode: ASDisplayNode { } } - override var accessibilityElements: [Any]? { + /*override var accessibilityElements: [Any]? { get { var accessibilityElements: [Any] = [] if let navigationBar = self.navigationBar { - addAccessibilityChildren(of: navigationBar, to: &accessibilityElements) + addAccessibilityChildren(of: navigationBar, container: self, to: &accessibilityElements) } if let currentControllerNode = self.currentControllerNode { - addAccessibilityChildren(of: currentControllerNode, to: &accessibilityElements) + addAccessibilityChildren(of: currentControllerNode, container: self, to: &accessibilityElements) } return accessibilityElements } set(value) { } - } + }*/ - init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool) -> Void, toolbarActionSelected: @escaping (Bool) -> Void) { + init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, toolbarActionSelected: @escaping (Bool) -> Void) { self.theme = theme self.navigationBar = navigationBar self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 1a73baf619..69ee1c55a3 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -25,6 +25,38 @@ public final class TabBarControllerTheme { } } +public final class TabBarItemInfo: NSObject { + public let previewing: Bool + + public init(previewing: Bool) { + self.previewing = previewing + + super.init() + } + + override public func isEqual(_ object: Any?) -> Bool { + if let object = object as? TabBarItemInfo { + if self.previewing != object.previewing { + return false + } + return true + } else { + return false + } + } + + public static func ==(lhs: TabBarItemInfo, rhs: TabBarItemInfo) -> Bool { + if lhs.previewing != rhs.previewing { + return false + } + return true + } +} + +public protocol TabBarContainedController { + func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) +} + open class TabBarController: ViewController { private var validLayout: ContainerViewLayout? @@ -87,8 +119,13 @@ open class TabBarController: ViewController { private var debugTapCounter: (Double, Int) = (0.0, 0) override open func loadDisplayNode() { - self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap in + self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in if let strongSelf = self { + if longTap, let controller = strongSelf.controllers[index] as? TabBarContainedController { + controller.presentTabBarPreviewingController(sourceNodes: itemNodes) + return + } + if strongSelf.selectedIndex == index { let timestamp = CACurrentMediaTime() if strongSelf.debugTapCounter.0 < timestamp - 0.4 { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 9ac68cc438..15856a6c7c 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import AsyncDisplayKit +import SwiftSignalKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool) -> (UIImage, CGFloat) { @@ -24,14 +25,14 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: let size: CGSize let contentWidth: CGFloat if horizontal { - size = CGSize(width: ceil(titleSize.width) + horizontalSpacing + imageSize.width, height: 34.0) + size = CGSize(width: max(1.0, ceil(titleSize.width) + horizontalSpacing + imageSize.width), height: 34.0) contentWidth = size.width } else { - size = CGSize(width: max(ceil(titleSize.width), imageSize.width), height: 45.0) + size = CGSize(width: max(1.0, max(ceil(titleSize.width), imageSize.width), 1.0), height: 45.0) contentWidth = imageSize.width } - UIGraphicsBeginImageContextWithOptions(size, true, 0.0) + UIGraphicsBeginImageContextWithOptions(size, false, 0.0) if let context = UIGraphicsGetCurrentContext() { context.setFillColor(backgroundColor.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) @@ -43,9 +44,13 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: context.translateBy(x: imageRect.midX, y: imageRect.midY) context.scaleBy(x: 1.0, y: -1.0) context.translateBy(x: -imageRect.midX, y: -imageRect.midY) - context.clip(to: imageRect, mask: image.cgImage!) - context.setFillColor(tintColor.cgColor) - context.fill(imageRect) + if image.renderingMode == .alwaysOriginal { + context.draw(image.cgImage!, in: imageRect) + } else { + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + } context.restoreGState() } else { let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize) @@ -53,9 +58,13 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: context.translateBy(x: imageRect.midX, y: imageRect.midY) context.scaleBy(x: 1.0, y: -1.0) context.translateBy(x: -imageRect.midX, y: -imageRect.midY) - context.clip(to: imageRect, mask: image.cgImage!) - context.setFillColor(tintColor.cgColor) - context.fill(imageRect) + if image.renderingMode == .alwaysOriginal { + context.draw(image.cgImage!, in: imageRect) + } else { + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(tintColor.cgColor) + context.fill(imageRect) + } context.restoreGState() } } @@ -67,10 +76,10 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) } - let image = UIGraphicsGetImageFromCurrentImageContext() + let resultImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() - return (image!, contentWidth) + return (resultImage!, contentWidth) } private let badgeFont = Font.regular(13.0) @@ -174,7 +183,7 @@ class TabBarNode: ASDisplayNode { } } - private let itemSelected: (Int, Bool) -> Void + private let itemSelected: (Int, Bool, [ASDisplayNode]) -> Void private var theme: TabBarControllerTheme private var validLayout: (CGSize, CGFloat, CGFloat, CGFloat)? @@ -185,7 +194,7 @@ class TabBarNode: ASDisplayNode { let separatorNode: ASDisplayNode private var tabBarNodeContainers: [TabBarNodeContainer] = [] - init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool) -> Void) { + init(theme: TabBarControllerTheme, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void) { self.itemSelected = itemSelected self.theme = theme @@ -259,11 +268,11 @@ class TabBarNode: ASDisplayNode { self?.updateNodeImage(i, layout: true) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { - let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) node.image = image node.contentWidth = contentWidth } else { - let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) node.image = image node.contentWidth = contentWidth } @@ -288,11 +297,11 @@ class TabBarNode: ASDisplayNode { let previousImage = node.image if let selectedIndex = self.selectedIndex, selectedIndex == index { - let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) node.image = image node.contentWidth = contentWidth } else { - let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: self.theme.tabBarBackgroundColor, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) + let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) node.image = image node.contentWidth = contentWidth } @@ -400,7 +409,8 @@ class TabBarNode: ASDisplayNode { } if let closestNode = closestNode { - self.itemSelected(closestNode.0, longTap) + let container = self.tabBarNodeContainers[closestNode.0] + self.itemSelected(closestNode.0, longTap, [container.imageNode, container.badgeContainerNode]) } } } diff --git a/Display/UINavigationItem+Proxy.h b/Display/UINavigationItem+Proxy.h index 8a891d8a2f..f378a98aa2 100644 --- a/Display/UINavigationItem+Proxy.h +++ b/Display/UINavigationItem+Proxy.h @@ -48,4 +48,7 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem * _Nonnull item, UITabBa - (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener _Nonnull)listener; - (void)removeSetSelectedImageListener:(NSInteger)key; +- (NSObject * _Nullable)userInfo; +- (void)setUserInfo:(NSObject * _Nullable)userInfo; + @end diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index f52fcfa669..8b166e5cd4 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -16,6 +16,7 @@ static const void *setMultipleRightBarButtonItemsListenerKey = &setMultipleRight static const void *setBackBarButtonItemListenerBagKey = &setBackBarButtonItemListenerBagKey; static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey; static const void *badgeKey = &badgeKey; +static const void *userInfoKey = &userInfoKey; @implementation UINavigationItem (Proxy) @@ -401,4 +402,12 @@ NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem *item, UITabBarItemSetBa [(NSBag *)[self associatedObjectForKey:setSelectedImageListenerBagKey] removeItem:key]; } +- (NSObject * _Nullable)userInfo { + return [self associatedObjectForKey:userInfoKey]; +} + +- (void)setUserInfo:(NSObject * _Nullable)userInfo { + [self setAssociatedObject:userInfo forKey:userInfoKey]; +} + @end diff --git a/Display/ViewController.swift b/Display/ViewController.swift index beaf00f3e2..c599f392e2 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -16,6 +16,16 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { } } +private func findWindow(_ view: UIView) -> WindowHost? { + if let view = view as? WindowHost { + return view + } else if let superview = view.superview { + return findWindow(superview) + } else { + return nil + } +} + public enum ViewControllerPresentationAnimation { case none case modalSheet @@ -346,14 +356,14 @@ open class ViewControllerPresentationArguments { public final var window: WindowHost? { if let window = self.view.window as? WindowHost { return window - } else if let superwindow = self.view.window { - for subview in superwindow.subviews { - if let subview = subview as? WindowHost { - return subview - } + } else if let result = findWindow(self.view) { + return result + } else { + if let parent = self.parent as? ViewController { + return parent.window } + return nil } - return nil } public func present(_ controller: ViewController, in context: PresentationContextType, with arguments: Any? = nil, blockInteraction: Bool = false, completion: @escaping () -> Void = {}) { diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index dfdf9b6899..d559a78a8e 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -630,12 +630,10 @@ public class Window1 { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) } - if let coveringView = self.coveringView { - self.hostView.containerView.insertSubview(rootController.view, belowSubview: coveringView) - } else { - self.hostView.containerView.insertSubview(rootController.view, belowSubview: self.volumeControlStatusBarNode.view) - } + self.hostView.containerView.insertSubview(rootController.view, at: 0) } + + self.hostView.eventView.setNeedsLayout() } } From e065d5af3fdf9c7d9eeed508411a0ddebfad1406 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 8 Feb 2019 20:08:42 +0400 Subject: [PATCH 162/245] ActionSheetController: added dismissed API --- Display/ActionSheetController.swift | 7 +++++-- Display/ActionSheetControllerNode.swift | 10 +++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 6acc6fdcfe..6f6ff08e7c 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -17,6 +17,8 @@ open class ActionSheetController: ViewController { private var isDismissed: Bool = false + public var dismissed: ((Bool) -> Void)? + public init(theme: ActionSheetControllerTheme) { self.theme = theme @@ -30,7 +32,7 @@ open class ActionSheetController: ViewController { public func dismissAnimated() { if !self.isDismissed { self.isDismissed = true - self.actionSheetNode.animateOut() + self.actionSheetNode.animateOut(cancelled: false) } } @@ -38,7 +40,8 @@ open class ActionSheetController: ViewController { self.displayNode = ActionSheetControllerNode(theme: self.theme) self.displayNodeDidLoad() - self.actionSheetNode.dismiss = { [weak self] in + self.actionSheetNode.dismiss = { [weak self] cancelled in + self?.dismissed?(cancelled) self?.presentingViewController?.dismiss(animated: false) } diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index ec2f67c6a5..7d03e0996f 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -28,7 +28,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { private let scrollView: UIScrollView - var dismiss: () -> Void = { } + var dismiss: (Bool) -> Void = { _ in } private var validLayout: ContainerViewLayout? @@ -127,7 +127,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { }) } - func animateOut() { + func animateOut(cancelled: Bool) { let tempDimView = UIView() tempDimView.backgroundColor = self.theme.dimColor tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height) @@ -141,7 +141,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.layer.animateBounds(from: self.bounds, to: self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height), duration: 0.35, timingFunction: kCAMediaTimingFunctionEaseOut, removeOnCompletion: false, completion: { [weak self, weak tempDimView] _ in tempDimView?.removeFromSuperview() - self?.dismiss() + self?.dismiss(cancelled) }) } @@ -152,7 +152,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { @objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) { if case .ended = recognizer.state { - self.animateOut() + self.animateOut(cancelled: true) } } @@ -174,7 +174,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { let additionalTopHeight = max(0.0, -contentOffset.y) if additionalTopHeight >= 30.0 { - self.animateOut() + self.animateOut(cancelled: true) } } From bff731ee3777a4d0e318b77fcb91551072c5da75 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 9 Feb 2019 00:04:51 +0400 Subject: [PATCH 163/245] ActionSheetController: added right-aligned checkbox item Copy layer mask on snapshot --- Display/ActionSheetCheckboxItem.swift | 20 +++++++++++++++++--- Display/ActionSheetTheme.swift | 7 ++++++- Display/UIKitUtils.swift | 8 ++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift index 80c89e559b..8c4b8916ff 100644 --- a/Display/ActionSheetCheckboxItem.swift +++ b/Display/ActionSheetCheckboxItem.swift @@ -1,16 +1,23 @@ import Foundation import AsyncDisplayKit +public enum ActionSheetCheckboxStyle { + case `default` + case alignRight +} + public class ActionSheetCheckboxItem: ActionSheetItem { public let title: String public let label: String public let value: Bool + public let style: ActionSheetCheckboxStyle public let action: (Bool) -> Void - public init(title: String, label: String, value: Bool, action: @escaping (Bool) -> Void) { + public init(title: String, label: String, value: Bool, style: ActionSheetCheckboxStyle = .default, action: @escaping (Bool) -> Void) { self.title = title self.label = label self.value = value + self.style = style self.action = action } @@ -114,13 +121,20 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode { self.button.frame = CGRect(origin: CGPoint(), size: size) + var titleOrigin: CGFloat = 44.0 + var checkOrigin: CGFloat = 22.0 + if let item = self.item, item.style == .alignRight { + titleOrigin = 24.0 + checkOrigin = size.width - 22.0 + } + let labelSize = self.labelNode.updateLayout(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height)) let titleSize = self.titleNode.updateLayout(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height)) - self.titleNode.frame = CGRect(origin: CGPoint(x: 44.0, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + self.titleNode.frame = CGRect(origin: CGPoint(x: titleOrigin, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) if let image = self.checkNode.image { - self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) + self.checkNode.frame = CGRect(origin: CGPoint(x: floor(checkOrigin - (image.size.width / 2.0)), y: floor((size.height - image.size.height) / 2.0)), size: image.size) } } diff --git a/Display/ActionSheetTheme.swift b/Display/ActionSheetTheme.swift index 932de4569e..0496a9af38 100644 --- a/Display/ActionSheetTheme.swift +++ b/Display/ActionSheetTheme.swift @@ -17,8 +17,9 @@ public final class ActionSheetControllerTheme: Equatable { public let primaryTextColor: UIColor public let secondaryTextColor: UIColor public let controlAccentColor: UIColor + public let controlColor: UIColor - public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor) { + public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor, controlColor: UIColor) { self.dimColor = dimColor self.backgroundType = backgroundType self.itemBackgroundColor = itemBackgroundColor @@ -29,6 +30,7 @@ public final class ActionSheetControllerTheme: Equatable { self.primaryTextColor = primaryTextColor self.secondaryTextColor = secondaryTextColor self.controlAccentColor = controlAccentColor + self.controlColor = controlColor } public static func ==(lhs: ActionSheetControllerTheme, rhs: ActionSheetControllerTheme) -> Bool { @@ -62,6 +64,9 @@ public final class ActionSheetControllerTheme: Equatable { if lhs.controlAccentColor != rhs.controlAccentColor { return false } + if lhs.controlColor != rhs.controlColor { + return false + } return true } } diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index b08054a641..eaaaccadc8 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -242,6 +242,14 @@ private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) -> view.layer.contentsCenter = layer.contentsCenter view.layer.contentsGravity = layer.contentsGravity view.layer.masksToBounds = layer.masksToBounds + if let mask = layer.mask { + let maskLayer = CALayer() + maskLayer.bounds = mask.bounds + maskLayer.contents = mask.contents + maskLayer.contentsScale = mask.contentsScale + maskLayer.contentsCenter = mask.contentsCenter + view.layer.mask = maskLayer + } view.layer.cornerRadius = layer.cornerRadius view.layer.backgroundColor = layer.backgroundColor if let sublayers = layer.sublayers { From 2e8c33d43d9736ba997b3e37451bbcd82f39ee9f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 13 Feb 2019 01:05:14 +0400 Subject: [PATCH 164/245] Fixed mask copying on layer snapshot --- Display/UIKitUtils.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index eaaaccadc8..25d1d222a4 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -244,10 +244,11 @@ private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) -> view.layer.masksToBounds = layer.masksToBounds if let mask = layer.mask { let maskLayer = CALayer() - maskLayer.bounds = mask.bounds maskLayer.contents = mask.contents + maskLayer.contentsRect = mask.contentsRect maskLayer.contentsScale = mask.contentsScale maskLayer.contentsCenter = mask.contentsCenter + maskLayer.contentsGravity = mask.contentsGravity view.layer.mask = maskLayer } view.layer.cornerRadius = layer.cornerRadius @@ -261,6 +262,9 @@ private func makeSubtreeSnapshot(layer: CALayer, keepTransform: Bool = false) -> } subtree.frame = sublayer.frame subtree.bounds = sublayer.bounds + if let maskLayer = subtree.layer.mask { + maskLayer.frame = sublayer.bounds + } view.addSubview(subtree) } else { return nil From e908f22334e715845848373679ce592ad162fe52 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 13 Feb 2019 23:56:32 +0400 Subject: [PATCH 165/245] Move MPVolumeView out of screen --- Display/VolumeControlStatusBar.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 0e590f21c8..e7342c8c08 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -18,7 +18,8 @@ final class VolumeControlStatusBar: UIView { private var ignoreAdjustmentOnce = false init(frame: CGRect, shouldBeVisible: Signal) { - self.control = MPVolumeView(frame: CGRect(origin: CGPoint(), size: CGSize(width: 100.0, height: 20.0))) + self.control = MPVolumeView(frame: CGRect(origin: CGPoint(x: -100.0, y: -100.0), size: CGSize(width: 100.0, height: 20.0))) + self.control.alpha = 0.0001 self.currentValue = AVAudioSession.sharedInstance().outputVolume super.init(frame: frame) From 9a2b81c465c26a823bec6aaecc42e51de4773562 Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Fri, 15 Feb 2019 17:30:30 +0400 Subject: [PATCH 166/245] Expose voice over status --- Display.xcodeproj/project.pbxproj | 25 ++-- Display/ContainerViewLayout.swift | 10 +- Display/ListView.swift | 154 +++++++++++++++++++++---- Display/ListViewItemNode.swift | 4 + Display/NavigationButtonNode.swift | 21 ++-- Display/NavigationController.swift | 8 +- Display/TabBarNode.swift | 5 + Display/ViewControllerPreviewing.swift | 4 +- Display/WindowContent.swift | 36 ++++-- 9 files changed, 209 insertions(+), 58 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 0c4a107e9e..0a0e2aff9a 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -1455,6 +1455,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1468,7 +1469,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -1650,8 +1651,10 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; + DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; DYLIB_INSTALL_NAME_BASE = "@rpath"; @@ -1663,7 +1666,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -1677,6 +1680,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1690,7 +1694,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -1776,6 +1780,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1789,7 +1794,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -1856,6 +1861,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1869,7 +1875,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -1936,6 +1942,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1949,7 +1956,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -2082,6 +2089,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2095,7 +2103,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_REFLECTION_METADATA_LEVEL = none; @@ -2228,6 +2236,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2241,7 +2250,7 @@ OTHER_SWIFT_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.telegram.Display; PRODUCT_NAME = "$(TARGET_NAME)"; - PROVISIONING_PROFILE_SPECIFIER = X834Q8SBVP/; + PROVISIONING_PROFILE_SPECIFIER = ""; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; SWIFT_REFLECTION_METADATA_LEVEL = none; diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 06eeacaa4e..8510728950 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -43,8 +43,9 @@ public struct ContainerViewLayout: Equatable { public var inputHeight: CGFloat? public let standardInputHeight: CGFloat public let inputHeightIsInteractivellyChanging: Bool + public let inVoiceOver: Bool - public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, standardInputHeight: CGFloat, inputHeightIsInteractivellyChanging: Bool) { + public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, standardInputHeight: CGFloat, inputHeightIsInteractivellyChanging: Bool, inVoiceOver: Bool) { self.size = size self.metrics = metrics self.intrinsicInsets = intrinsicInsets @@ -53,6 +54,7 @@ public struct ContainerViewLayout: Equatable { self.inputHeight = inputHeight self.standardInputHeight = standardInputHeight self.inputHeightIsInteractivellyChanging = inputHeightIsInteractivellyChanging + self.inVoiceOver = inVoiceOver } public func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets { @@ -67,14 +69,14 @@ public struct ContainerViewLayout: Equatable { } public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver) } public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) + return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver) } public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout { - return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging) + return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver) } } diff --git a/Display/ListView.swift b/Display/ListView.swift index a55ce03e3e..29319ad619 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -71,6 +71,10 @@ final class ListViewBackingView: UIView { } return super.hitTest(point, with: event) } + + override func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool { + return self.target?.accessibilityScroll(direction) ?? false + } } private final class ListViewTimerProxy: NSObject { @@ -112,7 +116,7 @@ public struct ListViewKeepTopItemOverscrollBackground { } } -open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDelegate { +open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() public private(set) final var insets = UIEdgeInsets() @@ -137,7 +141,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.invisibleInset = self.preloadPages ? 500.0 : 20.0 //self.invisibleInset = self.preloadPages ? 20.0 : 20.0 if self.preloadPages { - self.enqueueUpdateVisibleItems() + self.enqueueUpdateVisibleItems(synchronous: false) } } } @@ -248,7 +252,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private let waitingForNodesDisposable = MetaDisposable() - override open var accessibilityElements: [Any]? { + /*override open var accessibilityElements: [Any]? { get { var accessibilityElements: [Any] = [] self.forEachItemNode({ itemNode in @@ -257,7 +261,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return accessibilityElements } set(value) { } - } + }*/ override public init() { class DisplayLinkProxy: NSObject { @@ -595,6 +599,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } public func scrollViewDidScroll(_ scrollView: UIScrollView) { + self.updateScrollViewDidScroll(scrollView, synchronous: false) + } + + private func updateScrollViewDidScroll(_ scrollView: UIScrollView, synchronous: Bool) { if self.ignoreScrollingEvents || scroller !== self.scroller { return } @@ -615,7 +623,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.trackingOffset += -deltaY } - self.enqueueUpdateVisibleItems() + self.enqueueUpdateVisibleItems(synchronous: synchronous) var useScrollDynamics = false @@ -688,7 +696,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateVisibleContentOffset() self.updateVisibleItemRange() - self.updateItemNodesVisibilities() + self.updateItemNodesVisibilities(onlyPositive: false) //CATransaction.commit() } @@ -2014,6 +2022,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } else if let scrolledToItem = self.scrolledToItem { scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: false, curve: .Default(duration: nil), directionHint: .Down) } + + weak var highlightedItemNode: ListViewItemNode? + if let highlightedItemIndex = self.highlightedItemIndex { + for itemNode in self.itemNodes { + if itemNode.index == highlightedItemIndex { + highlightedItemNode = itemNode + break + } + } + } + /*if true { print("----------") for itemNode in self.itemNodes { @@ -2365,6 +2384,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) + var deferredUpdateVisible = false + if let updateSizeAndInsets = updateSizeAndInsets { if self.insets != updateSizeAndInsets.insets || self.headerInsets != updateSizeAndInsets.headerInsets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { let previousVisibleSize = self.visibleSize @@ -2445,6 +2466,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel animation = basicAnimation } + deferredUpdateVisible = true + animation.completion = { [weak self] _ in + self?.updateItemNodesVisibilities(onlyPositive: false) + } self.layer.add(animation, forKey: nil) } } else { @@ -2527,6 +2552,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) + if let highlightedItemNode = highlightedItemNode { + if highlightedItemNode.index != self.highlightedItemIndex { + highlightedItemNode.setHighlighted(false, at: CGPoint(), animated: false) + self.highlightedItemIndex = nil + } + } else if self.highlightedItemIndex != nil { + self.highlightedItemIndex = nil + } + if let scrollToItem = scrollToItem, scrollToItem.animated { if self.itemNodes.count != 0 { var offset: CGFloat? @@ -2737,7 +2771,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.updateItemNodesVisibilities() + self.updateItemNodesVisibilities(onlyPositive: deferredUpdateVisible) self.updateScroller(transition: headerNodesTransition.0) @@ -2757,7 +2791,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } else { self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) - self.updateItemNodesVisibilities() + self.updateItemNodesVisibilities(onlyPositive: deferredUpdateVisible) if animated { self.setNeedsAnimations() @@ -2932,7 +2966,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateItemNodesVisibilities() { + private func updateItemNodesVisibilities(onlyPositive: Bool) { let visibilityRect = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height - self.insets.top - self.insets.bottom)) for itemNode in self.itemNodes { let itemFrame = itemNode.apparentFrame @@ -2940,8 +2974,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel if visibilityRect.intersects(itemFrame) { visibility = .visible } - if visibility != itemNode.visibility { - itemNode.visibility = visibility + if !onlyPositive || visibility == .visible { + if visibility != itemNode.visibility { + itemNode.visibility = visibility + } } } } @@ -3165,14 +3201,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func enqueueUpdateVisibleItems() { + private func enqueueUpdateVisibleItems(synchronous: Bool) { if !self.enqueuedUpdateVisibleItems { self.enqueuedUpdateVisibleItems = true self.transactionQueue.addTransaction({ [weak self] completion in if let strongSelf = self { strongSelf.transactionOffset = 0.0 - strongSelf.updateVisibleItemsTransaction(completion: { + strongSelf.updateVisibleItemsTransaction(synchronous: synchronous, completion: { var repeatUpdate = false if let strongSelf = self { repeatUpdate = abs(strongSelf.transactionOffset) > 0.00001 @@ -3183,7 +3219,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() if repeatUpdate { - strongSelf.enqueueUpdateVisibleItems() + strongSelf.enqueueUpdateVisibleItems(synchronous: false) } }) } @@ -3191,7 +3227,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - private func updateVisibleItemsTransaction(completion: @escaping () -> Void) { + private func updateVisibleItemsTransaction(synchronous: Bool, completion: @escaping () -> Void) { if self.items.count == 0 && self.itemNodes.count == 0 { completion() return @@ -3213,8 +3249,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } let state = self.currentState() - self.async { - self.fillMissingNodes(synchronous: false, synchronousLoads: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in + + let begin: () -> Void = { + self.fillMissingNodes(synchronous: synchronous, synchronousLoads: false, animated: false, inputAnimatedInsertIndices: [], insertDirectionHints: [:], inputState: state, inputPreviousNodes: [:], inputOperations: []) { state, operations in var updatedState = state var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) @@ -3223,6 +3260,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } } + if synchronous { + begin() + } else { + self.async { + begin() + } + } } private func updateVisibleItemRange(force: Bool = false) { @@ -3405,7 +3449,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } if requestUpdateVisibleItems { - self.enqueueUpdateVisibleItems() + self.enqueueUpdateVisibleItems(synchronous: false) } self.checkItemReordering() @@ -3566,22 +3610,22 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - public func ensureItemNodeVisible(_ node: ListViewItemNode) { + public func ensureItemNodeVisible(_ node: ListViewItemNode, animated: Bool = true, overflow: CGFloat = 0.0) { if let index = node.index { if node.apparentHeight > self.visibleSize.height - self.insets.top - self.insets.bottom { if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(-overflow), animated: animated, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) }/* else if node.frame.minY < self.insets.top { self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) }*/ } else { if self.experimentalSnapScrollToItem { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: true, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.visible, animated: animated, curve: ListViewAnimationCurve.Default(duration: nil), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else { if node.frame.minY < self.insets.top { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.top(overflow), animated: animated, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Up), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } else if node.frame.maxY > self.visibleSize.height - self.insets.bottom { - self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(0.0), animated: true, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) + self.transaction(deleteIndices: [], insertIndicesAndItems: [], updateIndicesAndItems: [], options: ListViewDeleteAndInsertOptions(), scrollToItem: ListViewScrollToItem(index: index, position: ListViewScrollPosition.bottom(-overflow), animated: animated, curve: ListViewAnimationCurve.Default(duration: 0.25), directionHint: ListViewScrollToItemDirectionHint.Down), updateSizeAndInsets: nil, stationaryItemRange: nil, updateOpaqueState: nil, completion: { _ in }) } } } @@ -3751,4 +3795,68 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) } } + + public func scrollWithDirection(_ direction: UIAccessibilityScrollDirection) -> Bool { + var accessibilityFocusedNode: (ASDisplayNode, CGRect)? + for itemNode in self.itemNodes { + if findAccessibilityFocus(itemNode) { + accessibilityFocusedNode = (itemNode, itemNode.frame) + break + } + } + let scrollDistance = floor((self.visibleSize.height - self.insets.top - self.insets.bottom) / 2.0) + let initialOffset = self.scroller.contentOffset + switch direction { + case .up: + var contentOffset = initialOffset + contentOffset.y -= scrollDistance + contentOffset.y = max(self.scroller.contentInset.top, contentOffset.y) + if contentOffset.y < initialOffset.y { + self.ignoreScrollingEvents = true + self.scroller.setContentOffset(contentOffset, animated: false) + self.ignoreScrollingEvents = false + self.updateScrollViewDidScroll(self.scroller, synchronous: true) + } else { + return false + } + case .down: + var contentOffset = initialOffset + contentOffset.y += scrollDistance + contentOffset.y = max(self.scroller.contentInset.top, min(contentOffset.y, self.scroller.contentSize.height - self.visibleSize.height - self.insets.bottom - self.insets.top)) + if contentOffset.y > initialOffset.y { + self.ignoreScrollingEvents = true + self.scroller.setContentOffset(contentOffset, animated: false) + self.ignoreScrollingEvents = false + self.updateScrollViewDidScroll(self.scroller, synchronous: true) + } else { + return false + } + default: + return false + } + if let (_, frame) = accessibilityFocusedNode { + for itemNode in self.itemNodes { + if frame.intersects(itemNode.frame) { + UIAccessibilityPostNotification(UIAccessibilityLayoutChangedNotification, itemNode.view) + if let index = itemNode.index { + let scrollStatus = "Row \(index + 1) of \(self.items.count)" + UIAccessibilityPostNotification(UIAccessibilityPageScrolledNotification, scrollStatus) + } + break + } + } + } + return true + } + + override open func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool { + return self.scrollWithDirection(direction) + } +} + +private func findAccessibilityFocus(_ node: ASDisplayNode) -> Bool { + if node.view.accessibilityElementIsFocused() { + return true + } + return false } diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index b4399d3f54..5467af1255 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -525,4 +525,8 @@ open class ListViewItemNode: ASDisplayNode { open func updateTrailingItemSpace(_ height: CGFloat, transition: ContainedViewLayoutTransition) { } + + override open func accessibilityElementDidBecomeFocused() { + (self.supernode as? ListView)?.ensureItemNodeVisible(self, animated: false, overflow: 22.0) + } } diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 00647a78af..f69a5b3f30 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -46,6 +46,7 @@ private final class NavigationButtonItemNode: ASTextNode { _text = value self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState()) + self.item?.accessibilityLabel = value } } @@ -128,19 +129,16 @@ private final class NavigationButtonItemNode: ASTextNode { get { return true } set(value) { + super.isAccessibilityElement = true } } - override public var accessibilityLabel: String? { - get { - return self.item?.accessibilityLabel - } set(value) { - } - } override public init() { super.init() + self.isAccessibilityElement = true + self.isUserInteractionEnabled = true self.isExclusiveTouch = true self.hitTestSlop = UIEdgeInsets(top: -16.0, left: -10.0, bottom: -16.0, right: -10.0) @@ -283,7 +281,7 @@ final class NavigationButtonNode: ASDisplayNode { return self.nodes.first?.text ?? "" } - func updateManualText(_ text: String) { + func updateManualText(_ text: String, isBack: Bool = true) { let node: NavigationButtonItemNode if self.nodes.count > 0 { node = self.nodes[0] @@ -309,6 +307,15 @@ final class NavigationButtonNode: ASDisplayNode { } node.item = nil node.text = text + + /*if isBack { + node.accessibilityHint = "Back button" + node.accessibilityTraits = 0 + } else { + node.accessibilityHint = nil + node.accessibilityTraits = UIAccessibilityTraitButton + }*/ + node.image = nil node.bold = false node.isEnabled = true diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 666d7e1aa4..4b6694d71b 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -217,13 +217,13 @@ open class NavigationController: UINavigationController, ContainableController, let masterWidth: CGFloat = max(320.0, floor(layout.size.width / 3.0)) let detailWidth: CGFloat = layout.size.width - masterWidth if index == 0 { - return (CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: masterWidth, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + return (CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: masterWidth, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)) } else { let detailFrame = CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)) - return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)) } case .single: - return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver)) } } @@ -579,7 +579,7 @@ open class NavigationController: UINavigationController, ContainableController, self.updateControllerLayouts(previousControllers: self._viewControllers, layout: layout, transition: transition) if let presentedViewController = self.presentedViewController { - let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) + let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging, inVoiceOver: layout.inVoiceOver) if let presentedViewController = presentedViewController as? ContainableController { presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 15856a6c7c..47c1d24ba5 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -119,16 +119,19 @@ private final class TabBarNodeContainer { self.badgeContainerNode = ASDisplayNode() self.badgeContainerNode.isUserInteractionEnabled = false + self.badgeContainerNode.isAccessibilityElement = false self.badgeBackgroundNode = ASImageNode() self.badgeBackgroundNode.isUserInteractionEnabled = false self.badgeBackgroundNode.displayWithoutProcessing = true self.badgeBackgroundNode.displaysAsynchronously = false + self.badgeBackgroundNode.isAccessibilityElement = false self.badgeTextNode = ASTextNode() self.badgeTextNode.maximumNumberOfLines = 1 self.badgeTextNode.isUserInteractionEnabled = false self.badgeTextNode.displaysAsynchronously = false + self.badgeTextNode.isAccessibilityElement = false self.badgeContainerNode.addSubnode(self.badgeBackgroundNode) self.badgeContainerNode.addSubnode(self.badgeTextNode) @@ -207,6 +210,8 @@ class TabBarNode: ASDisplayNode { super.init() + self.isAccessibilityContainer = true + self.isOpaque = true self.backgroundColor = theme.tabBarBackgroundColor diff --git a/Display/ViewControllerPreviewing.swift b/Display/ViewControllerPreviewing.swift index 82b1356ff7..c81d9c2b81 100644 --- a/Display/ViewControllerPreviewing.swift +++ b/Display/ViewControllerPreviewing.swift @@ -63,14 +63,14 @@ private final class ViewControllerPeekContentNode: ASDisplayNode, PeekController if !self.hasValidLayout { self.hasValidLayout = true self.controller.view.frame = CGRect(origin: CGPoint(), size: size) - self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate) + self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate) self.controller.setIgnoreAppearanceMethodInvocations(true) self.view.addSubview(self.controller.view) self.controller.setIgnoreAppearanceMethodInvocations(false) self.controller.viewWillAppear(false) self.controller.viewDidAppear(false) } else { - self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: transition) + self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition) } return size diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index d559a78a8e..7f24a652bd 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -11,6 +11,7 @@ private struct WindowLayout: Equatable { let safeInsets: UIEdgeInsets let onScreenNavigationHeight: CGFloat? let upperKeyboardInputPositionBound: CGFloat? + let inVoiceOver: Bool } private struct UpdatingLayout { @@ -32,44 +33,50 @@ private struct UpdatingLayout { mutating func update(size: CGSize, metrics: LayoutMetrics, safeInsets: UIEdgeInsets, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) } mutating func update(forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) } mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) } mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) } mutating func update(safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) } mutating func update(onScreenNavigationHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) } mutating func update(upperKeyboardInputPositionBound: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) { self.update(transition: transition, override: overrideTransition) - self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: upperKeyboardInputPositionBound) + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: upperKeyboardInputPositionBound, inVoiceOver: self.layout.inVoiceOver) + } + + mutating func update(inVoiceOver: Bool) { + self.update(transition: transition, override: false) + + self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound, inVoiceOver: inVoiceOver) } } @@ -117,7 +124,7 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout, hasOnScreenN standardInputHeight += predictiveHeight - return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, standardInputHeight: standardInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil) + return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, standardInputHeight: standardInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil, inVoiceOver: layout.inVoiceOver) } private func encodeText(_ string: String, _ key: Int) -> String { @@ -285,6 +292,7 @@ public class Window1 { private var statusBarChangeObserver: AnyObject? private var keyboardFrameChangeObserver: AnyObject? private var keyboardTypeChangeObserver: AnyObject? + private var voiceOverStatusObserver: AnyObject? private var windowLayout: WindowLayout private var updatingLayout: UpdatingLayout? @@ -352,7 +360,7 @@ public class Window1 { let onScreenNavigationHeight = deviceMetrics?.onScreenNavigationHeight(inLandscape: boundsSize.width > boundsSize.height) - self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil) + self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil, inVoiceOver: UIAccessibility.isVoiceOverRunning) self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) self.presentationContext = PresentationContext() self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost) @@ -491,6 +499,14 @@ public class Window1 { }) } + if #available(iOSApplicationExtension 11.0, *) { + self.voiceOverStatusObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIAccessibilityVoiceOverStatusDidChange, object: nil, queue: OperationQueue.main, using: { [weak self] _ in + if let strongSelf = self { + strongSelf.updateLayout { $0.update(inVoiceOver: UIAccessibility.isVoiceOverRunning) } + } + }) + } + let recognizer = WindowPanRecognizer(target: self, action: #selector(self.panGesture(_:))) recognizer.cancelsTouchesInView = false recognizer.delaysTouchesBegan = false @@ -856,7 +872,7 @@ public class Window1 { let onScreenNavigationHeight = deviceMetrics?.onScreenNavigationHeight(inLandscape: boundsSize.width > boundsSize.height) - self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound) + self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound, inVoiceOver: updatingLayout.layout.inVoiceOver) let childLayout = containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation) let childLayoutUpdated = self.updatedContainerLayout != childLayout From e135276a3ddbcf46f5962615de78fbcb2c1305df Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 15 Feb 2019 18:25:38 +0400 Subject: [PATCH 167/245] Added partial list view item visibility --- Display/ListView.swift | 4 +++- Display/ListViewItemNode.swift | 22 +++++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 29319ad619..fff6c014c3 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2972,7 +2972,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let itemFrame = itemNode.apparentFrame var visibility: ListViewItemNodeVisibility = .none if visibilityRect.intersects(itemFrame) { - visibility = .visible + let itemContentFrame = itemNode.apparentContentFrame + let full = itemContentFrame.minY >= visibilityRect.minY && itemContentFrame.maxY <= visibilityRect.maxY + visibility = .visible(full) } if !onlyPositive || visibility == .visible { if visibility != itemNode.visibility { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 5467af1255..0aca5a656a 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -50,10 +50,26 @@ public struct ListViewItemNodeLayout { } } -public enum ListViewItemNodeVisibility { +public enum ListViewItemNodeVisibility: Equatable { case none - case nearlyVisible - case visible + case visible(Bool) + + public static func ==(lhs: ListViewItemNodeVisibility, rhs: ListViewItemNodeVisibility) -> Bool { + switch lhs { + case .none: + if case .none = rhs { + return true + } else { + return false + } + case let .visible(full): + if case .visible(full) = rhs { + return true + } else { + return false + } + } + } } public struct ListViewItemLayoutParams { From 209e6a11190e8f58545f413ae6e6727594a4adb3 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 15 Feb 2019 19:21:44 +0400 Subject: [PATCH 168/245] Fixed build --- Display/ListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index fff6c014c3..a3c4b04166 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2976,7 +2976,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let full = itemContentFrame.minY >= visibilityRect.minY && itemContentFrame.maxY <= visibilityRect.maxY visibility = .visible(full) } - if !onlyPositive || visibility == .visible { + if !onlyPositive, case .visible = visibility { if visibility != itemNode.visibility { itemNode.visibility = visibility } From 36847c88c72d7716730587ef163ba066a8c6f226 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 15 Feb 2019 23:35:51 +0400 Subject: [PATCH 169/245] GridNode: improve synchronous loading --- Display/GridNode.swift | 6 ++++-- Display/NavigationController.swift | 4 ++++ Display/TabBarController.swift | 7 +++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 96f200f009..3b9437f88f 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -831,13 +831,15 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { for item in presentationLayoutTransition.layout.items { existingItemIndices.insert(item.index) + let itemInBounds = bounds.intersects(item.frame) + if let itemNode = self.itemNodes[item.index] { if itemNode.frame != item.frame { itemNode.frame = item.frame } - itemNode.updateLayout(item: self.items[item.index], size: item.frame.size, isVisible: bounds.intersects(item.frame), synchronousLoads: synchronousLoads) + itemNode.updateLayout(item: self.items[item.index], size: item.frame.size, isVisible: bounds.intersects(item.frame), synchronousLoads: synchronousLoads && itemInBounds) } else { - let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, synchronousLoad: synchronousLoads) + let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout, synchronousLoad: synchronousLoads && itemInBounds) itemNode.frame = item.frame self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) addedNodes = true diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 4b6694d71b..764a8e5a49 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -662,6 +662,10 @@ open class NavigationController: UINavigationController, ContainableController, bottomController.viewWillAppear(true) let bottomView = bottomController.view! + if let bottomController = bottomController as? ViewController { + bottomController.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator } diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 69ee1c55a3..26d137e743 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -53,8 +53,15 @@ public final class TabBarItemInfo: NSObject { } } +public enum TabBarContainedControllerPresentationUpdate { + case dismiss + case present + case progress(CGFloat) +} + public protocol TabBarContainedController { func presentTabBarPreviewingController(sourceNodes: [ASDisplayNode]) + func updateTabBarPreviewingControllerPresentation(_ update: TabBarContainedControllerPresentationUpdate) } open class TabBarController: ViewController { From 6e0355b6326c684f465c691dbaef6d147bda38f3 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 15 Feb 2019 23:38:16 +0400 Subject: [PATCH 170/245] ListView: Fix updateVisibility --- Display/ListView.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index a3c4b04166..7dc6fa4960 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2976,7 +2976,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let full = itemContentFrame.minY >= visibilityRect.minY && itemContentFrame.maxY <= visibilityRect.maxY visibility = .visible(full) } - if !onlyPositive, case .visible = visibility { + var updateVisibility = false + if !onlyPositive { + updateVisibility = true + } + if case .visible = visibility { + updateVisibility = true + } + if updateVisibility { if visibility != itemNode.visibility { itemNode.visibility = visibility } From 4658244e4484cd6ea346e719966edcbd3e593ec3 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 18 Feb 2019 16:00:52 +0400 Subject: [PATCH 171/245] ListView: added visible fraction value for visibility property --- Display/ListView.swift | 5 +++-- Display/ListViewItemNode.swift | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 7dc6fa4960..06cbd5a42d 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2973,8 +2973,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var visibility: ListViewItemNodeVisibility = .none if visibilityRect.intersects(itemFrame) { let itemContentFrame = itemNode.apparentContentFrame - let full = itemContentFrame.minY >= visibilityRect.minY && itemContentFrame.maxY <= visibilityRect.maxY - visibility = .visible(full) + let intersection = itemContentFrame.intersection(visibilityRect) + let fraction = intersection.height / itemContentFrame.height + visibility = .visible(fraction) } var updateVisibility = false if !onlyPositive { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 0aca5a656a..9793f6f3a1 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -52,7 +52,7 @@ public struct ListViewItemNodeLayout { public enum ListViewItemNodeVisibility: Equatable { case none - case visible(Bool) + case visible(CGFloat) public static func ==(lhs: ListViewItemNodeVisibility, rhs: ListViewItemNodeVisibility) -> Bool { switch lhs { @@ -62,8 +62,8 @@ public enum ListViewItemNodeVisibility: Equatable { } else { return false } - case let .visible(full): - if case .visible(full) = rhs { + case let .visible(fraction): + if case .visible(fraction) = rhs { return true } else { return false From b9621400b08e417b198c4ea73ee1e2db9421ecd3 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 18 Feb 2019 17:48:03 +0400 Subject: [PATCH 172/245] VolumeBar: ignore implicit volume changes --- Display/VolumeControlStatusBar.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index e7342c8c08..b3b3fc50b1 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -6,6 +6,8 @@ import SwiftSignalKit private let volumeNotificationKey = "AVSystemController_SystemVolumeDidChangeNotification" private let volumeParameterKey = "AVSystemController_AudioVolumeNotificationParameter" +private let changeReasonParameterKey = "AVSystemController_AudioVolumeChangeReasonNotificationParameter" +private let explicitChangeReasonValue = "ExplicitVolumeChange" final class VolumeControlStatusBar: UIView { private let control: MPVolumeView @@ -27,9 +29,9 @@ final class VolumeControlStatusBar: UIView { self.addSubview(self.control) self.observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: volumeNotificationKey), object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self, let userInfo = notification.userInfo { - /*guard let category = userInfo["AVSystemController_AudioCategoryNotificationParameter"] as? String else { + if let reason = userInfo[changeReasonParameterKey], reason as? String != explicitChangeReasonValue { return - }*/ + } if let volume = userInfo[volumeParameterKey] as? Float { let previous = strongSelf.currentValue From c9ec21fdf17d3e852cca8bb092cc7a8cd98ff744 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 19 Feb 2019 00:06:13 +0300 Subject: [PATCH 173/245] TabBarController: added .ready implementation --- Display/TabBarController.swift | 43 ++++++++++++++++ Display/TabBarNode.swift | 89 ++++++++++++++++++++++++---------- 2 files changed, 106 insertions(+), 26 deletions(-) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 26d137e743..3a5de78807 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -75,6 +75,11 @@ open class TabBarController: ViewController { public private(set) var controllers: [ViewController] = [] + private let _ready = Promise() + override open var ready: Promise { + return self._ready + } + private var _selectedIndex: Int? public var selectedIndex: Int { get { @@ -125,6 +130,10 @@ open class TabBarController: ViewController { private var debugTapCounter: (Double, Int) = (0.0, 0) + public func sourceNodesForController(at index: Int) -> [ASDisplayNode]? { + return self.tabBarControllerNode.tabBarNode.sourceNodesForController(at: index) + } + override open func loadDisplayNode() { self.displayNode = TabBarControllerNode(theme: self.theme, navigationBar: self.navigationBar, itemSelected: { [weak self] index, longTap, itemNodes in if let strongSelf = self { @@ -288,6 +297,40 @@ open class TabBarController: ViewController { self.controllers = controllers self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem }) + let signals = combineLatest(self.controllers.map({ $0.tabBarItem }).map { tabBarItem -> Signal in + if let tabBarItem = tabBarItem, tabBarItem.image == nil { + return Signal { [weak tabBarItem] subscriber in + let index = tabBarItem?.addSetImageListener({ image in + if image != nil { + subscriber.putNext(true) + subscriber.putCompletion() + } + }) + return ActionDisposable { + Queue.mainQueue().async { + if let index = index { + tabBarItem?.removeSetImageListener(index) + } + } + } + } + |> runOn(.mainQueue()) + } else { + return .single(true) + } + }) + |> map { items -> Bool in + for item in items { + if !item { + return false + } + } + return true + } + |> filter { $0 } + |> take(1) + self._ready.set(signals) + if let updatedSelectedIndex = updatedSelectedIndex { self.selectedIndex = updatedSelectedIndex self.updateSelectedIndex() diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 47c1d24ba5..0015619cc9 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -4,7 +4,7 @@ import AsyncDisplayKit import SwiftSignalKit private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale -private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool) -> (UIImage, CGFloat) { +private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool, imageMode: Bool) -> (UIImage, CGFloat) { let font = horizontal ? Font.regular(13.0) : Font.medium(10.0) let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size @@ -37,7 +37,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: context.setFillColor(backgroundColor.cgColor) context.fill(CGRect(origin: CGPoint(), size: size)) - if let image = image { + if let image = image, imageMode { if horizontal { let imageRect = CGRect(origin: CGPoint(x: 0.0, y: floor((size.height - imageSize.height) / 2.0)), size: imageSize) context.saveGState() @@ -70,10 +70,12 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: } } - if horizontal { - (title as NSString).draw(at: CGPoint(x: imageSize.width + horizontalSpacing, y: floor((size.height - titleSize.height) / 2.0) - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) - } else { - (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + if !imageMode { + if horizontal { + (title as NSString).draw(at: CGPoint(x: imageSize.width + horizontalSpacing, y: floor((size.height - titleSize.height) / 2.0) - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + } else { + (title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor]) + } } let resultImage = UIGraphicsGetImageFromCurrentImageContext() @@ -84,8 +86,24 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: private let badgeFont = Font.regular(13.0) -private final class TabBarItemNode: ASImageNode { +private final class TabBarItemNode: ASDisplayNode { + let imageNode: ASImageNode + let textImageNode: ASImageNode var contentWidth: CGFloat? + + override init() { + self.imageNode = ASImageNode() + self.imageNode.displayWithoutProcessing = true + self.imageNode.displaysAsynchronously = false + self.textImageNode = ASImageNode() + self.textImageNode.displayWithoutProcessing = true + self.textImageNode.displaysAsynchronously = false + + super.init() + + self.addSubnode(self.textImageNode) + self.addSubnode(self.imageNode) + } } private final class TabBarNodeContainer { @@ -250,6 +268,11 @@ class TabBarNode: ASDisplayNode { } } + func sourceNodesForController(at index: Int) -> [ASDisplayNode]? { + let container = self.tabBarNodeContainers[index] + return [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode] + } + private func reloadTabBarItems() { for node in self.tabBarNodeContainers { node.imageNode.removeFromSupernode() @@ -260,8 +283,6 @@ class TabBarNode: ASDisplayNode { for i in 0 ..< self.tabBarItems.count { let item = self.tabBarItems[i] let node = TabBarItemNode() - node.displaysAsynchronously = false - node.displayWithoutProcessing = true node.isUserInteractionEnabled = false let container = TabBarNodeContainer(item: item, imageNode: node, updateBadge: { [weak self] value in self?.updateNodeBadge(i, value: value) @@ -273,13 +294,17 @@ class TabBarNode: ASDisplayNode { self?.updateNodeImage(i, layout: true) }) if let selectedIndex = self.selectedIndex, selectedIndex == i { - let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) - node.image = image - node.contentWidth = contentWidth + let (textImage, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false) + let (image, imageContentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true) + node.textImageNode.image = textImage + node.imageNode.image = image + node.contentWidth = max(contentWidth, imageContentWidth) } else { - let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) - node.image = image - node.contentWidth = contentWidth + let (textImage, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false) + let (image, imageContentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: true) + node.textImageNode.image = textImage + node.imageNode.image = image + node.contentWidth = max(contentWidth, imageContentWidth) } container.badgeBackgroundNode.image = self.badgeImage tabBarNodeContainers.append(container) @@ -300,17 +325,26 @@ class TabBarNode: ASDisplayNode { let node = self.tabBarNodeContainers[index].imageNode let item = self.tabBarItems[index] - let previousImage = node.image + let previousImageSize = node.imageNode.image?.size ?? CGSize() + let previousTextImageSize = node.textImageNode.image?.size ?? CGSize() if let selectedIndex = self.selectedIndex, selectedIndex == index { - let (image, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal) - node.image = image - node.contentWidth = contentWidth + let (textImage, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false) + let (image, imageContentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true) + node.textImageNode.image = textImage + node.imageNode.image = image + node.contentWidth = max(contentWidth, imageContentWidth) } else { - let (image, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal) - node.image = image - node.contentWidth = contentWidth + let (textImage, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false) + let (image, imageContentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: true) + node.textImageNode.image = textImage + node.imageNode.image = image + node.contentWidth = max(contentWidth, imageContentWidth) } - if previousImage?.size != node.image?.size { + + let updatedImageSize = node.imageNode.image?.size ?? CGSize() + let updatedTextImageSize = node.textImageNode.image?.size ?? CGSize() + + if previousImageSize != updatedImageSize || previousTextImageSize != updatedTextImageSize { if let validLayout = self.validLayout, layout { self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) } @@ -358,10 +392,13 @@ class TabBarNode: ASDisplayNode { for i in 0 ..< self.tabBarNodeContainers.count { let container = self.tabBarNodeContainers[i] let node = container.imageNode - let nodeSize = node.image?.size ?? CGSize() + let nodeSize = node.textImageNode.image?.size ?? CGSize() let originX = floor(leftNodeOriginX + CGFloat(i) * distanceBetweenNodes - nodeSize.width / 2.0) - transition.updateFrame(node: node, frame: CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize)) + let nodeFrame = CGRect(origin: CGPoint(x: originX, y: 4.0), size: nodeSize) + transition.updateFrame(node: node, frame: nodeFrame) + node.imageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) + node.textImageNode.frame = CGRect(origin: CGPoint(), size: nodeFrame.size) if container.badgeValue != container.appliedBadgeValue { container.appliedBadgeValue = container.badgeValue @@ -415,7 +452,7 @@ class TabBarNode: ASDisplayNode { if let closestNode = closestNode { let container = self.tabBarNodeContainers[closestNode.0] - self.itemSelected(closestNode.0, longTap, [container.imageNode, container.badgeContainerNode]) + self.itemSelected(closestNode.0, longTap, [container.imageNode.imageNode, container.imageNode.textImageNode, container.badgeContainerNode]) } } } From 96647e265fc183e0fe1c7e166e59676665b730a8 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 19 Feb 2019 17:13:03 +0300 Subject: [PATCH 174/245] Fix keyboard rotation --- Display/NativeWindowHostView.swift | 11 ++--------- Display/NavigationController.swift | 6 +++++- Display/UIWindow+OrientationChange.h | 2 +- Display/WindowContent.swift | 28 +++++++++++++++++++++++++++- 4 files changed, 35 insertions(+), 12 deletions(-) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index d6c15c4efe..ca119323ac 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -180,13 +180,6 @@ private final class NativeWindow: UIWindow, WindowHost { var forEachControllerImpl: (((ContainableController) -> Void) -> Void)? var getAccessibilityElementsImpl: (() -> [Any]?)? - /*override var accessibilityElements: [Any]? { - get { - return self.getAccessibilityElementsImpl?() - } set(value) { - } - }*/ - override var frame: CGRect { get { return super.frame @@ -248,13 +241,13 @@ private final class NativeWindow: UIWindow, WindowHost { self.layoutSubviewsEvent?() } - /*override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { + override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { self.updateIsUpdatingOrientationLayout?(true) super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) self.updateIsUpdatingOrientationLayout?(false) self.updateToInterfaceOrientation?() - }*/ + } func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { self.presentController?(controller, level, blockInteraction, completion) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 764a8e5a49..436635c397 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -812,8 +812,12 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) + if !controller.hasActiveInput { + self.view.endEditing(true) + } if let validLayout = self.validLayout { - let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + var (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + controllerLayout.inputHeight = nil controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in diff --git a/Display/UIWindow+OrientationChange.h b/Display/UIWindow+OrientationChange.h index 5ff3094647..b6f1320618 100644 --- a/Display/UIWindow+OrientationChange.h +++ b/Display/UIWindow+OrientationChange.h @@ -6,6 +6,6 @@ + (void)addPostDeviceOrientationDidChangeBlock:(void (^)())block; + (bool)isDeviceRotating; -//- (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3; +- (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3; @end diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 7f24a652bd..c50a785e97 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -290,6 +290,7 @@ public class Window1 { private let statusBarManager: StatusBarManager? private let keyboardManager: KeyboardManager? private var statusBarChangeObserver: AnyObject? + private var keyboardRotationChangeObserver: AnyObject? private var keyboardFrameChangeObserver: AnyObject? private var keyboardTypeChangeObserver: AnyObject? private var voiceOverStatusObserver: AnyObject? @@ -436,6 +437,28 @@ public class Window1 { strongSelf.updateLayout { $0.update(statusBarHeight: statusBarHeight, transition: transition, overrideTransition: false) } } }) + self.keyboardRotationChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name("UITextEffectsWindowDidRotateNotification"), object: nil, queue: nil, using: { [weak self] notification in + if let strongSelf = self { + if !strongSelf.hostView.isUpdatingOrientationLayout { + return + } + let keyboardHeight = max(0.0, strongSelf.keyboardManager?.getCurrentKeyboardHeight() ?? 0.0) + var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 + if duration > Double.ulpOfOne { + duration = 0.5 + } + let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7 + + let transitionCurve: ContainedViewLayoutTransitionCurve + if curve == 7 { + transitionCurve = .spring + } else { + transitionCurve = .easeInOut + } + + strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) } + } + }) self.keyboardFrameChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIKeyboardWillChangeFrame, object: nil, queue: nil, using: { [weak self] notification in if let strongSelf = self { @@ -536,6 +559,9 @@ public class Window1 { if let statusBarChangeObserver = self.statusBarChangeObserver { NotificationCenter.default.removeObserver(statusBarChangeObserver) } + if let keyboardRotationChangeObserver = self.keyboardRotationChangeObserver { + NotificationCenter.default.removeObserver(keyboardRotationChangeObserver) + } if let keyboardFrameChangeObserver = self.keyboardFrameChangeObserver { NotificationCenter.default.removeObserver(keyboardFrameChangeObserver) } @@ -794,7 +820,7 @@ public class Window1 { } if !UIWindow.isDeviceRotating() { - if true || !self.hostView.isUpdatingOrientationLayout { + if !self.hostView.isUpdatingOrientationLayout { self.commitUpdatingLayout() } else { self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in From 0fe6ff0e40aba8b9c26a1c4312a0cdb9186d1989 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 22 Feb 2019 18:22:10 +0300 Subject: [PATCH 175/245] TextNode: added textRangesRects --- Display/TextNode.swift | 52 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 96a34bbae9..2d53782392 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -216,6 +216,54 @@ public final class TextNodeLayout: NSObject { return rects } + public func textRangesRects(text: String) -> [[CGRect]] { + guard let attributedString = self.attributedString else { + return [] + } + var ranges: [Range] = [] + var searchRange = attributedString.string.startIndex ..< attributedString.string.endIndex + while searchRange.lowerBound != attributedString.string.endIndex { + if let range = attributedString.string.range(of: text, options: [.caseInsensitive, .diacriticInsensitive], range: searchRange, locale: nil) { + ranges.append(range) + searchRange = range.upperBound ..< attributedString.string.endIndex + } else { + break + } + } + var result: [[CGRect]] = [] + for stringRange in ranges { + var rects: [CGRect] = [] + let range = NSRange(stringRange, in: attributedString.string) + for line in self.lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != line.range.length { + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } + } + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + + rects.append(CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height))) + } + } + if !rects.isEmpty { + result.append(rects) + } + } + return result + } + public func attributeSubstring(name: String, index: Int) -> String? { if let attributedString = self.attributedString { var range = NSRange() @@ -284,6 +332,10 @@ public class TextNode: ASDisplayNode { } } + public func textRangesRects(text: String) -> [[CGRect]] { + return self.cachedLayout?.textRangesRects(text: text) ?? [] + } + public func attributeSubstring(name: String, index: Int) -> String? { return self.cachedLayout?.attributeSubstring(name: name, index: index) } From 3e894401d8d0ec83a6615038176cfe1c20aa8008 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 26 Feb 2019 18:23:05 +0300 Subject: [PATCH 176/245] NavigationBar: add navigation bar content to accessibility children TabBarNode: expose tabs to accessibility --- Display/NavigationBar.swift | 3 +++ Display/TabBarNode.swift | 18 ++++++++++++++---- 2 files changed, 17 insertions(+), 4 deletions(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 546f231b3f..98f4f559d2 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -340,6 +340,9 @@ open class NavigationBar: ASDisplayNode { if self.rightButtonNode.supernode != nil { addAccessibilityChildren(of: self.rightButtonNode, container: self, to: &accessibilityElements) } + if let contentNode = self.contentNode { + addAccessibilityChildren(of: contentNode, container: self, to: &accessibilityElements) + } return accessibilityElements } set(value) { } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 0015619cc9..4f633dd2f9 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -95,12 +95,16 @@ private final class TabBarItemNode: ASDisplayNode { self.imageNode = ASImageNode() self.imageNode.displayWithoutProcessing = true self.imageNode.displaysAsynchronously = false + self.imageNode.isAccessibilityElement = false self.textImageNode = ASImageNode() self.textImageNode.displayWithoutProcessing = true self.textImageNode.displaysAsynchronously = false + self.textImageNode.isAccessibilityElement = false super.init() + self.isAccessibilityElement = true + self.addSubnode(self.textImageNode) self.addSubnode(self.imageNode) } @@ -116,7 +120,7 @@ private final class TabBarNodeContainer { let imageNode: TabBarItemNode let badgeContainerNode: ASDisplayNode let badgeBackgroundNode: ASImageNode - let badgeTextNode: ASTextNode + let badgeTextNode: ImmediateTextNode var badgeValue: String? var appliedBadgeValue: String? @@ -134,6 +138,8 @@ private final class TabBarNodeContainer { self.item = item self.imageNode = imageNode + self.imageNode.isAccessibilityElement = true + self.imageNode.accessibilityTraits = UIAccessibilityTraitButton self.badgeContainerNode = ASDisplayNode() self.badgeContainerNode.isUserInteractionEnabled = false @@ -145,7 +151,7 @@ private final class TabBarNodeContainer { self.badgeBackgroundNode.displaysAsynchronously = false self.badgeBackgroundNode.isAccessibilityElement = false - self.badgeTextNode = ASTextNode() + self.badgeTextNode = ImmediateTextNode() self.badgeTextNode.maximumNumberOfLines = 1 self.badgeTextNode.isUserInteractionEnabled = false self.badgeTextNode.displaysAsynchronously = false @@ -228,7 +234,7 @@ class TabBarNode: ASDisplayNode { super.init() - self.isAccessibilityContainer = true + self.isAccessibilityContainer = false self.isOpaque = true self.backgroundColor = theme.tabBarBackgroundColor @@ -298,11 +304,13 @@ class TabBarNode: ASDisplayNode { let (image, imageContentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true) node.textImageNode.image = textImage node.imageNode.image = image + node.accessibilityLabel = item.title node.contentWidth = max(contentWidth, imageContentWidth) } else { let (textImage, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false) let (image, imageContentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: true) node.textImageNode.image = textImage + node.accessibilityLabel = item.title node.imageNode.image = image node.contentWidth = max(contentWidth, imageContentWidth) } @@ -331,12 +339,14 @@ class TabBarNode: ASDisplayNode { let (textImage, contentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: false) let (image, imageContentWidth) = tabBarItemImage(item.selectedImage, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarSelectedTextColor, horizontal: self.horizontal, imageMode: true) node.textImageNode.image = textImage + node.accessibilityLabel = item.title node.imageNode.image = image node.contentWidth = max(contentWidth, imageContentWidth) } else { let (textImage, contentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: false) let (image, imageContentWidth) = tabBarItemImage(item.image, title: item.title ?? "", backgroundColor: .clear, tintColor: self.theme.tabBarTextColor, horizontal: self.horizontal, imageMode: true) node.textImageNode.image = textImage + node.accessibilityLabel = item.title node.imageNode.image = image node.contentWidth = max(contentWidth, imageContentWidth) } @@ -411,7 +421,7 @@ class TabBarNode: ASDisplayNode { } if !container.badgeContainerNode.isHidden { - let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0)) + let badgeSize = container.badgeTextNode.updateLayout(CGSize(width: 200.0, height: 100.0)) let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame: CGRect if horizontal { From 39ba477add3cb9ce02d4041c50faf01e0eaf66cb Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 26 Feb 2019 23:32:27 +0400 Subject: [PATCH 177/245] Fixed empty detail placeholder --- Display/NavigationController.swift | 2 +- Display/VolumeControlStatusBar.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 436635c397..17bda998f1 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -188,7 +188,7 @@ open class NavigationController: UINavigationController, ContainableController, if let emptyDetailView = self.controllerView.emptyDetailView { emptyDetailView.image = theme.emptyDetailIcon if let image = theme.emptyDetailIcon { - emptyDetailView.frame = CGRect(origin: CGPoint(x: floor((self.controllerView.containerView.bounds.size.width - image.size.width) / 2.0), y: floor((self.controllerView.containerView.bounds.size.height - image.size.height) / 2.0)), size: image.size) + emptyDetailView.frame = CGRect(origin: CGPoint(x: floor(emptyDetailView.center.x - image.size.width / 2.0), y: floor(emptyDetailView.center.y - image.size.height / 2.0)), size: image.size) } } } diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index b3b3fc50b1..94b9ff972f 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -237,7 +237,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { if let graphics = self.innerGraphics { if self.value > 0.5 { self.iconNode.image = graphics.2 - } else if self.value > 0.0 { + } else if self.value > 0.001 { self.iconNode.image = graphics.1 } else { self.iconNode.image = graphics.0 From 9388b2bd2d9f9202358cec9df26fa41c710e1245 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 26 Feb 2019 22:52:56 +0300 Subject: [PATCH 178/245] Update project --- Display.xcodeproj/project.pbxproj | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 0a0e2aff9a..badce406d9 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -1454,7 +1454,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -1650,7 +1651,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -1679,7 +1681,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -1779,7 +1782,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -1860,7 +1864,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -1941,7 +1946,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -2088,7 +2094,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; @@ -2235,7 +2242,8 @@ buildSettings = { APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; - "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Distribution"; + CODE_SIGN_IDENTITY = ""; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; CODE_SIGN_STYLE = Manual; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; From 124cc2405952513c5ad3b1132d28b794b5881dae Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 27 Feb 2019 03:21:58 +0400 Subject: [PATCH 179/245] Added text with icon tooltip content --- Display/TooltipController.swift | 31 +++++++++++++++++++++++------ Display/TooltipControllerNode.swift | 23 +++++++++++++++++---- 2 files changed, 44 insertions(+), 10 deletions(-) diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index df87e4eda0..f396a03dab 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -2,6 +2,25 @@ import Foundation import AsyncDisplayKit import SwiftSignalKit +public enum TooltipControllerContent: Equatable { + case text(String) + case iconAndText(UIImage, String) + + var text: String { + switch self { + case let .text(text), let .iconAndText(_, text): + return text + } + } + + var image: UIImage? { + if case let .iconAndText(image, _) = self { + return image + } + return nil + } +} + private enum SourceAndRect { case node(() -> (ASDisplayNode, CGRect)?) case view(() -> (UIView, CGRect)?) @@ -38,11 +57,11 @@ public final class TooltipController: ViewController { return self.displayNode as! TooltipControllerNode } - public var text: String { + public var content: TooltipControllerContent { didSet { - if self.text != oldValue { + if self.content != oldValue { if self.isNodeLoaded { - self.controllerNode.updateText(self.text, transition: .animated(duration: 0.25, curve: .easeInOut)) + self.controllerNode.updateText(self.content.text, transition: .animated(duration: 0.25, curve: .easeInOut)) if self.timeoutTimer != nil { self.timeoutTimer?.invalidate() self.timeoutTimer = nil @@ -61,8 +80,8 @@ public final class TooltipController: ViewController { public var dismissed: (() -> Void)? - public init(text: String, timeout: Double = 2.0, dismissByTapOutside: Bool = false) { - self.text = text + public init(content: TooltipControllerContent, timeout: Double = 2.0, dismissByTapOutside: Bool = false) { + self.content = content self.timeout = timeout self.dismissByTapOutside = dismissByTapOutside @@ -78,7 +97,7 @@ public final class TooltipController: ViewController { } public override func loadDisplayNode() { - self.displayNode = TooltipControllerNode(text: self.text, dismiss: { [weak self] in + self.displayNode = TooltipControllerNode(content: self.content, dismiss: { [weak self] in self?.dismiss() }, dismissByTapOutside: self.dismissByTapOutside) self.displayNodeDidLoad() diff --git a/Display/TooltipControllerNode.swift b/Display/TooltipControllerNode.swift index a1c8d3c99c..2e555ccbaa 100644 --- a/Display/TooltipControllerNode.swift +++ b/Display/TooltipControllerNode.swift @@ -8,6 +8,7 @@ final class TooltipControllerNode: ASDisplayNode { private var validLayout: ContainerViewLayout? private let containerNode: ContextMenuContainerNode + private let imageNode: ASImageNode private let textNode: ImmediateTextNode private let dismissByTapOutside: Bool @@ -17,14 +18,17 @@ final class TooltipControllerNode: ASDisplayNode { private var dismissedByTouchOutside = false - init(text: String, dismiss: @escaping () -> Void, dismissByTapOutside: Bool) { + init(content: TooltipControllerContent, dismiss: @escaping () -> Void, dismissByTapOutside: Bool) { self.dismissByTapOutside = dismissByTapOutside self.containerNode = ContextMenuContainerNode() self.containerNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + self.imageNode = ASImageNode() + self.imageNode.image = content.image + self.textNode = ImmediateTextNode() - self.textNode.attributedText = NSAttributedString(string: text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) + self.textNode.attributedText = NSAttributedString(string: content.text, font: Font.regular(14.0), textColor: .white, paragraphAlignment: .center) self.textNode.isUserInteractionEnabled = false self.textNode.displaysAsynchronously = false self.textNode.maximumNumberOfLines = 0 @@ -33,6 +37,7 @@ final class TooltipControllerNode: ASDisplayNode { super.init() + self.containerNode.addSubnode(self.imageNode) self.containerNode.addSubnode(self.textNode) self.addSubnode(self.containerNode) @@ -58,10 +63,17 @@ final class TooltipControllerNode: ASDisplayNode { let maxActionsWidth = layout.size.width - 20.0 + var imageSize = CGSize() + var imageSizeWithInset = CGSize() + if let image = self.imageNode.image { + imageSize = image.size + imageSizeWithInset = CGSize(width: image.size.width + 12.0, height: image.size.height) + } + var textSize = self.textNode.updateLayout(CGSize(width: maxActionsWidth, height: CGFloat.greatestFiniteMagnitude)) textSize.width = ceil(textSize.width / 2.0) * 2.0 textSize.height = ceil(textSize.height / 2.0) * 2.0 - let contentSize = CGSize(width: textSize.width + 12.0, height: textSize.height + 34.0) + let contentSize = CGSize(width: imageSizeWithInset.width + textSize.width + 12.0, height: textSize.height + 34.0) let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize()) @@ -84,10 +96,13 @@ final class TooltipControllerNode: ASDisplayNode { self.containerNode.updateLayout(transition: transition) - let textFrame = CGRect(origin: CGPoint(x: 6.0, y: 17.0), size: textSize) + let textFrame = CGRect(origin: CGPoint(x: 6.0 + imageSizeWithInset.width, y: 17.0), size: textSize) if transition.isAnimated, textFrame.size != self.textNode.frame.size { transition.animatePositionAdditive(node: self.textNode, offset: CGPoint(x: textFrame.minX - self.textNode.frame.minX, y: 0.0)) } + + let imageFrame = CGRect(origin: CGPoint(x: 10.0, y: floor((contentSize.height - imageSize.height) / 2.0)), size: imageSize) + self.imageNode.frame = imageFrame self.textNode.frame = textFrame } From 798e2dda429d112e856e743bcae943f25a21f406 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 2 Mar 2019 00:01:24 +0400 Subject: [PATCH 180/245] ListView: added generalScrollDirectionUpdated --- Display/ListView.swift | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/Display/ListView.swift b/Display/ListView.swift index 06cbd5a42d..80a85dca70 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -116,6 +116,11 @@ public struct ListViewKeepTopItemOverscrollBackground { } } +public enum GeneralScrollDirection { + case up + case down +} + open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGestureRecognizerDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() @@ -228,6 +233,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public final var beganInteractiveDragging: () -> Void = { } public final var didEndScrolling: (() -> Void)? + private var currentGeneralScrollDirection: GeneralScrollDirection? + public final var generalScrollDirectionUpdated: (GeneralScrollDirection) -> Void = { _ in } + public final var reorderItem: (Int, Int, Any?) -> Signal = { _, _, _ in return .single(false) } private final var animations: [ListViewAnimation] = [] @@ -602,6 +610,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.updateScrollViewDidScroll(scrollView, synchronous: false) } + private var generalAccumulatedDeltaY: CGFloat = 0.0 + private func updateScrollViewDidScroll(_ scrollView: UIScrollView, synchronous: Bool) { if self.ignoreScrollingEvents || scroller !== self.scroller { return @@ -611,6 +621,15 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture //CATransaction.setDisableActions(true) let deltaY = scrollView.contentOffset.y - self.lastContentOffset.y + self.generalAccumulatedDeltaY += deltaY + if abs(self.generalAccumulatedDeltaY) > 14.0 { + let direction: GeneralScrollDirection = self.generalAccumulatedDeltaY < 0 ? .up : .down + self.generalAccumulatedDeltaY = 0.0 + if self.currentGeneralScrollDirection != direction { + self.currentGeneralScrollDirection = direction + self.generalScrollDirectionUpdated(direction) + } + } self.lastContentOffset = scrollView.contentOffset if !self.lastContentOffsetTimestamp.isZero { From 552b4a26610d66a087465913aa7e9dc2c46b931a Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 5 Mar 2019 13:36:50 +0000 Subject: [PATCH 181/245] VoiceOver updates --- Display/AlertController.swift | 2 ++ Display/ContainableController.swift | 2 ++ Display/NavigationController.swift | 12 +++++++++- Display/PresentationContext.swift | 37 +++++++++++++++++++++++++++++ Display/TabBarController.swift | 11 ++++++++- Display/ViewController.swift | 1 + Display/WindowContent.swift | 4 ++++ 7 files changed, 67 insertions(+), 2 deletions(-) diff --git a/Display/AlertController.swift b/Display/AlertController.swift index f829250ba5..de34d7b060 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -68,6 +68,8 @@ open class AlertController: ViewController { super.init(navigationBarPresentationData: nil) + self.blocksBackgroundWhenInOverlay = true + self.statusBar.statusBarStyle = .Ignore } diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 055fa46254..ec51a25542 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -4,8 +4,10 @@ import SwiftSignalKit public protocol ContainableController: class { var view: UIView! { get } + var displayNode: ASDisplayNode { get } var isViewLoaded: Bool { get } var isOpaqueWhenInOverlay: Bool { get } + var blocksBackgroundWhenInOverlay: Bool { get } var ready: Promise { get } func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 17bda998f1..97be54cde0 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -96,6 +96,7 @@ public enum NavigationControllerMode { open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { public var isOpaqueWhenInOverlay: Bool = true + public var blocksBackgroundWhenInOverlay: Bool = true public var ready: Promise = Promise(true) @@ -140,6 +141,11 @@ open class NavigationController: UINavigationController, ContainableController, return self._viewControllers.last?.controller } + private var _displayNode: ASDisplayNode? + public var displayNode: ASDisplayNode { + return self._displayNode! + } + public init(mode: NavigationControllerMode, theme: NavigationControllerTheme) { self.mode = mode self.theme = theme @@ -594,7 +600,11 @@ open class NavigationController: UINavigationController, ContainableController, } open override func loadView() { - self.view = NavigationControllerView() + self._displayNode = ASDisplayNode(viewBlock: { + return NavigationControllerView() + }, didLoad: nil) + + self.view = self.displayNode.view self.view.clipsToBounds = true self.view.autoresizingMask = [] diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 39a8922f40..766f6974de 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -33,6 +33,15 @@ final class PresentationContext { var updateIsInteractionBlocked: ((Bool) -> Void)? + var updateHasOpaqueOverlay: ((Bool) -> Void)? + private(set) var hasOpaqueOverlay: Bool = false { + didSet { + if self.hasOpaqueOverlay != oldValue { + self.updateHasOpaqueOverlay?(self.hasOpaqueOverlay) + } + } + } + private var layout: ContainerViewLayout? private var ready: Bool { @@ -56,6 +65,15 @@ final class PresentationContext { return false } + var currentlyBlocksBackgroundWhenInOverlay: Bool { + for (controller, _) in self.controllers { + if controller.isOpaqueWhenInOverlay || controller.blocksBackgroundWhenInOverlay { + return true + } + } + return false + } + private func topLevelSubview(for level: PresentationSurfaceLevel) -> UIView? { var topController: ContainableController? for (controller, controllerLevel) in self.controllers.reversed() { @@ -173,10 +191,12 @@ final class PresentationContext { controller.viewWillAppear(false) controller.viewDidAppear(false) } + strongSelf.updateViews() } })) } else { self.controllers.append((controller, level)) + self.updateViews() } } @@ -190,6 +210,7 @@ final class PresentationContext { controller.viewWillDisappear(false) controller.view.removeFromSuperview() controller.viewDidDisappear(false) + self.updateViews() } } @@ -227,6 +248,7 @@ final class PresentationContext { controller.containerLayoutUpdated(layout, transition: .immediate) controller.viewDidAppear(false) } + self.updateViews() } } @@ -238,6 +260,21 @@ final class PresentationContext { } } + private func updateViews() { + self.hasOpaqueOverlay = self.isCurrentlyOpaque + var topHasOpaque = false + for (controller, _) in self.controllers.reversed() { + if topHasOpaque { + controller.displayNode.accessibilityElementsHidden = true + } else { + if controller.isOpaqueWhenInOverlay || controller.blocksBackgroundWhenInOverlay { + topHasOpaque = true + } + controller.displayNode.accessibilityElementsHidden = false + } + } + } + func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (controller, _) in self.controllers.reversed() { if controller.isViewLoaded { diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 3a5de78807..d671c6a3bb 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -329,7 +329,16 @@ open class TabBarController: ViewController { } |> filter { $0 } |> take(1) - self._ready.set(signals) + + let allReady = signals + |> deliverOnMainQueue + |> mapToSignal { _ -> Signal in + // wait for tab bar items to be applied + return .single(true) + |> delay(0.0, queue: Queue.mainQueue()) + } + + self._ready.set(allReady) if let updatedSelectedIndex = updatedSelectedIndex { self.selectedIndex = updatedSelectedIndex diff --git a/Display/ViewController.swift b/Display/ViewController.swift index c599f392e2..cddb0e2eda 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -74,6 +74,7 @@ open class ViewControllerPresentationArguments { } public final var isOpaqueWhenInOverlay: Bool = false + public final var blocksBackgroundWhenInOverlay: Bool = false public func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { return self.supportedOrientations diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index c50a785e97..bc674b2c5f 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -370,6 +370,10 @@ public class Window1 { self?.isInteractionBlocked = value } + self.presentationContext.updateHasOpaqueOverlay = { [weak self] value in + self?._rootController?.displayNode.accessibilityElementsHidden = value + } + self.hostView.present = { [weak self] controller, level, blockInteraction, completion in self?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } From 7077ff31dc8786d143ba09b5874f0c014126e6c3 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 5 Mar 2019 13:47:54 +0000 Subject: [PATCH 182/245] Fix alert text accessibility --- Display/TextAlertController.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index b1e8fbe167..4ed12cb19e 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -141,6 +141,7 @@ public final class TextAlertContentNode: AlertContentNode { titleNode.isUserInteractionEnabled = false titleNode.maximumNumberOfLines = 2 titleNode.truncationMode = .byTruncatingTail + titleNode.isAccessibilityElement = true self.titleNode = titleNode } else { self.titleNode = nil @@ -151,6 +152,8 @@ public final class TextAlertContentNode: AlertContentNode { self.textNode.attributedText = text self.textNode.displaysAsynchronously = false self.textNode.isLayerBacked = false + self.textNode.isAccessibilityElement = true + self.textNode.accessibilityLabel = text.string if text.length != 0 { if let paragraphStyle = text.attribute(.paragraphStyle, at: 0, effectiveRange: nil) as? NSParagraphStyle { self.textNode.textAlignment = paragraphStyle.alignment From c278cf193b21fc73af56b3db8ac225d741e803c1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 6 Mar 2019 00:23:48 +0300 Subject: [PATCH 183/245] Volume bar fix --- Display/VolumeControlStatusBar.swift | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 94b9ff972f..1e6ce33c74 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -29,10 +29,6 @@ final class VolumeControlStatusBar: UIView { self.addSubview(self.control) self.observer = NotificationCenter.default.addObserver(forName: NSNotification.Name(rawValue: volumeNotificationKey), object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self, let userInfo = notification.userInfo { - if let reason = userInfo[changeReasonParameterKey], reason as? String != explicitChangeReasonValue { - return - } - if let volume = userInfo[volumeParameterKey] as? Float { let previous = strongSelf.currentValue if !previous.isEqual(to: volume) { @@ -41,6 +37,9 @@ final class VolumeControlStatusBar: UIView { strongSelf.ignoreAdjustmentOnce = false } else { if strongSelf.control.superview != nil { + if let reason = userInfo[changeReasonParameterKey], reason as? String != explicitChangeReasonValue { + return + } strongSelf.valueChanged?(previous, volume) } } From ca80cc40dce4ae5a3b6839c42cd6731097ba47ff Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 8 Mar 2019 18:33:53 +0300 Subject: [PATCH 184/245] Added bounds transition --- Display/ContainedViewLayoutTransition.swift | 22 +++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index 436e0c82e6..eff63bd626 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -91,6 +91,28 @@ public extension ContainedViewLayoutTransition { } } + func updateBounds(layer: CALayer, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if layer.bounds.equalTo(bounds) && !force { + completion?(true) + } else { + switch self { + case .immediate: + layer.bounds = bounds + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousBounds = layer.bounds + layer.bounds = bounds + layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + } + func updatePosition(node: ASDisplayNode, position: CGPoint, completion: ((Bool) -> Void)? = nil) { if node.position.equalTo(position) { completion?(true) From 26d26bb3a5341eda49deb555cdc83c93f267bc8b Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 15 Mar 2019 20:54:22 +0300 Subject: [PATCH 185/245] Pass interface orientation updates to view controller --- Display/ContainableController.swift | 1 + Display/ContextMenuContainerNode.swift | 20 +++++++++---------- .../GlobalOverlayPresentationContext.swift | 8 ++++++++ Display/NativeWindowHostView.swift | 9 +++++---- Display/NavigationController.swift | 8 ++++++++ Display/PresentationContext.swift | 8 ++++++++ Display/TooltipController.swift | 16 +++++++++++---- Display/ViewController.swift | 4 ++++ Display/WindowContent.swift | 15 ++++++++++---- 9 files changed, 67 insertions(+), 22 deletions(-) diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index ec51a25542..fb77d8e463 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -14,6 +14,7 @@ public protocol ContainableController: class { var deferScreenEdgeGestures: UIRectEdge { get } func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) + func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) func viewWillAppear(_ animated: Bool) func viewWillDisappear(_ animated: Bool) diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift index 3ba11b0d10..52951a9317 100644 --- a/Display/ContextMenuContainerNode.swift +++ b/Display/ContextMenuContainerNode.swift @@ -13,15 +13,15 @@ private final class ContextMenuContainerMaskView: UIView { } } -final class ContextMenuContainerNode: ASDisplayNode { +public final class ContextMenuContainerNode: ASDisplayNode { private var cachedMaskParams: CachedMaskParams? private let maskView = ContextMenuContainerMaskView() - var relativeArrowPosition: (CGFloat, Bool)? + public var relativeArrowPosition: (CGFloat, Bool)? //private let effectView: UIVisualEffectView - override init() { + override public init() { //self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) super.init() @@ -32,19 +32,19 @@ final class ContextMenuContainerNode: ASDisplayNode { self.view.mask = self.maskView } - override func didLoad() { + override public func didLoad() { super.didLoad() self.layer.allowsGroupOpacity = true } - override func layout() { + override public func layout() { super.layout() self.updateLayout(transition: .immediate) } - func updateLayout(transition: ContainedViewLayoutTransition) { + public func updateLayout(transition: ContainedViewLayoutTransition) { //self.effectView.frame = self.bounds let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true) @@ -58,23 +58,23 @@ final class ContextMenuContainerNode: ASDisplayNode { let arrowOnBottom = maskParams.arrowOnBottom path.move(to: CGPoint(x: 0.0, y: verticalInset + cornerRadius)) - path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat.pi, endAngle: CGFloat(3 * M_PI / 2), clockwise: true) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat.pi, endAngle: CGFloat(3.0 * CGFloat.pi / 2.0), clockwise: true) if !arrowOnBottom { path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: verticalInset)) path.addLine(to: CGPoint(x: arrowPosition, y: 0.0)) path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: verticalInset)) } path.addLine(to: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset)) - path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(3 * M_PI / 2), endAngle: 0.0, clockwise: true) + path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(3.0 * CGFloat.pi / 2.0), endAngle: 0.0, clockwise: true) path.addLine(to: CGPoint(x: maskParams.size.width, y: maskParams.size.height - cornerRadius - verticalInset)) - path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: 0.0, endAngle: CGFloat(M_PI / 2.0), clockwise: true) + path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: 0.0, endAngle: CGFloat(CGFloat.pi / 2.0), clockwise: true) if arrowOnBottom { path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: maskParams.size.height - verticalInset)) path.addLine(to: CGPoint(x: arrowPosition, y: maskParams.size.height)) path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: maskParams.size.height - verticalInset)) } path.addLine(to: CGPoint(x: cornerRadius, y: maskParams.size.height - verticalInset)) - path.addArc(withCenter: CGPoint(x: cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: CGFloat(M_PI / 2.0), endAngle: CGFloat(M_PI), clockwise: true) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: CGFloat(CGFloat.pi / 2.0), endAngle: CGFloat(M_PI), clockwise: true) path.close() self.cachedMaskParams = maskParams diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index c033cccae8..30f36ee169 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -158,6 +158,14 @@ final class GlobalOverlayPresentationContext { return nil } + func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { + if self.ready { + for controller in self.controllers { + controller.updateToInterfaceOrientation(orientation) + } + } + } + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index ca119323ac..359f637568 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -169,7 +169,7 @@ private final class NativeWindow: UIWindow, WindowHost { var updateSize: ((CGSize) -> Void)? var layoutSubviewsEvent: (() -> Void)? var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? - var updateToInterfaceOrientation: (() -> Void)? + var updateToInterfaceOrientation: ((UIInterfaceOrientation) -> Void)? var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)? var presentControllerInGlobalOverlay: ((_ controller: ContainableController) -> Void)? var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? @@ -246,7 +246,8 @@ private final class NativeWindow: UIWindow, WindowHost { super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) self.updateIsUpdatingOrientationLayout?(false) - self.updateToInterfaceOrientation?() + let orientation = UIInterfaceOrientation(rawValue: Int(arg1)) ?? .unknown + self.updateToInterfaceOrientation?(orientation) } func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) { @@ -316,8 +317,8 @@ public func nativeWindowHostView() -> (UIWindow & WindowHost, WindowHostView) { hostView?.isUpdatingOrientationLayout = value } - window.updateToInterfaceOrientation = { [weak hostView] in - hostView?.updateToInterfaceOrientation?() + window.updateToInterfaceOrientation = { [weak hostView] orientation in + hostView?.updateToInterfaceOrientation?(orientation) } window.presentController = { [weak hostView] controller, level, blockInteraction, completion in diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 97be54cde0..6b7828d385 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -599,6 +599,14 @@ open class NavigationController: UINavigationController, ContainableController, } } + public func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { + for record in self._viewControllers { + if let controller = record.controller as? ContainableController { + controller.updateToInterfaceOrientation(orientation) + } + } + } + open override func loadView() { self._displayNode = ASDisplayNode(viewBlock: { return NavigationControllerView() diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 766f6974de..bbfb45dc09 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -286,6 +286,14 @@ final class PresentationContext { return nil } + func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { + if self.ready { + for (controller, _) in self.controllers { + controller.updateToInterfaceOrientation(orientation) + } + } + } + func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations { var mask = ViewControllerSupportedOrientations(regularSize: .all, compactSize: .all) diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index f396a03dab..33fc6a5792 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -74,16 +74,18 @@ public final class TooltipController: ViewController { private let timeout: Double private let dismissByTapOutside: Bool + private let dismissImmediatelyOnLayoutUpdate: Bool private var timeoutTimer: SwiftSignalKit.Timer? private var layout: ContainerViewLayout? public var dismissed: (() -> Void)? - public init(content: TooltipControllerContent, timeout: Double = 2.0, dismissByTapOutside: Bool = false) { + public init(content: TooltipControllerContent, timeout: Double = 2.0, dismissByTapOutside: Bool = false, dismissImmediatelyOnLayoutUpdate: Bool = false) { self.content = content self.timeout = timeout self.dismissByTapOutside = dismissByTapOutside + self.dismissImmediatelyOnLayoutUpdate = dismissImmediatelyOnLayoutUpdate super.init(navigationBarPresentationData: nil) } @@ -114,9 +116,10 @@ public final class TooltipController: ViewController { super.containerLayoutUpdated(layout, transition: transition) if self.layout != nil && self.layout! != layout { - self.dismissed?() - self.controllerNode.animateOut { [weak self] in - self?.presentingViewController?.dismiss(animated: false) + if self.dismissImmediatelyOnLayoutUpdate { + self.dismissImmediately() + } else { + self.dismiss() } } else { self.layout = layout @@ -160,4 +163,9 @@ public final class TooltipController: ViewController { completion?() } } + + public func dismissImmediately() { + self.dismissed?() + self.presentingViewController?.dismiss(animated: false) + } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index cddb0e2eda..275de0ce6f 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -327,6 +327,10 @@ open class ViewControllerPresentationArguments { } } + open func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { + + } + public func setDisplayNavigationBar(_ displayNavigationBar: Bool, transition: ContainedViewLayoutTransition = .immediate) { if displayNavigationBar != self.displayNavigationBar { self.displayNavigationBar = displayNavigationBar diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index bc674b2c5f..bcf1bdc838 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -220,7 +220,7 @@ public final class WindowHostView { var presentNative: ((UIViewController) -> Void)? var updateSize: ((CGSize, Double) -> Void)? var layoutSubviews: (() -> Void)? - var updateToInterfaceOrientation: (() -> Void)? + var updateToInterfaceOrientation: ((UIInterfaceOrientation) -> Void)? var isUpdatingOrientationLayout = false var hitTest: ((CGPoint, UIEvent?) -> UIView?)? var invalidateDeferScreenEdgeGesture: (() -> Void)? @@ -398,8 +398,8 @@ public class Window1 { self?.layoutSubviews() } - self.hostView.updateToInterfaceOrientation = { [weak self] in - self?.updateToInterfaceOrientation() + self.hostView.updateToInterfaceOrientation = { [weak self] orientation in + self?.updateToInterfaceOrientation(orientation) } self.hostView.hitTest = { [weak self] point, event in @@ -844,12 +844,19 @@ public class Window1 { var postUpdateToInterfaceOrientationBlocks: [() -> Void] = [] - private func updateToInterfaceOrientation() { + private func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation) { let blocks = self.postUpdateToInterfaceOrientationBlocks self.postUpdateToInterfaceOrientationBlocks = [] for f in blocks { f() } + self._rootController?.updateToInterfaceOrientation(orientation) + self.presentationContext.updateToInterfaceOrientation(orientation) + self.overlayPresentationContext.updateToInterfaceOrientation(orientation) + + for controller in self.topLevelOverlayControllers { + controller.updateToInterfaceOrientation(orientation) + } } public func addPostUpdateToInterfaceOrientationBlock(f: @escaping () -> Void) { From c759393bd712698c3fd7a7427915e8e99fc8da6f Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 16 Mar 2019 19:08:31 +0400 Subject: [PATCH 186/245] Added AccessibilityAreaNode --- Display.xcodeproj/project.pbxproj | 4 ++++ Display/AccessibilityAreaNode.swift | 20 ++++++++++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 Display/AccessibilityAreaNode.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index badce406d9..decc094ebe 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -56,6 +56,7 @@ D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; D03310B3213F232600FC83CD /* ListViewTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */; }; + D033874E223D3E86007A2CE4 /* AccessibilityAreaNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */; }; D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; @@ -237,6 +238,7 @@ D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewTapGestureRecognizer.swift; sourceTree = ""; }; + D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityAreaNode.swift; sourceTree = ""; }; D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimizeKeyboardGestureRecognizer.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; @@ -565,6 +567,7 @@ D06B76DA20592A97006E9EEA /* LayoutSizes.swift */, D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */, 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */, + D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */, ); name = Utils; sourceTree = ""; @@ -1072,6 +1075,7 @@ D03AA4D7202C79600056C405 /* PeekControllerNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, + D033874E223D3E86007A2CE4 /* AccessibilityAreaNode.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, diff --git a/Display/AccessibilityAreaNode.swift b/Display/AccessibilityAreaNode.swift new file mode 100644 index 0000000000..064dd856f3 --- /dev/null +++ b/Display/AccessibilityAreaNode.swift @@ -0,0 +1,20 @@ +import Foundation +import AsyncDisplayKit + +public final class AccessibilityAreaNode: ASDisplayNode { + public var activate: (() -> Bool)? + + override public init() { + super.init() + + self.isAccessibilityElement = true + } + + override public func accessibilityActivate() -> Bool { + return self.activate?() ?? false + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return nil + } +} From 78e0eab894f7b0dc2696d605c8309fd1b2fd2dc6 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 17 Mar 2019 03:42:46 +0300 Subject: [PATCH 187/245] Added completion callback on ViewControllerPresentationArguments --- Display/ViewController.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 275de0ce6f..a6653141f7 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -47,9 +47,11 @@ public struct ViewControllerSupportedOrientations { open class ViewControllerPresentationArguments { public let presentationAnimation: ViewControllerPresentationAnimation + public let completion: (() -> Void)? - public init(presentationAnimation: ViewControllerPresentationAnimation) { + public init(presentationAnimation: ViewControllerPresentationAnimation, completion: (() -> Void)? = nil) { self.presentationAnimation = presentationAnimation + self.completion = completion } } From 14f51798573ce55090461271a1da4cdae9eaf556 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 18 Mar 2019 01:13:20 +0400 Subject: [PATCH 188/245] Added ActionSheetSwitchItem --- Display.xcodeproj/project.pbxproj | 16 +-- Display/ActionSheetSwitchItem.swift | 103 +++++++++++++++++++ Display/ActionSheetTheme.swift | 17 ++- Display/ImageCache.swift | 154 ---------------------------- 4 files changed, 123 insertions(+), 167 deletions(-) create mode 100644 Display/ActionSheetSwitchItem.swift delete mode 100644 Display/ImageCache.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index decc094ebe..fee09b13ac 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -57,6 +57,7 @@ D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; D03310B3213F232600FC83CD /* ListViewTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */; }; D033874E223D3E86007A2CE4 /* AccessibilityAreaNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */; }; + D0338750223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033874F223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift */; }; D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; @@ -177,7 +178,6 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A021DE473B900BC6096 /* HighlightableButton.swift */; }; - D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; D0E8175E2014ED7D00B82BBB /* CADisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */; }; D0E8175F2014F18F00B82BBB /* ListViewTransactionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; @@ -239,6 +239,7 @@ D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewTapGestureRecognizer.swift; sourceTree = ""; }; D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityAreaNode.swift; sourceTree = ""; }; + D033874F223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetSwitchItem.swift; sourceTree = ""; }; D036574A1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MinimizeKeyboardGestureRecognizer.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; @@ -364,7 +365,6 @@ D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E35A021DE473B900BC6096 /* HighlightableButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableButton.swift; sourceTree = ""; }; - D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CADisplayLink.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; @@ -480,6 +480,7 @@ D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */, D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */, D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */, + D033874F223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift */, ); name = "Action Sheet"; sourceTree = ""; @@ -604,7 +605,6 @@ D0FF9B2E1E7196E2000C66DB /* Keyboard */, D05CC3211B695AA600E235A3 /* Navigation */, D02BDAEC1B6A7053008AFAD2 /* Nodes */, - D0E49C861B83A1680099E553 /* Image Cache */, D05CC2E11B69534100E235A3 /* Supporting Files */, ); path = Display; @@ -787,14 +787,6 @@ name = Alert; sourceTree = ""; }; - D0E49C861B83A1680099E553 /* Image Cache */ = { - isa = PBXGroup; - children = ( - D0E49C871B83A3580099E553 /* ImageCache.swift */, - ); - name = "Image Cache"; - sourceTree = ""; - }; D0FF9B2E1E7196E2000C66DB /* Keyboard */ = { isa = PBXGroup; children = ( @@ -1005,7 +997,6 @@ D03AA5162030C5F80056C405 /* ListViewTempItemNode.swift in Sources */, D087BFB51F75181D003FD209 /* ChildWindowHostView.swift in Sources */, D0FA08C6204880C900DD23FC /* ImmediateTextNode.swift in Sources */, - D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, D03310B3213F232600FC83CD /* ListViewTapGestureRecognizer.swift in Sources */, @@ -1047,6 +1038,7 @@ D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */, D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */, D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */, + D0338750223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift in Sources */, D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, diff --git a/Display/ActionSheetSwitchItem.swift b/Display/ActionSheetSwitchItem.swift new file mode 100644 index 0000000000..dc74666402 --- /dev/null +++ b/Display/ActionSheetSwitchItem.swift @@ -0,0 +1,103 @@ +import Foundation +import AsyncDisplayKit + +public class ActionSheetSwitchItem: ActionSheetItem { + public let title: String + public let isOn: Bool + public let action: (Bool) -> Void + + public init(title: String, isOn: Bool, action: @escaping (Bool) -> Void) { + self.title = title + self.isOn = isOn + self.action = action + } + + public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode { + let node = ActionSheetSwitchNode(theme: theme) + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? ActionSheetSwitchNode else { + assertionFailure() + return + } + + node.setItem(self) + } +} + +public class ActionSheetSwitchNode: ActionSheetItemNode { + private let theme: ActionSheetControllerTheme + + private var item: ActionSheetSwitchItem? + + private let button: HighlightTrackingButton + private let label: ASTextNode + private let switchNode: SwitchNode + + override public init(theme: ActionSheetControllerTheme) { + self.theme = theme + + self.button = HighlightTrackingButton() + + self.label = ASTextNode() + self.label.isUserInteractionEnabled = false + self.label.maximumNumberOfLines = 1 + self.label.displaysAsynchronously = false + self.label.truncationMode = .byTruncatingTail + + self.switchNode = SwitchNode() + self.switchNode.frameColor = theme.switchFrameColor + self.switchNode.contentColor = theme.switchContentColor + self.switchNode.handleColor = theme.switchHandleColor + + super.init(theme: theme) + + self.view.addSubview(self.button) + + self.label.isUserInteractionEnabled = false + self.addSubnode(self.label) + self.addSubnode(self.switchNode) + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + self.switchNode.valueUpdated = { [weak self] value in + self?.item?.action(value) + } + } + + func setItem(_ item: ActionSheetSwitchItem) { + self.item = item + + self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: self.theme.primaryTextColor) + + self.switchNode.isOn = item.isOn + + self.setNeedsLayout() + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + let size = self.bounds.size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + + let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 51.0 - 16.0 * 2.0), height: size.height)) + self.label.frame = CGRect(origin: CGPoint(x: 16.0, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + + let switchSize = CGSize(width: 51.0, height: 31.0) + self.switchNode.frame = CGRect(origin: CGPoint(x: size.width - 16.0 - switchSize.width, y: floor((size.height - switchSize.height) / 2.0)), size: switchSize) + } + + @objc func buttonPressed() { + let value = !self.switchNode.isOn + self.switchNode.setOn(value, animated: true) + self.item?.action(value) + } +} diff --git a/Display/ActionSheetTheme.swift b/Display/ActionSheetTheme.swift index 0496a9af38..1911cb8b3d 100644 --- a/Display/ActionSheetTheme.swift +++ b/Display/ActionSheetTheme.swift @@ -18,8 +18,11 @@ public final class ActionSheetControllerTheme: Equatable { public let secondaryTextColor: UIColor public let controlAccentColor: UIColor public let controlColor: UIColor + public let switchFrameColor: UIColor + public let switchContentColor: UIColor + public let switchHandleColor: UIColor - public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor, controlColor: UIColor) { + public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor, controlColor: UIColor, switchFrameColor: UIColor, switchContentColor: UIColor, switchHandleColor: UIColor) { self.dimColor = dimColor self.backgroundType = backgroundType self.itemBackgroundColor = itemBackgroundColor @@ -31,6 +34,9 @@ public final class ActionSheetControllerTheme: Equatable { self.secondaryTextColor = secondaryTextColor self.controlAccentColor = controlAccentColor self.controlColor = controlColor + self.switchFrameColor = switchFrameColor + self.switchContentColor = switchContentColor + self.switchHandleColor = switchHandleColor } public static func ==(lhs: ActionSheetControllerTheme, rhs: ActionSheetControllerTheme) -> Bool { @@ -67,6 +73,15 @@ public final class ActionSheetControllerTheme: Equatable { if lhs.controlColor != rhs.controlColor { return false } + if lhs.switchFrameColor != rhs.switchFrameColor { + return false + } + if lhs.switchContentColor != rhs.switchContentColor { + return false + } + if lhs.switchHandleColor != rhs.switchHandleColor { + return false + } return true } } diff --git a/Display/ImageCache.swift b/Display/ImageCache.swift deleted file mode 100644 index 6e1d6efd85..0000000000 --- a/Display/ImageCache.swift +++ /dev/null @@ -1,154 +0,0 @@ -import Foundation - -/*private final class ImageCacheData { - let size: CGSize - let bytesPerRow: Int - var data: NSPurgeableData - - var isDiscarded: Bool { - return self.data.isContentDiscarded() - } - - var image: UIImage? { - if self.data.beginContentAccess() { - return self.createImage() - } - return nil - } - - init(size: CGSize, generator: (CGContext) -> Void, takenImage: @noescape(UIImage) -> Void) { - self.size = size - - self.bytesPerRow = (4 * Int(size.width) + 15) & (~15) - self.data = NSPurgeableData(length: self.bytesPerRow * Int(size.height))! - - let colorSpace = CGColorSpaceCreateDeviceRGB() - let bitmapInfo = CGImageAlphaInfo.PremultipliedFirst.rawValue | CGBitmapInfo.ByteOrder32Little.rawValue - - if let context = CGBitmapContextCreate(self.data.mutableBytes, Int(size.width), Int(size.height), 8, bytesPerRow, colorSpace, bitmapInfo) - { - CGContextTranslateCTM(context, size.width / 2.0, size.height / 2.0) - CGContextScaleCTM(context, 1.0, -1.0) - CGContextTranslateCTM(context, -size.width / 2.0, -size.height / 2.0) - - UIGraphicsPushContext(context) - - generator(context) - - UIGraphicsPopContext() - } - - takenImage(self.createImage()) - } - - private func createImage() -> UIImage { - let colorSpace = CGColorSpaceCreateDeviceRGB() - let bitmapInfo = CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue - - let unmanagedData = withUnsafePointer(&self.data, { pointer in - return Unmanaged.fromOpaque(COpaquePointer(pointer)) - }) - unmanagedData.retain() - let dataProvider = CGDataProviderCreateWithData(UnsafeMutablePointer(unmanagedData.toOpaque()), self.data.bytes, self.bytesPerRow, { info, _, _ in - let unmanagedData = Unmanaged.fromOpaque(COpaquePointer(info)) - dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), { - unmanagedData.takeUnretainedValue().endContentAccess() - unmanagedData.release() - }) - }) - - let image = CGImageCreate(Int(self.size.width), Int(self.size.height), 8, 32, self.bytesPerRow, colorSpace, CGBitmapInfo(rawValue: bitmapInfo), dataProvider, nil, false, CGColorRenderingIntent(rawValue: 0)!) - - let result = UIImage(CGImage: image!) - return result - } -} - -private final class ImageCacheResidentImage { - let key: String - let image: UIImage - var accessIndex: Int - - init(key: String, image: UIImage, accessIndex: Int) { - self.key = key - self.image = image - self.accessIndex = accessIndex - } -} - -public final class ImageCache { - let maxResidentSize: Int - var mutex = pthread_mutex_t() - - private var imageDatas: [String : ImageCacheData] = [:] - private var residentImages: [String : ImageCacheResidentImage] = [:] - var nextAccessIndex = 1 - var residentImagesSize = 0 - - public init(maxResidentSize: Int) { - self.maxResidentSize = maxResidentSize - pthread_mutex_init(&self.mutex, nil) - } - - deinit { - pthread_mutex_destroy(&self.mutex) - } - - public func addImageForKey(key: String, size: CGSize, generator: CGContextRef -> Void) { - var image: UIImage? - let imageData = ImageCacheData(size: size, generator: generator, takenImage: { image = $0 }) - - pthread_mutex_lock(&self.mutex) - self.imageDatas[key] = imageData - self.addResidentImage(image!, forKey: key) - pthread_mutex_unlock(&self.mutex) - } - - public func imageForKey(key: String) -> UIImage? { - var image: UIImage? - - pthread_mutex_lock(&self.mutex); - if let residentImage = self.residentImages[key] { - image = residentImage.image - self.nextAccessIndex += 1 - residentImage.accessIndex = self.nextAccessIndex - } else { - if let imageData = self.imageDatas[key] { - if let takenImage = imageData.image { - image = takenImage - self.addResidentImage(takenImage, forKey: key) - } else { - self.imageDatas.removeValueForKey(key) - } - } - } - pthread_mutex_unlock(&self.mutex) - - return image - } - - private func addResidentImage(image: UIImage, forKey key: String) { - let imageSize = Int(image.size.width * image.size.height * image.scale) * 4 - - if self.residentImagesSize + imageSize > self.maxResidentSize { - let sizeToRemove = self.residentImagesSize - (self.maxResidentSize - imageSize) - let sortedImages = self.residentImages.values.sort({ $0.accessIndex < $1.accessIndex }) - - var removedSize = 0 - var i = sortedImages.count - 1 - while i >= 0 && removedSize < sizeToRemove { - let currentImage = sortedImages[i] - let currentImageSize = Int(currentImage.image.size.width * currentImage.image.size.height * currentImage.image.scale) * 4 - removedSize += currentImageSize - self.residentImages.removeValueForKey(currentImage.key) - i -= 1 - } - - self.residentImagesSize = max(0, self.residentImagesSize - removedSize) - } - - self.residentImagesSize += imageSize - self.nextAccessIndex += 1 - self.residentImages[key] = ImageCacheResidentImage(key: key, image: image, accessIndex: self.nextAccessIndex) - } -}*/ From d8cd427f55802cd06d9caff75a6fc222a4c65331 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 19 Mar 2019 01:22:50 +0400 Subject: [PATCH 189/245] Accessibility updates --- Display/ContextMenuActionNode.swift | 28 +++++++++++++--- Display/NativeWindowHostView.swift | 52 +++++++++++++++++++++++++++-- Display/SwitchNode.swift | 2 ++ Display/WindowContent.swift | 11 +++--- 4 files changed, 82 insertions(+), 11 deletions(-) diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift index b5616cfb22..d614f7ed8a 100644 --- a/Display/ContextMenuActionNode.swift +++ b/Display/ContextMenuActionNode.swift @@ -12,20 +12,28 @@ final private class ContextMenuActionButton: HighlightTrackingButton { } final class ContextMenuActionNode: ASDisplayNode { - private let textNode: ASTextNode? + private let textNode: ImmediateTextNode? + private var textSize: CGSize? private let iconNode: ASImageNode? private let action: () -> Void private let button: ContextMenuActionButton + private let actionArea: AccessibilityAreaNode var dismiss: (() -> Void)? init(action: ContextMenuAction) { + self.actionArea = AccessibilityAreaNode() + self.actionArea.accessibilityTraits = UIAccessibilityTraitButton + switch action.content { case let .text(title): - let textNode = ASTextNode() + self.actionArea.accessibilityLabel = title + + let textNode = ImmediateTextNode() textNode.isUserInteractionEnabled = false textNode.displaysAsynchronously = false textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white) + textNode.isAccessibilityElement = false self.textNode = textNode self.iconNode = nil @@ -41,6 +49,7 @@ final class ContextMenuActionNode: ASDisplayNode { self.action = action.action self.button = ContextMenuActionButton() + self.button.isAccessibilityElement = false super.init() @@ -56,6 +65,12 @@ final class ContextMenuActionNode: ASDisplayNode { self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8) } self.view.addSubview(self.button) + self.addSubnode(self.actionArea) + + self.actionArea.activate = { [weak self] in + self?.buttonPressed() + return true + } } override func didLoad() { @@ -75,7 +90,8 @@ final class ContextMenuActionNode: ASDisplayNode { override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { if let textNode = self.textNode { - let textSize = textNode.measure(constrainedSize) + let textSize = textNode.updateLayout(constrainedSize) + self.textSize = textSize return CGSize(width: textSize.width + 36.0, height: 54.0) } else if let iconNode = self.iconNode, let image = iconNode.image { return CGSize(width: image.size.width + 36.0, height: 54.0) @@ -88,8 +104,10 @@ final class ContextMenuActionNode: ASDisplayNode { super.layout() self.button.frame = self.bounds - if let textNode = self.textNode { - textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - textNode.calculatedSize.width) / 2.0), y: floor((self.bounds.size.height - textNode.calculatedSize.height) / 2.0)), size: textNode.calculatedSize) + self.actionArea.frame = self.bounds + + if let textNode = self.textNode, let textSize = self.textSize { + textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - textSize.width) / 2.0), y: floor((self.bounds.size.height - textSize.height) / 2.0)), size: textSize) } if let iconNode = self.iconNode, let image = iconNode.image { let iconSize = image.size diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 359f637568..e1e3c12c75 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -52,6 +52,9 @@ private final class WindowRootViewControllerView: UIView { } private final class WindowRootViewController: UIViewController, UIViewControllerPreviewingDelegate { + private var voiceOverStatusObserver: AnyObject? + private var registeredForPreviewing = false + var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)? var transitionToSize: ((CGSize, Double) -> Void)? @@ -106,12 +109,26 @@ private final class WindowRootViewController: UIViewController, UIViewController super.init(nibName: nil, bundle: nil) self.extendedLayoutIncludesOpaqueBars = true + + if #available(iOSApplicationExtension 11.0, *) { + self.voiceOverStatusObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIAccessibilityVoiceOverStatusDidChange, object: nil, queue: OperationQueue.main, using: { [weak self] _ in + if let strongSelf = self { + strongSelf.updatePreviewingRegistration() + } + }) + } } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } + deinit { + if let voiceOverStatusObserver = self.voiceOverStatusObserver { + NotificationCenter.default.removeObserver(voiceOverStatusObserver) + } + } + override func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge { return self.gestureEdges } @@ -132,14 +149,45 @@ private final class WindowRootViewController: UIViewController, UIViewController self.view.isOpaque = false self.view.backgroundColor = nil - if #available(iOSApplicationExtension 9.0, *) { - self.registerForPreviewing(with: self, sourceView: self.view) + self.updatePreviewingRegistration() + } + + private var previewingContext: AnyObject? + + private func updatePreviewingRegistration() { + var shouldRegister = false + + var isVoiceOverRunning = false + if #available(iOSApplicationExtension 10.0, *) { + isVoiceOverRunning = UIAccessibility.isVoiceOverRunning + } + if !isVoiceOverRunning { + shouldRegister = true + } + + if shouldRegister != self.registeredForPreviewing { + self.registeredForPreviewing = shouldRegister + if shouldRegister { + if #available(iOSApplicationExtension 9.0, *) { + self.previewingContext = self.registerForPreviewing(with: self, sourceView: self.view) + } + } else if let previewingContext = self.previewingContext { + self.previewingContext = nil + if let previewingContext = previewingContext as? UIViewControllerPreviewing { + if #available(iOSApplicationExtension 9.0, *) { + self.unregisterForPreviewing(withContext: previewingContext) + } + } + } } } private weak var previousPreviewingHostView: (UIView & PreviewingHostView)? public func previewingContext(_ previewingContext: UIViewControllerPreviewing, viewControllerForLocation location: CGPoint) -> UIViewController? { + if UIAccessibility.isVoiceOverRunning { + return nil + } if #available(iOSApplicationExtension 9.0, *) { guard let result = self.view.hitTest(location, with: nil) else { return nil diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index cdfc498be6..5d5052de69 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -62,6 +62,8 @@ open class SwitchNode: ASDisplayNode { override open func didLoad() { super.didLoad() + self.view.isAccessibilityElement = false + (self.view as! UISwitch).backgroundColor = self.backgroundColor (self.view as! UISwitch).tintColor = self.frameColor //(self.view as! UISwitch).thumbTintColor = self.handleColor diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index bcf1bdc838..38e6d0970b 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -333,9 +333,9 @@ public class Window1 { private var isInteractionBlocked = false - private var accessibilityElements: [Any]? { + /*private var accessibilityElements: [Any]? { return self.viewController?.view.accessibilityElements - } + }*/ public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { self.hostView = hostView @@ -425,9 +425,9 @@ public class Window1 { }) } - self.hostView.getAccessibilityElements = { [weak self] in + /*self.hostView.getAccessibilityElements = { [weak self] in return self?.accessibilityElements - } + }*/ self.presentationContext.view = self.hostView.containerView self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) @@ -572,6 +572,9 @@ public class Window1 { if let keyboardTypeChangeObserver = self.keyboardTypeChangeObserver { NotificationCenter.default.removeObserver(keyboardTypeChangeObserver) } + if let voiceOverStatusObserver = self.voiceOverStatusObserver { + NotificationCenter.default.removeObserver(voiceOverStatusObserver) + } } public func setupVolumeControlStatusBarGraphics(_ graphics: (UIImage, UIImage, UIImage)) { From be6bbbc6ae6cda29b9dd0eceb83ecefe82323658 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 19 Mar 2019 18:54:41 +0400 Subject: [PATCH 190/245] Accessibility updates --- Display/ContextMenuAction.swift | 2 +- Display/ContextMenuActionNode.swift | 4 +- Display/TextNode.swift | 147 ++++++++++++++++++++++++++++ 3 files changed, 150 insertions(+), 3 deletions(-) diff --git a/Display/ContextMenuAction.swift b/Display/ContextMenuAction.swift index 1aa73ccc42..235dda12ab 100644 --- a/Display/ContextMenuAction.swift +++ b/Display/ContextMenuAction.swift @@ -1,6 +1,6 @@ public enum ContextMenuActionContent { - case text(String) + case text(title: String, accessibilityLabel: String) case icon(UIImage) } diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift index d614f7ed8a..7771bd24dd 100644 --- a/Display/ContextMenuActionNode.swift +++ b/Display/ContextMenuActionNode.swift @@ -26,8 +26,8 @@ final class ContextMenuActionNode: ASDisplayNode { self.actionArea.accessibilityTraits = UIAccessibilityTraitButton switch action.content { - case let .text(title): - self.actionArea.accessibilityLabel = title + case let .text(title, accessibilityLabel): + self.actionArea.accessibilityLabel = accessibilityLabel let textNode = ImmediateTextNode() textNode.isUserInteractionEnabled = false diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 2d53782392..191b53391b 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -275,6 +275,49 @@ public final class TextNodeLayout: NSObject { return nil } + public func allAttributeRects(name: String) -> [(Any, CGRect)] { + guard let attributedString = self.attributedString else { + return [] + } + var result: [(Any, CGRect)] = [] + attributedString.enumerateAttribute(NSAttributedStringKey(rawValue: name), in: NSRange(location: 0, length: attributedString.length), options: []) { (value, range, _) in + if let value = value, range.length != 0 { + var coveringRect = CGRect() + for line in self.lines { + let lineRange = NSIntersectionRange(range, line.range) + if lineRange.length != 0 { + var leftOffset: CGFloat = 0.0 + if lineRange.location != line.range.location { + leftOffset = floor(CTLineGetOffsetForStringIndex(line.line, lineRange.location, nil)) + } + var rightOffset: CGFloat = line.frame.width + if lineRange.location + lineRange.length != line.range.length { + var secondaryOffset: CGFloat = 0.0 + let rawOffset = CTLineGetOffsetForStringIndex(line.line, lineRange.location + lineRange.length, &secondaryOffset) + rightOffset = ceil(rawOffset) + if !rawOffset.isEqual(to: secondaryOffset) { + rightOffset = ceil(secondaryOffset) + } + } + var lineFrame = CGRect(origin: CGPoint(x: line.frame.origin.x, y: line.frame.origin.y - line.frame.size.height + self.firstLineOffset), size: line.frame.size) + lineFrame = displayLineFrame(frame: lineFrame, isRTL: line.isRTL, boundingRect: CGRect(origin: CGPoint(), size: self.size), cutout: self.cutout) + + let rect = CGRect(origin: CGPoint(x: lineFrame.minX + leftOffset + self.insets.left, y: lineFrame.minY + self.insets.top), size: CGSize(width: rightOffset - leftOffset, height: lineFrame.size.height)) + if coveringRect.isEmpty { + coveringRect = rect + } else { + coveringRect = coveringRect.union(rect) + } + } + } + if !coveringRect.isEmpty { + result.append((value, coveringRect)) + } + } + } + return result + } + public func lineAndAttributeRects(name: String, at index: Int) -> [(CGRect, CGRect)]? { if let attributedString = self.attributedString { var range = NSRange() @@ -313,6 +356,110 @@ public final class TextNodeLayout: NSObject { } } +private final class TextAccessibilityOverlayElement: UIAccessibilityElement { + private let url: String + private let openUrl: (String) -> Void + + init(accessibilityContainer: Any, url: String, openUrl: @escaping (String) -> Void) { + self.url = url + self.openUrl = openUrl + + super.init(accessibilityContainer: accessibilityContainer) + } + + override func accessibilityActivate() -> Bool { + self.openUrl(self.url) + return true + } +} + +private final class TextAccessibilityOverlayNodeView: UIView { + fileprivate var cachedLayout: TextNodeLayout? { + didSet { + self.currentAccessibilityNodes?.forEach({ $0.removeFromSupernode() }) + self.currentAccessibilityNodes = nil + } + } + fileprivate let openUrl: (String) -> Void + + private var currentAccessibilityNodes: [AccessibilityAreaNode]? + + override var accessibilityElements: [Any]? { + get { + if let _ = self.currentAccessibilityNodes { + return nil + } + guard let cachedLayout = self.cachedLayout else { + return nil + } + let urlAttributesAndRects = cachedLayout.allAttributeRects(name: "UrlAttributeT") + + var urlElements: [AccessibilityAreaNode] = [] + for (value, rect) in urlAttributesAndRects { + let element = AccessibilityAreaNode() + element.accessibilityLabel = value as? String ?? "" + element.frame = rect + element.accessibilityTraits = UIAccessibilityTraitLink + element.activate = { [weak self] in + self?.openUrl(value as? String ?? "") + return true + } + self.addSubnode(element) + urlElements.append(element) + } + self.currentAccessibilityNodes = urlElements + return nil + } set(value) { + } + } + + init(openUrl: @escaping (String) -> Void) { + self.openUrl = openUrl + + super.init(frame: CGRect()) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} + +public final class TextAccessibilityOverlayNode: ASDisplayNode { + public var cachedLayout: TextNodeLayout? { + didSet { + if self.isNodeLoaded { + (self.view as? TextAccessibilityOverlayNodeView)?.cachedLayout = self.cachedLayout + } + } + } + + public var openUrl: ((String) -> Void)? + + override public init() { + super.init() + + let openUrl: (String) -> Void = { [weak self] url in + self?.openUrl?(url) + } + + self.isAccessibilityElement = false + + self.setViewBlock({ + return TextAccessibilityOverlayNodeView(openUrl: openUrl) + }) + } + + override public func didLoad() { + super.didLoad() + + (self.view as? TextAccessibilityOverlayNodeView)?.cachedLayout = self.cachedLayout + } + + override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return nil + } +} + public class TextNode: ASDisplayNode { public private(set) var cachedLayout: TextNodeLayout? From 4033e28c742f7a4f72ca773ac93509195dfbd4f1 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 19 Mar 2019 19:02:22 +0400 Subject: [PATCH 191/245] Added dismissal callback on AlertController --- Display/AlertController.swift | 3 +++ Display/TabBarNode.swift | 7 ++++++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/Display/AlertController.swift b/Display/AlertController.swift index de34d7b060..08d1094a38 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -61,6 +61,8 @@ open class AlertController: ViewController { private let contentNode: AlertContentNode private let allowInputInset: Bool + public var dismissed: (() -> Void)? + public init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool = true) { self.theme = theme self.contentNode = contentNode @@ -103,6 +105,7 @@ open class AlertController: ViewController { } override open func dismiss(completion: (() -> Void)? = nil) { + self.dismissed?() self.presentingViewController?.dismiss(animated: false, completion: completion) } diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index 4f633dd2f9..c7c2f85c55 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -271,6 +271,10 @@ class TabBarNode: ASDisplayNode { self.tabBarNodeContainers[i].badgeBackgroundNode.image = self.badgeImage } + + if let validLayout = self.validLayout { + self.updateLayout(size: validLayout.0, leftInset: validLayout.1, rightInset: validLayout.2, bottomInset: validLayout.3, transition: .immediate) + } } } @@ -421,8 +425,9 @@ class TabBarNode: ASDisplayNode { } if !container.badgeContainerNode.isHidden { + let hasSingleLetterValue = container.badgeTextNode.attributedText?.string.count == 1 let badgeSize = container.badgeTextNode.updateLayout(CGSize(width: 200.0, height: 100.0)) - let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) + let backgroundSize = CGSize(width: hasSingleLetterValue ? 18.0 : max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0) let backgroundFrame: CGRect if horizontal { backgroundFrame = CGRect(origin: CGPoint(x: originX + 8.0, y: 2.0), size: backgroundSize) From 5203c8a1561ff95c20eacedd4a16162b664d2bd9 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 19 Mar 2019 20:51:31 +0400 Subject: [PATCH 192/245] Accessibility updates --- Display/ActionSheetController.swift | 14 ++++++++++---- Display/ActionSheetControllerNode.swift | 3 ++- Display/ContainableController.swift | 4 ++++ Display/PresentationContext.swift | 24 +++++++++++++++++++++--- Display/ViewController.swift | 4 ++++ 5 files changed, 41 insertions(+), 8 deletions(-) diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 6f6ff08e7c..6977020358 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -1,6 +1,6 @@ import Foundation -open class ActionSheetController: ViewController { +open class ActionSheetController: ViewController, PresentableController { private var actionSheetNode: ActionSheetControllerNode { return self.displayNode as! ActionSheetControllerNode } @@ -23,6 +23,8 @@ open class ActionSheetController: ViewController { self.theme = theme super.init(navigationBarPresentationData: nil) + + self.blocksBackgroundWhenInOverlay = true } required public init(coder aDecoder: NSCoder) { @@ -54,10 +56,14 @@ open class ActionSheetController: ViewController { self.actionSheetNode.containerLayoutUpdated(layout, transition: transition) } - open override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) + open override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) - self.actionSheetNode.animateIn() + self.viewDidAppear(completion: {}) + } + + public func viewDidAppear(completion: @escaping () -> Void) { + self.actionSheetNode.animateIn(completion: completion) } public func setItemGroups(_ groups: [ActionSheetItemGroup]) { diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 7d03e0996f..820547892d 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -110,7 +110,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.updateScrollDimViews(size: layout.size, insets: insets) } - func animateIn() { + func animateIn(completion: @escaping () -> Void) { let tempDimView = UIView() tempDimView.backgroundColor = self.theme.dimColor tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height) @@ -124,6 +124,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.layer.animateBounds(from: self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height), to: self.bounds, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring, completion: { [weak tempDimView] _ in tempDimView?.removeFromSuperview() + completion() }) } diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index fb77d8e463..6e940cec68 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -2,6 +2,10 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +public protocol PresentableController: class { + func viewDidAppear(completion: @escaping () -> Void) +} + public protocol ContainableController: class { var view: UIView! { get } var displayNode: ASDisplayNode { get } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index bbfb45dc09..88b3198c8e 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -189,7 +189,14 @@ final class PresentationContext { (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) - controller.viewDidAppear(false) + if let controller = controller as? PresentableController { + controller.viewDidAppear(completion: { [weak self] in + self?.notifyAccessibilityScreenChanged() + }) + } else { + controller.viewDidAppear(false) + strongSelf.notifyAccessibilityScreenChanged() + } } strongSelf.updateViews() } @@ -246,7 +253,14 @@ final class PresentationContext { } controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) controller.containerLayoutUpdated(layout, transition: .immediate) - controller.viewDidAppear(false) + if let controller = controller as? PresentableController { + controller.viewDidAppear(completion: { [weak self] in + self?.notifyAccessibilityScreenChanged() + }) + } else { + controller.viewDidAppear(false) + self.notifyAccessibilityScreenChanged() + } } self.updateViews() } @@ -261,7 +275,7 @@ final class PresentationContext { } private func updateViews() { - self.hasOpaqueOverlay = self.isCurrentlyOpaque + self.hasOpaqueOverlay = self.currentlyBlocksBackgroundWhenInOverlay var topHasOpaque = false for (controller, _) in self.controllers.reversed() { if topHasOpaque { @@ -275,6 +289,10 @@ final class PresentationContext { } } + private func notifyAccessibilityScreenChanged() { + UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil) + } + func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { for (controller, _) in self.controllers.reversed() { if controller.isViewLoaded { diff --git a/Display/ViewController.swift b/Display/ViewController.swift index a6653141f7..9308b6a5b5 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -321,6 +321,10 @@ open class ViewControllerPresentationArguments { layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard, disableChildrenTracingTags: 0)) } self.updateScrollToTopView() + if let backgroundColor = self.displayNode.backgroundColor, backgroundColor.alpha.isEqual(to: 1.0) { + self.blocksBackgroundWhenInOverlay = true + self.isOpaqueWhenInOverlay = true + } } public func requestLayout(transition: ContainedViewLayoutTransition) { From 61d43c3c4b5286f1bc26cfe0202979925d969e06 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 22 Mar 2019 15:22:19 +0400 Subject: [PATCH 193/245] TextNode: add areLinesEqual --- Display/TextNode.swift | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 191b53391b..26342414b1 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -125,6 +125,50 @@ public final class TextNodeLayout: NSObject { self.hasRTL = hasRTL } + public func areLinesEqual(to other: TextNodeLayout) -> Bool { + if self.lines.count != other.lines.count { + return false + } + for i in 0 ..< self.lines.count { + if !self.lines[i].frame.equalTo(other.lines[i].frame) { + return false + } + if self.lines[i].isRTL != other.lines[i].isRTL { + return false + } + if self.lines[i].range != other.lines[i].range { + return false + } + let lhsRuns = CTLineGetGlyphRuns(self.lines[i].line) as NSArray + let rhsRuns = CTLineGetGlyphRuns(other.lines[i].line) as NSArray + + if lhsRuns.count != rhsRuns.count { + return false + } + + for j in 0 ..< lhsRuns.count { + let lhsRun = lhsRuns[j] as! CTRun + let rhsRun = rhsRuns[j] as! CTRun + let lhsGlyphCount = CTRunGetGlyphCount(lhsRun) + let rhsGlyphCount = CTRunGetGlyphCount(rhsRun) + if lhsGlyphCount != rhsGlyphCount { + return false + } + + for k in 0 ..< lhsGlyphCount { + var lhsGlyph = CGGlyph() + var rhsGlyph = CGGlyph() + CTRunGetGlyphs(lhsRun, CFRangeMake(k, 1), &lhsGlyph) + CTRunGetGlyphs(rhsRun, CFRangeMake(k, 1), &rhsGlyph) + if lhsGlyph != rhsGlyph { + return false + } + } + } + } + return true + } + public var numberOfLines: Int { return self.lines.count } From 5c13b77e8112c6838af727991ff05ff4b5ac890d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 22 Mar 2019 19:42:38 +0400 Subject: [PATCH 194/245] ListView: added customizable scroll distance for scrolling --- Display/ListView.swift | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 80a85dca70..e23552f91e 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -96,6 +96,11 @@ public enum ListViewVisibleContentOffset { case none } +public enum ListViewScrollDirection { + case up + case down +} + public struct ListViewKeepTopItemOverscrollBackground { public let color: UIColor public let direction: Bool @@ -3825,7 +3830,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - public func scrollWithDirection(_ direction: UIAccessibilityScrollDirection) -> Bool { + public func scrollWithDirection(_ direction: ListViewScrollDirection, distance: CGFloat) -> Bool { var accessibilityFocusedNode: (ASDisplayNode, CGRect)? for itemNode in self.itemNodes { if findAccessibilityFocus(itemNode) { @@ -3833,12 +3838,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture break } } - let scrollDistance = floor((self.visibleSize.height - self.insets.top - self.insets.bottom) / 2.0) let initialOffset = self.scroller.contentOffset switch direction { case .up: var contentOffset = initialOffset - contentOffset.y -= scrollDistance + contentOffset.y -= distance contentOffset.y = max(self.scroller.contentInset.top, contentOffset.y) if contentOffset.y < initialOffset.y { self.ignoreScrollingEvents = true @@ -3850,7 +3854,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } case .down: var contentOffset = initialOffset - contentOffset.y += scrollDistance + contentOffset.y += distance contentOffset.y = max(self.scroller.contentInset.top, min(contentOffset.y, self.scroller.contentSize.height - self.visibleSize.height - self.insets.bottom - self.insets.top)) if contentOffset.y > initialOffset.y { self.ignoreScrollingEvents = true @@ -3860,8 +3864,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else { return false } - default: - return false } if let (_, frame) = accessibilityFocusedNode { for itemNode in self.itemNodes { @@ -3879,7 +3881,15 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } override open func accessibilityScroll(_ direction: UIAccessibilityScrollDirection) -> Bool { - return self.scrollWithDirection(direction) + let distance = floor((self.visibleSize.height - self.insets.top - self.insets.bottom) / 2.0) + let scrollDirection: ListViewScrollDirection + switch direction { + case .down: + scrollDirection = .down + default: + scrollDirection = .up + } + return self.scrollWithDirection(scrollDirection, distance: distance) } } From 7d0164259f2aa48516393a43822b5b8ab701d843 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 24 Mar 2019 22:03:15 +0400 Subject: [PATCH 195/245] TextNode: fix TextAccessibilityOverlayNode opaqueness --- Display/TextNode.swift | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 26342414b1..b8e2e2e49a 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -482,6 +482,9 @@ public final class TextAccessibilityOverlayNode: ASDisplayNode { override public init() { super.init() + self.isOpaque = false + self.backgroundColor = nil + let openUrl: (String) -> Void = { [weak self] url in self?.openUrl?(url) } From 0d65c31ae1f3510cdeb11cd73afa7e691634c172 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 2 Apr 2019 17:49:48 +0400 Subject: [PATCH 196/245] ListView: added flashHeaderItems --- Display/ListView.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index e23552f91e..281c8d0cfa 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -512,14 +512,18 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func resetHeaderItemsFlashTimer(start: Bool) { + public func flashHeaderItems(duration: Double = 2.0) { + self.resetHeaderItemsFlashTimer(start: true, duration: duration) + } + + private func resetHeaderItemsFlashTimer(start: Bool, duration: Double = 0.3) { if let flashNodesDelayTimer = self.flashNodesDelayTimer { flashNodesDelayTimer.invalidate() self.flashNodesDelayTimer = nil } if start { - let timer = Timer(timeInterval: 0.3, target: ListViewTimerProxy { [weak self] in + let timer = Timer(timeInterval: duration, target: ListViewTimerProxy { [weak self] in if let strongSelf = self { if let flashNodesDelayTimer = strongSelf.flashNodesDelayTimer { flashNodesDelayTimer.invalidate() From c1b0117bf5990c15c8a9af46ecdeced84a454f57 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 2 Apr 2019 17:57:18 +0200 Subject: [PATCH 197/245] Added haptic feedback on collection index scroll --- Display/CollectionIndexNode.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Display/CollectionIndexNode.swift b/Display/CollectionIndexNode.swift index 81faf5befb..feb54052c4 100644 --- a/Display/CollectionIndexNode.swift +++ b/Display/CollectionIndexNode.swift @@ -10,6 +10,7 @@ public final class CollectionIndexNode: ASDisplayNode { private var currentSections: [String] = [] private var currentColor: UIColor? private var titleNodes: [String: (node: ImmediateTextNode, size: CGSize)] = [:] + private var scrollFeedback: HapticFeedback? private var currentSelectedIndex: String? public var indexSelected: ((String) -> Void)? @@ -148,6 +149,11 @@ public final class CollectionIndexNode: ASDisplayNode { self.currentSelectedIndex = locationTitle if let locationTitle = locationTitle { self.indexSelected?(locationTitle) + + if self.scrollFeedback == nil { + self.scrollFeedback = HapticFeedback() + } + self.scrollFeedback?.tap() } } case .cancelled, .ended: From d80864dc37cd4ac3aecae93d2d278051fbb8f921 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 5 Apr 2019 21:04:43 +0400 Subject: [PATCH 198/245] ContainerViewLayout: added withUpdatedSize --- Display/ContainerViewLayout.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index 8510728950..abb7a66cbd 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -72,6 +72,10 @@ public struct ContainerViewLayout: Equatable { return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver) } + public func withUpdatedSize(_ size: CGSize) -> ContainerViewLayout { + return ContainerViewLayout(size: size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver) + } + public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout { return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver) } From a1abf6bf6aedc1ec91227d694a184ddedb5e844c Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 15 Apr 2019 01:29:32 +0100 Subject: [PATCH 199/245] ListView: improved scrollToItem persistence --- Display/CAAnimationUtils.swift | 21 ++++++++++++++++-- Display/ContainedViewLayoutTransition.swift | 18 ++++++++-------- Display/ListView.swift | 15 ++++++++++--- Display/ListViewIntermediateState.swift | 4 +++- Display/NavigationController.swift | 24 +++++++++++++++++++++ 5 files changed, 67 insertions(+), 15 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 53d9a31690..e101fe4c30 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -278,14 +278,31 @@ public extension CALayer { } } } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in + + var fromPosition = CGPoint(x: from.midX, y: from.midY) + var toPosition = CGPoint(x: to.midX, y: to.midY) + + var fromBounds = CGRect(origin: self.bounds.origin, size: from.size) + var toBounds = CGRect(origin: self.bounds.origin, size: to.size) + + if additive { + fromPosition.x = -(toPosition.x - fromPosition.x) + fromPosition.y = -(toPosition.y - fromPosition.y) + toPosition = CGPoint() + + fromBounds.size.width = -(toBounds.width - fromBounds.width) + fromBounds.size.height = -(toBounds.height - fromBounds.height) + toBounds = CGRect() + } + + self.animatePosition(from: fromPosition, to: toPosition, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } completedPosition = true partialCompletion() }) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in + self.animateBounds(from: fromBounds, to: toBounds, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index eff63bd626..4fd089c39d 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -191,18 +191,18 @@ public extension ContainedViewLayoutTransition { } } - func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + func animateFrame(node: ASDisplayNode, from frame: CGRect, to toFrame: CGRect? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + case .immediate: if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + node.layer.animateFrame(from: frame, to: toFrame ?? node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in + if let completion = completion { + completion(result) + } + }) } } diff --git a/Display/ListView.swift b/Display/ListView.swift index 281c8d0cfa..f120a90aa4 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1331,7 +1331,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - self.scrolledToItem = nil + if !deleteIndices.isEmpty || !insertIndicesAndItems.isEmpty || !updateIndicesAndItems.isEmpty { + self.scrolledToItem = nil + } let startTime = CACurrentMediaTime() var state = self.currentState() @@ -2048,7 +2050,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.scrolledToItem = (originalScrollToItem.index, originalScrollToItem.position) } } else if let scrolledToItem = self.scrolledToItem { - scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: false, curve: .Default(duration: nil), directionHint: .Down) + var curve: ListViewAnimationCurve = .Default(duration: nil) + var animated = false + if let updateSizeAndInsets = updateSizeAndInsets { + curve = updateSizeAndInsets.curve + animated = !updateSizeAndInsets.duration.isZero + } + scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: animated, curve: curve, directionHint: .Down) } weak var highlightedItemNode: ListViewItemNode? @@ -2413,6 +2421,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) var deferredUpdateVisible = false + var insetTransitionOffset: CGFloat = 0.0 if let updateSizeAndInsets = updateSizeAndInsets { if self.insets != updateSizeAndInsets.insets || self.headerInsets != updateSizeAndInsets.headerInsets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { @@ -2420,7 +2429,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.visibleSize = updateSizeAndInsets.size var offsetFix: CGFloat - if self.isTracking { + if self.isTracking || scrollToItem != nil { offsetFix = 0.0 } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index d8e1ef4d93..5380f80bcc 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -465,12 +465,14 @@ struct ListViewState { } } + var fixedNodeIsStationary = false if fixedNode == nil { if let (fixedIndex, _) = self.stationaryOffset { for i in 0 ..< self.nodes.count { let node = self.nodes[i] if let index = node.index , index == fixedIndex { fixedNode = (i, index, node.frame) + fixedNodeIsStationary = true break } } @@ -523,7 +525,7 @@ struct ListViewState { if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne { directionHint = ListViewInsertionOffsetDirection(hint) } - } else if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne { + } else if currentUpperNode.frame.minY >= self.insets.top - CGFloat.ulpOfOne && !fixedNodeIsStationary { directionHint = .Down } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 6b7828d385..061a5715f6 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -849,6 +849,30 @@ open class NavigationController: UINavigationController, ContainableController, })) } + public func replaceControllersAndPush(controllers: [UIViewController], controller: ViewController, animated: Bool, ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { + self.view.endEditing(true) + self.scheduleAfterLayout { [weak self] in + guard let strongSelf = self else { + return + } + if let validLayout = strongSelf.validLayout { + var (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + controllerLayout.inputHeight = nil + controller.containerLayoutUpdated(controllerLayout, transition: .immediate) + } + strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + guard let strongSelf = self else { + return + } + ready?.set(true) + var controllers = controllers + controllers.append(controller) + strongSelf.setViewControllers(controllers, animated: animated) + completion() + })) + } + } + public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { self.view.endEditing(true) self.scheduleAfterLayout { [weak self] in From a36ef160d3a2ab83f82fe3d4b782522fb5e5c058 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 15 Apr 2019 23:13:36 +0100 Subject: [PATCH 200/245] ListView: check if experimentalSnapScrollItem is still enabled when applying saved scrollToItem --- Display/ListView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index f120a90aa4..7489a4a652 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2049,7 +2049,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if self.experimentalSnapScrollToItem { self.scrolledToItem = (originalScrollToItem.index, originalScrollToItem.position) } - } else if let scrolledToItem = self.scrolledToItem { + } else if let scrolledToItem = self.scrolledToItem, self.experimentalSnapScrollToItem { var curve: ListViewAnimationCurve = .Default(duration: nil) var animated = false if let updateSizeAndInsets = updateSizeAndInsets { From 1991d52f6b06c2653695b7d36c6a946aaf38f8b9 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 19 Apr 2019 16:13:29 +0100 Subject: [PATCH 201/245] ListView updates --- Display/ListView.swift | 31 ++++++++++++++++--------------- Display/ToolbarNode.swift | 13 +++++++++---- Display/ViewController.swift | 14 +++++++++++++- 3 files changed, 38 insertions(+), 20 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 7489a4a652..e351eef2a4 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -130,6 +130,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() public private(set) final var insets = UIEdgeInsets() + public final var visualInsets: UIEdgeInsets? public private(set) final var headerInsets = UIEdgeInsets() public private(set) final var scrollIndicatorInsets = UIEdgeInsets() private final var ensureTopInsetForOverlayHighlightedItems: CGFloat? @@ -2069,18 +2070,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - /*if true { - print("----------") - for itemNode in self.itemNodes { - var anim = "" - if let animation = itemNode.animationForKey("apparentHeight") { - anim = "\(animation.from)->\(animation.to)" - } - print("\(itemNode.index) \(itemNode.apparentFrame.height) \(anim)") - } - print("----------") - }*/ - let timestamp = CACurrentMediaTime() let listInsets = updateSizeAndInsets?.insets ?? self.insets @@ -2429,7 +2418,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.visibleSize = updateSizeAndInsets.size var offsetFix: CGFloat - if self.isTracking || scrollToItem != nil { + if self.isTracking { offsetFix = 0.0 } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom @@ -3446,10 +3435,22 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let updatedApparentHeight = itemNode.apparentHeight let apparentHeightDelta = updatedApparentHeight - previousApparentHeight if abs(apparentHeightDelta) > CGFloat.ulpOfOne { - if itemNode.apparentFrame.maxY < self.insets.top + CGFloat.ulpOfOne { + let visualInsets = self.visualInsets ?? self.insets + + if itemNode.apparentFrame.maxY <= visualInsets.top { offsetRanges.offset(IndexRange(first: 0, last: index), offset: -apparentHeightDelta) } else { - offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: apparentHeightDelta) + var offsetDelta = apparentHeightDelta + if offsetDelta < 0.0 { + let maxDelta = visualInsets.top - itemNode.apparentFrame.maxY + if maxDelta > offsetDelta { + let remainingOffset = maxDelta - offsetDelta + offsetRanges.offset(IndexRange(first: 0, last: index), offset: remainingOffset) + offsetDelta = maxDelta + } + } + + offsetRanges.offset(IndexRange(first: index + 1, last: Int.max), offset: offsetDelta) } if let accessoryItemNode = itemNode.accessoryItemNode { diff --git a/Display/ToolbarNode.swift b/Display/ToolbarNode.swift index 8adf7d6107..ab504b4a83 100644 --- a/Display/ToolbarNode.swift +++ b/Display/ToolbarNode.swift @@ -1,8 +1,9 @@ import Foundation import AsyncDisplayKit -final class ToolbarNode: ASDisplayNode { +public final class ToolbarNode: ASDisplayNode { private var theme: TabBarControllerTheme + private let displaySeparator: Bool private let left: () -> Void private let right: () -> Void @@ -12,8 +13,9 @@ final class ToolbarNode: ASDisplayNode { private let rightTitle: ImmediateTextNode private let rightButton: HighlightableButtonNode - init(theme: TabBarControllerTheme, left: @escaping () -> Void, right: @escaping () -> Void) { + public init(theme: TabBarControllerTheme, displaySeparator: Bool = false, left: @escaping () -> Void, right: @escaping () -> Void) { self.theme = theme + self.displaySeparator = displaySeparator self.left = left self.right = right @@ -33,6 +35,9 @@ final class ToolbarNode: ASDisplayNode { self.addSubnode(self.leftButton) self.addSubnode(self.rightTitle) self.addSubnode(self.rightButton) + if self.displaySeparator { + self.addSubnode(self.separatorNode) + } self.updateTheme(theme) @@ -62,12 +67,12 @@ final class ToolbarNode: ASDisplayNode { } } - func updateTheme(_ theme: TabBarControllerTheme) { + public func updateTheme(_ theme: TabBarControllerTheme) { self.separatorNode.backgroundColor = theme.tabBarSeparatorColor self.backgroundColor = theme.tabBarBackgroundColor } - func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, toolbar: Toolbar, transition: ContainedViewLayoutTransition) { + public func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, bottomInset: CGFloat, toolbar: Toolbar, transition: ContainedViewLayoutTransition) { transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: size.width, height: UIScreenPixel))) let sideInset: CGFloat = 16.0 diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 9308b6a5b5..af804aefe2 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -174,6 +174,18 @@ open class ViewControllerPresentationArguments { } } + open var visualNavigationInsetHeight: CGFloat { + if let navigationBar = self.navigationBar { + var height = navigationBar.frame.maxY + if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode { + height += contentNode.height + } + return height + } else { + return 0.0 + } + } + private let _ready = Promise(true) open var ready: Promise { return self._ready @@ -467,7 +479,7 @@ open class ViewControllerPresentationArguments { return traceViewVisibility(view: self.view, rect: self.view.bounds) } - public func setToolbar(_ toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { + open func setToolbar(_ toolbar: Toolbar?, transition: ContainedViewLayoutTransition) { if self.toolbar != toolbar { self.toolbar = toolbar if let parent = self.parent as? TabBarController { From 8fbc832d3aeaa4e2bf31105b166ee212968ba632 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 21 Apr 2019 23:48:17 +0400 Subject: [PATCH 202/245] ListView: improve scroll indicator insets --- Display/ListView.swift | 45 +++++++++++++++++++++++--------------- Display/ListViewItem.swift | 5 +++++ 2 files changed, 32 insertions(+), 18 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index e351eef2a4..1c02abbd19 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -208,6 +208,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } } + public final var verticalScrollIndicatorFollowsOverscroll: Bool = false private var touchesPosition = CGPoint() public private(set) var isTracking = false @@ -3154,7 +3155,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var topIndexAndBoundary: (Int, CGFloat, CGFloat)? var bottomIndexAndBoundary: (Int, CGFloat, CGFloat)? for itemNode in self.itemNodes { - if itemNode.apparentFrame.maxY > self.insets.top, let index = itemNode.index { + if itemNode.apparentFrame.maxY >= self.insets.top, let index = itemNode.index { topIndexAndBoundary = (index, itemNode.apparentFrame.minY, itemNode.apparentFrame.height) break } @@ -3166,10 +3167,14 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } if let topIndexAndBoundary = topIndexAndBoundary, let bottomIndexAndBoundary = bottomIndexAndBoundary { - let averageRangeItemHeight: CGFloat = 44.0 //(bottomIndexAndBoundary.1 - topIndexAndBoundary.1) / CGFloat(bottomIndexAndBoundary.0 - topIndexAndBoundary.0 + 1) + let averageRangeItemHeight: CGFloat = 44.0 - let upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0)) - let approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight + var upperItemsHeight = floor(averageRangeItemHeight * CGFloat(topIndexAndBoundary.0)) + var approximateContentHeight = CGFloat(self.items.count) * averageRangeItemHeight + if topIndexAndBoundary.0 >= 0 && self.items[topIndexAndBoundary.0].approximateHeight.isZero { + upperItemsHeight -= averageRangeItemHeight + approximateContentHeight -= averageRangeItemHeight + } var convertedTopBoundary: CGFloat if topIndexAndBoundary.1 < self.insets.top { @@ -3192,32 +3197,36 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let approximateVisibleHeight = max(0.0, convertedBottomBoundary - approximateOffset) let approximateScrollingProgress = approximateOffset / (approximateContentHeight - approximateVisibleHeight) - /*#if targetEnvironment(simulator) - print("approximateOffset = \(approximateOffset), convertedBottomBoundary = \(convertedBottomBoundary) / \(vanillaBoundary), approximateScrollingProgress = \(approximateScrollingProgress)") - #endif*/ - let indicatorInsets: CGFloat = 3.0 + let indicatorSideInset: CGFloat = 3.0 + var indicatorTopInset: CGFloat = 3.0 + if self.verticalScrollIndicatorFollowsOverscroll { + if topIndexAndBoundary.0 == 0 { + indicatorTopInset = max(topIndexAndBoundary.1 + 3.0 - self.insets.top, 3.0) + } + } + let indicatorBottomInset: CGFloat = 3.0 let minIndicatorContentHeight: CGFloat = 12.0 let minIndicatorHeight: CGFloat = 6.0 - let visibleHeightWithoutIndicatorInsets = self.visibleSize.height - self.scrollIndicatorInsets.top - self.scrollIndicatorInsets.bottom - indicatorInsets * 2.0 + let visibleHeightWithoutIndicatorInsets = self.visibleSize.height - self.scrollIndicatorInsets.top - self.scrollIndicatorInsets.bottom - indicatorTopInset - indicatorBottomInset let indicatorHeight: CGFloat = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight)) - let upperBound = self.scrollIndicatorInsets.top + indicatorInsets - let lowerBound = self.visibleSize.height - self.scrollIndicatorInsets.bottom - indicatorInsets - indicatorHeight + let upperBound = self.scrollIndicatorInsets.top + indicatorTopInset + let lowerBound = self.visibleSize.height - self.scrollIndicatorInsets.bottom - indicatorTopInset - indicatorBottomInset - indicatorHeight let indicatorOffset = ceilToScreenPixels(upperBound * (1.0 - approximateScrollingProgress) + lowerBound * approximateScrollingProgress) - var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorInsets : (self.visibleSize.width - 3.0 - indicatorInsets), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) - if indicatorFrame.minY < self.scrollIndicatorInsets.top + indicatorInsets { - indicatorFrame.size.height -= self.scrollIndicatorInsets.top + indicatorInsets - indicatorFrame.minY - indicatorFrame.origin.y = self.scrollIndicatorInsets.top + indicatorInsets + var indicatorFrame = CGRect(origin: CGPoint(x: self.rotated ? indicatorSideInset : (self.visibleSize.width - 3.0 - indicatorSideInset), y: indicatorOffset), size: CGSize(width: 3.0, height: indicatorHeight)) + if indicatorFrame.minY < self.scrollIndicatorInsets.top + indicatorTopInset { + indicatorFrame.size.height -= self.scrollIndicatorInsets.top + indicatorTopInset - indicatorFrame.minY + indicatorFrame.origin.y = self.scrollIndicatorInsets.top + indicatorTopInset indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height) } - if indicatorFrame.maxY > self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets) { - indicatorFrame.size.height -= indicatorFrame.maxY - (self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets)) + if indicatorFrame.maxY > self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorTopInset + indicatorBottomInset) { + indicatorFrame.size.height -= indicatorFrame.maxY - (self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorTopInset)) indicatorFrame.size.height = max(minIndicatorHeight, indicatorFrame.height) - indicatorFrame.origin.y = self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorInsets) - indicatorFrame.height + indicatorFrame.origin.y = self.visibleSize.height - (self.scrollIndicatorInsets.bottom + indicatorBottomInset) - indicatorFrame.height } if indicatorHeight >= visibleHeightWithoutIndicatorInsets { diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index bc8e15dc55..c605f4a813 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -47,6 +47,7 @@ public protocol ListViewItem { var accessoryItem: ListViewAccessoryItem? { get } var headerAccessoryItem: ListViewAccessoryItem? { get } var selectable: Bool { get } + var approximateHeight: CGFloat { get } func selected(listView: ListView) } @@ -64,6 +65,10 @@ public extension ListViewItem { return false } + var approximateHeight: CGFloat { + return 44.0 + } + func selected(listView: ListView) { } } From 6ae1baad940abd703ccf37505c2270a8cd4fbfc6 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 22 Apr 2019 17:23:20 +0400 Subject: [PATCH 203/245] ListView: fix for scrollToItem when simultaneously changing layout --- Display/ListView.swift | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 1c02abbd19..755685ff06 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2046,6 +2046,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { var scrollToItem: ListViewScrollToItem? + var isExperimentalSnapToScrollToItem = false if let originalScrollToItem = originalScrollToItem { scrollToItem = originalScrollToItem if self.experimentalSnapScrollToItem { @@ -2059,6 +2060,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture animated = !updateSizeAndInsets.duration.isZero } scrollToItem = ListViewScrollToItem(index: scrolledToItem.0, position: scrolledToItem.1, animated: animated, curve: curve, directionHint: .Down) + isExperimentalSnapToScrollToItem = true } weak var highlightedItemNode: ListViewItemNode? @@ -2328,36 +2330,38 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for itemNode in self.itemNodes { if let index = itemNode.index, index == scrollToItem.index { + let insets = updateSizeAndInsets?.insets ?? self.insets + let offset: CGFloat switch scrollToItem.position { case let .bottom(additionalOffset): - offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset + offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + additionalOffset case let .top(additionalOffset): - offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + additionalOffset + offset = insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + additionalOffset case let .center(overflow): - let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top + let contentAreaHeight = self.visibleSize.height - insets.bottom - insets.top if itemNode.apparentFrame.size.height <= contentAreaHeight + CGFloat.ulpOfOne { - offset = self.insets.top + floor(((self.visibleSize.height - self.insets.bottom - self.insets.top) - itemNode.frame.size.height) / 2.0) - itemNode.apparentFrame.minY + offset = insets.top + floor(((self.visibleSize.height - insets.bottom - insets.top) - itemNode.frame.size.height) / 2.0) - itemNode.apparentFrame.minY } else { switch overflow { case .top: - offset = self.insets.top - itemNode.apparentFrame.minY + offset = insets.top - itemNode.apparentFrame.minY case .bottom: - offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY } } case .visible: - if itemNode.apparentFrame.size.height > self.visibleSize.height - self.insets.top - self.insets.bottom { - if itemNode.apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { - offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + if itemNode.apparentFrame.size.height > self.visibleSize.height - insets.top - insets.bottom { + if itemNode.apparentFrame.maxY > self.visibleSize.height - insets.bottom { + offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom } else { offset = 0.0 } } else { - if itemNode.apparentFrame.maxY > self.visibleSize.height - self.insets.bottom { - offset = (self.visibleSize.height - self.insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom - } else if itemNode.apparentFrame.minY < self.insets.top { - offset = self.insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top + if itemNode.apparentFrame.maxY > self.visibleSize.height - insets.bottom { + offset = (self.visibleSize.height - insets.bottom) - itemNode.apparentFrame.maxY + itemNode.scrollPositioningInsets.bottom + } else if itemNode.apparentFrame.minY < insets.top { + offset = insets.top - itemNode.apparentFrame.minY - itemNode.scrollPositioningInsets.top } else { offset = 0.0 } @@ -2419,7 +2423,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.visibleSize = updateSizeAndInsets.size var offsetFix: CGFloat - if self.isTracking { + if self.isTracking || isExperimentalSnapToScrollToItem { offsetFix = 0.0 } else if self.snapToBottomInsetUntilFirstInteraction { offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom From 2bf1de59b64b8c4bb43f98c8c2fd975e6661bd36 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 23 Apr 2019 18:04:15 +0400 Subject: [PATCH 204/245] CAAnimationUtils: added mediaTimingFunction to all methods ListView: don't snap to overscroll bounds while tracking --- Display/CAAnimationUtils.swift | 42 ++-- Display/ContainedViewLayoutTransition.swift | 223 +++++++++----------- Display/ListView.swift | 21 +- Display/NavigationBarContentNode.swift | 4 + 4 files changed, 138 insertions(+), 152 deletions(-) diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index e101fe4c30..7fb263ffe2 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -186,7 +186,7 @@ public extension CALayer { self.add(animation, forKey: keyPath) } - public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + public func animateAdditive(from: NSValue, to: NSValue, keyPath: String, key: String, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { let k = Float(UIView.animationDurationFactor()) var speed: Float = 1.0 if k != 0 && k != 1 { @@ -197,7 +197,11 @@ public extension CALayer { animation.fromValue = from animation.toValue = to animation.duration = duration - animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + if let mediaTimingFunction = mediaTimingFunction { + animation.timingFunction = mediaTimingFunction + } else { + animation.timingFunction = CAMediaTimingFunction(name: timingFunction) + } animation.isRemovedOnCompletion = removeOnCompletion animation.fillMode = kCAFillModeForwards animation.speed = speed @@ -209,44 +213,44 @@ public extension CALayer { self.add(animation, forKey: key) } - public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) + public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion) } - public func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) + public func animateScale(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion) } - public func animateRotation(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.rotation.z", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion) + public func animateRotation(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.rotation.z", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: completion) } - func animatePosition(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { completion(true) } return } - self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { completion(true) } return } - self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) + self.animate(from: NSValue(cgRect: from), to: NSValue(cgRect: to), keyPath: "bounds", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.x", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } - public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { - self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + public func animateBoundsOriginYAdditive(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { + self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } public func animateBoundsOriginXAdditive(from: CGFloat, to: CGFloat, duration: Double, mediaTimingFunction: CAMediaTimingFunction) { @@ -261,7 +265,7 @@ public extension CALayer { self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position") } - public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { if from == to && !force { if let completion = completion { completion(true) @@ -295,14 +299,14 @@ public extension CALayer { toBounds = CGRect() } - self.animatePosition(from: fromPosition, to: toPosition, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in + self.animatePosition(from: fromPosition, to: toPosition, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } completedPosition = true partialCompletion() }) - self.animateBounds(from: fromBounds, to: toBounds, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in + self.animateBounds(from: fromBounds, to: toBounds, duration: duration, timingFunction: timingFunction, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index 4fd089c39d..e1e6648b1f 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -9,6 +9,7 @@ import Foundation public enum ContainedViewLayoutTransitionCurve { case easeInOut case spring + case custom(Float, Float, Float, Float) } public extension ContainedViewLayoutTransitionCurve { @@ -18,6 +19,19 @@ public extension ContainedViewLayoutTransitionCurve { return kCAMediaTimingFunctionEaseInEaseOut case .spring: return kCAMediaTimingFunctionSpring + case .custom: + return kCAMediaTimingFunctionEaseInEaseOut + } + } + + var mediaTimingFunction: CAMediaTimingFunction? { + switch self { + case .easeInOut: + return nil + case .spring: + return nil + case let .custom(p1, p2, p3, p4): + return CAMediaTimingFunction(controlPoints: p1, p2, p3, p4) } } @@ -28,6 +42,8 @@ public extension ContainedViewLayoutTransitionCurve { return [.curveEaseInOut] case .spring: return UIViewAnimationOptions(rawValue: 7 << 16) + case .custom: + return [] } } #endif @@ -52,19 +68,19 @@ public extension ContainedViewLayoutTransition { completion?(true) } else { switch self { - case .immediate: - node.frame = frame - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - let previousFrame = node.frame - node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + case .immediate: + node.frame = frame if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + let previousFrame = node.frame + node.frame = frame + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in + if let completion = completion { + completion(result) + } + }) } } } @@ -82,7 +98,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousBounds = node.bounds node.bounds = bounds - node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in if let completion = completion { completion(result) } @@ -104,7 +120,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousBounds = layer.bounds layer.bounds = bounds - layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in if let completion = completion { completion(result) } @@ -126,7 +142,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousPosition = node.position node.position = position - node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -148,7 +164,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousPosition = layer.position layer.position = position - layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + layer.animatePosition(from: previousPosition, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -164,7 +180,7 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animatePosition(from: position, to: node.position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -182,7 +198,7 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + node.layer.animatePosition(from: node.position, to: position, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { result in if let completion = completion { completion(result) } @@ -198,7 +214,7 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - node.layer.animateFrame(from: frame, to: toFrame ?? node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in + node.layer.animateFrame(from: frame, to: toFrame ?? node.layer.frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { result in if let completion = completion { completion(result) } @@ -208,32 +224,25 @@ public extension ContainedViewLayoutTransition { func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { switch self { - case .immediate: - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + case .immediate: if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + layer.animateBounds(from: bounds, to: layer.bounds, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, completion: { result in + if let completion = completion { + completion(result) + } + }) } } func animateOffsetAdditive(node: ASDisplayNode, offset: CGFloat) { switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + case .immediate: + break + case let .animated(duration, curve): + node.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction) } } @@ -242,32 +251,18 @@ public extension ContainedViewLayoutTransition { case .immediate: break case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction) + node.layer.animateBoundsOriginXAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction) } } func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) { switch self { - case .immediate: - completion?() - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in + case .immediate: completion?() - }) + case let .animated(duration, curve): + layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { _ in + completion?() + }) } } @@ -276,14 +271,7 @@ public extension ContainedViewLayoutTransition { case .immediate: break case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } } @@ -292,32 +280,18 @@ public extension ContainedViewLayoutTransition { case .immediate: break case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } } func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) { switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in - completion?() - }) + case .immediate: + break + case let .animated(duration, curve): + node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in + completion?() + }) } } @@ -326,14 +300,7 @@ public extension ContainedViewLayoutTransition { case .immediate: break case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in + layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in completion?() }) } @@ -352,7 +319,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousFrame = view.frame view.frame = frame - view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in + view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, force: force, completion: { result in if let completion = completion { completion(result) } @@ -374,7 +341,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousFrame = layer.frame layer.frame = frame - layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -400,7 +367,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousAlpha = node.alpha node.alpha = alpha - node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -425,7 +392,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousAlpha = layer.opacity layer.opacity = Float(alpha) - layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + layer.animateAlpha(from: CGFloat(previousAlpha), to: alpha, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -450,7 +417,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): if let nodeColor = node.backgroundColor { node.backgroundColor = color - node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in + node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -481,7 +448,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousCornerRadius = node.cornerRadius node.cornerRadius = cornerRadius - node.layer.animate(from: NSNumber(value: Float(previousCornerRadius)), to: NSNumber(value: Float(cornerRadius)), keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, completion: { result in + node.layer.animate(from: NSNumber(value: Float(previousCornerRadius)), to: NSNumber(value: Float(cornerRadius)), keyPath: "cornerRadius", timingFunction: curve.timingFunction, duration: duration, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -505,7 +472,7 @@ public extension ContainedViewLayoutTransition { completion(true) } case let .animated(duration, curve): - node.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animateScale(from: fromScale, to: currentScale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -531,7 +498,7 @@ public extension ContainedViewLayoutTransition { } case let .animated(duration, curve): node.layer.transform = CATransform3DMakeScale(scale, scale, 1.0) - node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -557,7 +524,7 @@ public extension ContainedViewLayoutTransition { } case let .animated(duration, curve): layer.transform = CATransform3DMakeScale(scale, scale, 1.0) - layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, completion: { result in + layer.animateScale(from: currentScale, to: scale, duration: duration, timingFunction: curve.timingFunction, mediaTimingFunction: curve.mediaTimingFunction, completion: { result in if let completion = completion { completion(result) } @@ -587,7 +554,7 @@ public extension ContainedViewLayoutTransition { } case let .animated(duration, curve): node.layer.sublayerTransform = CATransform3DMakeScale(scale, scale, 1.0) - node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: { result in if let completion = completion { completion(result) @@ -615,19 +582,19 @@ public extension ContainedViewLayoutTransition { } switch self { - case .immediate: - node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) - node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { - result in + case .immediate: + node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + node.layer.sublayerTransform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) } } @@ -637,7 +604,7 @@ public extension ContainedViewLayoutTransition { return } let t = node.layer.transform - var currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) + let currentScaleX = sqrt((t.m11 * t.m11) + (t.m12 * t.m12) + (t.m13 * t.m13)) var currentScaleY = sqrt((t.m21 * t.m21) + (t.m22 * t.m22) + (t.m23 * t.m23)) if t.m22 < 0.0 { currentScaleY = -currentScaleY @@ -650,19 +617,19 @@ public extension ContainedViewLayoutTransition { } switch self { - case .immediate: - node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) - if let completion = completion { - completion(true) - } - case let .animated(duration, curve): - node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) - node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { - result in + case .immediate: + node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) if let completion = completion { - completion(result) + completion(true) } - }) + case let .animated(duration, curve): + node.layer.transform = CATransform3DMakeScale(scale.x, scale.y, 1.0) + node.layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: node.layer.transform), keyPath: "transform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: { + result in + if let completion = completion { + completion(result) + } + }) } } @@ -684,7 +651,7 @@ public extension ContainedViewLayoutTransition { } case let .animated(duration, curve): layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0) - layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: { + layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: curve.mediaTimingFunction, removeOnCompletion: true, additive: false, completion: { result in if let completion = completion { completion(result) diff --git a/Display/ListView.swift b/Display/ListView.swift index 755685ff06..57664f2373 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -884,6 +884,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } + if self.isTracking { + offset = 0.0 + } + if abs(offset) > CGFloat.ulpOfOne { for itemNode in self.itemNodes { var frame = itemNode.frame @@ -2581,7 +2585,12 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) + var accessoryNodesTransition: ContainedViewLayoutTransition = .immediate + if let scrollToItem = scrollToItem, scrollToItem.animated { + accessoryNodesTransition = .animated(duration: 0.3, curve: .easeInOut) + } + + self.updateAccessoryNodes(transition: accessoryNodesTransition, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) if let highlightedItemNode = highlightedItemNode { if highlightedItemNode.index != self.highlightedItemIndex { @@ -2918,6 +2927,8 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture switch curve { case .spring: transition.0.animateOffsetAdditive(node: headerNode, offset: offset) + case let .custom(p1, p2, p3, p4): + headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: p1, p2, p3, p4)) case .easeInOut: if transition.1 { headerNode.layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, mediaTimingFunction: CAMediaTimingFunction(controlPoints: 0.33, 0.52, 0.25, 0.99)) @@ -3023,7 +3034,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { + private func updateAccessoryNodes(transition: ContainedViewLayoutTransition, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { var totalVisibleHeight: CGFloat = 0.0 var index = -1 let count = self.itemNodes.count @@ -3235,13 +3246,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if indicatorHeight >= visibleHeightWithoutIndicatorInsets { verticalScrollIndicator.isHidden = true - verticalScrollIndicator.frame = indicatorFrame + transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame) } else { if verticalScrollIndicator.isHidden { verticalScrollIndicator.isHidden = false - verticalScrollIndicator.frame = indicatorFrame + transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame) } else { - verticalScrollIndicator.frame = indicatorFrame + transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame) } } } else { diff --git a/Display/NavigationBarContentNode.swift b/Display/NavigationBarContentNode.swift index 12a193f20e..7fff0f07eb 100644 --- a/Display/NavigationBarContentNode.swift +++ b/Display/NavigationBarContentNode.swift @@ -13,6 +13,10 @@ open class NavigationBarContentNode: ASDisplayNode { return self.nominalHeight } + open var clippedHeight: CGFloat { + return self.nominalHeight + } + open var nominalHeight: CGFloat { return 44.0 } From ca08be316e0ac54187f5dac8f3ff79ad50e707d8 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 23 Apr 2019 19:04:23 +0400 Subject: [PATCH 205/245] Attempt to fix visual search navigation insets --- Display/ViewController.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index af804aefe2..29a5be2113 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -178,7 +178,7 @@ open class ViewControllerPresentationArguments { if let navigationBar = self.navigationBar { var height = navigationBar.frame.maxY if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode { - height += contentNode.height + //height += contentNode.height } return height } else { From 786b0207db1b0f02cfbca0d06ab4421646f6ba8e Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 23 Apr 2019 20:43:44 +0400 Subject: [PATCH 206/245] Revert some changes --- Display/ListView.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 57664f2373..46ee590734 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -884,10 +884,6 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - if self.isTracking { - offset = 0.0 - } - if abs(offset) > CGFloat.ulpOfOne { for itemNode in self.itemNodes { var frame = itemNode.frame @@ -2334,7 +2330,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture for itemNode in self.itemNodes { if let index = itemNode.index, index == scrollToItem.index { - let insets = updateSizeAndInsets?.insets ?? self.insets + let insets = self.insets// updateSizeAndInsets?.insets ?? self.insets let offset: CGFloat switch scrollToItem.position { From 108a074f74f7a41daa63502e061b55187adb9fbd Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 23 Apr 2019 21:32:58 +0400 Subject: [PATCH 207/245] generateScaledImage: added opaque parameter --- Display/GenerateImage.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index d69e2cc4ed..dac84c3a37 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -217,14 +217,17 @@ public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor return tintedImage } -public func generateScaledImage(image: UIImage?, size: CGSize, scale: CGFloat? = nil) -> UIImage? { +public func generateScaledImage(image: UIImage?, size: CGSize, opaque: Bool = true, scale: CGFloat? = nil) -> UIImage? { guard let image = image else { return nil } return generateImage(size, contextGenerator: { size, context in + if !opaque { + context.clear(CGRect(origin: CGPoint(), size: size)) + } context.draw(image.cgImage!, in: CGRect(origin: CGPoint(), size: size)) - }, opaque: true, scale: scale) + }, opaque: opaque, scale: scale) } private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { From 99638081bfc74d7ed991a0f8668a40466189204b Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 26 Apr 2019 11:32:06 +0400 Subject: [PATCH 208/245] ListView: fix visibleContentOffset to a placeholder node if higher --- Display/ListView.swift | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 46ee590734..e157dfedbf 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -905,16 +905,30 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public func visibleContentOffset() -> ListViewVisibleContentOffset { var offset: ListViewVisibleContentOffset = .unknown - var topItemIndexAndFrame: (Int, CGRect) = (-1, CGRect()) + var topItemIndexAndMinY: (Int, CGFloat) = (-1, 0.0) + + var currentMinY: CGFloat? for itemNode in self.itemNodes { if let index = itemNode.index { - topItemIndexAndFrame = (index, itemNode.apparentFrame) + let updatedMinY: CGFloat + if let currentMinY = currentMinY { + if itemNode.apparentFrame.minY < currentMinY { + updatedMinY = itemNode.apparentFrame.minY + } else { + updatedMinY = currentMinY + } + } else { + updatedMinY = itemNode.apparentFrame.minY + } + topItemIndexAndMinY = (index, updatedMinY) break + } else if currentMinY == nil { + currentMinY = itemNode.apparentFrame.minY } } - if topItemIndexAndFrame.0 == 0 { - offset = .known(-(topItemIndexAndFrame.1.minY - self.insets.top)) - } else if topItemIndexAndFrame.0 == -1 { + if topItemIndexAndMinY.0 == 0 { + offset = .known(-(topItemIndexAndMinY.1 - self.insets.top)) + } else if topItemIndexAndMinY.0 == -1 { offset = .none } return offset From 51c2d5e992255b9801cfdb0704fde95fa9afd64d Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 26 Apr 2019 15:28:49 +0400 Subject: [PATCH 209/245] ImmediateTextNode: added trailingLineWidth --- Display/ImmediateTextNode.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index 65094245e4..7eb149227b 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -18,6 +18,8 @@ public class ImmediateTextNode: TextNode { public var linkHighlightColor: UIColor? + public var trailingLineWidth: CGFloat? + public var highlightAttributeAction: (([NSAttributedStringKey: Any]) -> NSAttributedStringKey?)? { didSet { if self.isNodeLoaded { @@ -33,6 +35,11 @@ public class ImmediateTextNode: TextNode { let makeLayout = TextNode.asyncLayout(self) let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: self.truncationType, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) let _ = apply() + if layout.numberOfLines > 1 { + self.trailingLineWidth = layout.trailingLineWidth + } else { + self.trailingLineWidth = nil + } return layout.size } From 304535df848911252038c6387031c849a3a9f0af Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 26 Apr 2019 15:51:28 +0400 Subject: [PATCH 210/245] Disable scroll indicator animations --- Display/ListView.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index e157dfedbf..dcf643ae84 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -3256,13 +3256,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture if indicatorHeight >= visibleHeightWithoutIndicatorInsets { verticalScrollIndicator.isHidden = true - transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame) + verticalScrollIndicator.frame = indicatorFrame } else { if verticalScrollIndicator.isHidden { verticalScrollIndicator.isHidden = false - transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame) + verticalScrollIndicator.frame = indicatorFrame } else { - transition.updateFrame(node: verticalScrollIndicator, frame: indicatorFrame) + verticalScrollIndicator.frame = indicatorFrame } } } else { From 05c5fec8cb9783ed49d1396e2afba3ce4726131f Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 26 Apr 2019 19:08:12 +0400 Subject: [PATCH 211/245] PageControlNode: update colors --- Display/PageControlNode.swift | 48 ++++++++++++++++++++++++----------- 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/Display/PageControlNode.swift b/Display/PageControlNode.swift index 83f16de106..4286e8efbd 100644 --- a/Display/PageControlNode.swift +++ b/Display/PageControlNode.swift @@ -4,16 +4,42 @@ import AsyncDisplayKit public final class PageControlNode: ASDisplayNode { private let dotSize: CGFloat private let dotSpacing: CGFloat - private let dotColor: UIColor + public var dotColor: UIColor { + didSet { + if !oldValue.isEqual(self.dotColor) { + self.normalDotImage = generateFilledCircleImage(diameter: dotSize, color: self.dotColor)! + for dotNode in self.dotNodes { + if dotNode === oldValue { + dotNode.image = self.normalDotImage + } + } + } + } + } + public var inactiveDotColor: UIColor { + didSet { + if !oldValue.isEqual(self.inactiveDotColor) { + self.inactiveDotImage = generateFilledCircleImage(diameter: dotSize, color: self.inactiveDotColor)! + for dotNode in self.dotNodes { + if dotNode === oldValue { + dotNode.image = self.inactiveDotImage + } + } + } + } + } private var dotNodes: [ASImageNode] = [] - private let normalDotImage: UIImage + private var normalDotImage: UIImage + private var inactiveDotImage: UIImage - public init(dotSize: CGFloat = 7.0, dotSpacing: CGFloat = 9.0, dotColor: UIColor) { + public init(dotSize: CGFloat = 7.0, dotSpacing: CGFloat = 9.0, dotColor: UIColor, inactiveDotColor: UIColor) { self.dotSize = dotSize self.dotSpacing = dotSpacing self.dotColor = dotColor + self.inactiveDotColor = inactiveDotColor self.normalDotImage = generateFilledCircleImage(diameter: dotSize, color: dotColor)! + self.inactiveDotImage = generateFilledCircleImage(diameter: dotSize, color: inactiveDotColor)! super.init() } @@ -39,22 +65,14 @@ public final class PageControlNode: ASDisplayNode { } public func setPage(_ pageValue: CGFloat) { - let page = max(0.0, min(CGFloat(self.pagesCount - 1), pageValue)) + let page = Int(round(pageValue)) for i in 0 ..< self.dotNodes.count { - var alpha: CGFloat = 0.0 - let delta = abs(CGFloat(i) - page) - if delta >= 1.0 { - alpha = 0.5 + if i != page { + self.dotNodes[i].image = self.inactiveDotImage } else { - alpha = 1.0 - delta - alpha *= alpha * alpha + self.dotNodes[i].image = self.normalDotImage } - if alpha < 0.5 { - alpha = 0.5 - } - - self.dotNodes[i].alpha = alpha } } From 6f1f814217a31ab41ba4db713f5e43f779e089dc Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 26 Apr 2019 20:06:26 +0400 Subject: [PATCH 212/245] Toolbar: add middle action --- Display/TabBarContollerNode.swift | 30 ++++++++++-------------- Display/TabBarController.swift | 4 ++-- Display/Toolbar.swift | 4 +++- Display/ToolbarNode.swift | 38 ++++++++++++++++++++++++++++++- Display/ViewController.swift | 2 +- 5 files changed, 55 insertions(+), 23 deletions(-) diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index f3ba582203..10c81fc011 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -1,12 +1,18 @@ import Foundation import AsyncDisplayKit +public enum ToolbarActionOption { + case left + case right + case middle +} + final class TabBarControllerNode: ASDisplayNode { private var theme: TabBarControllerTheme let tabBarNode: TabBarNode private let navigationBar: NavigationBar? private var toolbarNode: ToolbarNode? - private let toolbarActionSelected: (Bool) -> Void + private let toolbarActionSelected: (ToolbarActionOption) -> Void var currentControllerNode: ASDisplayNode? { didSet { @@ -18,21 +24,7 @@ final class TabBarControllerNode: ASDisplayNode { } } - /*override var accessibilityElements: [Any]? { - get { - var accessibilityElements: [Any] = [] - if let navigationBar = self.navigationBar { - addAccessibilityChildren(of: navigationBar, container: self, to: &accessibilityElements) - } - if let currentControllerNode = self.currentControllerNode { - addAccessibilityChildren(of: currentControllerNode, container: self, to: &accessibilityElements) - } - return accessibilityElements - } set(value) { - } - }*/ - - init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, toolbarActionSelected: @escaping (Bool) -> Void) { + init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) { self.theme = theme self.navigationBar = navigationBar self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected) @@ -81,9 +73,11 @@ final class TabBarControllerNode: ASDisplayNode { toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: transition) } else { let toolbarNode = ToolbarNode(theme: self.theme, left: { [weak self] in - self?.toolbarActionSelected(true) + self?.toolbarActionSelected(.left) }, right: { [weak self] in - self?.toolbarActionSelected(false) + self?.toolbarActionSelected(.right) + }, middle: { [weak self] in + self?.toolbarActionSelected(.middle) }) toolbarNode.frame = tabBarFrame toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index d671c6a3bb..42bade03ae 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -180,8 +180,8 @@ open class TabBarController: ViewController { } })) } - }, toolbarActionSelected: { [weak self] left in - self?.currentController?.toolbarActionSelected(left: left) + }, toolbarActionSelected: { [weak self] action in + self?.currentController?.toolbarActionSelected(action: action) }) self.updateSelectedIndex() diff --git a/Display/Toolbar.swift b/Display/Toolbar.swift index b9c103f024..b609dc3534 100644 --- a/Display/Toolbar.swift +++ b/Display/Toolbar.swift @@ -13,9 +13,11 @@ public struct ToolbarAction: Equatable { public struct Toolbar: Equatable { public let leftAction: ToolbarAction? public let rightAction: ToolbarAction? + public let middleAction: ToolbarAction? - public init(leftAction: ToolbarAction?, rightAction: ToolbarAction?) { + public init(leftAction: ToolbarAction?, rightAction: ToolbarAction?, middleAction: ToolbarAction?) { self.leftAction = leftAction self.rightAction = rightAction + self.middleAction = middleAction } } diff --git a/Display/ToolbarNode.swift b/Display/ToolbarNode.swift index ab504b4a83..06bbda961c 100644 --- a/Display/ToolbarNode.swift +++ b/Display/ToolbarNode.swift @@ -6,18 +6,22 @@ public final class ToolbarNode: ASDisplayNode { private let displaySeparator: Bool private let left: () -> Void private let right: () -> Void + private let middle: () -> Void private let separatorNode: ASDisplayNode private let leftTitle: ImmediateTextNode private let leftButton: HighlightableButtonNode private let rightTitle: ImmediateTextNode private let rightButton: HighlightableButtonNode + private let middleTitle: ImmediateTextNode + private let middleButton: HighlightableButtonNode - public init(theme: TabBarControllerTheme, displaySeparator: Bool = false, left: @escaping () -> Void, right: @escaping () -> Void) { + public init(theme: TabBarControllerTheme, displaySeparator: Bool = false, left: @escaping () -> Void, right: @escaping () -> Void, middle: @escaping () -> Void) { self.theme = theme self.displaySeparator = displaySeparator self.left = left self.right = right + self.middle = middle self.separatorNode = ASDisplayNode() self.separatorNode.isLayerBacked = true @@ -28,6 +32,9 @@ public final class ToolbarNode: ASDisplayNode { self.rightTitle = ImmediateTextNode() self.rightTitle.displaysAsynchronously = false self.rightButton = HighlightableButtonNode() + self.middleTitle = ImmediateTextNode() + self.middleTitle.displaysAsynchronously = false + self.middleButton = HighlightableButtonNode() super.init() @@ -35,6 +42,8 @@ public final class ToolbarNode: ASDisplayNode { self.addSubnode(self.leftButton) self.addSubnode(self.rightTitle) self.addSubnode(self.rightButton) + self.addSubnode(self.middleTitle) + self.addSubnode(self.middleButton) if self.displaySeparator { self.addSubnode(self.separatorNode) } @@ -65,6 +74,18 @@ public final class ToolbarNode: ASDisplayNode { } } } + self.middleButton.addTarget(self, action: #selector(self.middlePressed), forControlEvents: .touchUpInside) + self.middleButton.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.middleTitle.layer.removeAnimation(forKey: "opacity") + strongSelf.middleTitle.alpha = 0.4 + } else { + strongSelf.middleTitle.alpha = 1.0 + strongSelf.middleTitle.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } } public func updateTheme(_ theme: TabBarControllerTheme) { @@ -79,11 +100,14 @@ public final class ToolbarNode: ASDisplayNode { self.leftTitle.attributedText = NSAttributedString(string: toolbar.leftAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.leftAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor) self.rightTitle.attributedText = NSAttributedString(string: toolbar.rightAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.rightAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor) + self.middleTitle.attributedText = NSAttributedString(string: toolbar.middleAction?.title ?? "", font: Font.regular(17.0), textColor: (toolbar.middleAction?.isEnabled ?? false) ? self.theme.tabBarSelectedTextColor : self.theme.tabBarTextColor) let leftSize = self.leftTitle.updateLayout(size) let rightSize = self.rightTitle.updateLayout(size) + let middleSize = self.middleTitle.updateLayout(size) let leftFrame = CGRect(origin: CGPoint(x: leftInset + sideInset, y: floor((size.height - bottomInset - leftSize.height) / 2.0)), size: leftSize) let rightFrame = CGRect(origin: CGPoint(x: size.width - rightInset - sideInset - rightSize.width, y: floor((size.height - bottomInset - rightSize.height) / 2.0)), size: rightSize) + let middleFrame = CGRect(origin: CGPoint(x: floor((size.width - middleSize.width) / 2.0), y: floor((size.height - bottomInset - middleSize.height) / 2.0)), size: middleSize) if leftFrame.size == self.leftTitle.frame.size { transition.updateFrame(node: self.leftTitle, frame: leftFrame) @@ -97,11 +121,19 @@ public final class ToolbarNode: ASDisplayNode { self.rightTitle.frame = rightFrame } + if middleFrame.size == self.middleTitle.frame.size { + transition.updateFrame(node: self.middleTitle, frame: middleFrame) + } else { + self.middleTitle.frame = middleFrame + } + self.leftButton.isEnabled = toolbar.leftAction?.isEnabled ?? false self.rightButton.isEnabled = toolbar.rightAction?.isEnabled ?? false + self.middleButton.isEnabled = toolbar.middleAction?.isEnabled ?? false self.leftButton.frame = CGRect(origin: CGPoint(x: leftInset, y: 0.0), size: CGSize(width: leftSize.width + sideInset * 2.0, height: size.height - bottomInset)) self.rightButton.frame = CGRect(origin: CGPoint(x: size.width - rightInset - sideInset * 2.0 - rightSize.width, y: 0.0), size: CGSize(width: rightSize.width + sideInset * 2.0, height: size.height - bottomInset)) + self.middleButton.frame = CGRect(origin: CGPoint(x: floor((size.width - middleSize.width) / 2.0), y: 0.0), size: CGSize(width: middleSize.width + sideInset * 2.0, height: size.height - bottomInset)) } @objc private func leftPressed() { @@ -111,4 +143,8 @@ public final class ToolbarNode: ASDisplayNode { @objc private func rightPressed() { self.right() } + + @objc private func middlePressed() { + self.middle() + } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 29a5be2113..ee9b509d4a 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -490,7 +490,7 @@ open class ViewControllerPresentationArguments { } } - open func toolbarActionSelected(left: Bool) { + open func toolbarActionSelected(action: ToolbarActionOption) { } } From 44c84bc116ce9faf63233e6a72f65804955ca5c4 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sun, 28 Apr 2019 17:18:14 +0400 Subject: [PATCH 213/245] Remove ongoing navigation bar frame animations on intermediate transition --- Display/ViewController.swift | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index ee9b509d4a..636aaf4855 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -296,8 +296,12 @@ open class ViewControllerPresentationArguments { if let contentNode = navigationBar.contentNode, case .expansion = contentNode.mode, !self.displayNavigationBar { navigationBarFrame.origin.y += contentNode.height + statusBarHeight } - transition.updateFrame(node: navigationBar, frame: navigationBarFrame) navigationBar.updateLayout(size: navigationBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, transition: transition) + if !transition.isAnimated { + navigationBar.layer.cancelAnimationsRecursive(key: "bounds") + navigationBar.layer.cancelAnimationsRecursive(key: "position") + } + transition.updateFrame(node: navigationBar, frame: navigationBarFrame) navigationBar.setHidden(!self.displayNavigationBar, animated: transition.isAnimated) } From cfc191bc16af3beb4212345ba10e8e36a3b51937 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 29 Apr 2019 20:26:23 +0400 Subject: [PATCH 214/245] ViewController: added forEachController --- Display/ViewController.swift | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 636aaf4855..ec37788fcc 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -398,12 +398,19 @@ open class ViewControllerPresentationArguments { switch context { case .current: self.presentationContext.present(controller, on: PresentationSurfaceLevel(rawValue: 0), completion: completion) - completion() case let .window(level): self.window?.present(controller, on: level, blockInteraction: blockInteraction, completion: completion) } } + public func forEachController(_ f: (ContainableController) -> Bool) { + for (controller, _) in self.presentationContext.controllers { + if !f(controller) { + break + } + } + } + public func presentInGlobalOverlay(_ controller: ViewController, with arguments: Any? = nil) { controller.presentationArguments = arguments self.window?.presentInGlobalOverlay(controller) From 1223538a60cd1bbb4fe45874865a48ce4456abc4 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 30 Apr 2019 16:48:12 +0400 Subject: [PATCH 215/245] Stats --- Display/TabBarController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 42bade03ae..900e286a55 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -163,9 +163,15 @@ open class TabBarController: ViewController { if let validLayout = strongSelf.validLayout { strongSelf.controllers[index].containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate) } + let startTime = CFAbsoluteTimeGetCurrent() strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in if let strongSelf = self { + let readyTime = CFAbsoluteTimeGetCurrent() - startTime + if readyTime > 0.5 { + print("TabBarController: controller took \(readyTime) to become ready") + } + if strongSelf.selectedIndex == index { if let controller = strongSelf.currentController { if longTap { From 6d3d242c1a73ed4c11772978799fd5492fc44e77 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 30 Apr 2019 20:44:21 +0400 Subject: [PATCH 216/245] Window: enumerate topLevelOverlayControllers --- Display/WindowContent.swift | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 38e6d0970b..ba5af1ad94 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -1068,5 +1068,10 @@ public class Window1 { break } } + for controller in self.topLevelOverlayControllers { + if !f(controller) { + break + } + } } } From febf3d3f0e55e6ac7aab72b18fc075d88d24dbd5 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Wed, 1 May 2019 17:17:38 +0400 Subject: [PATCH 217/245] Attempt to fix initial positioning when content is smaller than screen size --- Display/ListView.swift | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index dcf643ae84..76c475923b 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -828,7 +828,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture case let .Spring(duration): transition = .animated(duration: duration, curve: .spring) case let .Default(duration): - transition = .animated(duration: duration ?? 0.3, curve: .easeInOut) + if let duration = duration, duration.isZero { + transition = .immediate + } else { + transition = .animated(duration: duration ?? 0.3, curve: .easeInOut) + } } } @@ -854,7 +858,11 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else { let areaHeight = min(completeHeight, visibleAreaHeight) if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll { - offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge + if snapTopItem && topItemEdge < effectiveInsets.top { + offset = (effectiveInsets.top - overscroll) - topItemEdge + } else { + offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge + } } else if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { offset = (effectiveInsets.top - overscroll) - topItemEdge } @@ -3235,7 +3243,12 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let minIndicatorHeight: CGFloat = 6.0 let visibleHeightWithoutIndicatorInsets = self.visibleSize.height - self.scrollIndicatorInsets.top - self.scrollIndicatorInsets.bottom - indicatorTopInset - indicatorBottomInset - let indicatorHeight: CGFloat = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight)) + let indicatorHeight: CGFloat + if approximateContentHeight <= 0 { + indicatorHeight = 0.0 + } else { + indicatorHeight = max(minIndicatorContentHeight, floor(visibleHeightWithoutIndicatorInsets * (self.visibleSize.height - self.insets.top - self.insets.bottom) / approximateContentHeight)) + } let upperBound = self.scrollIndicatorInsets.top + indicatorTopInset let lowerBound = self.visibleSize.height - self.scrollIndicatorInsets.bottom - indicatorTopInset - indicatorBottomInset - indicatorHeight From 401a7b33c488c83469bb53765736ba69b00b3d82 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Wed, 1 May 2019 16:16:05 +0400 Subject: [PATCH 218/245] NavigationTransition: use bottom navigation bar edge for dim offset --- Display/NavigationTransitionCoordinator.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 41ab61d274..a79111cf17 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -94,8 +94,8 @@ class NavigationTransitionCoordinator { } var dimInset: CGFloat = 0.0 - if let topNavigationBar = self.topNavigationBar , self.inlineNavigationBarTransition { - dimInset = topNavigationBar.frame.maxY + if let bottomNavigationBar = self.bottomNavigationBar , self.inlineNavigationBarTransition { + dimInset = bottomNavigationBar.frame.maxY } let containerSize = self.container.bounds.size From 41107f978a86bc5f9951b66f84094665dbf9cf0a Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 3 May 2019 15:02:38 +0400 Subject: [PATCH 219/245] Add assertions --- Display/ListView.swift | 3 +++ Display/ListViewTransactionQueue.swift | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/Display/ListView.swift b/Display/ListView.swift index 76c475923b..ef0293f032 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1386,6 +1386,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var previousNodes: [Int: QueueLocalObject] = [:] for insertedItem in sortedIndicesAndItems { + if insertedItem.index < 0 || insertedItem.index > self.items.count { + fatalError("insertedItem.index \(insertedItem.index) is out of bounds 0 ... \(self.items.count)") + } self.items.insert(insertedItem.item, at: insertedItem.index) if let previousIndex = insertedItem.previousIndex { for itemNode in self.itemNodes { diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 689c8dbc81..55c904f0db 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -15,11 +15,14 @@ public final class ListViewTransactionQueue { } public func addTransaction(_ transaction: @escaping ListViewTransaction) { + assert(Thread.isMainThread) let beginTransaction = self.transactions.count == 0 self.transactions.append(transaction) if beginTransaction { transaction({ [weak self] in + assert(Thread.isMainThread) + if Thread.isMainThread { if let strongSelf = self { strongSelf.endTransaction() @@ -36,6 +39,7 @@ public final class ListViewTransactionQueue { } private func endTransaction() { + assert(Thread.isMainThread) Queue.mainQueue().async { self.transactionCompleted() if !self.transactions.isEmpty { @@ -44,6 +48,8 @@ public final class ListViewTransactionQueue { if let nextTransaction = self.transactions.first { nextTransaction({ [weak self] in + assert(Thread.isMainThread) + if Thread.isMainThread { if let strongSelf = self { strongSelf.endTransaction() From 3417ed445c1615cf1ad8edfec5ec11060854e6a4 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 5 May 2019 18:42:11 +0400 Subject: [PATCH 220/245] NavigationController: added filterController --- Display/NavigationController.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 061a5715f6..d952d5136e 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -849,6 +849,13 @@ open class NavigationController: UINavigationController, ContainableController, })) } + public func filterController(_ controller: ViewController, animated: Bool) { + let controllers = self.viewControllers.filter({ $0 !== controller }) + if controllers.count != self.viewControllers.count { + self.setViewControllers(controllers, animated: animated) + } + } + public func replaceControllersAndPush(controllers: [UIViewController], controller: ViewController, animated: Bool, ready: ValuePromise? = nil, completion: @escaping () -> Void = {}) { self.view.endEditing(true) self.scheduleAfterLayout { [weak self] in From df839bd80bc5e7cf3bf3d5cf201075c68f18d4e4 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 9 May 2019 14:58:31 +0200 Subject: [PATCH 221/245] NavigationController: finish current transition before setViewControllers --- Display/NavigationController.swift | 3 ++ Display/NavigationTransitionCoordinator.swift | 30 +++++++++++++++++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index d952d5136e..8412cefb43 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -966,6 +966,9 @@ open class NavigationController: UINavigationController, ContainableController, } let previousControllers = self._viewControllers self._viewControllers = resultControllers + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + navigationTransitionCoordinator.complete() + } if let layout = self.validLayout { self.updateControllerLayouts(previousControllers: previousControllers, layout: layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) } diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index a79111cf17..809f669aec 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -38,6 +38,7 @@ class NavigationTransitionCoordinator { private let inlineNavigationBarTransition: Bool private(set) var animatingCompletion = false + private var currentCompletion: (() -> Void)? init(transition: NavigationTransition, container: UIView, topView: UIView, topNavigationBar: NavigationBar?, bottomView: UIView, bottomNavigationBar: NavigationBar?) { self.transition = transition @@ -148,6 +149,8 @@ class NavigationTransitionCoordinator { } func animateCancel(_ completion: @escaping () -> ()) { + self.currentCompletion = completion + UIView.animate(withDuration: 0.1, delay: 0.0, options: UIViewAnimationOptions(), animations: { () -> Void in self.progress = 0.0 }) { (completed) -> Void in @@ -173,13 +176,33 @@ class NavigationTransitionCoordinator { self.endNavigationBarTransition() - completion() + if let currentCompletion = self.currentCompletion { + self.currentCompletion = nil + currentCompletion() + } + } + } + + func complete() { + self.animatingCompletion = true + + self.progress = 1.0 + + self.dimView.removeFromSuperview() + self.shadowView.removeFromSuperview() + + self.endNavigationBarTransition() + + if let currentCompletion = self.currentCompletion { + self.currentCompletion = nil + currentCompletion() } } func animateCompletion(_ velocity: CGFloat, completion: @escaping () -> ()) { self.animatingCompletion = true let distance = (1.0 - self.progress) * self.container.bounds.size.width + self.currentCompletion = completion let f = { /*switch self.transition { case .Push: @@ -201,7 +224,10 @@ class NavigationTransitionCoordinator { self.endNavigationBarTransition() - completion() + if let currentCompletion = self.currentCompletion { + self.currentCompletion = nil + currentCompletion() + } } if abs(velocity) < CGFloat.ulpOfOne && abs(self.progress) < CGFloat.ulpOfOne { From d2592223e0e82ddb4de5b94a2b40bbb6e91e0c7d Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 10 May 2019 17:43:01 +0200 Subject: [PATCH 222/245] Fixed view controller presentation Fixed volume indicator color in landscape Added dark outline for volume indicator in landscape to separate from underlying content --- Display/PresentationContext.swift | 22 ++++++++++++++++---- Display/StatusBarManager.swift | 6 +++++- Display/VolumeControlStatusBar.swift | 30 ++++++++++++++++++++++++---- Display/WindowContent.swift | 1 + 4 files changed, 50 insertions(+), 9 deletions(-) diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 88b3198c8e..87f6cca26b 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -31,6 +31,8 @@ final class PresentationContext { } } + weak var volumeControlStatusBarNodeView: UIView? + var updateIsInteractionBlocked: ((Bool) -> Void)? var updateHasOpaqueOverlay: ((Bool) -> Void)? @@ -89,7 +91,7 @@ final class PresentationContext { if let topController = topController { return topController.view } else { - return topLevelSubview + return self.topLevelSubview } } @@ -176,14 +178,22 @@ final class PresentationContext { if let topLevelSubview = strongSelf.topLevelSubview(for: level) { view.insertSubview(controller.view, belowSubview: topLevelSubview) } else { - view.addSubview(controller.view) + if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView { + view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView) + } else { + view.addSubview(controller.view) + } } controller.containerLayoutUpdated(layout, transition: .immediate) } else { if let topLevelSubview = strongSelf.topLevelSubview(for: level) { view.insertSubview(controller.view, belowSubview: topLevelSubview) } else { - view.addSubview(controller.view) + if let volumeControlStatusBarNodeView = strongSelf.volumeControlStatusBarNodeView { + view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView) + } else { + view.addSubview(controller.view) + } } } (controller as? UIViewController)?.setIgnoreAppearanceMethodInvocations(false) @@ -249,7 +259,11 @@ final class PresentationContext { if let topLevelSubview = self.topLevelSubview { view.insertSubview(controller.view, belowSubview: topLevelSubview) } else { - view.addSubview(controller.view) + if let volumeControlStatusBarNodeView = self.volumeControlStatusBarNodeView { + view.insertSubview(controller.view, belowSubview: volumeControlStatusBarNodeView) + } else { + view.addSubview(controller.view) + } } controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) controller.containerLayoutUpdated(layout, transition: .immediate) diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index a8fcf6f8a6..e09107be51 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -271,7 +271,11 @@ class StatusBarManager { if self.volumeTimer != nil { globalStatusBar?.1 = 0.0 } - self.volumeControlStatusBarNode.isDark = globalStatusBar?.0.systemStyle == UIStatusBarStyle.lightContent + var isDark = true + if let globalStatusBar = globalStatusBar { + isDark = globalStatusBar.0.systemStyle == UIStatusBarStyle.lightContent + } + self.volumeControlStatusBarNode.isDark = isDark if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows { let statusBarStyle: UIStatusBarStyle diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 1e6ce33c74..1ecb917183 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -92,6 +92,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { } } } + private let outlineNode: ASImageNode private let backgroundNode: ASImageNode private let iconNode: ASImageNode private let foregroundNode: ASImageNode @@ -103,11 +104,13 @@ final class VolumeControlStatusBarNode: ASDisplayNode { didSet { if self.isDark != oldValue { if self.isDark { + self.outlineNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: UIColor(white: 0.0, alpha: 0.7)) self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .white) self.innerGraphics = generateDarkGraphics(self.graphics) } else { + self.outlineNode.image = nil self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(rgb: 0xc5c5c5)) self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) @@ -122,6 +125,11 @@ final class VolumeControlStatusBarNode: ASDisplayNode { private var value: CGFloat = 1.0 override init() { + self.outlineNode = ASImageNode() + self.outlineNode.isLayerBacked = true + self.outlineNode.displaysAsynchronously = false + self.outlineNode.displayWithoutProcessing = true + self.backgroundNode = ASImageNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.displaysAsynchronously = false @@ -147,6 +155,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { self.isUserInteractionEnabled = false + self.addSubnode(self.outlineNode) self.addSubnode(self.backgroundNode) self.addSubnode(self.foregroundClippingNode) self.addSubnode(self.iconNode) @@ -165,9 +174,11 @@ final class VolumeControlStatusBarNode: ASDisplayNode { func updateGraphics() { if self.isDark { + self.outlineNode.image = generateStretchableFilledCircleImage(diameter: 12.0, color: UIColor(white: 0.0, alpha: 0.7)) self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .white) } else { + self.outlineNode.image = nil self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: UIColor(white: 0.6, alpha: 1.0)) self.foregroundNode.image = generateStretchableFilledCircleImage(diameter: 4.0, color: .black) } @@ -184,7 +195,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { if let actual = layout.statusBarHeight { statusBarHeight = actual } else { - statusBarHeight = 20.0 + statusBarHeight = 24.0 } if layout.safeInsets.left.isZero && layout.safeInsets.top.isZero && layout.intrinsicInsets.left.isZero && layout.intrinsicInsets.top.isZero { sideInset = 4.0 @@ -199,10 +210,20 @@ final class VolumeControlStatusBarNode: ASDisplayNode { } else { barWidth = 80.0 - sideInset * 2.0 } + if layout.size.width < layout.size.height { + self.outlineNode.isHidden = true + } else { + self.outlineNode.isHidden = false + } if self.graphics != nil { - self.iconNode.isHidden = false - barWidth -= iconRect.width - 8.0 - sideInset += iconRect.width + 8.0 + if layout.size.width < layout.size.height { + self.iconNode.isHidden = false + barWidth -= iconRect.width - 8.0 + sideInset += iconRect.width + 8.0 + } else { + sideInset += layout.safeInsets.left + self.iconNode.isHidden = true + } } } else { self.iconNode.isHidden = true @@ -212,6 +233,7 @@ final class VolumeControlStatusBarNode: ASDisplayNode { let boundingRect = CGRect(origin: CGPoint(x: sideInset, y: floor((statusBarHeight - barHeight) / 2.0)), size: CGSize(width: barWidth, height: barHeight)) transition.updateFrame(node: self.iconNode, frame: iconRect) + transition.updateFrame(node: self.outlineNode, frame: boundingRect.insetBy(dx: -4.0, dy: -4.0)) transition.updateFrame(node: self.backgroundNode, frame: boundingRect) transition.updateFrame(node: self.foregroundNode, frame: CGRect(origin: CGPoint(), size: boundingRect.size)) transition.updateFrame(node: self.foregroundClippingNode, frame: CGRect(origin: boundingRect.origin, size: CGSize(width: self.value * boundingRect.width, height: boundingRect.height))) diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index ba5af1ad94..d33b80933e 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -430,6 +430,7 @@ public class Window1 { }*/ self.presentationContext.view = self.hostView.containerView + self.presentationContext.volumeControlStatusBarNodeView = self.volumeControlStatusBarNode.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout, hasOnScreenNavigation: self.hostView.hasOnScreenNavigation), transition: .immediate) From a1aecb45d3320ecedf70d6a6f160a402fd4ee437 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 13 May 2019 22:15:51 +0200 Subject: [PATCH 223/245] ListViewTransactionQueue: changed assert to precondition ListView: fixed header view flashing --- Display/ListView.swift | 7 ++++--- Display/ListViewTransactionQueue.swift | 8 ++++---- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index ef0293f032..f8630da8a9 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1142,12 +1142,13 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } else if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { self.itemHighlightOverlayBackground = nil - transition.updateAlpha(node: itemHighlightOverlayBackground, alpha: 0.0, completion: { [weak itemHighlightOverlayBackground] _ in - itemHighlightOverlayBackground?.removeFromSupernode() - }) for (_, headerNode) in self.itemHeaderNodes { self.view.bringSubview(toFront: headerNode.view) } + self.view.bringSubview(toFront: itemHighlightOverlayBackground.view) + transition.updateAlpha(node: itemHighlightOverlayBackground, alpha: 0.0, completion: { [weak itemHighlightOverlayBackground] _ in + itemHighlightOverlayBackground?.removeFromSupernode() + }) if let verticalScrollIndicator = self.verticalScrollIndicator { verticalScrollIndicator.view.superview?.bringSubview(toFront: verticalScrollIndicator.view) } diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 55c904f0db..3e6f1793dd 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -15,13 +15,13 @@ public final class ListViewTransactionQueue { } public func addTransaction(_ transaction: @escaping ListViewTransaction) { - assert(Thread.isMainThread) + precondition(Thread.isMainThread) let beginTransaction = self.transactions.count == 0 self.transactions.append(transaction) if beginTransaction { transaction({ [weak self] in - assert(Thread.isMainThread) + precondition(Thread.isMainThread) if Thread.isMainThread { if let strongSelf = self { @@ -39,7 +39,7 @@ public final class ListViewTransactionQueue { } private func endTransaction() { - assert(Thread.isMainThread) + precondition(Thread.isMainThread) Queue.mainQueue().async { self.transactionCompleted() if !self.transactions.isEmpty { @@ -48,7 +48,7 @@ public final class ListViewTransactionQueue { if let nextTransaction = self.transactions.first { nextTransaction({ [weak self] in - assert(Thread.isMainThread) + precondition(Thread.isMainThread) if Thread.isMainThread { if let strongSelf = self { From b75596aaf67347e97d0e3ae867c293558f1fc364 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Fri, 17 May 2019 00:30:31 +0200 Subject: [PATCH 224/245] Fixed tooltip fade in animation --- Display/TooltipControllerNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/TooltipControllerNode.swift b/Display/TooltipControllerNode.swift index 2e555ccbaa..b3a478bbb7 100644 --- a/Display/TooltipControllerNode.swift +++ b/Display/TooltipControllerNode.swift @@ -107,7 +107,7 @@ final class TooltipControllerNode: ASDisplayNode { } func animateIn() { - self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.1) + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) } func animateOut(completion: @escaping () -> Void) { From 64d6ec70717f3c0876f4144fa329d0b3ac0c6466 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 21 May 2019 15:50:54 +0200 Subject: [PATCH 225/245] Remove DisplayMac --- Display.xcodeproj/project.pbxproj | 646 --------------------------- DisplayMac/ASDisplayNode.swift | 111 ----- DisplayMac/CADisplayLink.swift | 89 ---- DisplayMac/DisplayMac.h | 19 - DisplayMac/Info.plist | 26 -- DisplayMac/NSValueAdditions.swift | 12 - DisplayMac/UIGestureRecognizer.swift | 47 -- DisplayMac/UIKit.swift | 55 --- DisplayMac/UIScrollView.swift | 28 -- DisplayMac/UISlider.swift | 5 - DisplayMac/UITouch.swift | 5 - DisplayMac/UIView.swift | 82 ---- 12 files changed, 1125 deletions(-) delete mode 100644 DisplayMac/ASDisplayNode.swift delete mode 100644 DisplayMac/CADisplayLink.swift delete mode 100644 DisplayMac/DisplayMac.h delete mode 100644 DisplayMac/Info.plist delete mode 100644 DisplayMac/NSValueAdditions.swift delete mode 100644 DisplayMac/UIGestureRecognizer.swift delete mode 100644 DisplayMac/UIKit.swift delete mode 100644 DisplayMac/UIScrollView.swift delete mode 100644 DisplayMac/UISlider.swift delete mode 100644 DisplayMac/UITouch.swift delete mode 100644 DisplayMac/UIView.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index fee09b13ac..000ccd30c0 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -17,33 +17,10 @@ D0076F2221ACA5020059500A /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0076F2121ACA5020059500A /* Toolbar.swift */; }; D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; }; D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; }; - D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */ = {isa = PBXBuildFile; fileRef = D01159B91F40E96C0039383E /* DisplayMac.h */; settings = {ATTRIBUTES = (Public, ); }; }; D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; - D01847611FFA703B00075256 /* UIKit.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847601FFA703B00075256 /* UIKit.swift */; }; - D01847631FFA70FC00075256 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847621FFA70FC00075256 /* UIView.swift */; }; - D01847641FFA723600075256 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; D01847661FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */; }; - D01847671FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */; }; - D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */; }; - D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC51CC4431D0044FF83 /* ListViewAccessoryItemNode.swift */; }; - D018476A1FFA75EE00075256 /* Spring.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBD1CC4431D0044FF83 /* Spring.swift */; }; - D018476D1FFA765D00075256 /* NSValueAdditions.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018476B1FFA765D00075256 /* NSValueAdditions.swift */; }; - D018476E1FFA76DC00075256 /* ListViewItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */; }; - D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC31CC4431D0044FF83 /* ListViewAccessoryItem.swift */; }; - D01847701FFA773100075256 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; - D01847711FFA778100075256 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; - D01847741FFA780400075256 /* UIScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847721FFA780400075256 /* UIScrollView.swift */; }; - D01847751FFA78B200075256 /* ListViewScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC41CC4431D0044FF83 /* ListViewScroller.swift */; }; - D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847761FFA78C100075256 /* UIGestureRecognizer.swift */; }; - D01847791FFA7A4E00075256 /* UITouch.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01847781FFA7A4E00075256 /* UITouch.swift */; }; - D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AA840F1FED2887005C6E91 /* ListViewOverscrollBackgroundNode.swift */; }; - D018477C1FFA7ABF00075256 /* UISlider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D018477B1FFA7ABF00075256 /* UISlider.swift */; }; - D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01C06C11FC254F8001561AB /* ASDisplayNode.swift */; }; - D01C06C31FC2552C001561AB /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; - D01C06C41FC25561001561AB /* ListViewItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */; }; - D01C06C51FC2558F001561AB /* SwiftSignalKitMac.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */; }; D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDD1D9049620066BF65 /* GridNode.swift */; }; D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; @@ -178,8 +155,6 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A021DE473B900BC6096 /* HighlightableButton.swift */; }; - D0E8175E2014ED7D00B82BBB /* CADisplayLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */; }; - D0E8175F2014F18F00B82BBB /* ListViewTransactionQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFC21CC4431D0044FF83 /* ListViewTransactionQueue.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; D0F8C3932014FB7C00236FC5 /* ListView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBE1CC4431D0044FF83 /* ListView.swift */; }; @@ -210,21 +185,10 @@ D0076F2121ACA5020059500A /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = ""; }; D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = ""; }; - D01159B71F40E96B0039383E /* DisplayMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DisplayMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - D01159B91F40E96C0039383E /* DisplayMac.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DisplayMac.h; sourceTree = ""; }; - D01159BA1F40E96C0039383E /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; - D01847601FFA703B00075256 /* UIKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKit.swift; sourceTree = ""; }; - D01847621FFA70FC00075256 /* UIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainedViewLayoutTransition.swift; sourceTree = ""; }; - D018476B1FFA765D00075256 /* NSValueAdditions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSValueAdditions.swift; sourceTree = ""; }; - D01847721FFA780400075256 /* UIScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIScrollView.swift; sourceTree = ""; }; - D01847761FFA78C100075256 /* UIGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIGestureRecognizer.swift; sourceTree = ""; }; - D01847781FFA7A4E00075256 /* UITouch.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITouch.swift; sourceTree = ""; }; - D018477B1FFA7ABF00075256 /* UISlider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UISlider.swift; sourceTree = ""; }; - D01C06C11FC254F8001561AB /* ASDisplayNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ASDisplayNode.swift; sourceTree = ""; }; D01C06C61FC2558F001561AB /* SwiftSignalKitMac.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = SwiftSignalKitMac.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D01E2BDD1D9049620066BF65 /* GridNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNode.swift; sourceTree = ""; }; D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; @@ -365,7 +329,6 @@ D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E35A021DE473B900BC6096 /* HighlightableButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableButton.swift; sourceTree = ""; }; - D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CADisplayLink.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; D0FA08C120487A8600DD23FC /* HapticFeedback.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HapticFeedback.swift; sourceTree = ""; }; @@ -375,14 +338,6 @@ /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ - D01159B31F40E96B0039383E /* Frameworks */ = { - isa = PBXFrameworksBuildPhase; - buildActionMask = 2147483647; - files = ( - D01C06C51FC2558F001561AB /* SwiftSignalKitMac.framework in Frameworks */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05CC25F1B69316F00E235A3 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -421,24 +376,6 @@ name = Tooltip; sourceTree = ""; }; - D01159B81F40E96C0039383E /* DisplayMac */ = { - isa = PBXGroup; - children = ( - D01159B91F40E96C0039383E /* DisplayMac.h */, - D01159BA1F40E96C0039383E /* Info.plist */, - D01C06C11FC254F8001561AB /* ASDisplayNode.swift */, - D01847601FFA703B00075256 /* UIKit.swift */, - D01847621FFA70FC00075256 /* UIView.swift */, - D01847721FFA780400075256 /* UIScrollView.swift */, - D018476B1FFA765D00075256 /* NSValueAdditions.swift */, - D01847761FFA78C100075256 /* UIGestureRecognizer.swift */, - D01847781FFA7A4E00075256 /* UITouch.swift */, - D018477B1FFA7ABF00075256 /* UISlider.swift */, - D0E8175C2014ED7D00B82BBB /* CADisplayLink.swift */, - ); - path = DisplayMac; - sourceTree = ""; - }; D015F7501D1ADC6800E269B5 /* Window */ = { isa = PBXGroup; children = ( @@ -576,7 +513,6 @@ D05CC2591B69316F00E235A3 = { isa = PBXGroup; children = ( - D01159B81F40E96C0039383E /* DisplayMac */, D05CC2A31B6932D500E235A3 /* Frameworks */, D05CC2651B69316F00E235A3 /* Display */, D05CC2711B69316F00E235A3 /* DisplayTests */, @@ -589,7 +525,6 @@ children = ( D05CC2631B69316F00E235A3 /* Display.framework */, D05CC26D1B69316F00E235A3 /* DisplayTests.xctest */, - D01159B71F40E96B0039383E /* DisplayMac.framework */, ); name = Products; sourceTree = ""; @@ -800,14 +735,6 @@ /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ - D01159B41F40E96B0039383E /* Headers */ = { - isa = PBXHeadersBuildPhase; - buildActionMask = 2147483647; - files = ( - D01159BB1F40E96C0039383E /* DisplayMac.h in Headers */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05CC2601B69316F00E235A3 /* Headers */ = { isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; @@ -833,24 +760,6 @@ /* End PBXHeadersBuildPhase section */ /* Begin PBXNativeTarget section */ - D01159B61F40E96B0039383E /* DisplayMac */ = { - isa = PBXNativeTarget; - buildConfigurationList = D01159C01F40E96C0039383E /* Build configuration list for PBXNativeTarget "DisplayMac" */; - buildPhases = ( - D01159B21F40E96B0039383E /* Sources */, - D01159B31F40E96B0039383E /* Frameworks */, - D01159B41F40E96B0039383E /* Headers */, - D01159B51F40E96B0039383E /* Resources */, - ); - buildRules = ( - ); - dependencies = ( - ); - name = DisplayMac; - productName = DisplayMac; - productReference = D01159B71F40E96B0039383E /* DisplayMac.framework */; - productType = "com.apple.product-type.framework"; - }; D05CC2621B69316F00E235A3 /* Display */ = { isa = PBXNativeTarget; buildConfigurationList = D05CC2771B69316F00E235A3 /* Build configuration list for PBXNativeTarget "Display" */; @@ -897,12 +806,6 @@ LastUpgradeCheck = 0800; ORGANIZATIONNAME = Telegram; TargetAttributes = { - D01159B61F40E96B0039383E = { - CreatedOnToolsVersion = 8.3.2; - DevelopmentTeam = X834Q8SBVP; - LastSwiftMigration = 0830; - ProvisioningStyle = Automatic; - }; D05CC2621B69316F00E235A3 = { CreatedOnToolsVersion = 7.0; ProvisioningStyle = Manual; @@ -926,19 +829,11 @@ targets = ( D05CC2621B69316F00E235A3 /* Display */, D05CC26C1B69316F00E235A3 /* DisplayTests */, - D01159B61F40E96B0039383E /* DisplayMac */, ); }; /* End PBXProject section */ /* Begin PBXResourcesBuildPhase section */ - D01159B51F40E96B0039383E /* Resources */ = { - isa = PBXResourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05CC2611B69316F00E235A3 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -958,36 +853,6 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ - D01159B21F40E96B0039383E /* Sources */ = { - isa = PBXSourcesBuildPhase; - buildActionMask = 2147483647; - files = ( - D01847691FFA756600075256 /* ListViewAccessoryItemNode.swift in Sources */, - D01847611FFA703B00075256 /* UIKit.swift in Sources */, - D01847701FFA773100075256 /* ListView.swift in Sources */, - D0E8175F2014F18F00B82BBB /* ListViewTransactionQueue.swift in Sources */, - D01847771FFA78C100075256 /* UIGestureRecognizer.swift in Sources */, - D01C06C21FC254F8001561AB /* ASDisplayNode.swift in Sources */, - D01847631FFA70FC00075256 /* UIView.swift in Sources */, - D01847791FFA7A4E00075256 /* UITouch.swift in Sources */, - D01C06C41FC25561001561AB /* ListViewItemNode.swift in Sources */, - D018476D1FFA765D00075256 /* NSValueAdditions.swift in Sources */, - D01847641FFA723600075256 /* CAAnimationUtils.swift in Sources */, - D018476A1FFA75EE00075256 /* Spring.swift in Sources */, - D01C06C31FC2552C001561AB /* ListViewItemHeader.swift in Sources */, - D018477C1FFA7ABF00075256 /* UISlider.swift in Sources */, - D01847671FFA72E000075256 /* ContainedViewLayoutTransition.swift in Sources */, - D018476E1FFA76DC00075256 /* ListViewItem.swift in Sources */, - D01847751FFA78B200075256 /* ListViewScroller.swift in Sources */, - D01847711FFA778100075256 /* ListViewIntermediateState.swift in Sources */, - D018476F1FFA76FD00075256 /* ListViewAccessoryItem.swift in Sources */, - D018477A1FFA7A8800075256 /* ListViewOverscrollBackgroundNode.swift in Sources */, - D01847741FFA780400075256 /* UIScrollView.swift in Sources */, - D0E8175E2014ED7D00B82BBB /* CADisplayLink.swift in Sources */, - D01847681FFA749F00075256 /* ListViewAnimation.swift in Sources */, - ); - runOnlyForDeploymentPostprocessing = 0; - }; D05CC25E1B69316F00E235A3 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -1148,254 +1013,6 @@ /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ - D01159BC1F40E96C0039383E /* DebugHockeyapp */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = DebugHockeyapp; - }; - D01159BD1F40E96C0039383E /* DebugAppStore */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = DebugAppStore; - }; - D01159BE1F40E96C0039383E /* ReleaseHockeyapp */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = ReleaseHockeyapp; - }; - D01159BF1F40E96C0039383E /* ReleaseAppStore */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = ReleaseAppStore; - }; D021D4F4219CB1AD0064BEBA /* DebugFork */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1485,72 +1102,6 @@ }; name = DebugFork; }; - D021D4F7219CB1AD0064BEBA /* DebugFork */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = DebugFork; - }; D05CC2751B69316F00E235A3 /* DebugHockeyapp */ = { isa = XCBuildConfiguration; buildSettings = { @@ -1978,64 +1529,6 @@ }; name = ReleaseHockeyappInternal; }; - D0924FD71FE52BE9003F693F /* ReleaseHockeyappInternal */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = ReleaseHockeyappInternal; - }; D0ADF920212B3ABC00310BBC /* DebugAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2125,72 +1618,6 @@ }; name = DebugAppStoreLLC; }; - D0ADF923212B3ABC00310BBC /* DebugAppStoreLLC */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = dwarf; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_STRICT_OBJC_MSGSEND = YES; - ENABLE_TESTABILITY = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_DYNAMIC_NO_PIC = NO; - GCC_NO_COMMON_BLOCKS = YES; - GCC_OPTIMIZATION_LEVEL = 0; - GCC_PREPROCESSOR_DEFINITIONS = ( - "DEBUG=1", - "$(inherited)", - ); - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = YES; - ONLY_ACTIVE_ARCH = YES; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = DebugAppStoreLLC; - }; D0CE6EE1213DB54B00BCD44B /* ReleaseAppStoreLLC */ = { isa = XCBuildConfiguration; buildSettings = { @@ -2273,82 +1700,9 @@ }; name = ReleaseAppStoreLLC; }; - D0CE6EE4213DB54B00BCD44B /* ReleaseAppStoreLLC */ = { - isa = XCBuildConfiguration; - buildSettings = { - ALWAYS_SEARCH_USER_PATHS = NO; - CLANG_ANALYZER_NONNULL = YES; - CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; - CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; - CLANG_CXX_LIBRARY = "libc++"; - CLANG_ENABLE_MODULES = YES; - CLANG_ENABLE_OBJC_ARC = YES; - CLANG_WARN_BOOL_CONVERSION = YES; - CLANG_WARN_CONSTANT_CONVERSION = YES; - CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; - CLANG_WARN_DOCUMENTATION_COMMENTS = YES; - CLANG_WARN_EMPTY_BODY = YES; - CLANG_WARN_ENUM_CONVERSION = YES; - CLANG_WARN_INFINITE_RECURSION = YES; - CLANG_WARN_INT_CONVERSION = YES; - CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; - CLANG_WARN_SUSPICIOUS_MOVE = YES; - CLANG_WARN_UNREACHABLE_CODE = YES; - CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; - CODE_SIGN_IDENTITY = "-"; - COMBINE_HIDPI_IMAGES = YES; - COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 1; - DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; - DEFINES_MODULE = YES; - DEVELOPMENT_TEAM = X834Q8SBVP; - DYLIB_COMPATIBILITY_VERSION = 1; - DYLIB_CURRENT_VERSION = 1; - DYLIB_INSTALL_NAME_BASE = "@rpath"; - ENABLE_NS_ASSERTIONS = NO; - ENABLE_STRICT_OBJC_MSGSEND = YES; - FRAMEWORK_VERSION = A; - GCC_C_LANGUAGE_STANDARD = gnu99; - GCC_NO_COMMON_BLOCKS = YES; - GCC_WARN_64_TO_32_BIT_CONVERSION = YES; - GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; - GCC_WARN_UNDECLARED_SELECTOR = YES; - GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; - GCC_WARN_UNUSED_FUNCTION = YES; - GCC_WARN_UNUSED_VARIABLE = YES; - INFOPLIST_FILE = DisplayMac/Info.plist; - INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/../Frameworks @loader_path/Frameworks"; - MACOSX_DEPLOYMENT_TARGET = 10.12; - MTL_ENABLE_DEBUG_INFO = NO; - PRODUCT_BUNDLE_IDENTIFIER = org.Telegram.DisplayMac; - PRODUCT_NAME = "$(TARGET_NAME)"; - SDKROOT = macosx; - SKIP_INSTALL = YES; - SWIFT_VERSION = 4.0; - VERSIONING_SYSTEM = "apple-generic"; - VERSION_INFO_PREFIX = ""; - }; - name = ReleaseAppStoreLLC; - }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D01159C01F40E96C0039383E /* Build configuration list for PBXNativeTarget "DisplayMac" */ = { - isa = XCConfigurationList; - buildConfigurations = ( - D01159BC1F40E96C0039383E /* DebugHockeyapp */, - D021D4F7219CB1AD0064BEBA /* DebugFork */, - D01159BD1F40E96C0039383E /* DebugAppStore */, - D0ADF923212B3ABC00310BBC /* DebugAppStoreLLC */, - D01159BE1F40E96C0039383E /* ReleaseHockeyapp */, - D0924FD71FE52BE9003F693F /* ReleaseHockeyappInternal */, - D01159BF1F40E96C0039383E /* ReleaseAppStore */, - D0CE6EE4213DB54B00BCD44B /* ReleaseAppStoreLLC */, - ); - defaultConfigurationIsVisible = 0; - defaultConfigurationName = ReleaseHockeyapp; - }; D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/DisplayMac/ASDisplayNode.swift b/DisplayMac/ASDisplayNode.swift deleted file mode 100644 index f6052f61ca..0000000000 --- a/DisplayMac/ASDisplayNode.swift +++ /dev/null @@ -1,111 +0,0 @@ -import Foundation - -open class ASDisplayNode: NSObject { - var layer: CALayer { - preconditionFailure() - } - - var view: UIView { - preconditionFailure() - } - - weak var supernode: ASDisplayNode? - private(set) var subnodes: [ASDisplayNode] = [] - - open var frame: CGRect { - get { - return self.layer.frame - } set(value) { - self.layer.frame = value - } - } - - open var bounds: CGRect { - get { - return self.layer.bounds - } set(value) { - self.layer.bounds = value - } - } - - open var position: CGPoint { - get { - return self.layer.position - } set(value) { - self.layer.position = value - } - } - - var alpha: CGFloat { - get { - return CGFloat(self.layer.opacity) - } set(value) { - self.layer.opacity = Float(value) - } - } - - var backgroundColor: UIColor? { - get { - if let backgroundColor = self.layer.backgroundColor { - return UIColor(cgColor: backgroundColor) - } else { - return nil - } - } set(value) { - self.layer.backgroundColor = value?.cgColor - } - } - - var isLayerBacked: Bool = false - - var clipsToBounds: Bool { - get { - return self.layer.masksToBounds - } set(value) { - self.layer.masksToBounds = value - } - } - - override init() { - super.init() - } - - func setLayerBlock(_ f: @escaping () -> CALayer) { - - } - - func setViewBlock(_ f: @escaping () -> UIView) { - - } - - open func layout() { - } - - open func addSubnode(_ subnode: ASDisplayNode) { - - } - - open func insertSubnode(_ subnode: ASDisplayNode, belowSubnode: ASDisplayNode) { - - } - - open func insertSubnode(_ subnode: ASDisplayNode, aboveSubnode: ASDisplayNode) { - - } - - open func insertSubnode(_ subnode: ASDisplayNode, at: Int) { - - } - - open func removeFromSupernode() { - - } - - func recursivelyEnsureDisplaySynchronously(_ synchronously: Bool) { - - } -} - -func ASPerformMainThreadDeallocation(_ ref: inout AnyObject?) { - -} diff --git a/DisplayMac/CADisplayLink.swift b/DisplayMac/CADisplayLink.swift deleted file mode 100644 index 36a084b4c8..0000000000 --- a/DisplayMac/CADisplayLink.swift +++ /dev/null @@ -1,89 +0,0 @@ -import Foundation -import CoreVideo -import SwiftSignalKitMac - -private final class CADisplayLinkContext { - weak var impl: CADisplayLink? - - init(_ impl: CADisplayLink) { - self.impl = impl - } -} - -private final class CADisplayLinkContexts { - private var nextId: Int32 = 0 - var contexts: [Int32: CADisplayLinkContext] = [:] - - func add(_ impl: CADisplayLink) -> Int32 { - let id = self.nextId - self.nextId += 1 - self.contexts[id] = CADisplayLinkContext(impl) - return id - } - - func remove(_ id: Int32) { - self.contexts.removeValue(forKey: id) - } - - func get(id: Int32) -> CADisplayLink? { - return self.contexts[id]?.impl - } -} - -private let contexts = Atomic(value: CADisplayLinkContexts()) - -public final class CADisplayLink { - private var id: Int32? - private var displayLink: CVDisplayLink? - - public var isPaused: Bool = true { - didSet { - if self.isPaused != oldValue { - - } - } - } - - private let target: Any? - private let action: Selector? - - init(target: Any?, selector: Selector?) { - self.target = target - self.action = selector - - let id = contexts.with { contexts in - return contexts.add(self) - } - self.id = id - CVDisplayLinkCreateWithActiveCGDisplays(&self.displayLink) - if let displayLink = self.displayLink { - CVDisplayLinkSetOutputCallback(displayLink, { _, _, _, _, _, ref in - let id: Int32 = Int32(unsafeBitCast(ref, to: intptr_t.self)) - if let impl = (contexts.with { contexts in - return contexts.get(id: id) - }) { - impl.performAction() - } - return kCVReturnSuccess - }, UnsafeMutableRawPointer(bitPattern: Int(id))) - } - } - - deinit { - if let id = self.id { - contexts.with { contexts in - contexts.remove(id) - } - } - } - - public func invalidate() { - - } - - private func performAction() { - if let target = self.target, let action = self.action { - let _ = (target as? AnyObject)?.perform(action) - } - } -} diff --git a/DisplayMac/DisplayMac.h b/DisplayMac/DisplayMac.h deleted file mode 100644 index 09491004ba..0000000000 --- a/DisplayMac/DisplayMac.h +++ /dev/null @@ -1,19 +0,0 @@ -// -// DisplayMac.h -// DisplayMac -// -// Created by Peter on 8/13/17. -// Copyright © 2017 Telegram. All rights reserved. -// - -#import - -//! Project version number for DisplayMac. -FOUNDATION_EXPORT double DisplayMacVersionNumber; - -//! Project version string for DisplayMac. -FOUNDATION_EXPORT const unsigned char DisplayMacVersionString[]; - -// In this header, you should import all the public headers of your framework using statements like #import - - diff --git a/DisplayMac/Info.plist b/DisplayMac/Info.plist deleted file mode 100644 index d35efc62b4..0000000000 --- a/DisplayMac/Info.plist +++ /dev/null @@ -1,26 +0,0 @@ - - - - - CFBundleDevelopmentRegion - en - CFBundleExecutable - $(EXECUTABLE_NAME) - CFBundleIdentifier - $(PRODUCT_BUNDLE_IDENTIFIER) - CFBundleInfoDictionaryVersion - 6.0 - CFBundleName - $(PRODUCT_NAME) - CFBundlePackageType - FMWK - CFBundleShortVersionString - 1.0 - CFBundleVersion - $(CURRENT_PROJECT_VERSION) - NSHumanReadableCopyright - Copyright © 2017 Telegram. All rights reserved. - NSPrincipalClass - - - diff --git a/DisplayMac/NSValueAdditions.swift b/DisplayMac/NSValueAdditions.swift deleted file mode 100644 index 7f3047d0c8..0000000000 --- a/DisplayMac/NSValueAdditions.swift +++ /dev/null @@ -1,12 +0,0 @@ -import Foundation -import Cocoa - -extension NSValue { - convenience init(cgRect: CGRect) { - self.init(rect: NSRect(origin: cgRect.origin, size: cgRect.size)) - } - - convenience init(cgPoint: CGPoint) { - self.init(point: NSPoint(x: cgPoint.x, y: cgPoint.y)) - } -} diff --git a/DisplayMac/UIGestureRecognizer.swift b/DisplayMac/UIGestureRecognizer.swift deleted file mode 100644 index b209e97153..0000000000 --- a/DisplayMac/UIGestureRecognizer.swift +++ /dev/null @@ -1,47 +0,0 @@ -import Foundation - -public enum UIGestureRecognizerState : Int { - case possible - case began - case changed - case ended - case cancelled - case failed -} -open class UIGestureRecognizer: NSObject { - public init(target: Any?, action: Selector?) { - super.init() - } - - open var state: UIGestureRecognizerState = .possible { - didSet { - - } - } - - weak open var delegate: UIGestureRecognizerDelegate? - - open var isEnabled: Bool = true - - open var view: UIView? { - return nil - } - - open var cancelsTouchesInView: Bool = true - open var delaysTouchesBegan: Bool = false - open var delaysTouchesEnded: Bool = true - - open func location(in view: UIView?) -> CGPoint { - return CGPoint() - } - - open var numberOfTouches: Int { - return 0 - } -} - -@objc public protocol UIGestureRecognizerDelegate : NSObjectProtocol { - @objc optional func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool - @objc optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool - @objc optional func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool -} diff --git a/DisplayMac/UIKit.swift b/DisplayMac/UIKit.swift deleted file mode 100644 index 09f6956cce..0000000000 --- a/DisplayMac/UIKit.swift +++ /dev/null @@ -1,55 +0,0 @@ -import Foundation -import QuartzCore - -public struct UIEdgeInsets: Equatable { - public var top: CGFloat - public var left: CGFloat - public var bottom: CGFloat - public var right: CGFloat - - public init() { - self.top = 0.0 - self.left = 0.0 - self.bottom = 0.0 - self.right = 0.0 - } - - public init(top: CGFloat, left: CGFloat, bottom: CGFloat, right: CGFloat) { - self.top = top - self.left = left - self.bottom = bottom - self.right = right - } -} - -public final class UIColor: NSObject { - let cgColor: CGColor - - init(rgb: Int32) { - preconditionFailure() - } - - init(cgColor: CGColor) { - self.cgColor = cgColor - } -} - -open class CASeeThroughTracingLayer: CALayer { - -} - -open class CASeeThroughTracingView: UIView { - -} - -func makeSpringAnimation(_ keyPath: String) -> CABasicAnimation { - return CABasicAnimation(keyPath: keyPath) -} - -func makeSpringBounceAnimation(_ keyPath: String, _ initialVelocity: CGFloat, _ damping: CGFloat) -> CABasicAnimation { - return CABasicAnimation(keyPath: keyPath) -} - -func springAnimationValueAt(_ animation: CABasicAnimation, _ t: CGFloat) -> CGFloat { - return t -} diff --git a/DisplayMac/UIScrollView.swift b/DisplayMac/UIScrollView.swift deleted file mode 100644 index a1ba22d1a3..0000000000 --- a/DisplayMac/UIScrollView.swift +++ /dev/null @@ -1,28 +0,0 @@ -import Foundation -import QuartzCore - -public protocol UIScrollViewDelegate { -} - -open class UIScrollView: UIView { - public var contentOffset: CGPoint { - get { - return self.bounds.origin - } set(value) { - self.bounds.origin = value - } - } - - public var contentSize: CGSize = CGSize() { - didSet { - - } - } - - public var alwaysBounceVertical: Bool = false - public var alwaysBounceHorizontal: Bool = false - - public func setContentOffset(_ contentOffset: CGPoint, animated: Bool) { - self.contentOffset = contentOffset - } -} diff --git a/DisplayMac/UISlider.swift b/DisplayMac/UISlider.swift deleted file mode 100644 index 622661f29b..0000000000 --- a/DisplayMac/UISlider.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -final class UISlider: UIView { - -} diff --git a/DisplayMac/UITouch.swift b/DisplayMac/UITouch.swift deleted file mode 100644 index 21e0edace9..0000000000 --- a/DisplayMac/UITouch.swift +++ /dev/null @@ -1,5 +0,0 @@ -import Foundation - -public final class UITouch: NSObject { - -} diff --git a/DisplayMac/UIView.swift b/DisplayMac/UIView.swift deleted file mode 100644 index 3a323cd80c..0000000000 --- a/DisplayMac/UIView.swift +++ /dev/null @@ -1,82 +0,0 @@ -import Foundation -import QuartzCore - -open class UIView: NSObject { - public let layer: CALayer - - open var frame: CGRect { - get { - return self.layer.frame - } set(value) { - self.layer.frame = value - } - } - - open var bounds: CGRect { - get { - return self.layer.bounds - } set(value) { - self.layer.bounds = value - } - } - - open var center: CGPoint { - get { - return self.layer.position - } set(value) { - self.layer.position = value - } - } - - open var isHidden: Bool { - get { - return self.layer.isHidden - } set(value) { - self.layer.isHidden = value - } - } - - open class var layerClass: AnyClass { - return CALayer.self - } - - public init(frame: CGRect) { - self.layer = CALayer() - self.layer.frame = frame - - super.init() - } - - convenience override init() { - self.init(frame: CGRect()) - } - - static func animationDurationFactor() -> Double { - return 1.0 - } - - public func bringSubview(toFront: UIView) { - - } - - public func addSubview(_ subview: UIView) { - - } - - public func removeFromSuperview() { - - } - - open func setNeedsLayout() { - } - - open func layoutSubviews() { - } - - open func setNeedsDisplay() { - } - - open func snapshotView(afterScreenUpdates: Bool) -> UIView? { - return nil - } -} From 774d601b0ac2719ca28fabacb2b5e272753a3973 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 23 May 2019 00:21:04 +0200 Subject: [PATCH 226/245] ListView: apply opaqueTransactionState on empty transaction --- Display/ListView.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Display/ListView.swift b/Display/ListView.swift index f8630da8a9..072ee93483 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1316,6 +1316,9 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero { + if let updateOpaqueState = updateOpaqueState { + self.opaqueTransactionState = updateOpaqueState + } completion(self.immediateDisplayedItemRange()) return } @@ -1351,6 +1354,10 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture self.updateScroller(transition: .immediate) + if let updateOpaqueState = updateOpaqueState { + self.opaqueTransactionState = updateOpaqueState + } + completion() return } From 64c069c038c95f5a2ceaf82691c96d83186a59ec Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sat, 25 May 2019 00:13:41 +0200 Subject: [PATCH 227/245] Fix highlight --- Display/ListView.swift | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Display/ListView.swift b/Display/ListView.swift index 072ee93483..63fa283421 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1143,9 +1143,12 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } else if let itemHighlightOverlayBackground = self.itemHighlightOverlayBackground { self.itemHighlightOverlayBackground = nil for (_, headerNode) in self.itemHeaderNodes { - self.view.bringSubview(toFront: headerNode.view) + //self.view.bringSubview(toFront: headerNode.view) + } + //self.view.bringSubview(toFront: itemHighlightOverlayBackground.view) + for itemNode in self.itemNodes { + //self.view.bringSubview(toFront: itemNode.view) } - self.view.bringSubview(toFront: itemHighlightOverlayBackground.view) transition.updateAlpha(node: itemHighlightOverlayBackground, alpha: 0.0, completion: { [weak itemHighlightOverlayBackground] _ in itemHighlightOverlayBackground?.removeFromSupernode() }) From 1d9c41ed3ac5dd128ff5e6eb25184cbedef0419f Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 25 May 2019 15:13:35 +0200 Subject: [PATCH 228/245] Added icon support for UIMenuItems --- Display.xcodeproj/project.pbxproj | 9 ++ Display/Display.h | 1 + Display/TextNode.swift | 43 +++++++-- Display/UIMenuItem+Icons.h | 8 ++ Display/UIMenuItem+Icons.m | 139 ++++++++++++++++++++++++++++++ 5 files changed, 195 insertions(+), 5 deletions(-) create mode 100644 Display/UIMenuItem+Icons.h create mode 100644 Display/UIMenuItem+Icons.m diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 000ccd30c0..b01a500168 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 09167E20229803DC005734A7 /* UIMenuItem+Icons.h in Headers */ = {isa = PBXBuildFile; fileRef = 09167E1E229803DC005734A7 /* UIMenuItem+Icons.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 09167E21229803DC005734A7 /* UIMenuItem+Icons.m in Sources */ = {isa = PBXBuildFile; fileRef = 09167E1F229803DC005734A7 /* UIMenuItem+Icons.m */; }; 09C147D8216CCEF700390252 /* KeyShortcutsController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */; }; 09C147DA216CD7E500390252 /* KeyShortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09C147D9216CD7E500390252 /* KeyShortcut.swift */; }; 09DD88EB21BCA5E0000766BC /* EditableTextNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = 09DD88EA21BCA5E0000766BC /* EditableTextNode.swift */; }; @@ -175,6 +177,8 @@ /* End PBXContainerItemProxy section */ /* Begin PBXFileReference section */ + 09167E1E229803DC005734A7 /* UIMenuItem+Icons.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIMenuItem+Icons.h"; sourceTree = ""; }; + 09167E1F229803DC005734A7 /* UIMenuItem+Icons.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIMenuItem+Icons.m"; sourceTree = ""; }; 09C147D7216CCEF700390252 /* KeyShortcutsController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyShortcutsController.swift; sourceTree = ""; }; 09C147D9216CD7E500390252 /* KeyShortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyShortcut.swift; sourceTree = ""; }; 09DD88EA21BCA5E0000766BC /* EditableTextNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditableTextNode.swift; sourceTree = ""; }; @@ -611,6 +615,8 @@ D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */, D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */, D01F728121F13891006AB634 /* Accessibility.swift */, + 09167E1E229803DC005734A7 /* UIMenuItem+Icons.h */, + 09167E1F229803DC005734A7 /* UIMenuItem+Icons.m */, ); name = Utils; sourceTree = ""; @@ -751,6 +757,7 @@ D05CC2FD1B6955D000E235A3 /* UIKitUtils.h in Headers */, D05174B31EAA833200A1BF36 /* CASeeThroughTracingLayer.h in Headers */, D05CC3081B69575900E235A3 /* NSBag.h in Headers */, + 09167E20229803DC005734A7 /* UIMenuItem+Icons.h in Headers */, D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */, D05CC2671B69316F00E235A3 /* Display.h in Headers */, D05CC2F91B6955D000E235A3 /* UIViewController+Navigation.h in Headers */, @@ -820,6 +827,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, ); mainGroup = D05CC2591B69316F00E235A3; @@ -877,6 +885,7 @@ D06D37A220779C82009219B6 /* VolumeControlStatusBar.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, + 09167E21229803DC005734A7 /* UIMenuItem+Icons.m in Sources */, D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, diff --git a/Display/Display.h b/Display/Display.h index 49c0e1fb5b..59ea13a08f 100644 --- a/Display/Display.h +++ b/Display/Display.h @@ -31,3 +31,4 @@ FOUNDATION_EXPORT const unsigned char DisplayVersionString[]; #import #import #import +#import diff --git a/Display/TextNode.swift b/Display/TextNode.swift index b8e2e2e49a..4c10f71938 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -9,12 +9,22 @@ private final class TextNodeLine { let frame: CGRect let range: NSRange let isRTL: Bool + let strikethroughs: [TextNodeStrikethrough] - init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool) { + init(line: CTLine, frame: CGRect, range: NSRange, isRTL: Bool, strikethroughs: [TextNodeStrikethrough]) { self.line = line self.frame = frame self.range = range self.isRTL = isRTL + self.strikethroughs = strikethroughs + } +} + +private final class TextNodeStrikethrough { + let frame: CGRect + + init(frame: CGRect) { + self.frame = frame } } @@ -615,6 +625,8 @@ public class TextNode: ASDisplayNode { var truncated = false var first = true while true { + var strikethroughs: [TextNodeStrikethrough] = [] + var lineConstrainedWidth = constrainedSize.width var lineOriginY = floorToScreenPixels(layoutSize.height + fontAscent) if !first { @@ -648,13 +660,11 @@ public class TextNode: ASDisplayNode { } let lineRange = CFRange(location: lastLineCharacterIndex, length: stringLength - lastLineCharacterIndex) - if lineRange.length == 0 { break } let coreTextLine: CTLine - let originalLine = CTTypesetterCreateLineWithOffset(typesetter, lineRange, 0.0) if CTLineGetTypographicBounds(originalLine, nil, nil, nil) - CTLineGetTrailingWhitespaceWidth(originalLine) < Double(constrainedSize.width) { @@ -685,7 +695,15 @@ public class TextNode: ASDisplayNode { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL)) + attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) + } + } + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs)) break } else { @@ -714,7 +732,15 @@ public class TextNode: ASDisplayNode { } } - lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL)) + attributedString.enumerateAttributes(in: NSMakeRange(lineRange.location, lineRange.length), options: []) { attributes, range, _ in + if let _ = attributes[NSAttributedStringKey.strikethroughStyle] { + let lowerX = floor(CTLineGetOffsetForStringIndex(coreTextLine, range.location, nil)) + let upperX = ceil(CTLineGetOffsetForStringIndex(coreTextLine, range.location + range.length, nil)) + let x = lowerX < upperX ? lowerX : upperX + strikethroughs.append(TextNodeStrikethrough(frame: CGRect(x: x, y: 0.0, width: abs(upperX - lowerX), height: fontLineHeight))) + } + } + lines.append(TextNodeLine(line: coreTextLine, frame: lineFrame, range: NSMakeRange(lineRange.location, lineRange.length), isRTL: isRTL, strikethroughs: strikethroughs)) } else { if !lines.isEmpty { layoutSize.height += fontLineSpacing @@ -796,6 +822,13 @@ public class TextNode: ASDisplayNode { } context.textPosition = CGPoint(x: lineFrame.minX, y: lineFrame.minY) CTLineDraw(line.line, context) + + if !line.strikethroughs.isEmpty { + for strikethrough in line.strikethroughs { + let frame = strikethrough.frame.offsetBy(dx: lineFrame.minX, dy: lineFrame.minY) + context.fill(CGRect(x: frame.minX, y: frame.minY - 5.0, width: frame.width, height: 1.0)) + } + } } //CGContextRestoreGState(context) diff --git a/Display/UIMenuItem+Icons.h b/Display/UIMenuItem+Icons.h new file mode 100644 index 0000000000..9db66d10b1 --- /dev/null +++ b/Display/UIMenuItem+Icons.h @@ -0,0 +1,8 @@ +#import + +@interface UIMenuItem (Icons) + +- (instancetype)initWithTitle:(NSString *)title icon:(UIImage *)icon action:(SEL)action; + +@end + diff --git a/Display/UIMenuItem+Icons.m b/Display/UIMenuItem+Icons.m new file mode 100644 index 0000000000..1bd837c10c --- /dev/null +++ b/Display/UIMenuItem+Icons.m @@ -0,0 +1,139 @@ +#import "UIMenuItem+Icons.h" + +#import "NSBag.h" +#import "RuntimeUtils.h" + +static const void *imageKey = &imageKey; +static const void *imageViewKey = &imageViewKey; +static NSString *const imageItemIdetifier = @"\uFEFF\u200B"; + +@interface UIMenuController (Icons) + +@end + +@implementation UIMenuController (Icons) + +- (UIMenuItem *)findImageItemByTitle:(NSString *)title { + if ([title hasSuffix:imageItemIdetifier]) { + for (UIMenuItem *item in self.menuItems) { + if ([item.title isEqualToString:title]) { + return item; + } + } + } + return nil; +} + +@end + + +@implementation UIMenuItem (Icons) + +- (instancetype)initWithTitle:(NSString *)title icon:(UIImage *)icon action:(SEL)action { + NSString *combinedTitle = title; + if (icon != nil) { + combinedTitle = [NSString stringWithFormat:@"%@%@", title, imageItemIdetifier]; + } + self = [self initWithTitle:combinedTitle action:action]; + if (self != nil) { + if (icon != nil) { + [self _tg_setImage:icon]; + } + } + return self; +} + +- (UIImage *)_tg_image { + return (UIImage *)[self associatedObjectForKey:imageKey]; +} + +- (void)_tg_setImage:(UIImage *)image { + [self setAssociatedObject:image forKey:imageKey associationPolicy:NSObjectAssociationPolicyRetain]; +} + +@end + +@interface NSString (Items) + +@end + +@implementation NSString (Items) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[NSString class] currentSelector:@selector(sizeWithAttributes:) newSelector:@selector(_78724db9_sizeWithAttributes:)]; + }); +} + +- (CGSize)_78724db9_sizeWithAttributes:(NSDictionary *)attrs { + UIMenuItem *item = [[UIMenuController sharedMenuController] findImageItemByTitle:self]; + UIImage *image = item._tg_image; + if (image != nil) { + return image.size; + } else { + return [self _78724db9_sizeWithAttributes:attrs]; + } +} + +@end + + +@interface UILabel (Icons) + +@end + +@implementation UILabel (Icons) + ++ (void)load +{ + static dispatch_once_t onceToken; + dispatch_once(&onceToken, ^ + { + [RuntimeUtils swizzleInstanceMethodOfClass:[UILabel class] currentSelector:@selector(drawTextInRect:) newSelector:@selector(_78724db9_drawTextInRect:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UILabel class] currentSelector:@selector(layoutSubviews) newSelector:@selector(_78724db9_layoutSubviews)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UILabel class] currentSelector:@selector(setFrame:) newSelector:@selector(_78724db9_setFrame:)]; + }); +} + +- (void)_78724db9_drawTextInRect:(CGRect)rect { + UIMenuItem *item = [[UIMenuController sharedMenuController] findImageItemByTitle:self.text]; + UIImage *image = item._tg_image; + if (image == nil) { + [self _78724db9_drawTextInRect:rect]; + } +} + +- (void)_78724db9_layoutSubviews { + UIMenuItem *item = [[UIMenuController sharedMenuController] findImageItemByTitle:self.text]; + UIImage *image = item._tg_image; + if (image == nil) { + [self _78724db9_layoutSubviews]; + return; + } + + CGPoint point = CGPointMake(ceil((self.bounds.size.width - image.size.width) / 2.0), ceil((self.bounds.size.height - image.size.height) / 2.0)); + UIImageView *imageView = [self associatedObjectForKey:imageViewKey]; + if (imageView == nil) { + imageView = [[UIImageView alloc] init]; + [self addSubview:imageView]; + [self setAssociatedObject:imageView forKey:imageViewKey associationPolicy:NSObjectAssociationPolicyRetain]; + } + + imageView.image = image; + imageView.frame = CGRectMake(point.x, point.y, image.size.width, image.size.height); +} + +- (void)_78724db9_setFrame:(CGRect)frame +{ + bool hasImage = [[UIMenuController sharedMenuController] findImageItemByTitle:self.text]._tg_image != nil; + CGRect rect = frame; + if (hasImage && self.superview != nil) { + rect = self.superview.bounds; + } + [self _78724db9_setFrame:rect]; +} + +@end From b2f6b05ffd6a67bfd770e400247375ca83366b17 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Mon, 27 May 2019 02:04:18 +0200 Subject: [PATCH 229/245] CollectionIndexNode: check for enabled user interaction before hit testing positively --- Display/CollectionIndexNode.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/CollectionIndexNode.swift b/Display/CollectionIndexNode.swift index feb54052c4..0c6908d69d 100644 --- a/Display/CollectionIndexNode.swift +++ b/Display/CollectionIndexNode.swift @@ -113,7 +113,7 @@ public final class CollectionIndexNode: ASDisplayNode { } override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { - if self.bounds.insetBy(dx: -5.0, dy: 0.0).contains(point) { + if self.isUserInteractionEnabled, self.bounds.insetBy(dx: -5.0, dy: 0.0).contains(point) { return self.view } else { return nil From 1b48bb8f35450c718cc6cdc9f797b925505055cb Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 28 May 2019 15:19:01 +0200 Subject: [PATCH 230/245] NavigationBar: even out title insets for title view layouting --- Display/NavigationBar.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 98f4f559d2..6503d0253e 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -962,7 +962,7 @@ open class NavigationBar: ASDisplayNode { titleView.frame = titleFrame if let titleView = titleView as? NavigationBarTitleView { - let titleWidth = size.width - leftTitleInset - rightTitleInset + let titleWidth = size.width - (leftTitleInset > 0.0 ? leftTitleInset : rightTitleInset) - (rightTitleInset > 0.0 ? rightTitleInset : leftTitleInset) titleView.updateLayout(size: titleFrame.size, clearBounds: CGRect(origin: CGPoint(x: leftTitleInset - titleFrame.minX, y: 0.0), size: CGSize(width: titleWidth, height: titleFrame.height)), transition: transition) } From ffb131ac55bed21c37052a09c27150d5d380321e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 28 May 2019 20:10:33 +0200 Subject: [PATCH 231/245] AlertController: added disabled theme color DeviceMetrics: match device when double height status bar is active --- Display/AlertController.swift | 7 ++++++- Display/DeviceMetrics.swift | 5 ++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 08d1094a38..6e2ae4bb07 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -9,8 +9,9 @@ public final class AlertControllerTheme: Equatable { public let secondaryColor: UIColor public let accentColor: UIColor public let destructiveColor: UIColor + public let disabledColor: UIColor - public init(backgroundColor: UIColor, separatorColor: UIColor, highlightedItemColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, accentColor: UIColor, destructiveColor: UIColor) { + public init(backgroundColor: UIColor, separatorColor: UIColor, highlightedItemColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, accentColor: UIColor, destructiveColor: UIColor, disabledColor: UIColor) { self.backgroundColor = backgroundColor self.separatorColor = separatorColor self.highlightedItemColor = highlightedItemColor @@ -18,6 +19,7 @@ public final class AlertControllerTheme: Equatable { self.secondaryColor = secondaryColor self.accentColor = accentColor self.destructiveColor = destructiveColor + self.disabledColor = disabledColor } public static func ==(lhs: AlertControllerTheme, rhs: AlertControllerTheme) -> Bool { @@ -42,6 +44,9 @@ public final class AlertControllerTheme: Equatable { if lhs.destructiveColor != rhs.destructiveColor { return false } + if lhs.disabledColor != rhs.disabledColor { + return false + } return true } } diff --git a/Display/DeviceMetrics.swift b/Display/DeviceMetrics.swift index e74cd1b80d..7e7f03e235 100644 --- a/Display/DeviceMetrics.swift +++ b/Display/DeviceMetrics.swift @@ -14,17 +14,20 @@ public enum DeviceMetrics: CaseIterable { case iPadPro3rdGen public static func forScreenSize(_ size: CGSize, hintHasOnScreenNavigation: Bool = false) -> DeviceMetrics? { + let additionalSize = CGSize(width: size.width, height: size.height + 20.0) for device in DeviceMetrics.allCases { let width = device.screenSize.width let height = device.screenSize.height - if (size.width.isEqual(to: width) && size.height.isEqual(to: height)) || size.height.isEqual(to: width) && size.width.isEqual(to: height) { + if ((size.width.isEqual(to: width) && size.height.isEqual(to: height)) || size.height.isEqual(to: width) && size.width.isEqual(to: height)) || ((additionalSize.width.isEqual(to: width) && additionalSize.height.isEqual(to: height)) || additionalSize.height.isEqual(to: width) && additionalSize.width.isEqual(to: height)) { if hintHasOnScreenNavigation && device.onScreenNavigationHeight(inLandscape: false) == nil { continue } return device } } + + return nil } From a9e374131912f68bb599f6c87b9da26a3687513e Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 6 Jun 2019 00:12:01 +0100 Subject: [PATCH 232/245] BUCK configuration --- BUCK | 53 +++++++++++++++++++ Display.xcodeproj/project.pbxproj | 16 ++---- Display/ASTransformLayerNode.swift | 1 + Display/AccessibilityAreaNode.swift | 1 + Display/ActionSheetButtonItem.swift | 1 + Display/ActionSheetCheckboxItem.swift | 1 + Display/ActionSheetController.swift | 1 + Display/ActionSheetItemGroup.swift | 1 + Display/ActionSheetSwitchItem.swift | 1 + Display/ActionSheetTextItem.swift | 1 + Display/AlertContentNode.swift | 1 + Display/AlertController.swift | 1 + Display/AlertControllerNode.swift | 1 + Display/CAAnimationUtils.swift | 8 +-- Display/CATracingLayer.h | 6 --- Display/CATracingLayer.m | 37 ------------- Display/CollectionIndexNode.swift | 1 + Display/ContainedViewLayoutTransition.swift | 8 +-- Display/ContainerViewLayout.swift | 1 + Display/ContextMenuAction.swift | 1 + Display/ContextMenuActionNode.swift | 1 + Display/ContextMenuContainerNode.swift | 1 + Display/ContextMenuController.swift | 1 + Display/DisplayLinkDispatcher.swift | 1 + Display/EditableTextNode.swift | 1 + .../GlobalOverlayPresentationContext.swift | 1 + Display/GridItem.swift | 2 + Display/GridItemNode.swift | 1 + Display/GridNode.swift | 1 + Display/GridNodeScroller.swift | 1 + Display/ImmediateTextNode.swift | 1 + Display/KeyboardManager.swift | 5 ++ Display/LayoutSizes.swift | 1 + Display/LegacyPresentedController.swift | 4 ++ Display/LegacyPresentedControllerNode.swift | 1 + Display/LinkHighlightingNode.swift | 1 + Display/ListView.swift | 4 ++ Display/ListViewAccessoryItemNode.swift | 6 +-- Display/ListViewAnimation.swift | 5 ++ Display/ListViewFloatingHeaderNode.swift | 1 + Display/ListViewIntermediateState.swift | 5 +- Display/ListViewItem.swift | 5 +- Display/ListViewItemHeader.swift | 5 +- Display/ListViewItemNode.swift | 7 +-- .../ListViewOverscrollBackgroundNode.swift | 4 +- Display/ListViewReorderingItemNode.swift | 1 + Display/ListViewScroller.swift | 3 -- Display/ListViewTempItemNode.swift | 3 +- Display/ListViewTransactionQueue.swift | 5 +- Display/NativeWindowHostView.swift | 1 + Display/NavigationBarBadge.swift | 1 + Display/NavigationBarContentNode.swift | 1 + .../NavigationBarTransitionContainer.swift | 1 + Display/NavigationBarTransitionState.swift | 1 + Display/NavigationController.swift | 4 ++ Display/PageControlNode.swift | 1 + Display/PeekController.swift | 1 + Display/PeekControllerContent.swift | 1 + Display/PeekControllerGestureRecognizer.swift | 1 + Display/PeekControllerMenuItemNode.swift | 1 + Display/PeekControllerMenuNode.swift | 1 + Display/PeekControllerNode.swift | 1 + Display/PresentationContext.swift | 1 + Display/Spring.swift | 1 + Display/StatusBar.swift | 5 ++ Display/StatusBarManager.swift | 1 + Display/SwitchNode.swift | 1 + Display/TabBarContollerNode.swift | 1 + Display/TabBarNode.swift | 4 ++ ...pLongTapOrDoubleTapGestureRecognizer.swift | 1 + Display/TextAlertController.swift | 1 + Display/TextFieldNode.swift | 1 + Display/TextNode.swift | 1 + Display/Theme.swift | 9 ---- Display/Toolbar.swift | 1 + Display/ToolbarNode.swift | 1 + Display/TooltipController.swift | 1 + Display/UITracingLayerView.swift | 36 +++++++++++++ Display/ViewController.swift | 4 ++ Display/ViewControllerPreviewing.swift | 1 + Display/ViewControllerTracingNode.swift | 1 + Display/WindowContent.swift | 5 ++ Display/WindowPanRecognizer.swift | 1 + 83 files changed, 212 insertions(+), 102 deletions(-) create mode 100644 BUCK delete mode 100644 Display/Theme.swift create mode 100644 Display/UITracingLayerView.swift diff --git a/BUCK b/BUCK new file mode 100644 index 0000000000..bcaf40a09a --- /dev/null +++ b/BUCK @@ -0,0 +1,53 @@ +load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'configs_with_config') +load('//tools:buck_defs.bzl', 'combined_config', 'SHARED_CONFIGS', 'LIB_SPECIFIC_CONFIG') + +apple_library( + name = 'DisplayPrivate', + srcs = glob([ + 'Display/*.m', + ]), + headers = glob([ + 'Display/*.h', + ]), + header_namespace = 'DisplayPrivate', + exported_headers = glob([ + 'Display/*.h', + ], exclude = ['Display/Display.h']), + modular = True, + configs = configs_with_config(combined_config([SHARED_CONFIGS, LIB_SPECIFIC_CONFIG])), + compiler_flags = ['-w'], + preprocessor_flags = ['-fobjc-arc'], + visibility = ['//submodules/Display:Display'], + deps = [ + '//submodules/AsyncDisplayKit:AsyncDisplayKit', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + ], +) + +apple_library( + name = 'Display', + srcs = glob([ + 'Display/*.swift', + ]), + configs = configs_with_config(combined_config([SHARED_CONFIGS, LIB_SPECIFIC_CONFIG])), + swift_compiler_flags = [ + '-suppress-warnings', + '-application-extension', + ], + visibility = ['PUBLIC'], + deps = [ + ':DisplayPrivate', + '//submodules/AsyncDisplayKit:AsyncDisplayKit', + '//submodules/SSignalKit:SwiftSignalKit', + ], + frameworks = [ + '$SDKROOT/System/Library/Frameworks/Foundation.framework', + '$SDKROOT/System/Library/Frameworks/UIKit.framework', + '$SDKROOT/System/Library/Frameworks/QuartzCore.framework', + '$SDKROOT/System/Library/Frameworks/CoreText.framework', + '$SDKROOT/System/Library/Frameworks/CoreGraphics.framework', + ], +) diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index b01a500168..c9893ec242 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA4EA202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift */; }; D03AA5162030C5F80056C405 /* ListViewTempItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03AA5152030C5F80056C405 /* ListViewTempItemNode.swift */; }; D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */; }; - D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* Theme.swift */; }; D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */; }; D03E7DF81C96C5F200C07816 /* NSWeakReference.h in Headers */ = {isa = PBXBuildFile; fileRef = D03E7DF61C96C5F200C07816 /* NSWeakReference.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -107,6 +106,7 @@ D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D077B8E81F4637040046D27A /* NavigationBarBadge.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; D087BFB51F75181D003FD209 /* ChildWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */; }; + D08B61BE22A752FD000A46A8 /* UITracingLayerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08B61BD22A752FD000A46A8 /* UITracingLayerView.swift */; }; D08CAA7B1ED73C990000FDA8 /* ViewControllerTracingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */; }; D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90391D24159200533158 /* ActionSheetItem.swift */; }; D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; @@ -223,7 +223,6 @@ D03AA4EA202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewReorderingGestureRecognizer.swift; sourceTree = ""; }; D03AA5152030C5F80056C405 /* ListViewTempItemNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewTempItemNode.swift; sourceTree = ""; }; D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHost.swift; sourceTree = ""; }; - D03BCCEA1C72AE590097A291 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; D03E7DE51C96B96E00C07816 /* NavigationBarTransitionContainer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionContainer.swift; sourceTree = ""; }; D03E7DF61C96C5F200C07816 /* NSWeakReference.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NSWeakReference.h; sourceTree = ""; }; @@ -281,6 +280,7 @@ D077B8E81F4637040046D27A /* NavigationBarBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarBadge.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChildWindowHostView.swift; sourceTree = ""; }; + D08B61BD22A752FD000A46A8 /* UITracingLayerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITracingLayerView.swift; sourceTree = ""; }; D08CAA7A1ED73C990000FDA8 /* ViewControllerTracingNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewControllerTracingNode.swift; sourceTree = ""; }; D08E90391D24159200533158 /* ActionSheetItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItem.swift; sourceTree = ""; }; D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; @@ -489,14 +489,6 @@ name = Peek; sourceTree = ""; }; - D03BCCE91C72AE4B0097A291 /* Theme */ = { - isa = PBXGroup; - children = ( - D03BCCEA1C72AE590097A291 /* Theme.swift */, - ); - name = Theme; - sourceTree = ""; - }; D05BE4AC1D217F33002BD72C /* Utils */ = { isa = PBXGroup; children = ( @@ -510,6 +502,7 @@ D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */, 09E12475214D0978009FC9C3 /* DeviceMetrics.swift */, D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */, + D08B61BD22A752FD000A46A8 /* UITracingLayerView.swift */, ); name = Utils; sourceTree = ""; @@ -538,7 +531,6 @@ children = ( D08122991D19A9E0005F7395 /* User Interface */, D01E2BE51D904A530066BF65 /* Collection Nodes */, - D03BCCE91C72AE4B0097A291 /* Theme */, D05CC3001B6955D500E235A3 /* Utils */, D07921AA1B6FC911005C23D9 /* Status Bar */, D0FF9B2E1E7196E2000C66DB /* Keyboard */, @@ -916,7 +908,6 @@ D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */, D03E7E011C974AB300C07816 /* DisplayLinkDispatcher.swift in Sources */, D05CC3191B695A9600E235A3 /* NavigationBackButtonNode.swift in Sources */, - D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */, D0C2DFC81CC4431D0044FF83 /* Spring.swift in Sources */, D05CC3071B69575900E235A3 /* NSBag.m in Sources */, D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */, @@ -930,6 +921,7 @@ D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */, D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, + D08B61BE22A752FD000A46A8 /* UITracingLayerView.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, 09C147D8216CCEF700390252 /* KeyShortcutsController.swift in Sources */, D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */, diff --git a/Display/ASTransformLayerNode.swift b/Display/ASTransformLayerNode.swift index 6e8fa21298..e995d3915c 100644 --- a/Display/ASTransformLayerNode.swift +++ b/Display/ASTransformLayerNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit class ASTransformLayer: CATransformLayer { diff --git a/Display/AccessibilityAreaNode.swift b/Display/AccessibilityAreaNode.swift index 064dd856f3..6f351dd33e 100644 --- a/Display/AccessibilityAreaNode.swift +++ b/Display/AccessibilityAreaNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class AccessibilityAreaNode: ASDisplayNode { diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index bdbe6a6aed..ff08e0c293 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum ActionSheetButtonColor { diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift index 8c4b8916ff..0b3af0046b 100644 --- a/Display/ActionSheetCheckboxItem.swift +++ b/Display/ActionSheetCheckboxItem.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum ActionSheetCheckboxStyle { diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index 6977020358..46df40e0ca 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit open class ActionSheetController: ViewController, PresentableController { private var actionSheetNode: ActionSheetControllerNode { diff --git a/Display/ActionSheetItemGroup.swift b/Display/ActionSheetItemGroup.swift index 69acc5c379..4db0b3dcd7 100644 --- a/Display/ActionSheetItemGroup.swift +++ b/Display/ActionSheetItemGroup.swift @@ -1,3 +1,4 @@ +import UIKit public final class ActionSheetItemGroup { let items: [ActionSheetItem] diff --git a/Display/ActionSheetSwitchItem.swift b/Display/ActionSheetSwitchItem.swift index dc74666402..dc29f57538 100644 --- a/Display/ActionSheetSwitchItem.swift +++ b/Display/ActionSheetSwitchItem.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public class ActionSheetSwitchItem: ActionSheetItem { diff --git a/Display/ActionSheetTextItem.swift b/Display/ActionSheetTextItem.swift index 80f8d53678..3577b46f9b 100644 --- a/Display/ActionSheetTextItem.swift +++ b/Display/ActionSheetTextItem.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public class ActionSheetTextItem: ActionSheetItem { diff --git a/Display/AlertContentNode.swift b/Display/AlertContentNode.swift index 72a52412c2..7659a9ab30 100644 --- a/Display/AlertContentNode.swift +++ b/Display/AlertContentNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit open class AlertContentNode: ASDisplayNode { diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 6e2ae4bb07..c221ac7a51 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class AlertControllerTheme: Equatable { diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 72bf2308c1..70854f3835 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final class AlertControllerNode: ASDisplayNode { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 7fb263ffe2..44cfa25f4e 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -1,7 +1,7 @@ -#if os(macOS) - import Cocoa -#else - import UIKit +import UIKit + +#if BUCK +import DisplayPrivate #endif @objc private class CALayerAnimationDelegate: NSObject, CAAnimationDelegate { diff --git a/Display/CATracingLayer.h b/Display/CATracingLayer.h index 47d364fb0d..22037b19a4 100644 --- a/Display/CATracingLayer.h +++ b/Display/CATracingLayer.h @@ -15,12 +15,6 @@ @end -@interface UITracingLayerView : UIView - -- (void)scheduleWithLayout:(void (^_Nonnull)())block; - -@end - @interface CALayer (Tracing) - (CATracingLayerInfo * _Nullable)traceableInfo; diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 78f4fd1827..dd463b5b56 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -362,40 +362,3 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull } @end - -@interface UITracingLayerView () { - void (^_scheduledWithLayout)(); -} - -@end - -@implementation UITracingLayerView - -- (void)setFrame:(CGRect)frame { - [super setFrame:frame]; -} - -- (void)setAutoresizingMask:(UIViewAutoresizing)autoresizingMask { - [super setAutoresizingMask:0]; -} - -+ (Class)layerClass { - return [CATracingLayer class]; -} - -- (void)scheduleWithLayout:(void (^_Nonnull)())block { - _scheduledWithLayout = [block copy]; - [self setNeedsLayout]; -} - -- (void)layoutSubviews { - [super layoutSubviews]; - - if (_scheduledWithLayout) { - void (^block)() = [_scheduledWithLayout copy]; - _scheduledWithLayout = nil; - block(); - } -} - -@end diff --git a/Display/CollectionIndexNode.swift b/Display/CollectionIndexNode.swift index 0c6908d69d..60fd7bbe36 100644 --- a/Display/CollectionIndexNode.swift +++ b/Display/CollectionIndexNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit private let titleFont = Font.bold(11.0) diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index e1e6648b1f..d7f3581ad2 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -1,10 +1,6 @@ import Foundation - -#if os(macOS) - import QuartzCore -#else - import AsyncDisplayKit -#endif +import UIKit +import AsyncDisplayKit public enum ContainedViewLayoutTransitionCurve { case easeInOut diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index abb7a66cbd..a516e86638 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -1,3 +1,4 @@ +import UIKit public struct ContainerViewLayoutInsetOptions: OptionSet { public let rawValue: Int diff --git a/Display/ContextMenuAction.swift b/Display/ContextMenuAction.swift index 235dda12ab..3646cc5c94 100644 --- a/Display/ContextMenuAction.swift +++ b/Display/ContextMenuAction.swift @@ -1,3 +1,4 @@ +import UIKit public enum ContextMenuActionContent { case text(title: String, accessibilityLabel: String) diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift index 7771bd24dd..135e5c9c06 100644 --- a/Display/ContextMenuActionNode.swift +++ b/Display/ContextMenuActionNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final private class ContextMenuActionButton: HighlightTrackingButton { diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift index 52951a9317..b8b5a282ca 100644 --- a/Display/ContextMenuContainerNode.swift +++ b/Display/ContextMenuContainerNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit private struct CachedMaskParams: Equatable { diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 45cd12438c..24382972de 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class ContextMenuControllerPresentationArguments { diff --git a/Display/DisplayLinkDispatcher.swift b/Display/DisplayLinkDispatcher.swift index dc23141aea..2519977ad3 100644 --- a/Display/DisplayLinkDispatcher.swift +++ b/Display/DisplayLinkDispatcher.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit public class DisplayLinkDispatcher: NSObject { private var displayLink: CADisplayLink! diff --git a/Display/EditableTextNode.swift b/Display/EditableTextNode.swift index 954238778c..aa1f1add3c 100644 --- a/Display/EditableTextNode.swift +++ b/Display/EditableTextNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public class EditableTextNode: ASEditableTextNode { diff --git a/Display/GlobalOverlayPresentationContext.swift b/Display/GlobalOverlayPresentationContext.swift index 30f36ee169..7da0302467 100644 --- a/Display/GlobalOverlayPresentationContext.swift +++ b/Display/GlobalOverlayPresentationContext.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit import SwiftSignalKit diff --git a/Display/GridItem.swift b/Display/GridItem.swift index d0a54461e8..bace93a89e 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -1,4 +1,6 @@ import Foundation +import UIKit +import AsyncDisplayKit public protocol GridSection { var height: CGFloat { get } diff --git a/Display/GridItemNode.swift b/Display/GridItemNode.swift index 613a6b168c..cfcf3d1384 100644 --- a/Display/GridItemNode.swift +++ b/Display/GridItemNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit open class GridItemNode: ASDisplayNode { diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 3b9437f88f..72727165aa 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum GridNodeVisibleContentOffset { diff --git a/Display/GridNodeScroller.swift b/Display/GridNodeScroller.swift index 9adaac922f..cbd6801478 100644 --- a/Display/GridNodeScroller.swift +++ b/Display/GridNodeScroller.swift @@ -1,4 +1,5 @@ import UIKit +import AsyncDisplayKit private class GridNodeScrollerLayer: CALayer { override func setNeedsDisplay() { diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index 7eb149227b..add3df7e25 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit public struct ImmediateTextNodeLayoutInfo { public let size: CGSize diff --git a/Display/KeyboardManager.swift b/Display/KeyboardManager.swift index d77b09d346..086f578ea0 100644 --- a/Display/KeyboardManager.swift +++ b/Display/KeyboardManager.swift @@ -1,6 +1,11 @@ import Foundation +import UIKit import AsyncDisplayKit +#if BUCK +import DisplayPrivate +#endif + struct KeyboardSurface { let host: UIView } diff --git a/Display/LayoutSizes.swift b/Display/LayoutSizes.swift index d03f8bb1aa..74d89e96f8 100644 --- a/Display/LayoutSizes.swift +++ b/Display/LayoutSizes.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit public func horizontalContainerFillingSizeForLayout(layout: ContainerViewLayout, sideInset: CGFloat) -> CGFloat { if case .regular = layout.metrics.widthClass { diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index 43e2775fd6..496b976d23 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -2,6 +2,10 @@ import Foundation import UIKit import AsyncDisplayKit +#if BUCK +import DisplayPrivate +#endif + public enum LegacyPresentedControllerPresentation { case custom case modal diff --git a/Display/LegacyPresentedControllerNode.swift b/Display/LegacyPresentedControllerNode.swift index 129f33670c..4aa8fc293d 100644 --- a/Display/LegacyPresentedControllerNode.swift +++ b/Display/LegacyPresentedControllerNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final class LegacyPresentedControllerNode: ASDisplayNode { diff --git a/Display/LinkHighlightingNode.swift b/Display/LinkHighlightingNode.swift index bed1008336..5600a3cbd2 100644 --- a/Display/LinkHighlightingNode.swift +++ b/Display/LinkHighlightingNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit private enum CornerType { diff --git a/Display/ListView.swift b/Display/ListView.swift index 63fa283421..ffc31aa7cd 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -2,6 +2,10 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +#if BUCK +import DisplayPrivate +#endif + private let useBackgroundDeallocation = false private let infiniteScrollSize: CGFloat = 10000.0 diff --git a/Display/ListViewAccessoryItemNode.swift b/Display/ListViewAccessoryItemNode.swift index 1ac1354c09..c36795e903 100644 --- a/Display/ListViewAccessoryItemNode.swift +++ b/Display/ListViewAccessoryItemNode.swift @@ -1,8 +1,6 @@ import Foundation -#if os(macOS) -#else - import AsyncDisplayKit -#endif +import UIKit +import AsyncDisplayKit open class ListViewAccessoryItemNode: ASDisplayNode { var transitionOffset: CGPoint = CGPoint() { diff --git a/Display/ListViewAnimation.swift b/Display/ListViewAnimation.swift index db07f932cf..d546703e23 100644 --- a/Display/ListViewAnimation.swift +++ b/Display/ListViewAnimation.swift @@ -1,4 +1,9 @@ import Foundation +import UIKit + +#if BUCK +import DisplayPrivate +#endif public protocol Interpolatable { static func interpolator() -> (Interpolatable, Interpolatable, CGFloat) -> (Interpolatable) diff --git a/Display/ListViewFloatingHeaderNode.swift b/Display/ListViewFloatingHeaderNode.swift index ad2e7bdb5c..cb77020b30 100644 --- a/Display/ListViewFloatingHeaderNode.swift +++ b/Display/ListViewFloatingHeaderNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit open class ListViewFloatingHeaderNode: ASDisplayNode { diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift index 5380f80bcc..30733d3ebf 100644 --- a/Display/ListViewIntermediateState.swift +++ b/Display/ListViewIntermediateState.swift @@ -1,9 +1,6 @@ import Foundation -#if os(macOS) -import SwiftSignalKitMac -#else +import UIKit import SwiftSignalKit -#endif public enum ListViewCenterScrollPositionOverflow { case top diff --git a/Display/ListViewItem.swift b/Display/ListViewItem.swift index c605f4a813..9ca2c287c6 100644 --- a/Display/ListViewItem.swift +++ b/Display/ListViewItem.swift @@ -1,9 +1,6 @@ import Foundation -#if os(macOS) -import SwiftSignalKitMac -#else +import UIKit import SwiftSignalKit -#endif public enum ListViewItemUpdateAnimation { case None diff --git a/Display/ListViewItemHeader.swift b/Display/ListViewItemHeader.swift index db5a74e35f..17bd083862 100644 --- a/Display/ListViewItemHeader.swift +++ b/Display/ListViewItemHeader.swift @@ -1,6 +1,9 @@ import Foundation -#if !os(macOS) +import UIKit import AsyncDisplayKit + +#if BUCK +import DisplayPrivate #endif public enum ListViewItemHeaderStickDirection { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 9793f6f3a1..64171bc291 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -1,9 +1,10 @@ import Foundation -#if os(macOS) -import SwiftSignalKitMac -#else +import UIKit import AsyncDisplayKit import SwiftSignalKit + +#if BUCK +import DisplayPrivate #endif var testSpringFrictionLimits: (CGFloat, CGFloat) = (3.0, 60.0) diff --git a/Display/ListViewOverscrollBackgroundNode.swift b/Display/ListViewOverscrollBackgroundNode.swift index e44586eb3d..8de76ef4a6 100644 --- a/Display/ListViewOverscrollBackgroundNode.swift +++ b/Display/ListViewOverscrollBackgroundNode.swift @@ -1,8 +1,6 @@ import Foundation -#if os(macOS) -#else +import UIKit import AsyncDisplayKit -#endif final class ListViewOverscrollBackgroundNode: ASDisplayNode { private let backgroundNode: ASDisplayNode diff --git a/Display/ListViewReorderingItemNode.swift b/Display/ListViewReorderingItemNode.swift index 49d3dd97e5..0fc0dc6b3f 100644 --- a/Display/ListViewReorderingItemNode.swift +++ b/Display/ListViewReorderingItemNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final class ListViewReorderingItemNode: ASDisplayNode { diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index b2b021e567..fe133f920e 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -1,7 +1,4 @@ -#if os(macOS) -#else import UIKit -#endif class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { override init(frame: CGRect) { diff --git a/Display/ListViewTempItemNode.swift b/Display/ListViewTempItemNode.swift index 0a6ebc9e18..f407a488f8 100644 --- a/Display/ListViewTempItemNode.swift +++ b/Display/ListViewTempItemNode.swift @@ -1,5 +1,4 @@ import Foundation -final class ListViewTempItemNode: ListViewItemNode { - +final class ListViewTempItemNode: ListViewItemNode { } diff --git a/Display/ListViewTransactionQueue.swift b/Display/ListViewTransactionQueue.swift index 3e6f1793dd..a790dc1e82 100644 --- a/Display/ListViewTransactionQueue.swift +++ b/Display/ListViewTransactionQueue.swift @@ -1,9 +1,6 @@ import Foundation -#if os(iOS) +import UIKit import SwiftSignalKit -#else -import SwiftSignalKitMac -#endif public typealias ListViewTransaction = (@escaping () -> Void) -> Void diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index e1e3c12c75..34db62ba67 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import SwiftSignalKit private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3 diff --git a/Display/NavigationBarBadge.swift b/Display/NavigationBarBadge.swift index c3a03fb19e..089b88b0e3 100644 --- a/Display/NavigationBarBadge.swift +++ b/Display/NavigationBarBadge.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final class NavigationBarBadgeNode: ASDisplayNode { diff --git a/Display/NavigationBarContentNode.swift b/Display/NavigationBarContentNode.swift index 7fff0f07eb..768628733b 100644 --- a/Display/NavigationBarContentNode.swift +++ b/Display/NavigationBarContentNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum NavigationBarContentMode { diff --git a/Display/NavigationBarTransitionContainer.swift b/Display/NavigationBarTransitionContainer.swift index 1cbd27c65d..2333a0e7a0 100644 --- a/Display/NavigationBarTransitionContainer.swift +++ b/Display/NavigationBarTransitionContainer.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit class NavigationBarTransitionContainer: ASDisplayNode { diff --git a/Display/NavigationBarTransitionState.swift b/Display/NavigationBarTransitionState.swift index 4f8225051b..dce45512df 100644 --- a/Display/NavigationBarTransitionState.swift +++ b/Display/NavigationBarTransitionState.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit enum NavigationBarTransitionRole { case top diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 8412cefb43..6410dbff2c 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -3,6 +3,10 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +#if BUCK +import DisplayPrivate +#endif + public final class NavigationControllerTheme { public let navigationBar: NavigationBarTheme public let emptyAreaColor: UIColor diff --git a/Display/PageControlNode.swift b/Display/PageControlNode.swift index 4286e8efbd..1353ef6dd4 100644 --- a/Display/PageControlNode.swift +++ b/Display/PageControlNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class PageControlNode: ASDisplayNode { diff --git a/Display/PeekController.swift b/Display/PeekController.swift index 14b2c71c13..456c3ceb29 100644 --- a/Display/PeekController.swift +++ b/Display/PeekController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class PeekControllerTheme { diff --git a/Display/PeekControllerContent.swift b/Display/PeekControllerContent.swift index 16054793cd..c2767e87c6 100644 --- a/Display/PeekControllerContent.swift +++ b/Display/PeekControllerContent.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum PeekControllerContentPresentation { diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index 12c103046a..505c039ee7 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -1,6 +1,7 @@ import Foundation import UIKit import SwiftSignalKit +import AsyncDisplayKit private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> Bool { if view.bounds.contains(point), let view = view as? UIScrollView, view.isDecelerating { diff --git a/Display/PeekControllerMenuItemNode.swift b/Display/PeekControllerMenuItemNode.swift index fb51bcb4b7..c1dba53c6d 100644 --- a/Display/PeekControllerMenuItemNode.swift +++ b/Display/PeekControllerMenuItemNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum PeekControllerMenuItemColor { diff --git a/Display/PeekControllerMenuNode.swift b/Display/PeekControllerMenuNode.swift index a89587d5ea..6b7e5c4fdb 100644 --- a/Display/PeekControllerMenuNode.swift +++ b/Display/PeekControllerMenuNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final class PeekControllerMenuNode: ASDisplayNode { diff --git a/Display/PeekControllerNode.swift b/Display/PeekControllerNode.swift index f7c9190cc6..89c65cbaeb 100644 --- a/Display/PeekControllerNode.swift +++ b/Display/PeekControllerNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit final class PeekControllerNode: ViewControllerTracingNode { diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 87f6cca26b..b61fe57e63 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -1,3 +1,4 @@ +import UIKit import SwiftSignalKit public struct PresentationSurfaceLevel: RawRepresentable { diff --git a/Display/Spring.swift b/Display/Spring.swift index 44fba08d85..169ed107db 100644 --- a/Display/Spring.swift +++ b/Display/Spring.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit struct ViewportItemSpring { let stiffness: CGFloat diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index faa8be4f8d..31da12342b 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -1,6 +1,11 @@ import Foundation +import UIKit import AsyncDisplayKit +#if BUCK +import DisplayPrivate +#endif + public class StatusBarSurface { var statusBars: [StatusBar] = [] diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index e09107be51..3aee140873 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit import SwiftSignalKit diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index 5d5052de69..e8790656af 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit private final class SwitchNodeViewLayer: CALayer { diff --git a/Display/TabBarContollerNode.swift b/Display/TabBarContollerNode.swift index 10c81fc011..69cff6073f 100644 --- a/Display/TabBarContollerNode.swift +++ b/Display/TabBarContollerNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum ToolbarActionOption { diff --git a/Display/TabBarNode.swift b/Display/TabBarNode.swift index c7c2f85c55..1e112e5baf 100644 --- a/Display/TabBarNode.swift +++ b/Display/TabBarNode.swift @@ -3,6 +3,10 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +#if BUCK +import DisplayPrivate +#endif + private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor, horizontal: Bool, imageMode: Bool) -> (UIImage, CGFloat) { let font = horizontal ? Font.regular(13.0) : Font.medium(10.0) diff --git a/Display/TapLongTapOrDoubleTapGestureRecognizer.swift b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift index b2de010908..8f6771b93f 100644 --- a/Display/TapLongTapOrDoubleTapGestureRecognizer.swift +++ b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import UIKit.UIGestureRecognizerSubclass private class TapLongTapOrDoubleTapGestureRecognizerTimerTarget: NSObject { diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 4ed12cb19e..b8474e9967 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public enum TextAlertActionType { diff --git a/Display/TextFieldNode.swift b/Display/TextFieldNode.swift index e755684b2b..0defa9ce46 100644 --- a/Display/TextFieldNode.swift +++ b/Display/TextFieldNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class TextFieldNodeView: UITextField { diff --git a/Display/TextNode.swift b/Display/TextNode.swift index 4c10f71938..b70b5400fa 100644 --- a/Display/TextNode.swift +++ b/Display/TextNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit import CoreText diff --git a/Display/Theme.swift b/Display/Theme.swift deleted file mode 100644 index fd448fb1fb..0000000000 --- a/Display/Theme.swift +++ /dev/null @@ -1,9 +0,0 @@ -import Foundation - -public class Theme { - public let accentColor: UIColor - - public init(accentColor: UIColor) { - self.accentColor = accentColor - } -} diff --git a/Display/Toolbar.swift b/Display/Toolbar.swift index b609dc3534..1d87078945 100644 --- a/Display/Toolbar.swift +++ b/Display/Toolbar.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit public struct ToolbarAction: Equatable { public let title: String diff --git a/Display/ToolbarNode.swift b/Display/ToolbarNode.swift index 06bbda961c..960400a2e7 100644 --- a/Display/ToolbarNode.swift +++ b/Display/ToolbarNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit public final class ToolbarNode: ASDisplayNode { diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index 33fc6a5792..51e0985d3c 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit import SwiftSignalKit diff --git a/Display/UITracingLayerView.swift b/Display/UITracingLayerView.swift new file mode 100644 index 0000000000..7834962799 --- /dev/null +++ b/Display/UITracingLayerView.swift @@ -0,0 +1,36 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +#if BUCK +import DisplayPrivate +#endif + +open class UITracingLayerView: UIView { + private var scheduledWithLayout: (() -> Void)? + + open func schedule(layout f: @escaping () -> Void) { + self.scheduledWithLayout = f + self.setNeedsLayout() + } + + override open var autoresizingMask: UIViewAutoresizing { + get { + return [] + } set(value) { + } + } + + override open class var layerClass: AnyClass { + return CATracingLayer.self + } + + override open func layoutSubviews() { + super.layoutSubviews() + + if let scheduledWithLayout = self.scheduledWithLayout { + self.scheduledWithLayout = nil + scheduledWithLayout() + } + } +} diff --git a/Display/ViewController.swift b/Display/ViewController.swift index ec37788fcc..949dce2011 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -3,6 +3,10 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit +#if BUCK +import DisplayPrivate +#endif + private func findCurrentResponder(_ view: UIView) -> UIResponder? { if view.isFirstResponder { return view diff --git a/Display/ViewControllerPreviewing.swift b/Display/ViewControllerPreviewing.swift index c81d9c2b81..e2442f3168 100644 --- a/Display/ViewControllerPreviewing.swift +++ b/Display/ViewControllerPreviewing.swift @@ -1,5 +1,6 @@ import Foundation import UIKit +import AsyncDisplayKit import SwiftSignalKit @available(iOSApplicationExtension 9.0, *) diff --git a/Display/ViewControllerTracingNode.swift b/Display/ViewControllerTracingNode.swift index 1367ad831e..91f678585a 100644 --- a/Display/ViewControllerTracingNode.swift +++ b/Display/ViewControllerTracingNode.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AsyncDisplayKit private final class ViewControllerTracingNodeView: UITracingLayerView { diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index d33b80933e..be3b7610ff 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -1,7 +1,12 @@ import Foundation +import UIKit import AsyncDisplayKit import SwiftSignalKit +#if BUCK +import DisplayPrivate +#endif + private struct WindowLayout: Equatable { let size: CGSize let metrics: LayoutMetrics diff --git a/Display/WindowPanRecognizer.swift b/Display/WindowPanRecognizer.swift index 7d92acd37a..d9180edbaa 100644 --- a/Display/WindowPanRecognizer.swift +++ b/Display/WindowPanRecognizer.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit public final class WindowPanRecognizer: UIGestureRecognizer { public var began: ((CGPoint) -> Void)? From 4fe76fd53bb0b561fde9aad5e32bd4d0ddca305c Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 6 Jun 2019 17:40:04 +0100 Subject: [PATCH 233/245] BUCK updates --- Display.xcodeproj/project.pbxproj | 4 ++++ Display/Keyboard.swift | 7 +++++++ 2 files changed, 11 insertions(+) create mode 100644 Display/Keyboard.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index c9893ec242..14e2d0aeba 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -34,6 +34,7 @@ D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D02C61F322A94BD600D4EC86 /* Keyboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02C61F222A94BD600D4EC86 /* Keyboard.swift */; }; D03310B3213F232600FC83CD /* ListViewTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */; }; D033874E223D3E86007A2CE4 /* AccessibilityAreaNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */; }; D0338750223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D033874F223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift */; }; @@ -205,6 +206,7 @@ D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D02C61F222A94BD600D4EC86 /* Keyboard.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Keyboard.swift; sourceTree = ""; }; D03310B2213F232600FC83CD /* ListViewTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ListViewTapGestureRecognizer.swift; sourceTree = ""; }; D033874D223D3E86007A2CE4 /* AccessibilityAreaNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccessibilityAreaNode.swift; sourceTree = ""; }; D033874F223EE5A4007A2CE4 /* ActionSheetSwitchItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetSwitchItem.swift; sourceTree = ""; }; @@ -609,6 +611,7 @@ D01F728121F13891006AB634 /* Accessibility.swift */, 09167E1E229803DC005734A7 /* UIMenuItem+Icons.h */, 09167E1F229803DC005734A7 /* UIMenuItem+Icons.m */, + D02C61F222A94BD600D4EC86 /* Keyboard.swift */, ); name = Utils; sourceTree = ""; @@ -955,6 +958,7 @@ D0FA08C42048803C00DD23FC /* TextNode.swift in Sources */, D0CA3F8C2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, + D02C61F322A94BD600D4EC86 /* Keyboard.swift in Sources */, D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, diff --git a/Display/Keyboard.swift b/Display/Keyboard.swift new file mode 100644 index 0000000000..912cd8fb44 --- /dev/null +++ b/Display/Keyboard.swift @@ -0,0 +1,7 @@ +import Foundation + +public enum Keyboard { + public static func applyAutocorrection() { + applyKeyboardAutocorrection() + } +} From 9974280abe4fb74260219a6afe93b63f21cddc0c Mon Sep 17 00:00:00 2001 From: Peter <> Date: Thu, 6 Jun 2019 23:16:33 +0100 Subject: [PATCH 234/245] Fix build --- Display/Keyboard.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Display/Keyboard.swift b/Display/Keyboard.swift index 912cd8fb44..44526b8f1d 100644 --- a/Display/Keyboard.swift +++ b/Display/Keyboard.swift @@ -1,5 +1,9 @@ import Foundation +#if BUCK +import DisplayPrivate +#endif + public enum Keyboard { public static func applyAutocorrection() { applyKeyboardAutocorrection() From 03718ebeb2cd6ffac5f4a746059ef8cc108dd981 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Fri, 7 Jun 2019 14:43:19 +0100 Subject: [PATCH 235/245] Fix immediate transition --- Display/NavigationController.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 6410dbff2c..139f6e08cf 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -390,7 +390,13 @@ open class NavigationController: UINavigationController, ContainableController, } record.controller.setIgnoreAppearanceMethodInvocations(false) } + var applyFrame = false if !isAppearing { + applyFrame = true + } else if case .immediate = transition { + applyFrame = true + } + if applyFrame { var isPartOfTransition = false if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { if navigationTransitionCoordinator.topView == record.controller.view || navigationTransitionCoordinator.bottomView == record.controller.view { From 123373e3ee97c179d534c6a97e701d59792b5ade Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 6 Jun 2019 15:56:07 +0200 Subject: [PATCH 236/245] TextAlertController: fixed width --- Display/TextAlertController.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index b8474e9967..59c6d4dac0 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -2,6 +2,8 @@ import Foundation import UIKit import AsyncDisplayKit +private let alertWidth: CGFloat = 270.0 + public enum TextAlertActionType { case genericAction case defaultAction @@ -229,6 +231,9 @@ public final class TextAlertContentNode: AlertContentNode { let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0) + var size = size + size.width = min(size.width, alertWidth) + var titleSize: CGSize? if let titleNode = self.titleNode { titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude)) @@ -265,10 +270,8 @@ public final class TextAlertContentNode: AlertContentNode { actionsHeight = actionButtonHeight * CGFloat(self.actionNodes.count) } - if let titleNode = titleNode, let titleSize = titleSize { - var contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth) - contentWidth = max(contentWidth, 150.0) - + let contentWidth = alertWidth - insets.left - insets.right + if let titleNode = self.titleNode, let titleSize = titleSize { let spacing: CGFloat = 6.0 let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize) transition.updateFrame(node: titleNode, frame: titleFrame) @@ -278,9 +281,6 @@ public final class TextAlertContentNode: AlertContentNode { resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom) } else { - var contentWidth = max(textSize.width, minActionsWidth) - contentWidth = max(contentWidth, 150.0) - let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: insets.top), size: textSize) transition.updateFrame(node: self.textNode, frame: textFrame) From a72ebae781bcb1012e4b267f58d77871dea3a551 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Thu, 6 Jun 2019 19:03:46 +0200 Subject: [PATCH 237/245] AlertController: added blurred background --- Display/AlertController.swift | 12 +++++++++++- Display/AlertControllerNode.swift | 6 ++++-- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Display/AlertController.swift b/Display/AlertController.swift index c221ac7a51..7e74a95ee9 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -2,7 +2,13 @@ import Foundation import UIKit import AsyncDisplayKit +public enum AlertControllerThemeBackgroundType { + case light + case dark +} + public final class AlertControllerTheme: Equatable { + public let backgroundType: ActionSheetControllerThemeBackgroundType public let backgroundColor: UIColor public let separatorColor: UIColor public let highlightedItemColor: UIColor @@ -12,7 +18,8 @@ public final class AlertControllerTheme: Equatable { public let destructiveColor: UIColor public let disabledColor: UIColor - public init(backgroundColor: UIColor, separatorColor: UIColor, highlightedItemColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, accentColor: UIColor, destructiveColor: UIColor, disabledColor: UIColor) { + public init(backgroundType: ActionSheetControllerThemeBackgroundType, backgroundColor: UIColor, separatorColor: UIColor, highlightedItemColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, accentColor: UIColor, destructiveColor: UIColor, disabledColor: UIColor) { + self.backgroundType = backgroundType self.backgroundColor = backgroundColor self.separatorColor = separatorColor self.highlightedItemColor = highlightedItemColor @@ -24,6 +31,9 @@ public final class AlertControllerTheme: Equatable { } public static func ==(lhs: AlertControllerTheme, rhs: AlertControllerTheme) -> Bool { + if lhs.backgroundType != rhs.backgroundType { + return false + } if lhs.backgroundColor != rhs.backgroundColor { return false } diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 70854f3835..09a9774c29 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -24,8 +24,7 @@ final class AlertControllerNode: ASDisplayNode { self.containerNode.layer.masksToBounds = true self.effectNode = ASDisplayNode(viewBlock: { - let view = UIView() - return view + return UIVisualEffectView(effect: UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark)) }) self.contentNode = contentNode @@ -53,6 +52,9 @@ final class AlertControllerNode: ASDisplayNode { } func updateTheme(_ theme: AlertControllerTheme) { + if let effectView = self.effectNode.view as? UIVisualEffectView { + effectView.effect = UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark) + } self.containerNode.backgroundColor = theme.backgroundColor self.contentNode.updateTheme(theme) } From 7657cdb00a9b5955f9c25e2aa6c0c12f9395562e Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Sat, 8 Jun 2019 00:06:23 +0200 Subject: [PATCH 238/245] AlertControllerNode: use blurred effect view for background TextAlertController: exposed TextAlertContentActionNode --- Display/AlertControllerNode.swift | 71 +++++++++++++++++++++++++------ Display/TextAlertController.swift | 58 +++++++++++++++---------- 2 files changed, 94 insertions(+), 35 deletions(-) diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index 09a9774c29..eb9ebf16e5 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -3,9 +3,15 @@ import UIKit import AsyncDisplayKit final class AlertControllerNode: ASDisplayNode { - private let dimmingNode: ASDisplayNode + private let centerDimView: UIImageView + private let topDimView: UIView + private let bottomDimView: UIView + private let leftDimView: UIView + private let rightDimView: UIView + private let containerNode: ASDisplayNode private let effectNode: ASDisplayNode + private let backgroundNode: ASDisplayNode private let contentNode: AlertContentNode private let allowInputInset: Bool @@ -15,14 +21,35 @@ final class AlertControllerNode: ASDisplayNode { init(contentNode: AlertContentNode, theme: AlertControllerTheme, allowInputInset: Bool) { self.allowInputInset = allowInputInset - self.dimmingNode = ASDisplayNode() - self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5) + + let dimColor = UIColor(white: 0.0, alpha: 0.5) + + self.centerDimView = UIImageView() + self.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: dimColor) + + self.topDimView = UIView() + self.topDimView.backgroundColor = dimColor + self.topDimView.isUserInteractionEnabled = false + + self.bottomDimView = UIView() + self.bottomDimView.backgroundColor = dimColor + self.bottomDimView.isUserInteractionEnabled = false + + self.leftDimView = UIView() + self.leftDimView.backgroundColor = dimColor + self.leftDimView.isUserInteractionEnabled = false + + self.rightDimView = UIView() + self.rightDimView.backgroundColor = dimColor + self.rightDimView.isUserInteractionEnabled = false self.containerNode = ASDisplayNode() - self.containerNode.backgroundColor = theme.backgroundColor self.containerNode.layer.cornerRadius = 14.0 self.containerNode.layer.masksToBounds = true + self.backgroundNode = ASDisplayNode() + self.backgroundNode.backgroundColor = theme.backgroundColor + self.effectNode = ASDisplayNode(viewBlock: { return UIVisualEffectView(effect: UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark)) }) @@ -31,10 +58,14 @@ final class AlertControllerNode: ASDisplayNode { super.init() - self.addSubnode(self.dimmingNode) - - self.addSubnode(self.effectNode) + self.view.addSubview(self.centerDimView) + self.view.addSubview(self.topDimView) + self.view.addSubview(self.bottomDimView) + self.view.addSubview(self.leftDimView) + self.view.addSubview(self.rightDimView) + self.containerNode.addSubnode(self.effectNode) + self.containerNode.addSubnode(self.backgroundNode) self.containerNode.addSubnode(self.contentNode) self.addSubnode(self.containerNode) @@ -48,7 +79,8 @@ final class AlertControllerNode: ASDisplayNode { override func didLoad() { super.didLoad() - self.dimmingNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) + self.topDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) + self.bottomDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) } func updateTheme(_ theme: AlertControllerTheme) { @@ -60,13 +92,21 @@ final class AlertControllerNode: ASDisplayNode { } func animateIn() { - self.dimmingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.centerDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.topDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.bottomDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.leftDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) + self.rightDimView.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3) self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25) self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil) } func animateOut(completion: @escaping () -> Void) { - self.dimmingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.centerDimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.topDimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.bottomDimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.leftDimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) + self.rightDimView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false) self.containerNode.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false, completion: { _ in completion() @@ -76,8 +116,6 @@ final class AlertControllerNode: ASDisplayNode { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { self.containerLayout = layout - transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size)) - var insetOptions: ContainerViewLayoutInsetOptions = [.statusBar] if self.allowInputInset { insetOptions.insert(.input) @@ -91,8 +129,15 @@ final class AlertControllerNode: ASDisplayNode { let containerSize = CGSize(width: contentSize.width, height: contentSize.height) let containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: contentAvailableFrame.minY + floor((contentAvailableFrame.size.height - containerSize.height) / 2.0)), size: containerSize) + transition.updateFrame(view: self.centerDimView, frame: containerFrame) + transition.updateFrame(view: self.topDimView, frame: CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: containerFrame.minY))) + transition.updateFrame(view: self.bottomDimView, frame: CGRect(origin: CGPoint(x: 0.0, y: containerFrame.maxY), size: CGSize(width: layout.size.width, height: layout.size.height - containerFrame.maxY))) + transition.updateFrame(view: self.leftDimView, frame: CGRect(origin: CGPoint(x: 0.0, y: containerFrame.minY), size: CGSize(width: containerFrame.minX, height: containerFrame.height))) + transition.updateFrame(view: self.rightDimView, frame: CGRect(origin: CGPoint(x: containerFrame.maxX, y: containerFrame.minY), size: CGSize(width: layout.size.width - containerFrame.maxX, height: containerFrame.height))) + transition.updateFrame(node: self.containerNode, frame: containerFrame) - transition.updateFrame(node: self.effectNode, frame: containerFrame) + transition.updateFrame(node: self.effectNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) + transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size)) } diff --git a/Display/TextAlertController.swift b/Display/TextAlertController.swift index 59c6d4dac0..7b4d6cc62a 100644 --- a/Display/TextAlertController.swift +++ b/Display/TextAlertController.swift @@ -22,18 +22,20 @@ public struct TextAlertAction { } } -private final class TextAlertContentActionNode: HighlightableButtonNode { - private let backgroundNode: ASDisplayNode - +public final class TextAlertContentActionNode: HighlightableButtonNode { + private var theme: AlertControllerTheme let action: TextAlertAction - init(theme: AlertControllerTheme, action: TextAlertAction) { + private let backgroundNode: ASDisplayNode + + public init(theme: AlertControllerTheme, action: TextAlertAction) { + self.theme = theme + self.action = action + self.backgroundNode = ASDisplayNode() self.backgroundNode.isLayerBacked = true self.backgroundNode.alpha = 0.0 - self.action = action - super.init() self.titleNode.maximumNumberOfLines = 2 @@ -56,16 +58,27 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { self.updateTheme(theme) } - func updateTheme(_ theme: AlertControllerTheme) { + public var actionEnabled: Bool = true { + didSet { + self.isUserInteractionEnabled = self.actionEnabled + self.updateTitle() + } + } + + public func updateTheme(_ theme: AlertControllerTheme) { + self.theme = theme self.backgroundNode.backgroundColor = theme.highlightedItemColor - + self.updateTitle() + } + + private func updateTitle() { var font = Font.regular(17.0) - var color = theme.accentColor + var color: UIColor switch self.action.type { case .defaultAction, .genericAction: - break + color = self.actionEnabled ? self.theme.accentColor : self.theme.disabledColor case .destructiveAction: - color = theme.destructiveColor + color = self.actionEnabled ? self.theme.destructiveColor : self.theme.disabledColor } switch self.action.type { case .defaultAction: @@ -76,7 +89,7 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { self.setAttributedTitle(NSAttributedString(string: self.action.title, font: font, textColor: color, paragraphAlignment: .center), for: []) } - override func didLoad() { + override public func didLoad() { super.didLoad() self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside) @@ -86,7 +99,7 @@ private final class TextAlertContentActionNode: HighlightableButtonNode { self.action.action() } - override func layout() { + override public func layout() { super.layout() self.backgroundNode.frame = self.bounds @@ -203,16 +216,17 @@ public final class TextAlertContentNode: AlertContentNode { override public func updateTheme(_ theme: AlertControllerTheme) { self.theme = theme - let textFont: UIFont - if let titleNode = self.titleNode { - titleNode.attributedText = NSAttributedString(string: titleNode.attributedText?.string ?? "", font: Font.medium(17.0), textColor: theme.primaryColor, paragraphAlignment: .center) - textFont = Font.regular(13.0) - } else { - textFont = Font.semibold(17.0) + if let titleNode = self.titleNode, let attributedText = titleNode.attributedText { + let updatedText = NSMutableAttributedString(attributedString: attributedText) + updatedText.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.primaryColor, range: NSRange(location: 0, length: updatedText.length)) + titleNode.attributedText = updatedText } - - self.textNode.attributedText = NSAttributedString(string: self.textNode.attributedText?.string ?? "", font: textFont, textColor: theme.primaryColor, paragraphAlignment: .center) - + if let attributedText = self.textNode.attributedText { + let updatedText = NSMutableAttributedString(attributedString: attributedText) + updatedText.addAttribute(NSAttributedStringKey.foregroundColor, value: theme.primaryColor, range: NSRange(location: 0, length: updatedText.length)) + self.textNode.attributedText = updatedText + } + self.actionNodesSeparator.backgroundColor = theme.separatorColor for actionNode in self.actionNodes { actionNode.updateTheme(theme) From f92a7b13356599f10eea8058a7ee32185f6d3cfa Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 9 Jun 2019 19:41:48 +0100 Subject: [PATCH 239/245] Rename project --- .../xcschemes/Display.xcscheme | 80 ------------------- .../project.pbxproj | 4 +- .../contents.xcworkspacedata | 0 3 files changed, 2 insertions(+), 82 deletions(-) delete mode 100644 Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme rename {Display.xcodeproj => Display_Xcode.xcodeproj}/project.pbxproj (99%) rename {Display.xcodeproj => Display_Xcode.xcodeproj}/project.xcworkspace/contents.xcworkspacedata (100%) diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme deleted file mode 100644 index aebd30386b..0000000000 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Display.xcodeproj/project.pbxproj b/Display_Xcode.xcodeproj/project.pbxproj similarity index 99% rename from Display.xcodeproj/project.pbxproj rename to Display_Xcode.xcodeproj/project.pbxproj index 14e2d0aeba..8edc164afd 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display_Xcode.xcodeproj/project.pbxproj @@ -817,7 +817,7 @@ }; }; }; - buildConfigurationList = D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */; + buildConfigurationList = D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display_Xcode" */; compatibilityVersion = "Xcode 3.2"; developmentRegion = English; hasScannedForEncodings = 0; @@ -1708,7 +1708,7 @@ /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ - D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display" */ = { + D05CC25D1B69316F00E235A3 /* Build configuration list for PBXProject "Display_Xcode" */ = { isa = XCConfigurationList; buildConfigurations = ( D05CC2751B69316F00E235A3 /* DebugHockeyapp */, diff --git a/Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/Display_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata similarity index 100% rename from Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to Display_Xcode.xcodeproj/project.xcworkspace/contents.xcworkspacedata From de4fff07b0ab438537dfe79e9ee2ddaf85cef951 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Sun, 9 Jun 2019 20:15:40 +0100 Subject: [PATCH 240/245] Update .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 249d3670f3..6e879fb6f0 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ DerivedData *.dSYM.zip *.ipa */xcuserdata/* +Display.xcodeproj/* From 4fbcb7e535a2121ad0d0a08235373d46f16a0ae1 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 10 Jun 2019 20:02:38 +0100 Subject: [PATCH 241/245] Allow static linking --- BUCK | 8 ++++---- Display/HapticFeedback.swift | 4 ++-- Display/NativeWindowHostView.swift | 2 +- Display/ViewController.swift | 6 +++--- Display/ViewControllerPreviewing.swift | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/BUCK b/BUCK index bcaf40a09a..d073d26cc1 100644 --- a/BUCK +++ b/BUCK @@ -1,5 +1,5 @@ -load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'configs_with_config') -load('//tools:buck_defs.bzl', 'combined_config', 'SHARED_CONFIGS', 'LIB_SPECIFIC_CONFIG') +load('//tools:buck_utils.bzl', 'config_with_updated_linker_flags', 'configs_with_config', 'combined_config') +load('//tools:buck_defs.bzl', 'SHARED_CONFIGS', 'EXTENSION_LIB_SPECIFIC_CONFIG') apple_library( name = 'DisplayPrivate', @@ -14,7 +14,7 @@ apple_library( 'Display/*.h', ], exclude = ['Display/Display.h']), modular = True, - configs = configs_with_config(combined_config([SHARED_CONFIGS, LIB_SPECIFIC_CONFIG])), + configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])), compiler_flags = ['-w'], preprocessor_flags = ['-fobjc-arc'], visibility = ['//submodules/Display:Display'], @@ -32,7 +32,7 @@ apple_library( srcs = glob([ 'Display/*.swift', ]), - configs = configs_with_config(combined_config([SHARED_CONFIGS, LIB_SPECIFIC_CONFIG])), + configs = configs_with_config(combined_config([SHARED_CONFIGS, EXTENSION_LIB_SPECIFIC_CONFIG])), swift_compiler_flags = [ '-suppress-warnings', '-application-extension', diff --git a/Display/HapticFeedback.swift b/Display/HapticFeedback.swift index 2379f0242d..2cb17e72cd 100644 --- a/Display/HapticFeedback.swift +++ b/Display/HapticFeedback.swift @@ -7,7 +7,7 @@ public enum ImpactHapticFeedbackStyle: Hashable { case heavy } -@available(iOSApplicationExtension 10.0, *) +@available(iOSApplicationExtension 10.0, iOS 10.0, *) private final class HapticFeedbackImpl { private lazy var impactGenerator: [ImpactHapticFeedbackStyle : UIImpactFeedbackGenerator] = { [.light: UIImpactFeedbackGenerator(style: .light), @@ -65,7 +65,7 @@ public final class HapticFeedback { }) } - @available(iOSApplicationExtension 10.0, *) + @available(iOSApplicationExtension 10.0, iOS 10.0, *) private func withImpl(_ f: (HapticFeedbackImpl) -> Void) { if self.impl == nil { self.impl = HapticFeedbackImpl() diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift index 34db62ba67..7361f152fa 100644 --- a/Display/NativeWindowHostView.swift +++ b/Display/NativeWindowHostView.swift @@ -23,7 +23,7 @@ public final class PreviewingHostViewDelegate { } public protocol PreviewingHostView { - @available(iOSApplicationExtension 9.0, *) + @available(iOSApplicationExtension 9.0, iOS 9.0, *) var previewingDelegate: PreviewingHostViewDelegate? { get } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 949dce2011..fea78f9d79 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -441,7 +441,7 @@ open class ViewControllerPresentationArguments { open func dismiss(completion: (() -> Void)? = nil) { } - @available(iOSApplicationExtension 9.0, *) + @available(iOSApplicationExtension 9.0, iOS 9.0, *) open func registerForPreviewing(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme, onlyNative: Bool) { if self.traitCollection.forceTouchCapability == .available { let _ = super.registerForPreviewing(with: delegate, sourceView: sourceView) @@ -455,7 +455,7 @@ open class ViewControllerPresentationArguments { } } - @available(iOSApplicationExtension 9.0, *) + @available(iOSApplicationExtension 9.0, iOS 9.0, *) public func registerForPreviewingNonNative(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme) { if self.traitCollection.forceTouchCapability != .available { if self.previewingContext == nil { @@ -467,7 +467,7 @@ open class ViewControllerPresentationArguments { } } - @available(iOSApplicationExtension 9.0, *) + @available(iOSApplicationExtension 9.0, iOS 9.0, *) open override func unregisterForPreviewing(withContext previewing: UIViewControllerPreviewing) { if self.previewingContext != nil { self.previewingContext = nil diff --git a/Display/ViewControllerPreviewing.swift b/Display/ViewControllerPreviewing.swift index e2442f3168..fafdce84e9 100644 --- a/Display/ViewControllerPreviewing.swift +++ b/Display/ViewControllerPreviewing.swift @@ -3,7 +3,7 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -@available(iOSApplicationExtension 9.0, *) +@available(iOSApplicationExtension 9.0, iOS 9.0, *) private final class ViewControllerPeekContent: PeekControllerContent { private let controller: ViewController private let menu: [PeekControllerMenuItem] @@ -85,7 +85,7 @@ private final class ViewControllerPeekContentNode: ASDisplayNode, PeekController } } -@available(iOSApplicationExtension 9.0, *) +@available(iOSApplicationExtension 9.0, iOS 9.0, *) final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreviewing { weak var delegateImpl: UIViewControllerPreviewingDelegate? var delegate: UIViewControllerPreviewingDelegate { From bc5c8f60d4f9a0dbd66243008563a86fa1a7c652 Mon Sep 17 00:00:00 2001 From: Peter <> Date: Mon, 10 Jun 2019 20:34:02 +0100 Subject: [PATCH 242/245] Expect ready on main queue --- Display/NavigationController.swift | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 139f6e08cf..5477849529 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -803,7 +803,9 @@ open class NavigationController: UINavigationController, ContainableController, let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) controller.containerLayoutUpdated(appliedLayout, transition: .immediate) - strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + strongSelf.currentPushDisposable.set((controller.ready.get() + |> deliverOnMainQueue + |> take(1)).start(next: { _ in guard let strongSelf = self else { return } @@ -848,7 +850,9 @@ open class NavigationController: UINavigationController, ContainableController, controllerLayout.inputHeight = nil controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } - self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in + self.currentPushDisposable.set((controller.ready.get() + |> deliverOnMainQueue + |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { ready?.set(true) var controllers = strongSelf.viewControllers @@ -877,7 +881,9 @@ open class NavigationController: UINavigationController, ContainableController, controllerLayout.inputHeight = nil controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } - strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + strongSelf.currentPushDisposable.set((controller.ready.get() + |> deliverOnMainQueue + |> take(1)).start(next: { _ in guard let strongSelf = self else { return } @@ -901,7 +907,9 @@ open class NavigationController: UINavigationController, ContainableController, controllerLayout.inputHeight = nil controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } - strongSelf.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { _ in + strongSelf.currentPushDisposable.set((controller.ready.get() + |> deliverOnMainQueue + |> take(1)).start(next: { _ in guard let strongSelf = self else { return } From b91c696d51854a82ada6043d907eb3abe931344b Mon Sep 17 00:00:00 2001 From: Peter <> Date: Tue, 11 Jun 2019 10:09:42 +0100 Subject: [PATCH 243/245] Add _updateWirelessRouteStatus --- Display/VolumeControlStatusBar.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Display/VolumeControlStatusBar.swift b/Display/VolumeControlStatusBar.swift index 1ecb917183..35501ba9d5 100644 --- a/Display/VolumeControlStatusBar.swift +++ b/Display/VolumeControlStatusBar.swift @@ -9,8 +9,13 @@ private let volumeParameterKey = "AVSystemController_AudioVolumeNotificationPara private let changeReasonParameterKey = "AVSystemController_AudioVolumeChangeReasonNotificationParameter" private let explicitChangeReasonValue = "ExplicitVolumeChange" +private final class VolumeView: MPVolumeView { + @objc func _updateWirelessRouteStatus() { + } +} + final class VolumeControlStatusBar: UIView { - private let control: MPVolumeView + private let control: VolumeView private var observer: Any? private var currentValue: Float @@ -20,7 +25,7 @@ final class VolumeControlStatusBar: UIView { private var ignoreAdjustmentOnce = false init(frame: CGRect, shouldBeVisible: Signal) { - self.control = MPVolumeView(frame: CGRect(origin: CGPoint(x: -100.0, y: -100.0), size: CGSize(width: 100.0, height: 20.0))) + self.control = VolumeView(frame: CGRect(origin: CGPoint(x: -100.0, y: -100.0), size: CGSize(width: 100.0, height: 20.0))) self.control.alpha = 0.0001 self.currentValue = AVAudioSession.sharedInstance().outputVolume From b63cc6797f224266d69a3244d73363004caf7b89 Mon Sep 17 00:00:00 2001 From: overtake <> Date: Tue, 11 Jun 2019 18:16:08 +0200 Subject: [PATCH 244/245] - ipad fixes --- Display/ContextMenuController.swift | 9 ++++++++- Display/KeyShortcutsController.swift | 1 + Display/ListView.swift | 14 +++++++------- Display/ListViewAccessoryItem.swift | 2 +- Display/NavigationBar.swift | 11 +++++++++++ 5 files changed, 28 insertions(+), 9 deletions(-) diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 24382972de..4e71c8d903 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -10,11 +10,18 @@ public final class ContextMenuControllerPresentationArguments { } } -public final class ContextMenuController: ViewController { +public final class ContextMenuController: ViewController, KeyShortcutResponder { private var contextMenuNode: ContextMenuNode { return self.displayNode as! ContextMenuNode } + public var keyShortcuts: [KeyShortcut] { + return [KeyShortcut(input: UIKeyInputEscape, action: { [weak self] in + if let strongSelf = self { + strongSelf.dismiss() + } + })] + } private let actions: [ContextMenuAction] private let catchTapsOutside: Bool private let hasHapticFeedback: Bool diff --git a/Display/KeyShortcutsController.swift b/Display/KeyShortcutsController.swift index 84ce20c38d..c6984d775e 100644 --- a/Display/KeyShortcutsController.swift +++ b/Display/KeyShortcutsController.swift @@ -29,6 +29,7 @@ public class KeyShortcutsController: UIResponder { guard let viewController = viewController as? KeyShortcutResponder else { return true } + shortcuts.removeAll(where: { viewController.keyShortcuts.contains($0) }) shortcuts.append(contentsOf: viewController.keyShortcuts) return true }) diff --git a/Display/ListView.swift b/Display/ListView.swift index ffc31aa7cd..024cd5d560 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -1640,7 +1640,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture let beginReplay = { [weak self] in if let strongSelf = self { - strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { + strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), synchronous: options.contains(.Synchronous), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: { if options.contains(.PreferSynchronousDrawing) { let startTime = CACurrentMediaTime() self?.recursivelyEnsureDisplaySynchronously(true) @@ -2084,7 +2084,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { + private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, synchronous: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set, scrollToItem originalScrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) { var scrollToItem: ListViewScrollToItem? var isExperimentalSnapToScrollToItem = false if let originalScrollToItem = originalScrollToItem { @@ -2626,7 +2626,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture accessoryNodesTransition = .animated(duration: 0.3, curve: .easeInOut) } - self.updateAccessoryNodes(transition: accessoryNodesTransition, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) + self.updateAccessoryNodes(transition: accessoryNodesTransition, synchronous: synchronous, currentTimestamp: timestamp, leftInset: listInsets.left, rightInset: listInsets.right) if let highlightedItemNode = highlightedItemNode { if highlightedItemNode.index != self.highlightedItemIndex { @@ -3070,7 +3070,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } } - private func updateAccessoryNodes(transition: ContainedViewLayoutTransition, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { + private func updateAccessoryNodes(transition: ContainedViewLayoutTransition, synchronous: Bool, currentTimestamp: Double, leftInset: CGFloat, rightInset: CGFloat) { var totalVisibleHeight: CGFloat = 0.0 var index = -1 let count = self.itemNodes.count @@ -3132,7 +3132,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } if !didStealAccessoryNode { - let accessoryNode = accessoryItem.node() + let accessoryNode = accessoryItem.node(synchronous: synchronous) itemNode.addSubnode(accessoryNode) itemNode.setAccessoryItemNode(accessoryNode, leftInset: leftInset, rightInset: rightInset) } @@ -3190,7 +3190,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture } if !didStealHeaderAccessoryNode { - let headerAccessoryNode = headerAccessoryItem.node() + let headerAccessoryNode = headerAccessoryItem.node(synchronous: synchronous) itemNode.addSubnode(headerAccessoryNode) itemNode.headerAccessoryItemNode = headerAccessoryNode } @@ -3357,7 +3357,7 @@ open class ListView: ASDisplayNode, UIScrollViewAccessibilityDelegate, UIGesture var updatedOperations = operations updatedState.removeInvisibleNodes(&updatedOperations) self.dispatchOnVSync { - self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) + self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, synchronous: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion) } } } diff --git a/Display/ListViewAccessoryItem.swift b/Display/ListViewAccessoryItem.swift index b2e067b4c8..37675f9d54 100644 --- a/Display/ListViewAccessoryItem.swift +++ b/Display/ListViewAccessoryItem.swift @@ -2,5 +2,5 @@ import Foundation public protocol ListViewAccessoryItem { func isEqualToItem(_ other: ListViewAccessoryItem) -> Bool - func node() -> ListViewAccessoryItemNode + func node(synchronous: Bool) -> ListViewAccessoryItemNode } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 6503d0253e..c8d2420b84 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -1123,6 +1123,17 @@ open class NavigationBar: ASDisplayNode { } } + public func executeBack() -> Bool { + if self.backButtonNode.isInHierarchy { + self.backButtonNode.pressed(0) + } else if self.leftButtonNode.isInHierarchy { + self.leftButtonNode.pressed(0) + } else { + self.backButtonNode.pressed(0) + } + return true + } + public func setHidden(_ hidden: Bool, animated: Bool) { if let contentNode = self.contentNode, case .replacement = contentNode.mode { } else { From 7bd11013ea936e3d49d937550d599f5816d32560 Mon Sep 17 00:00:00 2001 From: Ilya Laktyushin Date: Tue, 11 Jun 2019 19:07:43 +0200 Subject: [PATCH 245/245] AlertController: fix dismissal by outside tap NavigationController: fire completion callback after pushViewController --- Display/AlertControllerNode.swift | 8 +++----- Display/NavigationController.swift | 2 ++ 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Display/AlertControllerNode.swift b/Display/AlertControllerNode.swift index eb9ebf16e5..30ae994d00 100644 --- a/Display/AlertControllerNode.swift +++ b/Display/AlertControllerNode.swift @@ -29,19 +29,15 @@ final class AlertControllerNode: ASDisplayNode { self.topDimView = UIView() self.topDimView.backgroundColor = dimColor - self.topDimView.isUserInteractionEnabled = false self.bottomDimView = UIView() self.bottomDimView.backgroundColor = dimColor - self.bottomDimView.isUserInteractionEnabled = false self.leftDimView = UIView() self.leftDimView.backgroundColor = dimColor - self.leftDimView.isUserInteractionEnabled = false self.rightDimView = UIView() self.rightDimView.backgroundColor = dimColor - self.rightDimView.isUserInteractionEnabled = false self.containerNode = ASDisplayNode() self.containerNode.layer.cornerRadius = 14.0 @@ -81,13 +77,15 @@ final class AlertControllerNode: ASDisplayNode { self.topDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) self.bottomDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) + self.leftDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) + self.rightDimView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:)))) } func updateTheme(_ theme: AlertControllerTheme) { if let effectView = self.effectNode.view as? UIVisualEffectView { effectView.effect = UIBlurEffect(style: theme.backgroundType == .light ? .light : .dark) } - self.containerNode.backgroundColor = theme.backgroundColor + self.backgroundNode.backgroundColor = theme.backgroundColor self.contentNode.updateTheme(theme) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 5477849529..e0e2965134 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -818,10 +818,12 @@ open class NavigationController: UINavigationController, ContainableController, controller.containerLayoutUpdated(containerLayout, transition: .immediate) } strongSelf.pushViewController(controller, animated: animated) + completion() } })) } else { strongSelf.pushViewController(controller, animated: false) + completion() } }) }