From 95614c81215d3e89e129b96af1a73c5eb4715b6d Mon Sep 17 00:00:00 2001 From: Peter Iakovlev Date: Wed, 4 Apr 2018 11:00:29 +0400 Subject: [PATCH] no message --- Display.xcodeproj/project.pbxproj | 17 + .../xcschemes/xcschememanagement.plist | 4 +- Display/ActionSheetController.swift | 2 +- Display/ActionSheetControllerNode.swift | 30 +- Display/AlertController.swift | 2 +- Display/CATracingLayer.h | 3 +- Display/CATracingLayer.m | 6 +- Display/ContainedViewLayoutTransition.swift | 72 +- Display/ContextMenuController.swift | 2 +- Display/ContextMenuNode.swift | 2 +- Display/ImmediateTextNode.swift | 84 ++- Display/LayoutSizes.swift | 9 + Display/LegacyPresentedController.swift | 2 +- Display/LinkHighlightingNode.swift | 237 +++++++ Display/ListView.swift | 6 +- Display/ListViewItemNode.swift | 2 +- Display/NavigationBar.swift | 228 ++++-- Display/NavigationBarTitleView.swift | 2 + Display/NavigationController.swift | 668 ++++++++++++++---- Display/NavigationTransitionCoordinator.swift | 8 +- Display/PeekController.swift | 2 +- Display/PeekControllerGestureRecognizer.swift | 91 +-- Display/StatusBar.swift | 2 +- Display/TabBarController.swift | 15 +- ...pLongTapOrDoubleTapGestureRecognizer.swift | 257 +++++++ Display/TooltipController.swift | 15 +- Display/ViewController.swift | 28 +- Display/WindowContent.swift | 18 +- 28 files changed, 1448 insertions(+), 366 deletions(-) create mode 100644 Display/LayoutSizes.swift create mode 100644 Display/LinkHighlightingNode.swift create mode 100644 Display/TapLongTapOrDoubleTapGestureRecognizer.swift diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 387e79182f..ad08084c1c 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -111,6 +111,7 @@ D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */; }; D05CC3271B69725400E235A3 /* NavigationBackArrowLight@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */; }; D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */; }; + D06B76DB20592A97006E9EEA /* LayoutSizes.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06B76DA20592A97006E9EEA /* LayoutSizes.swift */; }; D06EE8451B7140FF00837186 /* Font.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06EE8441B7140FF00837186 /* Font.swift */; }; D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */ = {isa = PBXBuildFile; fileRef = D077B8E81F4637040046D27A /* NavigationBarBadge.swift */; }; D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */; }; @@ -149,6 +150,8 @@ D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; }; D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; + D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */; }; + D0CA3F8C2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */; }; D0CB78901F9822F8004AB79B /* WindowPanRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */; }; @@ -282,6 +285,7 @@ D05CC3231B695B0700E235A3 /* NavigationBarProxy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NavigationBarProxy.m; sourceTree = ""; }; D05CC3261B69725400E235A3 /* NavigationBackArrowLight@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationBackArrowLight@2x.png"; sourceTree = ""; }; D05CC3281B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = InteractiveTransitionGestureRecognizer.swift; sourceTree = ""; }; + D06B76DA20592A97006E9EEA /* LayoutSizes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LayoutSizes.swift; sourceTree = ""; }; D06EE8441B7140FF00837186 /* Font.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Font.swift; sourceTree = ""; }; D077B8E81F4637040046D27A /* NavigationBarBadge.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarBadge.swift; sourceTree = ""; }; D081229C1D19AA1C005F7395 /* ContainerViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainerViewLayout.swift; sourceTree = ""; }; @@ -321,6 +325,8 @@ D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = ""; }; D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; + D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LinkHighlightingNode.swift; sourceTree = ""; }; + D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TapLongTapOrDoubleTapGestureRecognizer.swift; sourceTree = ""; }; D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowPanRecognizer.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetTheme.swift; sourceTree = ""; }; @@ -476,6 +482,7 @@ D04C468D1F4C97BE00D30FE1 /* PageControlNode.swift */, D0FA08C32048803C00DD23FC /* TextNode.swift */, D0FA08C5204880C900DD23FC /* ImmediateTextNode.swift */, + D0CA3F892073F7650042D2B6 /* LinkHighlightingNode.swift */, ); name = Nodes; sourceTree = ""; @@ -522,6 +529,8 @@ D05174B21EAA833200A1BF36 /* CASeeThroughTracingLayer.m */, D01847651FFA72E000075256 /* ContainedViewLayoutTransition.swift */, D0FA08C120487A8600DD23FC /* HapticFeedback.swift */, + D06B76DA20592A97006E9EEA /* LayoutSizes.swift */, + D0CA3F8B2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift */, ); name = Utils; sourceTree = ""; @@ -977,6 +986,7 @@ D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, + D0CA3F8A2073F7650042D2B6 /* LinkHighlightingNode.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, @@ -992,6 +1002,7 @@ D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */, D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, + D06B76DB20592A97006E9EEA /* LayoutSizes.swift in Sources */, D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, D00701982029CAD6006B9E34 /* TooltipController.swift in Sources */, @@ -1044,6 +1055,7 @@ D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0FA08C42048803C00DD23FC /* TextNode.swift in Sources */, + D0CA3F8C2073F8240042D2B6 /* TapLongTapOrDoubleTapGestureRecognizer.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0AA840E1FEBFB72005C6E91 /* ListViewFloatingHeaderNode.swift in Sources */, D03AA4EB202E02B10056C405 /* ListViewReorderingGestureRecognizer.swift in Sources */, @@ -1388,6 +1400,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1429,6 +1442,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1554,6 +1568,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; TARGETED_DEVICE_FAMILY = "1,2"; @@ -1633,6 +1648,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; @@ -1712,6 +1728,7 @@ GCC_WARN_UNUSED_VARIABLE = YES; IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; + OTHER_CFLAGS = "-DMINIMAL_ASDK"; SDKROOT = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index f568be7cc9..e65b00a6be 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 5 + 6 DisplayMac.xcscheme @@ -17,7 +17,7 @@ DisplayTests.xcscheme orderHint - 7 + 8 SuppressBuildableAutocreation diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index b3643e8b07..f6f3532784 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -12,7 +12,7 @@ open class ActionSheetController: ViewController { public init(theme: ActionSheetControllerTheme) { self.theme = theme - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 90830cbe5a..76a8227cc7 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -79,8 +79,11 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { var insets = layout.insets(options: [.statusBar]) - insets.left += layout.safeInsets.left - insets.right += layout.safeInsets.right + + let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + + insets.left = floor((layout.size.width - containerWidth) / 2.0) + insets.right = insets.left self.validLayout = layout @@ -91,7 +94,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { self.itemGroupsContainerNode.frame = CGRect(origin: CGPoint(x: insets.left + containerInsets.left, y: layout.size.height - insets.bottom - containerInsets.bottom - self.itemGroupsContainerNode.calculatedSize.height), size: self.itemGroupsContainerNode.calculatedSize) self.itemGroupsContainerNode.layout() - self.updateScrollDimViews(size: layout.size, safeInsets: layout.safeInsets) + self.updateScrollDimViews(size: layout.size, insets: insets) } func animateIn() { @@ -141,8 +144,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } func scrollViewDidScroll(_ scrollView: UIScrollView) { - if let validLayout = self.validLayout { - self.updateScrollDimViews(size: validLayout.size, safeInsets: validLayout.safeInsets) + if let layout = self.validLayout { + var insets = layout.insets(options: [.statusBar]) + + let containerWidth = horizontalContainerFillingSizeForLayout(layout: layout, sideInset: layout.safeInsets.left) + + insets.left = floor((layout.size.width - containerWidth) / 2.0) + insets.right = insets.left + + self.updateScrollDimViews(size: layout.size, insets: insets) } } @@ -155,15 +165,15 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { } } - func updateScrollDimViews(size: CGSize, safeInsets: UIEdgeInsets) { + func updateScrollDimViews(size: CGSize, insets: UIEdgeInsets) { let additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y) let additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y) - self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) - self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) + self.topDimView.frame = CGRect(x: containerInsets.left + insets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right - insets.left - insets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight)) + self.bottomDimView.frame = CGRect(x: containerInsets.left + insets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right - insets.left - insets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight)) - self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left + safeInsets.left, height: size.height + additionalTopHeight + additionalBottomHeight) - self.rightDimView.frame = CGRect(x: size.width - containerInsets.right, y: -additionalTopHeight, width: containerInsets.right + safeInsets.right, height: size.height + additionalTopHeight + additionalBottomHeight) + self.leftDimView.frame = CGRect(x: 0.0, y: -additionalTopHeight, width: containerInsets.left + insets.left, height: size.height + additionalTopHeight + additionalBottomHeight) + self.rightDimView.frame = CGRect(x: size.width - containerInsets.right - insets.right, y: -additionalTopHeight, width: containerInsets.right + insets.right, height: size.height + additionalTopHeight + additionalBottomHeight) } func setGroups(_ groups: [ActionSheetItemGroup]) { diff --git a/Display/AlertController.swift b/Display/AlertController.swift index c8a1db2987..2f467f6f3a 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -33,7 +33,7 @@ open class AlertController: ViewController { self.theme = theme self.contentNode = contentNode - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) self.statusBar.statusBarStyle = .Ignore } diff --git a/Display/CATracingLayer.h b/Display/CATracingLayer.h index 0ca59876d3..47d364fb0d 100644 --- a/Display/CATracingLayer.h +++ b/Display/CATracingLayer.h @@ -9,8 +9,9 @@ @property (nonatomic, readonly) bool shouldBeAdjustedToInverseTransform; @property (nonatomic, weak, readonly) id _Nullable userData; @property (nonatomic, readonly) int32_t tracingTag; +@property (nonatomic, readonly) int32_t disableChildrenTracingTags; -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag; +- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags; @end diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 65389bdfd5..78f4fd1827 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -50,6 +50,9 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull [array addObject:sublayer]; hadTraceableSublayers = true; } + if (sublayerTraceableInfo.disableChildrenTracingTags & tracingTag) { + return; + } } if (!skipIfNoTraceableSublayers || !hadTraceableSublayers) { @@ -347,12 +350,13 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull @implementation CATracingLayerInfo -- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag { +- (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags { self = [super init]; if (self != nil) { _shouldBeAdjustedToInverseTransform = shouldBeAdjustedToInverseTransform; _userData = userData; _tracingTag = tracingTag; + _disableChildrenTracingTags = disableChildrenTracingTags; } return self; } diff --git a/Display/ContainedViewLayoutTransition.swift b/Display/ContainedViewLayoutTransition.swift index a63dc7e556..1a9d824924 100644 --- a/Display/ContainedViewLayoutTransition.swift +++ b/Display/ContainedViewLayoutTransition.swift @@ -233,23 +233,39 @@ public extension ContainedViewLayoutTransition { } } - func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat) { + func animatePositionAdditive(node: ASDisplayNode, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) { switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + node.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) } } - func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, completion: (() -> Void)? = nil) { + func animatePositionAdditive(layer: CALayer, offset: CGFloat, removeOnCompletion: Bool = true, completion: @escaping (Bool) -> Void) { + switch self { + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: completion) + } + } + + func animatePositionAdditive(node: ASDisplayNode, offset: CGPoint, removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) { switch self { case .immediate: break @@ -261,27 +277,27 @@ public extension ContainedViewLayoutTransition { case .spring: timingFunction = kCAMediaTimingFunctionSpring } - node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in + node.layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in completion?() }) } } - func animatePositionAdditive(layer: CALayer, offset: CGPoint, completion: (() -> Void)? = nil) { + func animatePositionAdditive(layer: CALayer, offset: CGPoint, to toOffset: CGPoint = CGPoint(), removeOnCompletion: Bool = true, completion: (() -> Void)? = nil) { switch self { - case .immediate: - break - case let .animated(duration, curve): - let timingFunction: String - switch curve { - case .easeInOut: - timingFunction = kCAMediaTimingFunctionEaseInEaseOut - case .spring: - timingFunction = kCAMediaTimingFunctionSpring - } - layer.animatePosition(from: offset, to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true, completion: { _ in - completion?() - }) + case .immediate: + break + case let .animated(duration, curve): + let timingFunction: String + switch curve { + case .easeInOut: + timingFunction = kCAMediaTimingFunctionEaseInEaseOut + case .spring: + timingFunction = kCAMediaTimingFunctionSpring + } + layer.animatePosition(from: offset, to: toOffset, duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: true, completion: { _ in + completion?() + }) } } diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift index 9fa6eb662d..732968f662 100644 --- a/Display/ContextMenuController.swift +++ b/Display/ContextMenuController.swift @@ -25,7 +25,7 @@ public final class ContextMenuController: ViewController { self.actions = actions self.catchTapsOutside = catchTapsOutside - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift index a20d03af4d..9ec3f64072 100644 --- a/Display/ContextMenuNode.swift +++ b/Display/ContextMenuNode.swift @@ -200,7 +200,7 @@ final class ContextMenuNode: ASDisplayNode { } self.arrowOnBottom = arrowOnBottom - let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) + let horizontalOrigin: CGFloat = floor(min(max(sourceRect.minX + 8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0)) self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) diff --git a/Display/ImmediateTextNode.swift b/Display/ImmediateTextNode.swift index c25c5b63fc..842eb7a01d 100644 --- a/Display/ImmediateTextNode.swift +++ b/Display/ImmediateTextNode.swift @@ -5,11 +5,93 @@ public class ImmediateTextNode: TextNode { public var textAlignment: NSTextAlignment = .natural public var maximumNumberOfLines: Int = 1 public var lineSpacing: CGFloat = 0.0 + public var insets: UIEdgeInsets = UIEdgeInsets() + + private var tapRecognizer: TapLongTapOrDoubleTapGestureRecognizer? + private var linkHighlightingNode: LinkHighlightingNode? + + public var linkHighlightColor: UIColor? + + public var highlightAttributeAction: (([NSAttributedStringKey: Any]) -> NSAttributedStringKey?)? { + didSet { + if self.isNodeLoaded { + self.updateInteractiveActions() + } + } + } + + public var tapAttributeAction: (([NSAttributedStringKey: Any]) -> Void)? public func updateLayout(_ constrainedSize: CGSize) -> CGSize { let makeLayout = TextNode.asyncLayout(self) - let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: UIEdgeInsets())) + let (layout, apply) = makeLayout(TextNodeLayoutArguments(attributedString: self.attributedText, backgroundColor: nil, maximumNumberOfLines: self.maximumNumberOfLines, truncationType: .end, constrainedSize: constrainedSize, alignment: self.textAlignment, lineSpacing: self.lineSpacing, cutout: nil, insets: self.insets)) let _ = apply() return layout.size } + + override public func didLoad() { + super.didLoad() + + self.updateInteractiveActions() + } + + private func updateInteractiveActions() { + if self.highlightAttributeAction != nil { + if self.tapRecognizer == nil { + let tapRecognizer = TapLongTapOrDoubleTapGestureRecognizer(target: self, action: #selector(self.tapAction(_:))) + tapRecognizer.highlight = { [weak self] point in + if let strongSelf = self { + var rects: [CGRect]? + if let point = point { + if let (index, attributes) = strongSelf.attributesAtPoint(CGPoint(x: point.x, y: point.y)) { + if let selectedAttribute = strongSelf.highlightAttributeAction?(attributes) { + rects = strongSelf.attributeRects(name: selectedAttribute.rawValue, at: index) + } + } + } + + if let rects = rects { + let linkHighlightingNode: LinkHighlightingNode + if let current = strongSelf.linkHighlightingNode { + linkHighlightingNode = current + } else { + linkHighlightingNode = LinkHighlightingNode(color: strongSelf.linkHighlightColor ?? .clear) + strongSelf.linkHighlightingNode = linkHighlightingNode + strongSelf.addSubnode(linkHighlightingNode) + } + linkHighlightingNode.frame = strongSelf.bounds + linkHighlightingNode.updateRects(rects.map { $0.offsetBy(dx: 0.0, dy: -3.0) }) + } else if let linkHighlightingNode = strongSelf.linkHighlightingNode { + strongSelf.linkHighlightingNode = nil + linkHighlightingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.18, removeOnCompletion: false, completion: { [weak linkHighlightingNode] _ in + linkHighlightingNode?.removeFromSupernode() + }) + } + } + } + self.view.addGestureRecognizer(tapRecognizer) + } + } else if let tapRecognizer = self.tapRecognizer { + self.tapRecognizer = nil + self.view.removeGestureRecognizer(tapRecognizer) + } + } + + @objc private func tapAction(_ recognizer: TapLongTapOrDoubleTapGestureRecognizer) { + switch recognizer.state { + case .ended: + if let (gesture, location) = recognizer.lastRecognizedGestureAndLocation { + switch gesture { + case .tap: + if let (_, attributes) = self.attributesAtPoint(CGPoint(x: location.x, y: location.y)) { + self.tapAttributeAction?(attributes) + } + default: + break + } + } + default: + break + } + } } diff --git a/Display/LayoutSizes.swift b/Display/LayoutSizes.swift new file mode 100644 index 0000000000..d03f8bb1aa --- /dev/null +++ b/Display/LayoutSizes.swift @@ -0,0 +1,9 @@ +import Foundation + +public func horizontalContainerFillingSizeForLayout(layout: ContainerViewLayout, sideInset: CGFloat) -> CGFloat { + if case .regular = layout.metrics.widthClass { + return min(layout.size.width, 414.0) - sideInset * 2.0 + } else { + return layout.size.width - sideInset * 2.0 + } +} diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index cf53e6d78b..43e2775fd6 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -33,7 +33,7 @@ open class LegacyPresentedController: ViewController { self.legacyController = legacyController self.presentation = presentation - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) /*legacyController.navigation_setDismiss { [weak self] in self?.dismiss() diff --git a/Display/LinkHighlightingNode.swift b/Display/LinkHighlightingNode.swift new file mode 100644 index 0000000000..df0420976f --- /dev/null +++ b/Display/LinkHighlightingNode.swift @@ -0,0 +1,237 @@ +import Foundation +import AsyncDisplayKit +import Display + +private enum CornerType { + case topLeft + case topRight + case bottomLeft + case bottomRight +} + +private func drawFullCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) { + context.setFillColor(color.cgColor) + switch type { + case .topLeft: + context.clear(CGRect(origin: point, size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: point, size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .topRight: + context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomLeft: + context.clear(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomRight: + context.clear(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + } +} + +private func drawConnectingCorner(context: CGContext, color: UIColor, at point: CGPoint, type: CornerType, radius: CGFloat) { + context.setFillColor(color.cgColor) + switch type { + case .topLeft: + context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .topRight: + context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomLeft: + context.fill(CGRect(origin: CGPoint(x: point.x - radius, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x - radius * 2.0, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + case .bottomRight: + context.fill(CGRect(origin: CGPoint(x: point.x, y: point.y - radius), size: CGSize(width: radius, height: radius))) + context.setFillColor(UIColor.clear.cgColor) + context.fillEllipse(in: CGRect(origin: CGPoint(x: point.x, y: point.y - radius * 2.0), size: CGSize(width: radius * 2.0, height: radius * 2.0))) + } +} + +private func generateRectsImage(color: UIColor, rects: [CGRect], inset: CGFloat, outerRadius: CGFloat, innerRadius: CGFloat) -> (CGPoint, UIImage?) { + if rects.isEmpty { + return (CGPoint(), nil) + } + + var topLeft = rects[0].origin + var bottomRight = CGPoint(x: rects[0].maxX, y: rects[0].maxY) + for i in 1 ..< rects.count { + topLeft.x = min(topLeft.x, rects[i].origin.x) + topLeft.y = min(topLeft.y, rects[i].origin.y) + bottomRight.x = max(bottomRight.x, rects[i].maxX) + bottomRight.y = max(bottomRight.y, rects[i].maxY) + } + + topLeft.x -= inset + topLeft.y -= inset + bottomRight.x += inset * 2.0 + bottomRight.y += inset * 2.0 + + return (topLeft, generateImage(CGSize(width: bottomRight.x - topLeft.x, height: bottomRight.y - topLeft.y), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setFillColor(color.cgColor) + + context.setBlendMode(.copy) + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset) + context.fill(rect.offsetBy(dx: -topLeft.x, dy: -topLeft.y)) + } + + for i in 0 ..< rects.count { + let rect = rects[i].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + + var previous: CGRect? + if i != 0 { + previous = rects[i - 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + var next: CGRect? + if i != rects.count - 1 { + next = rects[i + 1].insetBy(dx: -inset, dy: -inset).offsetBy(dx: -topLeft.x, dy: -topLeft.y) + } + + if let previous = previous { + if previous.contains(rect.topLeft) { + if abs(rect.topLeft.x - previous.minX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topLeft.x, y: previous.maxY), type: .topLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + } + if previous.contains(rect.topRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.topRight.x - previous.maxX) >= innerRadius { + var radius = innerRadius + if let next = next { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.topRight.x, y: previous.maxY), type: .topRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.topLeft, type: .topLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.topRight, type: .topRight, radius: outerRadius) + } + + if let next = next { + if next.contains(rect.bottomLeft) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomLeft.x, y: next.minY), type: .bottomLeft, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + } + if next.contains(rect.bottomRight.offsetBy(dx: -1.0, dy: 0.0)) { + if abs(rect.bottomRight.x - next.maxX) >= innerRadius { + var radius = innerRadius + if let previous = previous { + radius = min(radius, floor((next.minY - previous.maxY) / 2.0)) + } + drawConnectingCorner(context: context, color: color, at: CGPoint(x: rect.bottomRight.x, y: next.minY), type: .bottomRight, radius: radius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } else { + drawFullCorner(context: context, color: color, at: rect.bottomLeft, type: .bottomLeft, radius: outerRadius) + drawFullCorner(context: context, color: color, at: rect.bottomRight, type: .bottomRight, radius: outerRadius) + } + } + })) + +} + +public final class LinkHighlightingNode: ASDisplayNode { + private var rects: [CGRect] = [] + private let imageNode: ASImageNode + + public var innerRadius: CGFloat = 4.0 + public var outerRadius: CGFloat = 4.0 + public var inset: CGFloat = 2.0 + + private var _color: UIColor + public var color: UIColor { + get { + return _color + } set(value) { + self._color = value + if !self.rects.isEmpty { + self.updateImage() + } + } + } + + public init(color: UIColor) { + self._color = color + + self.imageNode = ASImageNode() + self.imageNode.isLayerBacked = true + self.imageNode.displaysAsynchronously = false + self.imageNode.displayWithoutProcessing = true + + super.init() + + self.addSubnode(self.imageNode) + } + + public func updateRects(_ rects: [CGRect]) { + if self.rects != rects { + self.rects = rects + + self.updateImage() + } + } + + private func updateImage() { + if rects.isEmpty { + self.imageNode.image = nil + } + let (offset, image) = generateRectsImage(color: self.color, rects: self.rects, inset: self.inset, outerRadius: self.outerRadius, innerRadius: self.innerRadius) + + if let image = image { + self.imageNode.image = image + self.imageNode.frame = CGRect(origin: offset, size: image.size) + } + } + + public func asyncLayout() -> (UIColor, [CGRect], CGFloat, CGFloat, CGFloat) -> () -> Void { + let currentRects = self.rects + let currentColor = self._color + let currentInnerRadius = self.innerRadius + let currentOuterRadius = self.outerRadius + let currentInset = self.inset + + return { [weak self] color, rects, innerRadius, outerRadius, inset in + var updatedImage: (CGPoint, UIImage?)? + if currentRects != rects || !currentColor.isEqual(color) || currentInnerRadius != innerRadius || currentOuterRadius != outerRadius || currentInset != inset { + updatedImage = generateRectsImage(color: color, rects: rects, inset: inset, outerRadius: outerRadius, innerRadius: innerRadius) + } + + return { + if let strongSelf = self { + strongSelf._color = color + strongSelf.rects = rects + strongSelf.innerRadius = innerRadius + strongSelf.outerRadius = outerRadius + strongSelf.inset = inset + + if let (offset, maybeImage) = updatedImage, let image = maybeImage { + strongSelf.imageNode.image = image + strongSelf.imageNode.frame = CGRect(origin: offset, size: image.size) + } + } + } + } + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index 1218fcc63c..8974ca3b54 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -951,7 +951,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var lowestOverlayNode: ListViewItemNode? for itemNode in self.itemNodes { - if itemNode.isHighligtedInOverlay { + if itemNode.isHighlightedInOverlay { lowestOverlayNode = itemNode itemNode.view.superview?.bringSubview(toFront: itemNode.view) } @@ -2472,7 +2472,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateScroller(transition: headerNodesTransition.0) if let topItemOverscrollBackground = self.topItemOverscrollBackground { - headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) } self.setNeedsAnimations() @@ -2496,7 +2496,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateScroller(transition: headerNodesTransition.0) if let topItemOverscrollBackground = self.topItemOverscrollBackground { - headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: -headerNodesTransition.2) + headerNodesTransition.0.animatePositionAdditive(node: topItemOverscrollBackground, offset: CGPoint(x: 0.0, y: -headerNodesTransition.2)) } self.updateVisibleContentOffset() diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 8260768d83..1b41f683a4 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -72,7 +72,7 @@ open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? - public var isHighligtedInOverlay: Bool = false + public var isHighlightedInOverlay: Bool = false public private(set) var accessoryItemNode: ListViewAccessoryItemNode? diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index 1fd46152f3..602098ce4d 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -38,6 +38,26 @@ public final class NavigationBarTheme { } } +public final class NavigationBarStrings { + public let back: String + public let close: String + + public init(back: String, close: String) { + self.back = back + self.close = close + } +} + +public final class NavigationBarPresentationData { + public let theme: NavigationBarTheme + public let strings: NavigationBarStrings + + public init(theme: NavigationBarTheme, strings: NavigationBarStrings) { + self.theme = theme + self.strings = strings + } +} + private func backArrowImage(color: UIColor) -> UIImage? { var red: CGFloat = 0.0 var green: CGFloat = 0.0 @@ -58,8 +78,30 @@ private func backArrowImage(color: UIColor) -> UIImage? { } } +enum NavigationPreviousAction: Equatable { + case item(UINavigationItem) + case close + + static func ==(lhs: NavigationPreviousAction, rhs: NavigationPreviousAction) -> Bool { + switch lhs { + case let .item(lhsItem): + if case let .item(rhsItem) = rhs, lhsItem === rhsItem { + return true + } else { + return false + } + case .close: + if case .close = rhs { + return true + } else { + return false + } + } + } +} + open class NavigationBar: ASDisplayNode { - private var theme: NavigationBarTheme + private var presentationData: NavigationBarPresentationData private var validLayout: (CGSize, CGFloat, CGFloat)? private var requestedLayout: Bool = false @@ -201,7 +243,7 @@ open class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.bold(17.0), textColor: self.presentationData.theme.primaryTextColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -234,52 +276,59 @@ open class NavigationBar: ASDisplayNode { var previousItemListenerKey: Int? var previousItemBackListenerKey: Int? - var _previousItem: UINavigationItem? - var previousItem: UINavigationItem? { + var _previousItem: NavigationPreviousAction? + var previousItem: NavigationPreviousAction? { get { return self._previousItem } set(value) { - if let previousValue = self._previousItem { - if let previousItemListenerKey = self.previousItemListenerKey { - previousValue.removeSetTitleListener(previousItemListenerKey) - self.previousItemListenerKey = nil - } - if let previousItemBackListenerKey = self.previousItemBackListenerKey { - previousValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) - self.previousItemBackListenerKey = nil - } - } - self._previousItem = value - - if let previousItem = value { - self.previousItemListenerKey = previousItem.addSetTitleListener { [weak self] _, _ in - if let strongSelf = self, let previousItem = strongSelf.previousItem { - if let backBarButtonItem = previousItem.backBarButtonItem { - strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") - } else { - strongSelf.backButtonNode.updateManualText(previousItem.title ?? "") - } - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() + if self._previousItem != value { + if let previousValue = self._previousItem, case let .item(itemValue) = previousValue { + if let previousItemListenerKey = self.previousItemListenerKey { + itemValue.removeSetTitleListener(previousItemListenerKey) + self.previousItemListenerKey = nil + } + if let previousItemBackListenerKey = self.previousItemBackListenerKey { + itemValue.removeSetBackBarButtonItemListener(previousItemBackListenerKey) + self.previousItemBackListenerKey = nil } } + self._previousItem = value - self.previousItemBackListenerKey = previousItem.addSetBackBarButtonItemListener { [weak self] _, _, _ in - if let strongSelf = self, let previousItem = strongSelf.previousItem { - if let backBarButtonItem = previousItem.backBarButtonItem { - strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") - } else { - strongSelf.backButtonNode.updateManualText(previousItem.title ?? "") - } - strongSelf.invalidateCalculatedLayout() - strongSelf.requestLayout() + if let previousItem = value { + switch previousItem { + case let .item(itemValue): + self.previousItemListenerKey = itemValue.addSetTitleListener { [weak self] _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { + if let backBarButtonItem = itemValue.backBarButtonItem { + strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") + } else { + strongSelf.backButtonNode.updateManualText(itemValue.title ?? "") + } + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + + self.previousItemBackListenerKey = itemValue.addSetBackBarButtonItemListener { [weak self] _, _, _ in + if let strongSelf = self, let previousItem = strongSelf.previousItem, case let .item(itemValue) = previousItem { + if let backBarButtonItem = itemValue.backBarButtonItem { + strongSelf.backButtonNode.updateManualText(backBarButtonItem.title ?? "") + } else { + strongSelf.backButtonNode.updateManualText(itemValue.title ?? "") + } + strongSelf.invalidateCalculatedLayout() + strongSelf.requestLayout() + } + } + case .close: + break } } + self.updateLeftButton(animated: false) + + self.invalidateCalculatedLayout() + self.requestLayout() } - self.updateLeftButton(animated: false) - - self.invalidateCalculatedLayout() - self.requestLayout() } } @@ -296,7 +345,14 @@ open class NavigationBar: ASDisplayNode { private func updateLeftButton(animated: Bool) { if let item = self.item { + var needsLeftButton = false if let leftBarButtonItem = item.leftBarButtonItem, !leftBarButtonItem.backButtonAppearance { + needsLeftButton = true + } else if let previousItem = self.previousItem, case .close = previousItem { + needsLeftButton = true + } + + if needsLeftButton { if animated { if self.leftButtonNode.view.superview != nil { if let snapshotView = self.leftButtonNode.view.snapshotContentTree() { @@ -343,7 +399,11 @@ open class NavigationBar: ASDisplayNode { self.backButtonArrow.removeFromSupernode() self.badgeNode.removeFromSupernode() - self.leftButtonNode.updateItems([leftBarButtonItem]) + if let leftBarButtonItem = item.leftBarButtonItem { + self.leftButtonNode.updateItems([leftBarButtonItem]) + } else { + self.leftButtonNode.updateItems([UIBarButtonItem(title: self.presentationData.strings.close, style: .plain, target: nil, action: nil)]) + } if self.leftButtonNode.supernode == nil { self.clippingNode.addSubnode(self.leftButtonNode) @@ -370,10 +430,15 @@ open class NavigationBar: ASDisplayNode { if let leftBarButtonItem = item.leftBarButtonItem, leftBarButtonItem.backButtonAppearance { backTitle = leftBarButtonItem.title } else if let previousItem = self.previousItem { - if let backBarButtonItem = previousItem.backBarButtonItem { - backTitle = backBarButtonItem.title ?? "Back" - } else { - backTitle = previousItem.title ?? "Back" + switch previousItem { + case let .item(itemValue): + if let backBarButtonItem = itemValue.backBarButtonItem { + backTitle = backBarButtonItem.title ?? self.presentationData.strings.back + } else { + backTitle = itemValue.title ?? self.presentationData.strings.back + } + case .close: + backTitle = nil } } @@ -483,7 +548,7 @@ open class NavigationBar: ASDisplayNode { if let value = value { switch value.role { case .top: - if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.theme.primaryTextColor) { + if let transitionTitleNode = value.navigationBar?.makeTransitionTitleNode(foregroundColor: self.presentationData.theme.primaryTextColor) { self.transitionTitleNode = transitionTitleNode if self.leftButtonNode.supernode != nil { self.clippingNode.insertSubnode(transitionTitleNode, belowSubnode: self.leftButtonNode) @@ -494,11 +559,11 @@ open class NavigationBar: ASDisplayNode { } } case .bottom: - if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.theme.buttonColor) { + if let transitionBackButtonNode = value.navigationBar?.makeTransitionBackButtonNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackButtonNode = transitionBackButtonNode self.clippingNode.addSubnode(transitionBackButtonNode) } - if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.theme.buttonColor) { + if let transitionBackArrowNode = value.navigationBar?.makeTransitionBackArrowNode(accentColor: self.presentationData.theme.buttonColor) { self.transitionBackArrowNode = transitionBackArrowNode self.clippingNode.addSubnode(transitionBackArrowNode) } @@ -520,13 +585,13 @@ open class NavigationBar: ASDisplayNode { private var transitionBackArrowNode: ASDisplayNode? private var transitionBadgeNode: ASDisplayNode? - public init(theme: NavigationBarTheme) { - self.theme = theme + public init(presentationData: NavigationBarPresentationData) { + self.presentationData = presentationData self.stripeNode = ASDisplayNode() self.titleNode = ASTextNode() self.backButtonNode = NavigationButtonNode() - self.badgeNode = NavigationBarBadgeNode(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor) + self.badgeNode = NavigationBarBadgeNode(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) self.badgeNode.isUserInteractionEnabled = false self.badgeNode.isHidden = true self.backButtonArrow = ASImageNode() @@ -538,20 +603,20 @@ open class NavigationBar: ASDisplayNode { self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true - self.backButtonNode.color = self.theme.buttonColor - self.leftButtonNode.color = self.theme.buttonColor - self.rightButtonNode.color = self.theme.buttonColor - self.backButtonArrow.image = backArrowImage(color: self.theme.buttonColor) + self.backButtonNode.color = self.presentationData.theme.buttonColor + self.leftButtonNode.color = self.presentationData.theme.buttonColor + self.rightButtonNode.color = self.presentationData.theme.buttonColor + self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) } - self.stripeNode.backgroundColor = self.theme.separatorColor + self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor super.init() self.addSubnode(self.clippingNode) - self.backgroundColor = self.theme.backgroundColor + self.backgroundColor = self.presentationData.theme.backgroundColor self.stripeNode.isLayerBacked = true self.stripeNode.displaysAsynchronously = false @@ -580,8 +645,10 @@ open class NavigationBar: ASDisplayNode { self.leftButtonNode.pressed = { [weak self] index in if let item = self?.item { if index == 0 { - if let leftBarButtonItem = item.leftBarButtonItem { - leftBarButtonItem.performActionOnTarget() + if let leftBarButtonItem = item.leftBarButtonItem { + leftBarButtonItem.performActionOnTarget() + } else if let previousItem = self?.previousItem, case .close = previousItem { + self?.backPressed() } } } @@ -600,22 +667,22 @@ open class NavigationBar: ASDisplayNode { } } - public func updateTheme(_ theme: NavigationBarTheme) { - if theme !== self.theme { - self.theme = theme + public func updatePresentationData(_ presentationData: NavigationBarPresentationData) { + if presentationData.theme !== self.presentationData.theme || presentationData.strings !== self.presentationData.strings { + self.presentationData = presentationData - self.backgroundColor = self.theme.backgroundColor + self.backgroundColor = self.presentationData.theme.backgroundColor - self.backButtonNode.color = self.theme.buttonColor - self.leftButtonNode.color = self.theme.buttonColor - self.rightButtonNode.color = self.theme.buttonColor - self.backButtonArrow.image = backArrowImage(color: self.theme.buttonColor) + self.backButtonNode.color = self.presentationData.theme.buttonColor + self.leftButtonNode.color = self.presentationData.theme.buttonColor + self.rightButtonNode.color = self.presentationData.theme.buttonColor + self.backButtonArrow.image = backArrowImage(color: self.presentationData.theme.buttonColor) if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.presentationData.theme.primaryTextColor) } - self.stripeNode.backgroundColor = self.theme.separatorColor + self.stripeNode.backgroundColor = self.presentationData.theme.separatorColor - self.badgeNode.updateTheme(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor) + self.badgeNode.updateTheme(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) } } @@ -649,11 +716,11 @@ open class NavigationBar: ASDisplayNode { let nominalHeight: CGFloat = self.collapsed ? 32.0 : 44.0 let contentVerticalOrigin = size.height - nominalHeight - var leftTitleInset: CGFloat = leftInset + 4.0 - var rightTitleInset: CGFloat = rightInset + 4.0 + var leftTitleInset: CGFloat = leftInset + 1.0 + var rightTitleInset: CGFloat = rightInset + 1.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) - leftTitleInset += backButtonSize.width + backButtonInset + 4.0 + 4.0 + leftTitleInset += backButtonSize.width + backButtonInset + 1.0 let topHitTestSlop = (nominalHeight - backButtonSize.height) * 0.5 self.backButtonNode.hitTestSlop = UIEdgeInsetsMake(-topHitTestSlop, -27.0, -topHitTestSlop, -8.0) @@ -698,7 +765,7 @@ open class NavigationBar: ASDisplayNode { } } else if self.leftButtonNode.supernode != nil { let leftButtonSize = self.leftButtonNode.updateLayout(constrainedSize: CGSize(width: size.width, height: nominalHeight)) - leftTitleInset += leftButtonSize.width + leftButtonInset + 8.0 + 8.0 + leftTitleInset += leftButtonSize.width + leftButtonInset + 1.0 self.leftButtonNode.alpha = 1.0 self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize) @@ -710,7 +777,7 @@ open class NavigationBar: ASDisplayNode { if self.rightButtonNode.supernode != nil { let rightButtonSize = self.rightButtonNode.updateLayout(constrainedSize: (CGSize(width: size.width, height: nominalHeight))) - rightTitleInset += rightButtonSize.width + leftButtonInset + 8.0 + 8.0 + rightTitleInset += rightButtonSize.width + leftButtonInset + 1.0 self.rightButtonNode.alpha = 1.0 self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize) } @@ -783,7 +850,14 @@ open class NavigationBar: ASDisplayNode { if let titleView = self.titleView { let titleSize = CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight) - titleView.frame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) + let titleFrame = CGRect(origin: CGPoint(x: leftTitleInset, y: contentVerticalOrigin), size: titleSize) + titleView.frame = titleFrame + + if let titleView = titleView as? NavigationBarTitleView { + let titleWidth = size.width - leftTitleInset - rightTitleInset + + titleView.updateLayout(size: titleFrame.size, clearBounds: CGRect(origin: CGPoint(x: leftTitleInset - titleFrame.minX, y: 0.0), size: CGSize(width: titleWidth, height: titleFrame.height)), transition: transition) + } if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { let progress = transitionState.progress @@ -861,7 +935,7 @@ open class NavigationBar: ASDisplayNode { private func makeTransitionBadgeNode() -> ASDisplayNode? { if self.badgeNode.supernode != nil && !self.badgeNode.isHidden { - let node = NavigationBarBadgeNode(fillColor: self.theme.badgeBackgroundColor, strokeColor: self.theme.badgeStrokeColor, textColor: self.theme.badgeTextColor) + let node = NavigationBarBadgeNode(fillColor: self.presentationData.theme.badgeBackgroundColor, strokeColor: self.presentationData.theme.badgeStrokeColor, textColor: self.presentationData.theme.badgeTextColor) node.text = self.badgeNode.text let nodeSize = node.measure(CGSize(width: 200.0, height: 100.0)) node.frame = CGRect(origin: CGPoint(), size: nodeSize) diff --git a/Display/NavigationBarTitleView.swift b/Display/NavigationBarTitleView.swift index 630e23606e..ee8fa822e5 100644 --- a/Display/NavigationBarTitleView.swift +++ b/Display/NavigationBarTitleView.swift @@ -3,4 +3,6 @@ import UIKit public protocol NavigationBarTitleView { func animateLayoutTransition() + + func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition) } diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 6cf72c003d..ac34943e2d 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -3,9 +3,48 @@ import UIKit import AsyncDisplayKit import SwiftSignalKit -private class NavigationControllerView: UIView { +public final class NavigationControllerTheme { + public let navigationBar: NavigationBarTheme + public let emptyAreaColor: UIColor + public let emptyDetailIcon: UIImage? + + public init(navigationBar: NavigationBarTheme, emptyAreaColor: UIColor, emptyDetailIcon: UIImage?) { + self.navigationBar = navigationBar + self.emptyAreaColor = emptyAreaColor + self.emptyDetailIcon = emptyDetailIcon + } +} + +private final class NavigationControllerContainerView: UIView { + override class var layerClass: AnyClass { + return CATracingLayer.self + } +} + +private final class NavigationControllerView: UIView { var inTransition = false + let sharedStatusBar: StatusBar + let containerView: NavigationControllerContainerView + let separatorView: UIView + var navigationBackgroundView: UIView? + var navigationSeparatorView: UIView? + var emptyDetailView: UIImageView? + + override init(frame: CGRect) { + self.containerView = NavigationControllerContainerView() + self.separatorView = UIView() + self.sharedStatusBar = StatusBar() + + super.init(frame: frame) + + self.addSubview(self.containerView) + } + + required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + override class var layerClass: AnyClass { return CATracingLayer.self } @@ -18,9 +57,40 @@ private class NavigationControllerView: UIView { } } +private enum ControllerTransition { + case none + case appearance +} + +private final class ControllerRecord { + let controller: UIViewController + var transition: ControllerTransition = .none + + init(controller: UIViewController) { + self.controller = controller + } +} + +private enum ControllerLayoutConfiguration { + case single + case masterDetail +} + +public enum NavigationControllerMode { + case single + case automaticMasterDetail +} + open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate { + private let mode: NavigationControllerMode + private var theme: NavigationControllerTheme + public private(set) weak var overlayPresentingController: ViewController? + private var controllerView: NavigationControllerView { + return self.view as! NavigationControllerView + } + private var validLayout: ContainerViewLayout? private var navigationTransitionCoordinator: NavigationTransitionCoordinator? @@ -28,35 +98,33 @@ open class NavigationController: UINavigationController, ContainableController, private var currentPushDisposable = MetaDisposable() private var currentPresentDisposable = MetaDisposable() - private var statusBarChangeObserver: AnyObject? - - //private var layout: NavigationControllerLayout? - //private var pendingLayout: (NavigationControllerLayout, Double, Bool)? - private var _presentedViewController: UIViewController? open override var presentedViewController: UIViewController? { return self._presentedViewController } - private var _viewControllers: [UIViewController] = [] + private var _viewControllers: [ControllerRecord] = [] open override var viewControllers: [UIViewController] { get { - return self._viewControllers + return self._viewControllers.map { $0.controller } } set(value) { - self.setViewControllers(_viewControllers, animated: false) + self.setViewControllers(value, animated: false) } } open override var topViewController: UIViewController? { - return self._viewControllers.last + return self._viewControllers.last?.controller } - public init() { + public init(mode: NavigationControllerMode, theme: NavigationControllerTheme) { + self.mode = mode + self.theme = theme + super.init(nibName: nil, bundle: nil) } public override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { - super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + preconditionFailure() } public required init(coder aDecoder: NSCoder) { @@ -68,6 +136,384 @@ open class NavigationController: UINavigationController, ContainableController, self.currentPresentDisposable.dispose() } + public func updateTheme(_ theme: NavigationControllerTheme) { + self.theme = theme + if self.isViewLoaded { + self.controllerView.backgroundColor = theme.emptyAreaColor + self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor + self.controllerView.navigationBackgroundView?.backgroundColor = theme.navigationBar.backgroundColor + self.controllerView.navigationSeparatorView?.backgroundColor = theme.navigationBar.separatorColor + if let emptyDetailView = self.controllerView.emptyDetailView { + emptyDetailView.image = theme.emptyDetailIcon + if let image = theme.emptyDetailIcon { + emptyDetailView.frame = CGRect(origin: CGPoint(x: floor((self.controllerView.containerView.bounds.size.width - image.size.width) / 2.0), y: floor((self.controllerView.containerView.bounds.size.height - image.size.height) / 2.0)), size: image.size) + } + } + } + } + + private var previouslyLaidOutMasterController: UIViewController? + private var previouslyLaidOutTopController: UIViewController? + + private func layoutConfiguration(for layout: ContainerViewLayout) -> ControllerLayoutConfiguration { + switch self.mode { + case .single: + return .single + case .automaticMasterDetail: + if case .regular = layout.metrics.widthClass, case .regular = layout.metrics.heightClass { + if layout.size.width > 690.0 { + return .masterDetail + } + } + return .single + } + } + + private func layoutDataForConfiguration(_ layoutConfiguration: ControllerLayoutConfiguration, layout: ContainerViewLayout, index: Int) -> (CGRect, ContainerViewLayout) { + switch layoutConfiguration { + case .masterDetail: + let masterWidth: CGFloat = max(320.0, floor(layout.size.width / 3.0)) + let detailWidth: CGFloat = layout.size.width - masterWidth + if index == 0 { + return (CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: masterWidth, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: masterWidth, height: layout.size.height), metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + } else { + let detailFrame = CGRect(origin: CGPoint(x: masterWidth, y: 0.0), size: CGSize(width: detailWidth, height: layout.size.height)) + return (CGRect(origin: CGPoint(), size: detailFrame.size), ContainerViewLayout(size: CGSize(width: detailWidth, height: layout.size.height), metrics: LayoutMetrics(widthClass: .regular, heightClass: .regular), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + } + case .single: + return (CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: layout.size.height)), ContainerViewLayout(size: CGSize(width: layout.size.width, height: layout.size.height), metrics: LayoutMetrics(widthClass: .compact, heightClass: .compact), intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)) + } + } + + private func updateControllerLayouts(previousControllers: [ControllerRecord], layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var firstControllerFrameAndLayout: (CGRect, ContainerViewLayout)? + let lastControllerFrameAndLayout: (CGRect, ContainerViewLayout) + + let layoutConfiguration = self.layoutConfiguration(for: layout) + + switch layoutConfiguration { + case .masterDetail: + self.viewControllers.first?.view.clipsToBounds = true + self.controllerView.containerView.clipsToBounds = true + let masterData = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 0) + firstControllerFrameAndLayout = masterData + lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) + if self.controllerView.separatorView.superview == nil { + self.controllerView.addSubview(self.controllerView.separatorView) + } + + let navigationBackgroundFrame = CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: CGSize(width: lastControllerFrameAndLayout.0.width, height: (layout.statusBarHeight ?? 0.0) + 44.0)) + + if let navigationBackgroundView = self.controllerView.navigationBackgroundView, let navigationSeparatorView = self.controllerView.navigationSeparatorView, let emptyDetailView = self.controllerView.emptyDetailView { + transition.updateFrame(view: navigationBackgroundView, frame: navigationBackgroundFrame) + transition.updateFrame(view: navigationSeparatorView, frame: CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel))) + if let image = emptyDetailView.image { + transition.updateFrame(view: emptyDetailView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size)) + } + } else { + let navigationBackgroundView = UIView() + navigationBackgroundView.backgroundColor = self.theme.navigationBar.backgroundColor + let navigationSeparatorView = UIView() + navigationSeparatorView.backgroundColor = self.theme.navigationBar.separatorColor + let emptyDetailView = UIImageView() + emptyDetailView.image = self.theme.emptyDetailIcon + emptyDetailView.alpha = 0.0 + + self.controllerView.navigationBackgroundView = navigationBackgroundView + self.controllerView.navigationSeparatorView = navigationSeparatorView + self.controllerView.emptyDetailView = emptyDetailView + + self.controllerView.insertSubview(navigationBackgroundView, at: 0) + self.controllerView.insertSubview(navigationSeparatorView, at: 1) + self.controllerView.insertSubview(emptyDetailView, at: 2) + + navigationBackgroundView.frame = navigationBackgroundFrame + navigationSeparatorView.frame = CGRect(origin: CGPoint(x: navigationBackgroundFrame.minX, y: navigationBackgroundFrame.maxY), size: CGSize(width: navigationBackgroundFrame.width, height: UIScreenPixel)) + + transition.animatePositionAdditive(layer: navigationBackgroundView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0)) + transition.animatePositionAdditive(layer: navigationSeparatorView.layer, offset: CGPoint(x: navigationBackgroundFrame.width, y: 0.0)) + + if let image = emptyDetailView.image { + emptyDetailView.frame = CGRect(origin: CGPoint(x: masterData.0.maxX + floor((lastControllerFrameAndLayout.0.size.width - image.size.width) / 2.0), y: floor((lastControllerFrameAndLayout.0.size.height - image.size.height) / 2.0)), size: image.size) + } + + transition.updateAlpha(layer: emptyDetailView.layer, alpha: 1.0) + } + transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: masterData.0.maxX, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height))) + case .single: + self.viewControllers.first?.view.clipsToBounds = false + if let navigationBackgroundView = self.controllerView.navigationBackgroundView, let navigationSeparatorView = self.controllerView.navigationSeparatorView { + self.controllerView.navigationBackgroundView = nil + self.controllerView.navigationSeparatorView = nil + + transition.updatePosition(layer: navigationBackgroundView.layer, position: CGPoint(x: layout.size.width + navigationBackgroundView.bounds.size.width / 2.0, y: navigationBackgroundView.center.y), completion: { [weak navigationBackgroundView] _ in + navigationBackgroundView?.removeFromSuperview() + }) + transition.updatePosition(layer: navigationSeparatorView.layer, position: CGPoint(x: layout.size.width + navigationSeparatorView.bounds.size.width / 2.0, y: navigationSeparatorView.center.y), completion: { [weak navigationSeparatorView] _ in + navigationSeparatorView?.removeFromSuperview() + }) + if let emptyDetailView = self.controllerView.emptyDetailView { + self.controllerView.emptyDetailView = nil + transition.updateAlpha(layer: emptyDetailView.layer, alpha: 0.0, completion: { [weak emptyDetailView] _ in + emptyDetailView?.removeFromSuperview() + }) + } + } + self.controllerView.containerView.clipsToBounds = false + lastControllerFrameAndLayout = layoutDataForConfiguration(layoutConfiguration, layout: layout, index: 1) + transition.updateFrame(view: self.controllerView.separatorView, frame: CGRect(origin: CGPoint(x: -UIScreenPixel, y: 0.0), size: CGSize(width: UIScreenPixel, height: layout.size.height)), completion: { [weak self] completed in + if let strongSelf = self, completed { + strongSelf.controllerView.separatorView.removeFromSuperview() + } + }) + } + transition.updateFrame(view: self.controllerView.containerView, frame: CGRect(origin: CGPoint(x: firstControllerFrameAndLayout?.0.maxX ?? 0.0, y: 0.0), size: lastControllerFrameAndLayout.0.size)) + + switch layoutConfiguration { + case .single: + if self.controllerView.sharedStatusBar.view.superview != nil { + self.controllerView.sharedStatusBar.removeFromSupernode() + self.controllerView.containerView.layer.setTraceableInfo(nil) + } + case .masterDetail: + if self.controllerView.sharedStatusBar.view.superview == nil { + self.controllerView.addSubnode(self.controllerView.sharedStatusBar) + self.controllerView.containerView.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: 0, disableChildrenTracingTags: WindowTracingTags.statusBar | WindowTracingTags.keyboard)) + } + } + + if let _ = layout.statusBarHeight { + self.controllerView.sharedStatusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0)) + } + + var controllersAndFrames: [(Bool, ControllerRecord, ContainerViewLayout)] = [] + for i in 0 ..< self._viewControllers.count { + if let controller = self._viewControllers[i].controller as? ViewController { + if i == 0 { + controller.navigationBar?.previousItem = nil + } else if case .masterDetail = layoutConfiguration, i == 1 { + controller.navigationBar?.previousItem = .close + } else { + controller.navigationBar?.previousItem = .item(viewControllers[i - 1].navigationItem) + } + } + viewControllers[i].navigation_setNavigationController(self) + + if i == 0, let (_, layout) = firstControllerFrameAndLayout { + controllersAndFrames.append((true, self._viewControllers[i], layout)) + } else if i == self._viewControllers.count - 1 { + controllersAndFrames.append((false, self._viewControllers[i], lastControllerFrameAndLayout.1)) + } + } + + var masterController: UIViewController? + var appearingMasterController: ControllerRecord? + var appearingDetailController: ControllerRecord? + + for (isMaster, record, layout) in controllersAndFrames { + let frame: CGRect + if isMaster, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { + masterController = record.controller + frame = firstControllerFrameAndLayout.0 + } else { + frame = lastControllerFrameAndLayout.0 + } + let isAppearing = record.controller.view.superview == nil + (record.controller as? ViewController)?.containerLayoutUpdated(layout, transition: isAppearing ? .immediate : transition) + if isAppearing { + if isMaster { + appearingMasterController = record + } else { + appearingDetailController = record + } + } else if record.controller.view.superview !== (isMaster ? self.controllerView : self.controllerView.containerView) { + record.controller.setIgnoreAppearanceMethodInvocations(true) + if isMaster { + self.controllerView.insertSubview(record.controller.view, at: 0) + } else { + self.controllerView.containerView.addSubview(record.controller.view) + } + record.controller.setIgnoreAppearanceMethodInvocations(false) + } + if !isAppearing { + var isPartOfTransition = false + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if navigationTransitionCoordinator.topView == record.controller.view || navigationTransitionCoordinator.bottomView == record.controller.view { + isPartOfTransition = true + } + } + if !isPartOfTransition { + transition.updateFrame(view: record.controller.view, frame: frame) + } + } + } + + var animatedAppearingDetailController = false + + if let previousController = self.previouslyLaidOutTopController, !controllersAndFrames.contains(where: { $0.1.controller === previousController }), previousController.view.superview != nil { + if transition.isAnimated, let record = appearingDetailController { + animatedAppearingDetailController = true + + previousController.viewWillDisappear(true) + record.controller.setIgnoreAppearanceMethodInvocations(true) + self.controllerView.containerView.addSubview(record.controller.view) + record.controller.setIgnoreAppearanceMethodInvocations(false) + + if let _ = previousControllers.index(where: { $0.controller === record.controller }) { + //previousControllers[index].transition = .appearance + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: previousController.view, topNavigationBar: (previousController as? ViewController)?.navigationBar, bottomView: record.controller.view, bottomNavigationBar: (record.controller as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + self.controllerView.inTransition = true + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + strongSelf.navigationTransitionCoordinator = nil + strongSelf.controllerView.inTransition = false + + record.controller.viewDidAppear(true) + + previousController.setIgnoreAppearanceMethodInvocations(true) + previousController.view.removeFromSuperview() + previousController.setIgnoreAppearanceMethodInvocations(false) + previousController.viewDidDisappear(true) + } + }) + } else { + if let index = self._viewControllers.index(where: { $0.controller === previousController }) { + self._viewControllers[index].transition = .appearance + } + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.controllerView.containerView, topView: record.controller.view, topNavigationBar: (record.controller as? ViewController)?.navigationBar, bottomView: previousController.view, bottomNavigationBar: (previousController as? ViewController)?.navigationBar) + self.navigationTransitionCoordinator = navigationTransitionCoordinator + + self.controllerView.inTransition = true + navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in + if let strongSelf = self { + if let index = strongSelf._viewControllers.index(where: { $0.controller === previousController }) { + strongSelf._viewControllers[index].transition = .none + } + strongSelf.navigationTransitionCoordinator = nil + strongSelf.controllerView.inTransition = false + + record.controller.viewDidAppear(true) + + previousController.setIgnoreAppearanceMethodInvocations(true) + previousController.view.removeFromSuperview() + previousController.setIgnoreAppearanceMethodInvocations(false) + previousController.viewDidDisappear(true) + } + }) + } + } else { + previousController.viewWillDisappear(false) + previousController.view.removeFromSuperview() + previousController.viewDidDisappear(false) + } + } + + if !animatedAppearingDetailController, let record = appearingDetailController { + record.controller.viewWillAppear(false) + record.controller.setIgnoreAppearanceMethodInvocations(true) + self.controllerView.containerView.addSubview(record.controller.view) + record.controller.setIgnoreAppearanceMethodInvocations(false) + record.controller.viewDidAppear(false) + if let controller = record.controller as? ViewController { + controller.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + } + + if let record = appearingMasterController, let firstControllerFrameAndLayout = firstControllerFrameAndLayout { + record.controller.viewWillAppear(false) + record.controller.setIgnoreAppearanceMethodInvocations(true) + self.controllerView.insertSubview(record.controller.view, belowSubview: self.controllerView.containerView) + record.controller.setIgnoreAppearanceMethodInvocations(false) + record.controller.viewDidAppear(false) + if let controller = record.controller as? ViewController { + controller.displayNode.recursivelyEnsureDisplaySynchronously(true) + } + + record.controller.view.frame = firstControllerFrameAndLayout.0 + record.controller.viewDidAppear(transition.isAnimated) + transition.animatePositionAdditive(layer: record.controller.view.layer, offset: CGPoint(x: -firstControllerFrameAndLayout.0.width, y: 0.0)) + } + + for record in self._viewControllers { + let controller = record.controller + if case .none = record.transition, !controllersAndFrames.contains(where: { $0.1.controller === controller }) { + if controller === self.previouslyLaidOutMasterController { + controller.viewWillDisappear(true) + record.transition = .appearance + transition.animatePositionAdditive(layer: controller.view.layer, offset: CGPoint(), to: CGPoint(x: -controller.view.bounds.size.width, y: 0.0), removeOnCompletion: false, completion: { [weak self] in + if let strongSelf = self { + controller.setIgnoreAppearanceMethodInvocations(true) + controller.view.removeFromSuperview() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(true) + controller.view.layer.removeAllAnimations() + for r in strongSelf._viewControllers { + if r.controller === controller { + r.transition = .none + } + } + } + }) + } else { + if controller.isViewLoaded && controller.view.superview != nil { + var isPartOfTransition = false + if let navigationTransitionCoordinator = self.navigationTransitionCoordinator { + if navigationTransitionCoordinator.topView == controller.view || navigationTransitionCoordinator.bottomView == controller.view { + isPartOfTransition = true + } + } + + if !isPartOfTransition { + controller.viewWillDisappear(false) + controller.setIgnoreAppearanceMethodInvocations(true) + controller.view.removeFromSuperview() + controller.setIgnoreAppearanceMethodInvocations(false) + controller.viewDidDisappear(false) + } + } + } + } + } + + for previous in previousControllers { + var isFound = false + inner: for current in self._viewControllers { + if previous.controller === current.controller { + isFound = true + break inner + } + } + if !isFound { + (previous.controller as? ViewController)?.navigationStackConfigurationUpdated(next: []) + } + } + + for i in 0 ..< self._viewControllers.count { + var currentNext: UIViewController? = (i == (self._viewControllers.count - 1)) ? nil : self._viewControllers[i + 1].controller + if case .single = layoutConfiguration { + currentNext = nil + } + + var previousNext: UIViewController? + inner: for j in 0 ..< previousControllers.count { + if previousControllers[j].controller === self._viewControllers[i].controller { + previousNext = (j == (previousControllers.count - 1)) ? nil : previousControllers[j + 1].controller + break inner + } + } + + if currentNext !== previousNext { + let next = currentNext as? ViewController + (self._viewControllers[i].controller as? ViewController)?.navigationStackConfigurationUpdated(next: next == nil ? [] : [next!]) + } + } + + self.previouslyLaidOutMasterController = masterController + self.previouslyLaidOutTopController = self._viewControllers.last?.controller + } + public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { if !self.isViewLoaded { self.loadView() @@ -75,17 +521,11 @@ open class NavigationController: UINavigationController, ContainableController, self.validLayout = layout transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size)) - let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) - - if let topViewController = self.topViewController { - if let topViewController = topViewController as? ContainableController { - topViewController.containerLayoutUpdated(containedLayout, transition: transition) - } else { - transition.updateFrame(view: topViewController.view, frame: CGRect(x: 0.0, y: 0.0, width: layout.size.width, height: layout.size.height)) - } - } + self.updateControllerLayouts(previousControllers: self._viewControllers, layout: layout, transition: transition) if let presentedViewController = self.presentedViewController { + let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging) + if let presentedViewController = presentedViewController as? ContainableController { presentedViewController.containerLayoutUpdated(containedLayout, transition: transition) } else { @@ -103,6 +543,9 @@ open class NavigationController: UINavigationController, ContainableController, self.view.clipsToBounds = true self.view.autoresizingMask = [] + self.controllerView.backgroundColor = self.theme.emptyAreaColor + self.controllerView.separatorView.backgroundColor = theme.navigationBar.separatorColor + if #available(iOSApplicationExtension 11.0, *) { self.navigationBar.prefersLargeTitles = false } @@ -122,7 +565,26 @@ open class NavigationController: UINavigationController, ContainableController, @objc func panGesture(_ recognizer: UIPanGestureRecognizer) { switch recognizer.state { case UIGestureRecognizerState.began: - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { + guard let layout = self.validLayout else { + return + } + guard self.navigationTransitionCoordinator == nil else { + return + } + let beginGesture: Bool + switch self.layoutConfiguration(for: layout) { + case .masterDetail: + let location = recognizer.location(in: self.controllerView.containerView) + if self.controllerView.containerView.bounds.contains(location) { + beginGesture = self._viewControllers.count >= 3 + } else { + beginGesture = false + } + case .single: + beginGesture = self._viewControllers.count >= 2 + } + + if beginGesture { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -131,7 +593,7 @@ open class NavigationController: UINavigationController, ContainableController, bottomController.viewWillAppear(true) let bottomView = bottomController.view! - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) + let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.controllerView.containerView, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) self.navigationTransitionCoordinator = navigationTransitionCoordinator } case UIGestureRecognizerState.changed: @@ -147,9 +609,8 @@ open class NavigationController: UINavigationController, ContainableController, (self.view as! NavigationControllerView).inTransition = true navigationTransitionCoordinator.animateCompletion(velocity, completion: { (self.view as! NavigationControllerView).inTransition = false - self.navigationTransitionCoordinator = nil - //self._navigationBar.endInteractivePopProgress() + self.navigationTransitionCoordinator = nil if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController @@ -165,8 +626,7 @@ open class NavigationController: UINavigationController, ContainableController, bottomController.viewDidAppear(true) } }) - } - else { + } else { if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -180,8 +640,6 @@ open class NavigationController: UINavigationController, ContainableController, (self.view as! NavigationControllerView).inTransition = false self.navigationTransitionCoordinator = nil - //self._navigationBar.endInteractivePopProgress() - if self.viewControllers.count >= 2 && self.navigationTransitionCoordinator == nil { let topController = self.viewControllers[self.viewControllers.count - 1] as UIViewController let bottomController = self.viewControllers[self.viewControllers.count - 2] as UIViewController @@ -226,11 +684,15 @@ open class NavigationController: UINavigationController, ContainableController, self.view.endEditing(true) } if let validLayout = self.validLayout { - let appliedLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil) + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + + let appliedLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) controller.containerLayoutUpdated(appliedLayout, transition: .immediate) self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self, let validLayout = strongSelf.validLayout { - let containerLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil) + let (_, controllerLayout) = strongSelf.layoutDataForConfiguration(strongSelf.layoutConfiguration(for: validLayout), layout: validLayout, index: strongSelf.viewControllers.count) + + let containerLayout = controllerLayout.withUpdatedInputHeight(controller.hasActiveInput ? controllerLayout.inputHeight : nil) if containerLayout != appliedLayout { controller.containerLayoutUpdated(containerLayout, transition: .immediate) } @@ -253,7 +715,8 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) if let validLayout = self.validLayout { - controller.containerLayoutUpdated(validLayout, transition: .immediate) + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { @@ -269,7 +732,8 @@ open class NavigationController: UINavigationController, ContainableController, public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise? = nil) { self.view.endEditing(true) if let validLayout = self.validLayout { - controller.containerLayoutUpdated(validLayout, transition: .immediate) + let (_, controllerLayout) = self.layoutDataForConfiguration(self.layoutConfiguration(for: validLayout), layout: validLayout, index: self.viewControllers.count) + controller.containerLayoutUpdated(controllerLayout, transition: .immediate) } self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in if let strongSelf = self { @@ -324,132 +788,24 @@ open class NavigationController: UINavigationController, ContainableController, } open override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { + var resultControllers: [ControllerRecord] = [] for controller in viewControllers { - controller.navigation_setNavigationController(self) - } - - if viewControllers.count > 0 { - let topViewController = viewControllers[viewControllers.count - 1] as UIViewController - - if let controller = topViewController as? ContainableController { - if let validLayout = self.validLayout { - var layoutToApply = validLayout - var hasActiveInput = false - if let controller = controller as? ViewController { - hasActiveInput = controller.hasActiveInput - } - if !hasActiveInput { - layoutToApply = layoutToApply.withUpdatedInputHeight(nil) - } - controller.containerLayoutUpdated(layoutToApply, transition: .immediate) + var found = false + inner: for current in self._viewControllers { + if current.controller === controller { + resultControllers.append(current) + found = true + break inner } - } else { - topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size) + } + if !found { + resultControllers.append(ControllerRecord(controller: controller)) } } - - if animated && self.viewControllers.count != 0 && viewControllers.count != 0 && self.viewControllers.last! !== viewControllers.last! { - if self.viewControllers.contains(where: { $0 === viewControllers.last }) { - let bottomController = viewControllers.last! as UIViewController - let topController = self.viewControllers.last! as UIViewController - - if let bottomController = bottomController as? ViewController { - if viewControllers.count >= 2 { - bottomController.navigationBar?.previousItem = viewControllers[viewControllers.count - 2].navigationItem - } else { - bottomController.navigationBar?.previousItem = nil - } - } - - bottomController.viewWillAppear(true) - let bottomView = bottomController.view! - topController.viewWillDisappear(true) - let topView = topController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Pop, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - (strongSelf.view as! NavigationControllerView).inTransition = false - strongSelf.navigationTransitionCoordinator = nil - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - strongSelf.setViewControllers(viewControllers, animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.viewDidDisappear(true) - bottomController.viewDidAppear(true) - - topView.removeFromSuperview() - } - }) - } else { - let topController = viewControllers.last! as UIViewController - let bottomController = self.viewControllers.last! as UIViewController - - if let topController = topController as? ViewController { - topController.navigationBar?.previousItem = bottomController.navigationItem - } - - bottomController.viewWillDisappear(true) - let bottomView = bottomController.view! - topController.viewWillAppear(true) - let topView = topController.view! - - let navigationTransitionCoordinator = NavigationTransitionCoordinator(transition: .Push, container: self.view, topView: topView, topNavigationBar: (topController as? ViewController)?.navigationBar, bottomView: bottomView, bottomNavigationBar: (bottomController as? ViewController)?.navigationBar) - self.navigationTransitionCoordinator = navigationTransitionCoordinator - - topView.isUserInteractionEnabled = false - - (self.view as! NavigationControllerView).inTransition = true - navigationTransitionCoordinator.animateCompletion(0.0, completion: { [weak self] in - if let strongSelf = self { - (strongSelf.view as! NavigationControllerView).inTransition = false - strongSelf.navigationTransitionCoordinator = nil - - topController.setIgnoreAppearanceMethodInvocations(true) - bottomController.setIgnoreAppearanceMethodInvocations(true) - strongSelf.setViewControllers(viewControllers, animated: false) - topController.setIgnoreAppearanceMethodInvocations(false) - bottomController.setIgnoreAppearanceMethodInvocations(false) - - topController.view.isUserInteractionEnabled = true - - bottomController.viewDidDisappear(true) - topController.viewDidAppear(true) - - bottomView.removeFromSuperview() - } - }) - } - } else { - if let topController = self.viewControllers.last , topController.isViewLoaded { - topController.navigation_setNavigationController(nil) - topController.viewWillDisappear(false) - topController.view.removeFromSuperview() - topController.viewDidDisappear(false) - } - - self._viewControllers = viewControllers - - if let topController = viewControllers.last { - if let topController = topController as? ViewController { - if viewControllers.count >= 2 { - topController.navigationBar?.previousItem = viewControllers[viewControllers.count - 2].navigationItem - } else { - topController.navigationBar?.previousItem = nil - } - } - - topController.navigation_setNavigationController(self) - topController.viewWillAppear(false) - self.view.addSubview(topController.view) - topController.viewDidAppear(false) - } + let previousControllers = self._viewControllers + self._viewControllers = resultControllers + if let layout = self.validLayout { + self.updateControllerLayouts(previousControllers: previousControllers, layout: layout, transition: animated ? .animated(duration: 0.5, curve: .spring) : .immediate) } } @@ -527,7 +883,7 @@ open class NavigationController: UINavigationController, ContainableController, } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - if let panRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer { + if let _ = otherGestureRecognizer as? UIPanGestureRecognizer { return true } return false diff --git a/Display/NavigationTransitionCoordinator.swift b/Display/NavigationTransitionCoordinator.swift index 8749945c1e..d13b498d3d 100644 --- a/Display/NavigationTransitionCoordinator.swift +++ b/Display/NavigationTransitionCoordinator.swift @@ -27,9 +27,9 @@ class NavigationTransitionCoordinator { private let container: UIView private let transition: NavigationTransition - private let topView: UIView + let topView: UIView private let viewSuperview: UIView? - private let bottomView: UIView + let bottomView: UIView private let topNavigationBar: NavigationBar? private let bottomNavigationBar: NavigationBar? private let dimView: UIView @@ -181,7 +181,7 @@ class NavigationTransitionCoordinator { self.animatingCompletion = true let distance = (1.0 - self.progress) * self.container.bounds.size.width let f = { - switch self.transition { + /*switch self.transition { case .Push: if let viewSuperview = self.viewSuperview { viewSuperview.addSubview(self.bottomView) @@ -194,7 +194,7 @@ class NavigationTransitionCoordinator { } else { self.topView.removeFromSuperview() } - } + }*/ self.dimView.removeFromSuperview() self.shadowView.removeFromSuperview() diff --git a/Display/PeekController.swift b/Display/PeekController.swift index 091e253a70..14b2c71c13 100644 --- a/Display/PeekController.swift +++ b/Display/PeekController.swift @@ -35,7 +35,7 @@ public final class PeekController: ViewController { self.content = content self.sourceNode = sourceNode - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { diff --git a/Display/PeekControllerGestureRecognizer.swift b/Display/PeekControllerGestureRecognizer.swift index 25ae8c9ffd..ba8c9bf4ce 100644 --- a/Display/PeekControllerGestureRecognizer.swift +++ b/Display/PeekControllerGestureRecognizer.swift @@ -90,29 +90,11 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { } private func longTapTimerFired() { - guard let _ = self.tapLocation, let (sourceNode, content) = self.candidateContent else { + guard let tapLocation = self.tapLocation else { return } - self.state = .began - - if let presentedController = self.present(content, sourceNode) { - self.menuActivation = content.menuActivation() - self.presentedController = presentedController - - switch content.menuActivation() { - case .drag: - break - case .press: - if #available(iOSApplicationExtension 9.0, *) { - if presentedController.traitCollection.forceTouchCapability != .available { - self.startPressTimer() - } - } else { - self.startPressTimer() - } - } - } + self.checkCandidateContent(at: tapLocation) } private func pressTimerFired() { @@ -136,27 +118,8 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { self.candidateContent = nil self.state = .failed } else { - if let contentSignal = self.contentAtPoint(tapLocation) { - self.candidateContentDisposable.set((contentSignal |> deliverOnMainQueue).start(next: { [weak self] result in - if let strongSelf = self { - switch strongSelf.state { - case .possible, .changed: - if let (sourceNode, content) = result { - strongSelf.tapLocation = tapLocation - strongSelf.candidateContent = (sourceNode, content) - strongSelf.menuActivation = content.menuActivation() - strongSelf.startLongTapTimer() - } else { - strongSelf.state = .failed - } - default: - break - } - } - })) - } else { - self.state = .failed - } + self.tapLocation = tapLocation + self.startLongTapTimer() } } } @@ -178,6 +141,8 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { self.tapLocation = nil self.candidateContent = nil + self.longTapTimer?.invalidate() + self.pressTimer?.invalidate() self.state = .failed } } @@ -199,9 +164,9 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { override public func touchesMoved(_ touches: Set, with event: UIEvent) { super.touchesMoved(touches, with: event) - if let touch = touches.first, let initialTapLocation = self.tapLocation, let menuActivation = self.menuActivation { + if let touch = touches.first, let initialTapLocation = self.tapLocation { let touchLocation = touch.location(in: self.view) - if let presentedController = self.presentedController { + if let menuActivation = self.menuActivation, let presentedController = self.presentedController { switch menuActivation { case .drag: var offset = touchLocation.y - initialTapLocation.y @@ -257,17 +222,39 @@ public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer { if let strongSelf = self { switch strongSelf.state { case .possible, .changed: - if let (sourceNode, content) = result, let currentContent = strongSelf.candidateContent, !currentContent.1.isEqual(to: content) { - strongSelf.tapLocation = touchLocation - strongSelf.candidateContent = (sourceNode, content) - strongSelf.menuActivation = content.menuActivation() - if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { - presentedController.sourceNode = { - return sourceNode + if let (sourceNode, content) = result { + if let currentContent = strongSelf.candidateContent { + if !currentContent.1.isEqual(to: content) { + strongSelf.tapLocation = touchLocation + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + if let presentedController = strongSelf.presentedController, presentedController.isNodeLoaded { + presentedController.sourceNode = { + return sourceNode + } + (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) + } } - (presentedController.displayNode as? PeekControllerNode)?.updateContent(content: content) } else { - strongSelf.startLongTapTimer() + if let presentedController = strongSelf.present(content, sourceNode) { + strongSelf.candidateContent = (sourceNode, content) + strongSelf.menuActivation = content.menuActivation() + strongSelf.presentedController = presentedController + strongSelf.state = .began + + switch content.menuActivation() { + case .drag: + break + case .press: + if #available(iOSApplicationExtension 9.0, *) { + if presentedController.traitCollection.forceTouchCapability != .available { + strongSelf.startPressTimer() + } + } else { + strongSelf.startPressTimer() + } + } + } } } else if strongSelf.presentedController == nil { strongSelf.state = .failed diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index 0cb39d4d89..ed325bacbd 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -113,7 +113,7 @@ public final class StatusBar: ASDisplayNode { self.addSubnode(self.offsetNode) self.addSubnode(self.inCallBackgroundNode) - self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar)) + self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar, disableChildrenTracingTags: 0)) self.clipsToBounds = true diff --git a/Display/TabBarController.swift b/Display/TabBarController.swift index 5f1b1434db..2117f752be 100644 --- a/Display/TabBarController.swift +++ b/Display/TabBarController.swift @@ -64,10 +64,10 @@ open class TabBarController: ViewController { private var theme: TabBarControllerTheme - public init(navigationBarTheme: NavigationBarTheme, theme: TabBarControllerTheme) { + public init(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) { self.theme = theme - super.init(navigationBarTheme: navigationBarTheme) + super.init(navigationBarPresentationData: navigationBarPresentationData) } required public init(coder aDecoder: NSCoder) { @@ -78,8 +78,8 @@ open class TabBarController: ViewController { self.pendingControllerDisposable.dispose() } - public func updateTheme(navigationBarTheme: NavigationBarTheme, theme: TabBarControllerTheme) { - self.navigationBar?.updateTheme(navigationBarTheme) + public func updateTheme(navigationBarPresentationData: NavigationBarPresentationData, theme: TabBarControllerTheme) { + self.navigationBar?.updatePresentationData(navigationBarPresentationData) if self.theme !== theme { self.theme = theme if self.isNodeLoaded { @@ -192,6 +192,13 @@ open class TabBarController: ViewController { } } + override open func navigationStackConfigurationUpdated(next: [ViewController]) { + super.navigationStackConfigurationUpdated(next: next) + for controller in self.controllers { + controller.navigationStackConfigurationUpdated(next: next) + } + } + override open func viewDidAppear(_ animated: Bool) { if let currentController = self.currentController { currentController.viewDidAppear(animated) diff --git a/Display/TapLongTapOrDoubleTapGestureRecognizer.swift b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift new file mode 100644 index 0000000000..a8a5fce557 --- /dev/null +++ b/Display/TapLongTapOrDoubleTapGestureRecognizer.swift @@ -0,0 +1,257 @@ +import Foundation +import UIKit.UIGestureRecognizerSubclass +import Display + +private class TapLongTapOrDoubleTapGestureRecognizerTimerTarget: NSObject { + weak var target: TapLongTapOrDoubleTapGestureRecognizer? + + init(target: TapLongTapOrDoubleTapGestureRecognizer) { + self.target = target + + super.init() + } + + @objc func longTapEvent() { + self.target?.longTapEvent() + } + + @objc func tapEvent() { + self.target?.tapEvent() + } + + @objc func holdEvent() { + self.target?.holdEvent() + } +} + +enum TapLongTapOrDoubleTapGesture { + case tap + case doubleTap + case longTap + case hold +} + +enum TapLongTapOrDoubleTapGestureRecognizerAction { + case waitForDoubleTap + case waitForSingleTap + case waitForHold(timeout: Double, acceptTap: Bool) + case fail +} + +public final class TapLongTapOrDoubleTapGestureRecognizer: UIGestureRecognizer, UIGestureRecognizerDelegate { + private var touchLocationAndTimestamp: (CGPoint, Double)? + private var touchCount: Int = 0 + private var tapCount: Int = 0 + + private var timer: Foundation.Timer? + private(set) var lastRecognizedGestureAndLocation: (TapLongTapOrDoubleTapGesture, CGPoint)? + + var tapActionAtPoint: ((CGPoint) -> TapLongTapOrDoubleTapGestureRecognizerAction)? + var highlight: ((CGPoint?) -> Void)? + + var hapticFeedback: HapticFeedback? + + private var highlightPoint: CGPoint? + + override public init(target: Any?, action: Selector?) { + super.init(target: target, action: action) + + self.delegate = self + } + + public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { + if otherGestureRecognizer is UIPanGestureRecognizer { + return false + } + return false + } + + override public func reset() { + self.timer?.invalidate() + self.timer = nil + self.touchLocationAndTimestamp = nil + self.tapCount = 0 + self.touchCount = 0 + self.hapticFeedback = nil + + if self.highlightPoint != nil { + self.highlightPoint = nil + self.highlight?(nil) + } + + super.reset() + } + + fileprivate func longTapEvent() { + self.timer?.invalidate() + self.timer = nil + if let (location, _) = self.touchLocationAndTimestamp { + self.lastRecognizedGestureAndLocation = (.longTap, location) + } else { + self.lastRecognizedGestureAndLocation = nil + } + self.state = .ended + } + + fileprivate func tapEvent() { + self.timer?.invalidate() + self.timer = nil + if let (location, _) = self.touchLocationAndTimestamp { + self.lastRecognizedGestureAndLocation = (.tap, location) + } else { + self.lastRecognizedGestureAndLocation = nil + } + self.state = .ended + } + + fileprivate func holdEvent() { + self.timer?.invalidate() + self.timer = nil + if let (location, _) = self.touchLocationAndTimestamp { + self.hapticFeedback?.tap() + self.lastRecognizedGestureAndLocation = (.hold, location) + } else { + self.lastRecognizedGestureAndLocation = nil + } + self.state = .began + } + + override public func touchesBegan(_ touches: Set, with event: UIEvent) { + self.lastRecognizedGestureAndLocation = nil + + super.touchesBegan(touches, with: event) + + self.touchCount += touches.count + + if let touch = touches.first { + let touchLocation = touch.location(in: self.view) + + if self.highlightPoint != touchLocation { + self.highlightPoint = touchLocation + self.highlight?(touchLocation) + } + + if let hitResult = self.view?.hitTest(touch.location(in: self.view), with: event), let _ = hitResult as? UIButton { + self.state = .failed + return + } + + self.tapCount += 1 + if self.tapCount == 2 && self.touchCount == 1 { + self.timer?.invalidate() + self.timer = nil + self.lastRecognizedGestureAndLocation = (.doubleTap, self.location(in: self.view)) + self.state = .ended + } else { + let touchLocationAndTimestamp = (touch.location(in: self.view), CACurrentMediaTime()) + self.touchLocationAndTimestamp = touchLocationAndTimestamp + + var tapAction: TapLongTapOrDoubleTapGestureRecognizerAction = .waitForDoubleTap + if let tapActionAtPoint = self.tapActionAtPoint { + tapAction = tapActionAtPoint(touchLocationAndTimestamp.0) + } + + switch tapAction { + case .waitForSingleTap, .waitForDoubleTap: + self.timer?.invalidate() + let timer = Timer(timeInterval: 0.3, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.longTapEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + case let .waitForHold(timeout, _): + self.hapticFeedback = HapticFeedback() + self.hapticFeedback?.prepareTap() + let timer = Timer(timeInterval: timeout, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.holdEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + case .fail: + self.state = .failed + } + } + } + } + + override public func touchesMoved(_ touches: Set, with event: UIEvent) { + super.touchesMoved(touches, with: event) + + guard let touch = touches.first else { + return + } + + if let (gesture, _) = self.lastRecognizedGestureAndLocation, case .hold = gesture { + let location = touch.location(in: self.view) + self.lastRecognizedGestureAndLocation = (.hold, location) + self.state = .changed + return + } + + if let touch = touches.first, let (touchLocation, _) = self.touchLocationAndTimestamp { + let location = touch.location(in: self.view) + let distance = CGPoint(x: location.x - touchLocation.x, y: location.y - touchLocation.y) + if distance.x * distance.x + distance.y * distance.y > 4.0 { + self.state = .cancelled + } + } + } + + override public func touchesEnded(_ touches: Set, with event: UIEvent) { + super.touchesEnded(touches, with: event) + + self.touchCount -= touches.count + + if self.highlightPoint != nil { + self.highlightPoint = nil + self.highlight?(nil) + } + + self.timer?.invalidate() + + if let (gesture, location) = self.lastRecognizedGestureAndLocation, case .hold = gesture { + self.lastRecognizedGestureAndLocation = (.hold, location) + self.state = .ended + return + } + + if self.tapCount == 1 { + var tapAction: TapLongTapOrDoubleTapGestureRecognizerAction = .waitForDoubleTap + if let tapActionAtPoint = self.tapActionAtPoint, let (touchLocation, _) = self.touchLocationAndTimestamp { + tapAction = tapActionAtPoint(touchLocation) + } + + switch tapAction { + case .waitForSingleTap: + if let (touchLocation, _) = self.touchLocationAndTimestamp { + self.lastRecognizedGestureAndLocation = (.tap, touchLocation) + } + self.state = .ended + case .waitForDoubleTap: + self.state = .began + let timer = Timer(timeInterval: 0.2, target: TapLongTapOrDoubleTapGestureRecognizerTimerTarget(target: self), selector: #selector(TapLongTapOrDoubleTapGestureRecognizerTimerTarget.tapEvent), userInfo: nil, repeats: false) + self.timer = timer + RunLoop.main.add(timer, forMode: RunLoopMode.commonModes) + case let .waitForHold(_, acceptTap): + if let (touchLocation, _) = self.touchLocationAndTimestamp, acceptTap { + if self.state != .began { + self.lastRecognizedGestureAndLocation = (.tap, touchLocation) + self.state = .began + } + } + self.state = .ended + case .fail: + self.state = .failed + } + } + } + + override public func touchesCancelled(_ touches: Set, with event: UIEvent) { + super.touchesCancelled(touches, with: event) + + self.touchCount -= touches.count + + if self.highlightPoint != nil { + self.highlightPoint = nil + self.highlight?(nil) + } + + self.state = .cancelled + } +} diff --git a/Display/TooltipController.swift b/Display/TooltipController.swift index ca828047a3..10ba66edcc 100644 --- a/Display/TooltipController.swift +++ b/Display/TooltipController.swift @@ -41,7 +41,7 @@ public final class TooltipController: ViewController { self.text = text self.timeout = timeout - super.init(navigationBarTheme: nil) + super.init(navigationBarPresentationData: nil) } required public init(coder aDecoder: NSCoder) { @@ -54,10 +54,7 @@ public final class TooltipController: ViewController { open override func loadDisplayNode() { self.displayNode = TooltipControllerNode(text: self.text, dismiss: { [weak self] in - self?.dismissed?() - self?.controllerNode.animateOut { [weak self] in - self?.presentingViewController?.dismiss(animated: false) - } + self?.dismiss() }) self.displayNodeDidLoad() } @@ -111,4 +108,12 @@ public final class TooltipController: ViewController { timeoutTimer.start() } } + + override public func dismiss(completion: (() -> Void)? = nil) { + self.dismissed?() + self.controllerNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + completion?() + } + } } diff --git a/Display/ViewController.swift b/Display/ViewController.swift index 4c12b141c1..0dffe12438 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -149,10 +149,10 @@ open class ViewControllerPresentationArguments { } } - public init(navigationBarTheme: NavigationBarTheme?) { + public init(navigationBarPresentationData: NavigationBarPresentationData?) { self.statusBar = StatusBar() - if let navigationBarTheme = navigationBarTheme { - self.navigationBar = NavigationBar(theme: navigationBarTheme) + if let navigationBarPresentationData = navigationBarPresentationData { + self.navigationBar = NavigationBar(presentationData: navigationBarPresentationData) } else { self.navigationBar = nil } @@ -218,6 +218,9 @@ open class ViewControllerPresentationArguments { } } + open func navigationStackConfigurationUpdated(next: [ViewController]) { + } + open override func loadView() { self.view = self.displayNode.view if let navigationBar = self.navigationBar { @@ -237,7 +240,7 @@ open class ViewControllerPresentationArguments { open func displayNodeDidLoad() { if let layer = self.displayNode.layer as? CATracingLayer { - layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard)) + layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard, disableChildrenTracingTags: 0)) } self.updateScrollToTopView() } @@ -248,9 +251,9 @@ open class ViewControllerPresentationArguments { } } - public func setDisplayNavigationBar(_ displayNavigtionBar: Bool, transition: ContainedViewLayoutTransition = .immediate) { - if displayNavigtionBar != self.displayNavigationBar { - self.displayNavigationBar = displayNavigtionBar + public func setDisplayNavigationBar(_ displayNavigationBar: Bool, transition: ContainedViewLayoutTransition = .immediate) { + if displayNavigationBar != self.displayNavigationBar { + self.displayNavigationBar = displayNavigationBar if let parent = self.parent as? TabBarController { if parent.currentController === self { parent.displayNavigationBar = displayNavigationBar @@ -346,4 +349,15 @@ open class ViewControllerPresentationArguments { super.unregisterForPreviewing(withContext: previewing) } } + + public final func navigationNextSibling() -> UIViewController? { + if let navigationController = self.navigationController as? NavigationController { + if let index = navigationController.viewControllers.index(where: { $0 === self }) { + if index != navigationController.viewControllers.count - 1 { + return navigationController.viewControllers[index + 1] + } + } + } + return nil + } } diff --git a/Display/WindowContent.swift b/Display/WindowContent.swift index 586f3820c2..e77c611266 100644 --- a/Display/WindowContent.swift +++ b/Display/WindowContent.swift @@ -296,8 +296,8 @@ public final class WindowHostView { } public struct WindowTracingTags { - public static let statusBar: Int32 = 0 - public static let keyboard: Int32 = 1 + public static let statusBar: Int32 = 1 << 0 + public static let keyboard: Int32 = 1 << 1 } public protocol WindowHost { @@ -309,7 +309,11 @@ public protocol WindowHost { } private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics { - return LayoutMetrics(widthClass: .compact, heightClass: .compact) + if size.width > 690.0 && size.height > 690.0 { + return LayoutMetrics(widthClass: .regular, heightClass: .regular) + } else { + return LayoutMetrics(widthClass: .compact, heightClass: .compact) + } } private func safeInsetsForScreenSize(_ size: CGSize) -> UIEdgeInsets { @@ -465,14 +469,14 @@ public class Window1 { let screenHeight: CGFloat - if true || !UIScreen.main.bounds.width.isEqual(to: strongSelf.windowLayout.size.width) { - if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) { + if keyboardFrame.width.isEqual(to: UIScreen.main.bounds.width) { + if abs(strongSelf.windowLayout.size.height - UIScreen.main.bounds.height) > 41.0 { screenHeight = UIScreen.main.bounds.height } else { - screenHeight = UIScreen.main.bounds.width + screenHeight = strongSelf.windowLayout.size.height } } else { - screenHeight = UIScreen.main.bounds.height + screenHeight = UIScreen.main.bounds.width } let keyboardHeight = max(0.0, screenHeight - keyboardFrame.minY)