diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 780cab3d93..b81f97ac7b 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -13,7 +13,11 @@ D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; }; D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; }; D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */; }; + D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; + D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; + D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */; }; + D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */; }; D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */; }; D03BCCEB1C72AE590097A291 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03BCCEA1C72AE590097A291 /* Theme.swift */; }; D03E7DE41C96A90100C07816 /* NavigationShadow@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */; }; @@ -96,6 +100,7 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; + D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,7 +120,11 @@ D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = ""; }; D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = ""; }; D015F7591D1B46B600E269B5 /* ActionSheetControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetControllerNode.swift; sourceTree = ""; }; + D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; + D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; + D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuAction.swift; sourceTree = ""; }; + D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuController.swift; sourceTree = ""; }; D03B0E6F1D6331FB00955575 /* StatusBarHost.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarHost.swift; sourceTree = ""; }; D03BCCEA1C72AE590097A291 /* Theme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; D03E7DE31C96A90100C07816 /* NavigationShadow@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "NavigationShadow@2x.png"; sourceTree = ""; }; @@ -202,6 +211,7 @@ D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; + D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -267,6 +277,18 @@ name = Nodes; sourceTree = ""; }; + D03725BF1D6DF57B007FC290 /* Context Menu */ = { + isa = PBXGroup; + children = ( + D03725C21D6DF7A6007FC290 /* ContextMenuAction.swift */, + D03725C41D6DF8B9007FC290 /* ContextMenuController.swift */, + D03725C01D6DF594007FC290 /* ContextMenuNode.swift */, + D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */, + D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */, + ); + name = "Context Menu"; + sourceTree = ""; + }; D03BCCE91C72AE4B0097A291 /* Theme */ = { isa = PBXGroup; children = ( @@ -456,6 +478,7 @@ D081229A1D19A9EB005F7395 /* Navigation */, D015F7551D1B142300E269B5 /* Tab Bar */, D015F7561D1B465600E269B5 /* Action Sheet */, + D03725BF1D6DF57B007FC290 /* Context Menu */, ); name = Controllers; sourceTree = ""; @@ -620,9 +643,11 @@ D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */, D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */, D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */, + D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */, D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */, D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */, D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */, + D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */, D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */, D05BE4AB1D1F25E3002BD72C /* PresentationContext.swift in Sources */, D0C2DFCA1CC4431D0044FF83 /* ListViewItem.swift in Sources */, @@ -660,8 +685,11 @@ D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, + D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, + D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */, D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */, D015F75A1D1B46B600E269B5 /* ActionSheetControllerNode.swift in Sources */, + D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */, D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */, D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */, D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */, diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme new file mode 100644 index 0000000000..e81aa39f54 --- /dev/null +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist index aa5ae0473f..b987a65bba 100644 --- a/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ Display.xcscheme orderHint - 0 + 20 DisplayTests.xcscheme diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 92bf148c4e..74c9a54705 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -108,7 +108,7 @@ public extension CALayer { } public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) { - self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) + self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) { diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index fc6451e52e..04f1b2fb64 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -40,6 +40,42 @@ public extension ContainedViewLayoutTransition { }) } } + + func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + layer.frame = frame + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousFrame = layer.frame + layer.frame = frame + layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } + + func updateAlpha(node: ASDisplayNode, alpha: CGFloat, completion: ((Bool) -> Void)? = nil) { + switch self { + case .immediate: + node.alpha = alpha + if let completion = completion { + completion(true) + } + case let .animated(duration, curve): + let previousAlpha = node.alpha + node.alpha = alpha + node.layer.animateAlpha(from: previousAlpha, to: alpha, duration: duration, timingFunction: curve.timingFunction, completion: { result in + if let completion = completion { + completion(result) + } + }) + } + } } public protocol ContainableController: class { diff --git a/Display/ContainerViewLayout.swift b/Display/ContainerViewLayout.swift index f2df5b9655..74a06eda9b 100644 --- a/Display/ContainerViewLayout.swift +++ b/Display/ContainerViewLayout.swift @@ -61,7 +61,9 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { if let lhsStatusBarHeight = lhs.statusBarHeight { if let rhsStatusBarHeight = rhs.statusBarHeight { - return lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) + if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) { + return false + } } else { return false } @@ -71,7 +73,9 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool { if let lhsInputHeight = lhs.inputHeight { if let rhsInputHeight = rhs.inputHeight { - return lhsInputHeight.isEqual(to: rhsInputHeight) + if !lhsInputHeight.isEqual(to: rhsInputHeight) { + return false + } } else { return false } diff --git a/Display/ContextMenuAction.swift b/Display/ContextMenuAction.swift new file mode 100644 index 0000000000..898a8db7c1 --- /dev/null +++ b/Display/ContextMenuAction.swift @@ -0,0 +1,14 @@ + +public enum ContextMenuActionContent { + case text(String) +} + +public struct ContextMenuAction { + public let content: ContextMenuActionContent + public let action: () -> Void + + public init(content: ContextMenuActionContent, action: @escaping () -> Void) { + self.content = content + self.action = action + } +} diff --git a/Display/ContextMenuActionNode.swift b/Display/ContextMenuActionNode.swift new file mode 100644 index 0000000000..8e7b91ce68 --- /dev/null +++ b/Display/ContextMenuActionNode.swift @@ -0,0 +1,58 @@ +import Foundation +import AsyncDisplayKit + +final class ContextMenuActionNode: ASDisplayNode { + private let textNode: ASTextNode + private let action: () -> Void + private let button: HighlightTrackingButton + + var dismiss: (() -> Void)? + + init(action: ContextMenuAction) { + self.textNode = ASTextNode() + switch action.content { + case let .text(title): + self.textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white) + } + self.action = action.action + + self.button = HighlightTrackingButton() + + super.init() + + self.backgroundColor = UIColor(white: 0.0, alpha: 0.8) + self.addSubnode(self.textNode) + + self.button.highligthedChanged = { [weak self] highlighted in + self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8) + } + self.view.addSubview(self.button) + } + + override func didLoad() { + super.didLoad() + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside]) + } + + @objc private func buttonPressed() { + self.backgroundColor = UIColor(white: 0.0, alpha: 0.4) + + self.action() + if let dismiss = self.dismiss { + dismiss() + } + } + + override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let textSize = self.textNode.measure(constrainedSize) + return CGSize(width: textSize.width + 36.0, height: 54.0) + } + + override func layout() { + super.layout() + + self.button.frame = self.bounds + self.textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - self.textNode.calculatedSize.width) / 2.0), y: floor((self.bounds.size.height - self.textNode.calculatedSize.height) / 2.0)), size: self.textNode.calculatedSize) + } +} diff --git a/Display/ContextMenuContainerNode.swift b/Display/ContextMenuContainerNode.swift new file mode 100644 index 0000000000..c7d22be218 --- /dev/null +++ b/Display/ContextMenuContainerNode.swift @@ -0,0 +1,84 @@ +import Foundation +import AsyncDisplayKit + +private struct CachedMaskParams: Equatable { + let size: CGSize + let relativeArrowPosition: CGFloat + let arrowOnBottom: Bool +} + +private func ==(lhs: CachedMaskParams, rhs: CachedMaskParams) -> Bool { + return lhs.size.equalTo(rhs.size) && lhs.relativeArrowPosition.isEqual(to: rhs.relativeArrowPosition) && lhs.arrowOnBottom == rhs.arrowOnBottom +} + +private final class ContextMenuContainerMaskView: UIView { + override class var layerClass: AnyClass { + return CAShapeLayer.self + } +} + +final class ContextMenuContainerNode: ASDisplayNode { + private var cachedMaskParams: CachedMaskParams? + private let maskView = ContextMenuContainerMaskView() + + var relativeArrowPosition: (CGFloat, Bool)? + + //private let effectView: UIVisualEffectView + + override init() { + //self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light)) + + super.init() + + self.backgroundColor = UIColor(0xeaecec) + //self.view.addSubview(self.effectView) + //self.effectView.mask = self.maskView + self.view.mask = self.maskView + } + + override func didLoad() { + super.didLoad() + + self.layer.allowsGroupOpacity = true + } + + override func layout() { + super.layout() + + //self.effectView.frame = self.bounds + + let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true) + if self.cachedMaskParams != maskParams { + let path = UIBezierPath() + let cornerRadius: CGFloat = 6.0 + let verticalInset: CGFloat = 9.0 + let arrowWidth: CGFloat = 18.0 + let requestedArrowPosition = maskParams.relativeArrowPosition + let arrowPosition = max(cornerRadius + arrowWidth / 2.0, min(maskParams.size.width - cornerRadius - arrowWidth / 2.0, requestedArrowPosition)) + let arrowOnBottom = maskParams.arrowOnBottom + + path.move(to: CGPoint(x: 0.0, y: verticalInset + cornerRadius)) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(M_PI), endAngle: CGFloat(3 * M_PI / 2), clockwise: true) + if !arrowOnBottom { + path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: verticalInset)) + path.addLine(to: CGPoint(x: arrowPosition, y: 0.0)) + path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: verticalInset)) + } + path.addLine(to: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset)) + path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(3 * M_PI / 2), endAngle: 0.0, clockwise: true) + path.addLine(to: CGPoint(x: maskParams.size.width, y: maskParams.size.height - cornerRadius - verticalInset)) + path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: 0.0, endAngle: CGFloat(M_PI / 2.0), clockwise: true) + if arrowOnBottom { + path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: maskParams.size.height - verticalInset)) + path.addLine(to: CGPoint(x: arrowPosition, y: maskParams.size.height)) + path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: maskParams.size.height - verticalInset)) + } + path.addLine(to: CGPoint(x: cornerRadius, y: maskParams.size.height - verticalInset)) + path.addArc(withCenter: CGPoint(x: cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: CGFloat(M_PI / 2.0), endAngle: CGFloat(M_PI), clockwise: true) + path.close() + + self.cachedMaskParams = maskParams + (self.maskView.layer as? CAShapeLayer)?.path = path.cgPath + } + } +} diff --git a/Display/ContextMenuController.swift b/Display/ContextMenuController.swift new file mode 100644 index 0000000000..3572374577 --- /dev/null +++ b/Display/ContextMenuController.swift @@ -0,0 +1,72 @@ +import Foundation +import AsyncDisplayKit + +public final class ContextMenuControllerPresentationArguments { + fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect)? + + public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect)?) { + self.sourceNodeAndRect = sourceNodeAndRect + } +} + +public final class ContextMenuController: ViewController { + private var contextMenuNode: ContextMenuNode { + return self.displayNode as! ContextMenuNode + } + + private let actions: [ContextMenuAction] + + private var layout: ContainerViewLayout? + + public init(actions: [ContextMenuAction]) { + self.actions = actions + + super.init() + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + open override func loadDisplayNode() { + self.displayNode = ContextMenuNode(actions: self.actions, dismiss: { [weak self] in + self?.contextMenuNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + }) + self.displayNodeDidLoad() + self.navigationBar.isHidden = true + } + + override public func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + self.contextMenuNode.animateIn() + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + if self.layout != nil && self.layout! != layout { + self.contextMenuNode.animateOut { [weak self] in + self?.presentingViewController?.dismiss(animated: false) + } + } else { + self.layout = layout + + if let presentationArguments = self.presentationArguments as? ContextMenuControllerPresentationArguments, let (sourceNode, sourceRect) = presentationArguments.sourceNodeAndRect() { + self.contextMenuNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil) + } else { + self.contextMenuNode.sourceRect = nil + } + + self.contextMenuNode.containerLayoutUpdated(layout, transition: transition) + } + } + + open override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + self.contextMenuNode.animateIn() + } +} diff --git a/Display/ContextMenuNode.swift b/Display/ContextMenuNode.swift new file mode 100644 index 0000000000..40b82270ff --- /dev/null +++ b/Display/ContextMenuNode.swift @@ -0,0 +1,99 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +final class ContextMenuNode: ASDisplayNode { + private let actions: [ContextMenuAction] + private let dismiss: () -> Void + + private let containerNode: ContextMenuContainerNode + private let actionNodes: [ContextMenuActionNode] + + var sourceRect: CGRect? + + private var dismissedByTouchOutside = false + + init(actions: [ContextMenuAction], dismiss: @escaping () -> Void) { + self.actions = actions + self.dismiss = dismiss + + self.containerNode = ContextMenuContainerNode() + + self.actionNodes = actions.map { action in + return ContextMenuActionNode(action: action) + } + + super.init() + + self.addSubnode(self.containerNode) + let dismissNode = { [weak self] in + dismiss() + } + for actionNode in self.actionNodes { + actionNode.dismiss = dismissNode + self.containerNode.addSubnode(actionNode) + } + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + var actionsWidth: CGFloat = 0.0 + let actionSeparatorWidth: CGFloat = UIScreenPixel + for actionNode in self.actionNodes { + if !actionsWidth.isZero { + actionsWidth += actionSeparatorWidth + } + let actionSize = actionNode.measure(CGSize(width: layout.size.width, height: 54.0)) + actionNode.frame = CGRect(origin: CGPoint(x: actionsWidth, y: 0.0), size: actionSize) + actionsWidth += actionSize.width + } + + let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize()) + + let insets = layout.insets(options: [.statusBar, .input]) + + let verticalOrigin: CGFloat + var arrowOnBottom = true + if sourceRect.minY - 54.0 > insets.top { + verticalOrigin = sourceRect.minY - 54.0 + } else { + verticalOrigin = min(layout.size.height - insets.bottom - 54.0, sourceRect.maxY) + arrowOnBottom = false + } + + let horizontalOrigin: CGFloat = floor(min(max(8.0, sourceRect.midX - actionsWidth / 2.0), layout.size.width - actionsWidth - 8.0)) + + self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0)) + self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom) + + self.containerNode.layout() + } + + func animateIn() { + self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2) + } + + func animateOut(completion: @escaping () -> Void) { + self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false, completion: { _ in + completion() + }) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let event = event { + var eventIsPresses = false + if #available(iOSApplicationExtension 9.0, *) { + eventIsPresses = event.type == .presses + } + if event.type == .touches || eventIsPresses { + if !self.containerNode.frame.contains(point) { + if !self.dismissedByTouchOutside { + self.dismissedByTouchOutside = true + self.dismiss() + } + return nil + } + } + } + return super.hitTest(point, with: event) + } +} diff --git a/Display/Font.swift b/Display/Font.swift index 862078c6ac..295dd1e4f2 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -7,7 +7,11 @@ public struct Font { } public static func medium(_ size: CGFloat) -> UIFont { - return UIFont.boldSystemFont(ofSize: size) + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: UIFontWeightMedium) + } else { + return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) + } } } diff --git a/Display/GenerateImage.swift b/Display/GenerateImage.swift index ecd6729ba5..3511197194 100644 --- a/Display/GenerateImage.swift +++ b/Display/GenerateImage.swift @@ -4,7 +4,7 @@ import UIKit let deviceColorSpace = CGColorSpaceCreateDeviceRGB() let deviceScale = UIScreen.main.scale -public func generateImage(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { +public func generateImagePixel(_ size: CGSize, pixelGenerator: (CGSize, UnsafeMutablePointer) -> Void) -> UIImage? { let scale = deviceScale let scaledSize = CGSize(width: size.width * scale, height: size.height * scale) let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15) @@ -84,6 +84,37 @@ public func generateStretchableFilledCircleImage(radius: CGFloat, color: UIColor return generateFilledCircleImage(radius: radius, color: color, backgroundColor: backgroundColor)?.stretchableImage(withLeftCapWidth: Int(radius), topCapHeight: Int(radius)) } +public func generateTintedImage(image: UIImage?, color: UIColor, backgroundColor: UIColor? = nil) -> UIImage? { + guard let image = image else { + return nil + } + + let imageSize = image.size + + UIGraphicsBeginImageContextWithOptions(imageSize, backgroundColor != nil, image.scale) + if let context = UIGraphicsGetCurrentContext() { + if let backgroundColor = backgroundColor { + context.setFillColor(backgroundColor.cgColor) + context.fill(CGRect(origin: CGPoint(), size: imageSize)) + } + + let imageRect = CGRect(origin: CGPoint(), size: imageSize) + context.saveGState() + context.translateBy(x: imageRect.midX, y: imageRect.midY) + context.scaleBy(x: 1.0, y: -1.0) + context.translateBy(x: -imageRect.midX, y: -imageRect.midY) + context.clip(to: imageRect, mask: image.cgImage!) + context.setFillColor(color.cgColor) + context.fill(imageRect) + context.restoreGState() + } + + let tintedImage = UIGraphicsGetImageFromCurrentImageContext()! + UIGraphicsEndImageContext() + + return tintedImage +} + private func generateSingleColorImage(size: CGSize, color: UIColor) -> UIImage? { return generateImage(size, contextGenerator: { size, context in context.setFillColor(color.cgColor) diff --git a/Display/ListView.swift b/Display/ListView.swift index 970e34e9f8..ded97be467 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -48,7 +48,7 @@ public enum ListViewScrollToItemDirectionHint { } public enum ListViewAnimationCurve { - case Spring(speed: CGFloat) + case Spring(duration: Double) case Default } @@ -922,6 +922,12 @@ private final class ListViewTimerProxy: NSObject { } } +public enum ListViewVisibleContentOffset { + case known(CGFloat) + case unknown + case none +} + public final class ListView: ASDisplayNode, UIScrollViewDelegate { private final let scroller: ListViewScroller private final var visibleSize: CGSize = CGSize() @@ -965,7 +971,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { public final var displayedItemRangeChanged: (ListViewDisplayedItemRange) -> Void = { _ in } public private(set) final var displayedItemRange: ListViewDisplayedItemRange = ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil) - public final var visibleContentOffsetChanged: (CGFloat?) -> Void = { _ in } + public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in } private final var animations: [ListViewAnimation] = [] private final var actionsForVSync: [() -> ()] = [] @@ -1029,7 +1035,7 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { self.scroller.isHidden = true self.scroller.delegate = self self.view.addSubview(self.scroller) - self.scroller.panGestureRecognizer.cancelsTouchesInView = false + self.scroller.panGestureRecognizer.cancelsTouchesInView = true self.view.addGestureRecognizer(self.scroller.panGestureRecognizer) self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent)) @@ -1279,9 +1285,18 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { } private func updateVisibleContentOffset() { - var offset: CGFloat? - if let itemNode = self.itemNodes.first, let index = itemNode.index , index == 0 { - offset = -(itemNode.apparentFrame.minY - self.insets.top) + var offset: ListViewVisibleContentOffset = .unknown + var topItemIndexAndFrame: (Int, CGRect) = (-1, CGRect()) + for itemNode in self.itemNodes { + if let index = itemNode.index { + topItemIndexAndFrame = (index, itemNode.apparentFrame) + break + } + } + if topItemIndexAndFrame.0 == 0 { + offset = .known(-(topItemIndexAndFrame.1.minY - self.insets.top)) + } else if topItemIndexAndFrame.0 == -1 { + offset = .none } self.visibleContentOffsetChanged(offset) @@ -1888,10 +1903,6 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { //self.addSubnode(node) } - if previousFrame == nil { - node.setupGestures() - } - var offsetHeight = node.apparentHeight var takenAnimation = false @@ -2202,12 +2213,19 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { if updateSizeAndInsets.duration > DBL_EPSILON { let animation: CABasicAnimation switch updateSizeAndInsets.curve { - case let .Spring(speed): + case let .Spring(duration): let springAnimation = makeSpringAnimation("sublayerTransform") - springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -completeOffset, 0.0)) springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) springAnimation.isRemovedOnCompletion = true + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + springAnimation.isAdditive = true animation = springAnimation case .Default: @@ -2284,14 +2302,21 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let animation: CABasicAnimation switch scrollToItem.curve { - case let .Spring(speed): + case let .Spring(duration): let springAnimation = makeSpringAnimation("sublayerTransform") springAnimation.fromValue = NSValue(caTransform3D: CATransform3DMakeTranslation(0.0, -offset, 0.0)) springAnimation.toValue = NSValue(caTransform3D: CATransform3DIdentity) springAnimation.isRemovedOnCompletion = true springAnimation.isAdditive = true springAnimation.fillMode = kCAFillModeForwards - springAnimation.speed = Float(speed) * Float(1.0 / UIView.animationDurationFactor()) + + let k = Float(UIView.animationDurationFactor()) + var speed: Float = 1.0 + if k != 0 && k != 1 { + speed = Float(1.0) / k + } + springAnimation.speed = speed * Float(springAnimation.duration / duration) + animation = springAnimation case .Default: let basicAnimation = CABasicAnimation(keyPath: "sublayerTransform") @@ -2877,9 +2902,8 @@ public final class ListView: ASDisplayNode, UIScrollViewDelegate { let timer = Timer(timeInterval: 0.08, target: ListViewTimerProxy { [weak self] in if let strongSelf = self , strongSelf.selectionTouchLocation != nil { strongSelf.clearHighlightAnimated(false) - let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) - if let index = index { + if let index = strongSelf.itemIndexAtPoint(strongSelf.touchesPosition) { if strongSelf.items[index].selectable { strongSelf.highlightedItemIndex = index for itemNode in strongSelf.itemNodes { diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index c2dfd53fbc..a4e3480e2d 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -413,9 +413,6 @@ open class ListViewItemNode: ASDisplayNode { open func setHighlighted(_ highlighted: Bool, animated: Bool) { } - open func setupGestures() { - } - open func animateFrameTransition(_ progress: CGFloat) { } diff --git a/Display/ListViewScroller.swift b/Display/ListViewScroller.swift index 73d8ff4b1a..935a6fa064 100644 --- a/Display/ListViewScroller.swift +++ b/Display/ListViewScroller.swift @@ -14,4 +14,8 @@ class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate { @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { return false } + + override func touchesShouldCancel(in view: UIView) -> Bool { + return true + } } diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index fa4dd9cc10..47458f96d7 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -44,6 +44,7 @@ public class NavigationBar: ASDisplayNode { didSet { self.backButtonNode.color = self.accentColor self.leftButtonNode.color = self.accentColor + self.rightButtonNode.color = self.accentColor self.backButtonArrow.image = backArrowImage(color: self.accentColor) } } @@ -68,6 +69,7 @@ public class NavigationBar: ASDisplayNode { private var itemTitleListenerKey: Int? private var itemTitleViewListenerKey: Int? private var itemLeftButtonListenerKey: Int? + private var itemRightButtonListenerKey: Int? private var _item: UINavigationItem? var item: UINavigationItem? { get { @@ -86,6 +88,7 @@ public class NavigationBar: ASDisplayNode { self._item = value self.leftButtonNode.removeFromSupernode() + self.rightButtonNode.removeFromSupernode() if let item = value { self.title = item.title @@ -105,13 +108,25 @@ public class NavigationBar: ASDisplayNode { self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] _, _ in if let strongSelf = self { strongSelf.updateLeftButton() + strongSelf.invalidateCalculatedLayout() + strongSelf.setNeedsLayout() + } + } + + self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] _, _ in + if let strongSelf = self { + strongSelf.updateRightButton() + strongSelf.invalidateCalculatedLayout() + strongSelf.setNeedsLayout() } } self.updateLeftButton() + self.updateRightButton() } else { self.title = nil self.updateLeftButton() + self.updateRightButton() } self.invalidateCalculatedLayout() } @@ -207,9 +222,26 @@ public class NavigationBar: ASDisplayNode { } } + private func updateRightButton() { + if let item = self.item { + if let rightBarButtonItem = item.rightBarButtonItem { + self.rightButtonNode.text = rightBarButtonItem.title ?? "" + self.rightButtonNode.node = rightBarButtonItem.customDisplayNode + if self.rightButtonNode.supernode == nil { + self.clippingNode.addSubnode(self.rightButtonNode) + } + } else { + self.rightButtonNode.removeFromSupernode() + } + } else { + self.rightButtonNode.removeFromSupernode() + } + } + private let backButtonNode: NavigationButtonNode private let backButtonArrow: ASImageNode private let leftButtonNode: NavigationButtonNode + private let rightButtonNode: NavigationButtonNode private var _transitionState: NavigationBarTransitionState? var transitionState: NavigationBarTransitionState? { @@ -280,6 +312,7 @@ public class NavigationBar: ASDisplayNode { self.backButtonArrow.displaysAsynchronously = false self.backButtonArrow.image = backArrowImage(color: self.accentColor) self.leftButtonNode = NavigationButtonNode() + self.rightButtonNode = NavigationButtonNode() self.clippingNode = ASDisplayNode() self.clippingNode.clipsToBounds = true @@ -316,6 +349,12 @@ public class NavigationBar: ASDisplayNode { leftBarButtonItem.performActionOnTarget() } } + + self.rightButtonNode.pressed = { [weak self] in + if let item = self?.item, let rightBarButtonItem = item.rightBarButtonItem { + rightBarButtonItem.performActionOnTarget() + } + } } public override func layout() { @@ -332,6 +371,7 @@ public class NavigationBar: ASDisplayNode { var contentVerticalOrigin = size.height - nominalHeight var leftTitleInset: CGFloat = 8.0 + var rightTitleInset: CGFloat = 8.0 if self.backButtonNode.supernode != nil { let backButtonSize = self.backButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) leftTitleInset += backButtonSize.width + backButtonInset + 8.0 + 8.0 @@ -382,6 +422,13 @@ public class NavigationBar: ASDisplayNode { self.leftButtonNode.frame = CGRect(origin: CGPoint(x: leftButtonInset, y: contentVerticalOrigin + floor((nominalHeight - leftButtonSize.height) / 2.0)), size: leftButtonSize) } + if self.rightButtonNode.supernode != nil { + let rightButtonSize = self.rightButtonNode.measure(CGSize(width: size.width, height: nominalHeight)) + rightTitleInset += rightButtonSize.width + leftButtonInset + 8.0 + 8.0 + self.rightButtonNode.alpha = 1.0 + self.rightButtonNode.frame = CGRect(origin: CGPoint(x: size.width - leftButtonInset - rightButtonSize.width, y: contentVerticalOrigin + floor((nominalHeight - rightButtonSize.height) / 2.0)), size: rightButtonSize) + } + if let transitionState = self.transitionState { let progress = transitionState.progress @@ -409,7 +456,7 @@ public class NavigationBar: ASDisplayNode { } if self.titleNode.supernode != nil { - let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - leftTitleInset - leftTitleInset), height: nominalHeight)) + let titleSize = self.titleNode.measure(CGSize(width: max(1.0, size.width - max(leftTitleInset, rightTitleInset) * 2.0), height: nominalHeight)) if let transitionState = self.transitionState, let otherNavigationBar = transitionState.navigationBar { let progress = transitionState.progress diff --git a/Display/NavigationButtonNode.swift b/Display/NavigationButtonNode.swift index 15f19a0a2d..fe0a874573 100644 --- a/Display/NavigationButtonNode.swift +++ b/Display/NavigationButtonNode.swift @@ -25,6 +25,19 @@ public class NavigationButtonNode: ASTextNode { } } + public var node: ASDisplayNode? { + didSet { + if self.node !== oldValue { + oldValue?.removeFromSupernode() + if let node = self.node { + self.addSubnode(node) + self.invalidateCalculatedLayout() + self.setNeedsLayout() + } + } + } + } + public var color: UIColor = UIColor(0x1195f2) { didSet { if let text = self._text { @@ -60,6 +73,23 @@ public class NavigationButtonNode: ASTextNode { self.displaysAsynchronously = false } + override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + let superSize = super.calculateSizeThatFits(constrainedSize) + if let node = self.node { + let nodeSize = node.measure(constrainedSize) + return CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height)) + } + return superSize + } + + override public func layout() { + super.layout() + + if let node = self.node { + node.frame = CGRect(origin: CGPoint(), size: node.calculatedSize) + } + } + private func touchInsideApparentBounds(_ touch: UITouch) -> Bool { var apparentBounds = self.bounds let hitTestSlop = self.hitTestSlop diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index f1f3e777cb..7114d4ca23 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -409,6 +409,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldBeRequiredToFailBy otherGestureRecognizer: UIGestureRecognizer) -> Bool { - return otherGestureRecognizer is UIPanGestureRecognizer + if let panRecognizer = otherGestureRecognizer as? UIPanGestureRecognizer { + return true + } + return false } } diff --git a/Display/StatusBarHost.swift b/Display/StatusBarHost.swift index 90707fe7da..073aeaf6c9 100644 --- a/Display/StatusBarHost.swift +++ b/Display/StatusBarHost.swift @@ -5,4 +5,6 @@ public protocol StatusBarHost { var statusBarStyle: UIStatusBarStyle { get set } var statusBarWindow: UIView? { get } var statusBarView: UIView? { get } + + var keyboardView: UIView? { get } } diff --git a/Display/UIBarButtonItem+Proxy.h b/Display/UIBarButtonItem+Proxy.h index 031bf15fb3..13ada65244 100644 --- a/Display/UIBarButtonItem+Proxy.h +++ b/Display/UIBarButtonItem+Proxy.h @@ -1,10 +1,15 @@ #import +#import typedef void (^UIBarButtonItemSetTitleListener)(NSString *); typedef void (^UIBarButtonItemSetEnabledListener)(BOOL); @interface UIBarButtonItem (Proxy) +@property (nonatomic, strong, readonly) ASDisplayNode *customDisplayNode; + +- (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode; + - (void)performActionOnTarget; - (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener; diff --git a/Display/UIBarButtonItem+Proxy.m b/Display/UIBarButtonItem+Proxy.m index 01e6ad0e0c..e9d486bc7a 100644 --- a/Display/UIBarButtonItem+Proxy.m +++ b/Display/UIBarButtonItem+Proxy.m @@ -5,6 +5,7 @@ static const void *setEnabledListenerBagKey = &setEnabledListenerBagKey; static const void *setTitleListenerBagKey = &setTitleListenerBagKey; +static const void *customDisplayNodeKey = &customDisplayNodeKey; @implementation UIBarButtonItem (Proxy) @@ -18,6 +19,18 @@ static const void *setTitleListenerBagKey = &setTitleListenerBagKey; }); } +- (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode { + self = [super init]; + if (self != nil) { + [self setAssociatedObject:customDisplayNode forKey:customDisplayNodeKey]; + } + return self; +} + +- (ASDisplayNode *)customDisplayNode { + return [self associatedObjectForKey:customDisplayNodeKey]; +} + - (void)_c1e56039_setEnabled:(BOOL)enabled { [self _c1e56039_setEnabled:enabled]; @@ -40,7 +53,9 @@ static const void *setTitleListenerBagKey = &setTitleListenerBagKey; - (void)performActionOnTarget { - NSAssert(self.target != nil, @"self.target != nil"); + if (self.target == nil) { + return; + } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Warc-performSelector-leaks" diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index 256b17d629..76acce5862 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -37,7 +37,8 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { springAnimation.stiffness = 1000.0f; springAnimation.damping = 500.0f; springAnimation.initialVelocity = 0.0f; - springAnimation.duration = springAnimation.settlingDuration; + springAnimation.duration = 0.5;//springAnimation.settlingDuration; + springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear]; return springAnimation; } diff --git a/Display/UINavigationItem+Proxy.m b/Display/UINavigationItem+Proxy.m index 2a8b1a1b5c..e8ad9c81a8 100644 --- a/Display/UINavigationItem+Proxy.m +++ b/Display/UINavigationItem+Proxy.m @@ -17,8 +17,10 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL { [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_ac91f40f_setTitle:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setTitleView:) newSelector:@selector(_ac91f40f_setTitleView:)]; - [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; - [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setLeftBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setLeftBarButtonItem:animated:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UINavigationItem class] currentSelector:@selector(setRightBarButtonItem:animated:) newSelector:@selector(_ac91f40f_setRightBarButtonItem:animated:)]; }); } @@ -40,6 +42,10 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL }]; } +- (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem { + [self setLeftBarButtonItem:leftBarButtonItem animated:false]; +} + - (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated { [self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated]; @@ -49,6 +55,10 @@ static const void *setRightBarButtonItemListenerBagKey = &setRightBarButtonItemL }]; } +- (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem { + [self setRightBarButtonItem:rightBarButtonItem animated:false]; +} + - (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem animated:(BOOL)animated { [self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated]; diff --git a/Display/Window.swift b/Display/Window.swift index bfe5fc62ea..d5ab98880d 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -157,6 +157,7 @@ public class Window: UIWindow { } else { transitionCurve = .easeInOut } + strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) } } })