From 38f701bfc984c023688a39dee16a5356e0d6d0e6 Mon Sep 17 00:00:00 2001 From: Peter Date: Fri, 16 Jun 2017 12:17:02 +0300 Subject: [PATCH] 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)