diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index 7007bb8f87..6cb0ea93f5 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -44,7 +44,7 @@ D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; }; D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; }; D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; }; - D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; }; + D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* WindowContent.swift */; }; D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; }; D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; }; D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; }; @@ -82,10 +82,12 @@ D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; }; D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; }; D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; }; + D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */; }; D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; }; D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; }; D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; }; D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; }; + D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */; }; D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; }; D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; }; D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; }; @@ -102,7 +104,6 @@ D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; }; D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; }; D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; }; - D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */; }; D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; }; D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; }; D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; }; @@ -171,7 +172,7 @@ D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = ""; }; D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = ""; }; - D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = ""; }; + D05CC2A11B69326C00E235A3 /* WindowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowContent.swift; sourceTree = ""; }; D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = ""; }; D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = ""; }; @@ -209,10 +210,12 @@ D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = ""; }; D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = ""; }; D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = ""; }; + D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetCheckboxItem.swift; sourceTree = ""; }; D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = ""; }; D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = ""; }; D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = ""; }; D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = ""; }; + D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWindowHostView.swift; sourceTree = ""; }; D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = ""; }; D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = ""; }; D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTransformLayerNode.swift; sourceTree = ""; }; @@ -229,7 +232,6 @@ D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = ""; }; D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = ""; }; D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = ""; }; - D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonNode.swift; sourceTree = ""; }; D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = ""; }; D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = ""; }; D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = ""; }; @@ -272,7 +274,8 @@ D015F7501D1ADC6800E269B5 /* Window */ = { isa = PBXGroup; children = ( - D05CC2A11B69326C00E235A3 /* Window.swift */, + D05CC2A11B69326C00E235A3 /* WindowContent.swift */, + D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */, ); name = Window; sourceTree = ""; @@ -298,7 +301,7 @@ D08E90391D24159200533158 /* ActionSheetItem.swift */, D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */, D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */, - D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */, + D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */, ); name = "Action Sheet"; sourceTree = ""; @@ -737,6 +740,7 @@ D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */, D06EE8451B7140FF00837186 /* Font.swift in Sources */, D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */, + D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */, D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */, D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */, D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */, @@ -746,7 +750,6 @@ D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */, D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */, D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */, - D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */, D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */, D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */, D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */, @@ -772,6 +775,7 @@ D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */, D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */, D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */, + D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */, D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */, D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */, D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */, @@ -806,7 +810,7 @@ D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, - D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, + D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */, D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, diff --git a/Display/ActionSheetButtonItem.swift b/Display/ActionSheetButtonItem.swift index 109daf1668..52bd2ef7df 100644 --- a/Display/ActionSheetButtonItem.swift +++ b/Display/ActionSheetButtonItem.swift @@ -1,24 +1,116 @@ import Foundation +import AsyncDisplayKit public enum ActionSheetButtonColor { case accent case destructive + case disabled } public class ActionSheetButtonItem: ActionSheetItem { public let title: String public let color: ActionSheetButtonColor + public let enabled: Bool public let action: () -> Void - public init(title: String, color: ActionSheetButtonColor = .accent, action: @escaping () -> Void) { + public init(title: String, color: ActionSheetButtonColor = .accent, enabled: Bool = true, action: @escaping () -> Void) { self.title = title self.color = color + self.enabled = enabled self.action = action } public func node() -> ActionSheetItemNode { - let textColorIsAccent = self.color == ActionSheetButtonColor.accent - let textColor = textColorIsAccent ? UIColor(0x007ee5) : UIColor.red - return ActionSheetButtonNode(title: NSAttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: textColor), action: self.action) + let node = ActionSheetButtonNode() + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? ActionSheetButtonNode else { + assertionFailure() + return + } + + node.setItem(self) + } +} + +public class ActionSheetButtonNode: ActionSheetItemNode { + public static let defaultFont: UIFont = Font.regular(20.0) + + private var item: ActionSheetButtonItem? + + private let button: HighlightTrackingButton + private let label: ASTextNode + + override public init() { + self.button = HighlightTrackingButton() + + self.label = ASTextNode() + self.label.isLayerBacked = true + self.label.maximumNumberOfLines = 1 + self.label.displaysAsynchronously = false + + super.init() + + self.view.addSubview(self.button) + + self.label.isUserInteractionEnabled = false + self.addSubnode(self.label) + + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + } else { + UIView.animate(withDuration: 0.3, animations: { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + }) + } + } + } + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + func setItem(_ item: ActionSheetButtonItem) { + self.item = item + + let textColor: UIColor + switch item.color { + case .accent: + textColor = UIColor(0x007ee5) + case .destructive: + textColor = .red + case .disabled: + textColor = .gray + } + self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: textColor) + + self.button.isEnabled = item.enabled + + self.setNeedsLayout() + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + let size = self.bounds.size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + + let labelSize = self.label.measure(size) + self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + } + + @objc func buttonPressed() { + if let item = self.item { + item.action() + } } } diff --git a/Display/ActionSheetButtonNode.swift b/Display/ActionSheetButtonNode.swift deleted file mode 100644 index 396d6623bf..0000000000 --- a/Display/ActionSheetButtonNode.swift +++ /dev/null @@ -1,63 +0,0 @@ -import UIKit -import AsyncDisplayKit - -public class ActionSheetButtonNode: ActionSheetItemNode { - public static let defaultFont: UIFont = Font.regular(20.0) - - private let action: () -> Void - - private let button: HighlightTrackingButton - private let label: UILabel - private var calculatedLabelSize: CGSize? - - public init(title: NSAttributedString, action: @escaping () -> Void) { - self.action = action - - self.button = HighlightTrackingButton() - self.label = UILabel() - - super.init() - - self.view.addSubview(self.button) - - self.label.attributedText = title - self.label.numberOfLines = 1 - self.label.isUserInteractionEnabled = false - self.view.addSubview(self.label) - - self.button.highligthedChanged = { [weak self] highlighted in - if let strongSelf = self { - if highlighted { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor - } else { - UIView.animate(withDuration: 0.3, animations: { - strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor - }) - } - } - } - - self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) - } - - public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { - self.label.sizeToFit() - self.calculatedLabelSize = self.label.frame.size - - return CGSize(width: constrainedSize.width, height: 57.0) - } - - public override func layout() { - super.layout() - - self.button.frame = CGRect(origin: CGPoint(), size: self.calculatedSize) - - if let calculatedLabelSize = self.calculatedLabelSize { - self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.calculatedSize.width - calculatedLabelSize.width) / 2.0), y: floorToScreenPixels((self.calculatedSize.height - calculatedLabelSize.height) / 2.0)), size: calculatedLabelSize) - } - } - - @objc func buttonPressed() { - self.action() - } -} diff --git a/Display/ActionSheetCheckboxItem.swift b/Display/ActionSheetCheckboxItem.swift new file mode 100644 index 0000000000..651e72e28f --- /dev/null +++ b/Display/ActionSheetCheckboxItem.swift @@ -0,0 +1,130 @@ +import Foundation +import AsyncDisplayKit + +private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in + context.clear(CGRect(origin: CGPoint(), size: size)) + context.setStrokeColor(UIColor(0x007ee5).cgColor) + context.setLineWidth(2.0) + context.move(to: CGPoint(x: 12.0, y: 1.0)) + context.addLine(to: CGPoint(x: 4.16482734, y: 9.0)) + context.addLine(to: CGPoint(x: 1.0, y: 5.81145833)) + context.strokePath() +}) + +public class ActionSheetCheckboxItem: ActionSheetItem { + public let title: String + public let label: String + public let value: Bool + public let action: (Bool) -> Void + + public init(title: String, label: String, value: Bool, action: @escaping (Bool) -> Void) { + self.title = title + self.label = label + self.value = value + self.action = action + } + + public func node() -> ActionSheetItemNode { + let node = ActionSheetCheckboxItemNode() + node.setItem(self) + return node + } + + public func updateNode(_ node: ActionSheetItemNode) { + guard let node = node as? ActionSheetCheckboxItemNode else { + assertionFailure() + return + } + + node.setItem(self) + } +} + +public class ActionSheetCheckboxItemNode: ActionSheetItemNode { + public static let defaultFont: UIFont = Font.regular(20.0) + + private var item: ActionSheetCheckboxItem? + + private let button: HighlightTrackingButton + private let titleNode: ASTextNode + private let labelNode: ASTextNode + private let checkNode: ASImageNode + + public override init() { + self.button = HighlightTrackingButton() + + self.titleNode = ASTextNode() + self.titleNode.maximumNumberOfLines = 1 + self.titleNode.isUserInteractionEnabled = false + self.titleNode.displaysAsynchronously = false + + self.labelNode = ASTextNode() + self.labelNode.maximumNumberOfLines = 1 + self.labelNode.isUserInteractionEnabled = false + self.labelNode.displaysAsynchronously = false + + self.checkNode = ASImageNode() + self.checkNode.isUserInteractionEnabled = false + self.checkNode.displayWithoutProcessing = true + self.checkNode.displaysAsynchronously = false + self.checkNode.image = checkIcon + + super.init() + + self.view.addSubview(self.button) + self.addSubnode(self.titleNode) + self.addSubnode(self.labelNode) + self.addSubnode(self.checkNode) + + self.button.highligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor + } else { + UIView.animate(withDuration: 0.3, animations: { + strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor + }) + } + } + } + + self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside) + } + + func setItem(_ item: ActionSheetCheckboxItem) { + self.item = item + + self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: .black) + self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: UIColor(0x8e8e93)) + self.checkNode.isHidden = !item.value + + self.setNeedsLayout() + } + + public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize { + return CGSize(width: constrainedSize.width, height: 57.0) + } + + public override func layout() { + super.layout() + + let size = self.bounds.size + + self.button.frame = CGRect(origin: CGPoint(), size: size) + + let labelSize = self.labelNode.measure(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height)) + let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height)) + self.titleNode.frame = CGRect(origin: CGPoint(x: 44.0, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize) + self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize) + + if let image = self.checkNode.image { + self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size) + } + } + + @objc func buttonPressed() { + if let item = self.item { + item.action(!item.value) + } + } +} diff --git a/Display/ActionSheetController.swift b/Display/ActionSheetController.swift index e47ecbbe5a..2302f01a9d 100644 --- a/Display/ActionSheetController.swift +++ b/Display/ActionSheetController.swift @@ -49,4 +49,10 @@ open class ActionSheetController: ViewController { self.actionSheetNode.setGroups(groups) } } + + public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) { + if self.isViewLoaded { + self.actionSheetNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f) + } + } } diff --git a/Display/ActionSheetControllerNode.swift b/Display/ActionSheetControllerNode.swift index 5db30c1173..f6d8914ec5 100644 --- a/Display/ActionSheetControllerNode.swift +++ b/Display/ActionSheetControllerNode.swift @@ -155,4 +155,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate { func setGroups(_ groups: [ActionSheetItemGroup]) { self.itemGroupsContainerNode.setGroups(groups) } + + public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) { + self.itemGroupsContainerNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f) + } } diff --git a/Display/ActionSheetItem.swift b/Display/ActionSheetItem.swift index 469f36888c..fdac4f2386 100644 --- a/Display/ActionSheetItem.swift +++ b/Display/ActionSheetItem.swift @@ -2,4 +2,5 @@ import Foundation public protocol ActionSheetItem { func node() -> ActionSheetItemNode + func updateNode(_ node: ActionSheetItemNode) -> Void } diff --git a/Display/ActionSheetItemGroupNode.swift b/Display/ActionSheetItemGroupNode.swift index 685e9bafa5..09d9ae277a 100644 --- a/Display/ActionSheetItemGroupNode.swift +++ b/Display/ActionSheetItemGroupNode.swift @@ -209,4 +209,8 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate { node.layer.animateAlpha(from: from, to: to, duration: duration) } } + + func itemNode(at index: Int) -> ActionSheetItemNode { + return self.itemNodes[index] + } } diff --git a/Display/ActionSheetItemGroupsContainerNode.swift b/Display/ActionSheetItemGroupsContainerNode.swift index e385cc73ef..21805872d0 100644 --- a/Display/ActionSheetItemGroupsContainerNode.swift +++ b/Display/ActionSheetItemGroupsContainerNode.swift @@ -4,6 +4,7 @@ import AsyncDisplayKit private let groupSpacing: CGFloat = 16.0 final class ActionSheetItemGroupsContainerNode: ASDisplayNode { + private var groups: [ActionSheetItemGroup] = [] private var groupNodes: [ActionSheetItemGroupNode] = [] override init() { @@ -11,6 +12,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { } func setGroups(_ groups: [ActionSheetItemGroup]) { + self.groups = groups + for groupNode in self.groupNodes { groupNode.removeFromSupernode() } @@ -72,4 +75,16 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode { node.animateDimViewsAlpha(from: from, to: to, duration: duration) } } + + public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) { + var item = self.groups[groupIndex].items[itemIndex] + let itemNode = self.groupNodes[groupIndex].itemNode(at: itemIndex) + item = f(item) + item.updateNode(itemNode) + + var groupItems = self.groups[groupIndex].items + groupItems[itemIndex] = item + + self.groups[groupIndex] = ActionSheetItemGroup(items: groupItems) + } } diff --git a/Display/AlertController.swift b/Display/AlertController.swift index 5e37e021c6..79340826bf 100644 --- a/Display/AlertController.swift +++ b/Display/AlertController.swift @@ -45,8 +45,8 @@ open class AlertController: ViewController { self.controllerNode.containerLayoutUpdated(layout, transition: transition) } - override open func dismiss() { - self.presentingViewController?.dismiss(animated: false, completion: nil) + override open func dismiss(completion: (() -> Void)? = nil) { + self.presentingViewController?.dismiss(animated: false, completion: completion) } public func dismissAnimated() { diff --git a/Display/CAAnimationUtils.swift b/Display/CAAnimationUtils.swift index 0a54ce4bd9..0756b29372 100644 --- a/Display/CAAnimationUtils.swift +++ b/Display/CAAnimationUtils.swift @@ -134,8 +134,8 @@ public extension CALayer { self.add(animation, forKey: keyPath) } - public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - let animation = makeSpringBounceAnimation(keyPath, initialVelocity) + public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { + let animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping) animation.fromValue = from animation.toValue = to animation.isRemovedOnCompletion = removeOnCompletion @@ -187,8 +187,8 @@ public extension CALayer { self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion) } - func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { + func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { if let completion = completion { completion(true) } @@ -197,8 +197,8 @@ public extension CALayer { self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion) } - func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { + func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { if let completion = completion { completion(true) } @@ -219,8 +219,8 @@ public extension CALayer { self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position") } - public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) { - if from == to { + public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if from == to && !force { if let completion = completion { completion(true) } @@ -236,14 +236,14 @@ public extension CALayer { } } } - self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in + self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } completedPosition = true partialCompletion() }) - self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in + self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in if !value { interrupted = true } diff --git a/Display/CATracingLayer.m b/Display/CATracingLayer.m index 6bbc6fbd6e..18b8828867 100644 --- a/Display/CATracingLayer.m +++ b/Display/CATracingLayer.m @@ -231,7 +231,41 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull - (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key { if ([anim isKindOfClass:[CABasicAnimation class]]) { - if ([key isEqualToString:@"position"]) { + if (false && [key isEqualToString:@"bounds.origin.y"]) { + CABasicAnimation *animCopy = [anim copy]; + CGFloat from = [animCopy.fromValue floatValue]; + CGFloat to = [animCopy.toValue floatValue]; + + animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, to - from, 0.0f)]; + animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + animCopy.keyPath = @"sublayerTransform"; + + __weak CATracingLayer *weakSelf = self; + anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + + [super addAnimation:anim forKey:key]; + + CABasicAnimation *positionAnimCopy = [animCopy copy]; + positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0f)]; + positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity]; + positionAnimCopy.additive = true; + positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{ + __strong CATracingLayer *strongSelf = weakSelf; + if (strongSelf != nil) { + [strongSelf invalidateUpTheTree]; + } + }]; + + [self invalidateUpTheTree]; + + [self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"]; + [self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"]; + } else if ([key isEqualToString:@"position"]) { CABasicAnimation *animCopy = [anim copy]; CGPoint from = [animCopy.fromValue CGPointValue]; CGPoint to = [animCopy.toValue CGPointValue]; diff --git a/Display/ContainableController.swift b/Display/ContainableController.swift index 85182f228b..a5a02e8400 100644 --- a/Display/ContainableController.swift +++ b/Display/ContainableController.swift @@ -31,8 +31,8 @@ public enum ContainedViewLayoutTransition { } public extension ContainedViewLayoutTransition { - func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) { - if node.frame.equalTo(frame) { + func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) { + if node.frame.equalTo(frame) && !force { completion?(true) } else { switch self { @@ -44,7 +44,7 @@ public extension ContainedViewLayoutTransition { case let .animated(duration, curve): let previousFrame = node.frame node.frame = frame - node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in + node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in if let completion = completion { completion(result) } diff --git a/Display/Font.swift b/Display/Font.swift index d118f534af..39bdf5b419 100644 --- a/Display/Font.swift +++ b/Display/Font.swift @@ -14,6 +14,14 @@ public struct Font { } } + public static func semibold(_ size: CGFloat) -> UIFont { + if #available(iOS 8.2, *) { + return UIFont.systemFont(ofSize: size, weight: UIFontWeightSemibold) + } else { + return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil) + } + } + public static func bold(_ size: CGFloat) -> UIFont { if #available(iOS 8.2, *) { return UIFont.boldSystemFont(ofSize: size) diff --git a/Display/GridItem.swift b/Display/GridItem.swift index cd112ce5c0..d5707c3d86 100644 --- a/Display/GridItem.swift +++ b/Display/GridItem.swift @@ -12,4 +12,11 @@ public protocol GridItem { var section: GridSection? { get } func node(layout: GridNodeLayout) -> GridItemNode func update(node: GridItemNode) + var aspectRatio: CGFloat { get } +} + +public extension GridItem { + var aspectRatio: CGFloat { + return 1.0 + } } diff --git a/Display/GridItemNode.swift b/Display/GridItemNode.swift index 8a4de56269..5acb7a1aa4 100644 --- a/Display/GridItemNode.swift +++ b/Display/GridItemNode.swift @@ -2,5 +2,16 @@ import Foundation import AsyncDisplayKit open class GridItemNode: ASDisplayNode { - + open var isVisibleInGrid = false + open var isGridScrolling = false + + final var cachedFrame: CGRect = CGRect() + override open var frame: CGRect { + get { + return self.cachedFrame + } set(value) { + self.cachedFrame = value + super.frame = value + } + } } diff --git a/Display/GridNode.swift b/Display/GridNode.swift index 0d8adc8e6d..485a83b182 100644 --- a/Display/GridNode.swift +++ b/Display/GridNode.swift @@ -47,21 +47,43 @@ public struct GridNodeScrollToItem { } } +public enum GridNodeLayoutType: Equatable { + case fixed(itemSize: CGSize) + case balanced(idealHeight: CGFloat) + + public static func ==(lhs: GridNodeLayoutType, rhs: GridNodeLayoutType) -> Bool { + switch lhs { + case let .fixed(itemSize): + if case .fixed(itemSize) = rhs { + return true + } else { + return false + } + case let .balanced(idealHeight): + if case .balanced(idealHeight) = rhs { + return true + } else { + return false + } + } + } +} + public struct GridNodeLayout: Equatable { public let size: CGSize public let insets: UIEdgeInsets public let preloadSize: CGFloat - public let itemSize: CGSize + public let type: GridNodeLayoutType - public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, itemSize: CGSize) { + public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, type: GridNodeLayoutType) { self.size = size self.insets = insets self.preloadSize = preloadSize - self.itemSize = itemSize + self.type = type } public static func ==(lhs: GridNodeLayout, rhs: GridNodeLayout) -> Bool { - return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.itemSize.equalTo(rhs.itemSize) + return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.type == rhs.type } } @@ -223,7 +245,7 @@ private struct WrappedGridItemNode: Hashable { } open class GridNode: GridNodeScroller, UIScrollViewDelegate { - private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize()) + private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize())) private var firstIndexInSectionOffset: Int = 0 private var items: [GridItem] = [] private var itemNodes: [Int: GridItemNode] = [:] @@ -235,6 +257,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)? public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)? + public final var floatingSections = false + public override init() { super.init() @@ -315,10 +339,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { self.items.insert(insertedItem.item, at: insertedItem.index) } + let sortedInsertItems = transaction.insertItems.sorted(by: { $0.index < $1.index }) + var remappedInsertionItemNodes: [Int: GridItemNode] = [:] for (index, itemNode) in remappedDeletionItemNodes { var indexOffset = 0 - for insertedItem in transaction.insertItems { + for insertedItem in sortedInsertItems { if insertedItem.index <= index + indexOffset { indexOffset += 1 } @@ -343,14 +369,26 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { generatedScrollToItem = nil } - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition) - - completion(self.displayedItemRange()) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, completion: completion) + } + + public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) { + self.updateItemNodeVisibilititesAndScrolling() + } + + public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { + if !decelerate { + self.updateItemNodeVisibilititesAndScrolling() + } + } + + public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + self.updateItemNodeVisibilititesAndScrolling() } public func scrollViewDidScroll(_ scrollView: UIScrollView) { if !self.applyingContentOffset { - self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil) + self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, completion: { _ in }) } } @@ -379,60 +417,144 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var items: [GridNodePresentationItem] = [] var sections: [GridNodePresentationSection] = [] - let itemsInRow = Int(gridLayout.size.width / gridLayout.itemSize.width) - let itemsInRowWidth = CGFloat(itemsInRow) * gridLayout.itemSize.width - let remainingWidth = gridLayout.size.width - itemsInRowWidth - - let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) - - var incrementedCurrentRow = false - var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) - var index = 0 - var previousSection: GridSection? - for item in self.items { - let section = item.section - var keepSection = true - if let previousSection = previousSection, let section = section { - keepSection = previousSection.isEqual(to: section) - } else if (previousSection != nil) != (section != nil) { - keepSection = false - } - - if !keepSection { - if incrementedCurrentRow { - nextItemOrigin.x = itemSpacing - nextItemOrigin.y += gridLayout.itemSize.height - incrementedCurrentRow = false + switch gridLayout.type { + case let .fixed(itemSize): + let itemsInRow = Int(gridLayout.size.width / itemSize.width) + let itemsInRowWidth = CGFloat(itemsInRow) * itemSize.width + let remainingWidth = gridLayout.size.width - itemsInRowWidth + + let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1)) + + var incrementedCurrentRow = false + var nextItemOrigin = CGPoint(x: itemSpacing, y: 0.0) + var index = 0 + var previousSection: GridSection? + for item in self.items { + let section = item.section + var keepSection = true + if let previousSection = previousSection, let section = section { + keepSection = previousSection.isEqual(to: section) + } else if (previousSection != nil) != (section != nil) { + keepSection = false + } + + if !keepSection { + if incrementedCurrentRow { + nextItemOrigin.x = itemSpacing + nextItemOrigin.y += itemSize.height + incrementedCurrentRow = false + } + + if let section = section { + sections.append(GridNodePresentationSection(section: section, frame: CGRect(origin: CGPoint(x: 0.0, y: nextItemOrigin.y), size: CGSize(width: gridLayout.size.width, height: section.height)))) + nextItemOrigin.y += section.height + contentSize.height += section.height + } + } + previousSection = section + + if !incrementedCurrentRow { + incrementedCurrentRow = true + contentSize.height += itemSize.height + } + + if index == 0 { + let itemsInRow = Int(gridLayout.size.width) / Int(itemSize.width) + let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow + nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) + } + + items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: itemSize))) + index += 1 + + nextItemOrigin.x += itemSize.width + itemSpacing + if nextItemOrigin.x + itemSize.width > gridLayout.size.width { + nextItemOrigin.x = itemSpacing + nextItemOrigin.y += itemSize.height + incrementedCurrentRow = false + } + } + case let .balanced(idealHeight): + var weights: [Int] = [] + for item in self.items { + weights.append(Int(item.aspectRatio * 100)) } - if let section = section { - sections.append(GridNodePresentationSection(section: section, frame: CGRect(origin: CGPoint(x: 0.0, y: nextItemOrigin.y), size: CGSize(width: gridLayout.size.width, height: section.height)))) - nextItemOrigin.y += section.height - contentSize.height += section.height + var totalItemSize: CGFloat = 0.0 + for i in 0 ..< self.items.count { + totalItemSize += self.items[i].aspectRatio * idealHeight } - } - previousSection = section - - if !incrementedCurrentRow { - incrementedCurrentRow = true - contentSize.height += gridLayout.itemSize.height - } - - if index == 0 { - let itemsInRow = Int(gridLayout.size.width) / Int(gridLayout.itemSize.width) - let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow - nextItemOrigin.x += (gridLayout.itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset) - } - - items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: gridLayout.itemSize))) - index += 1 - - nextItemOrigin.x += gridLayout.itemSize.width + itemSpacing - if nextItemOrigin.x + gridLayout.itemSize.width > gridLayout.size.width { - nextItemOrigin.x = itemSpacing - nextItemOrigin.y += gridLayout.itemSize.height - incrementedCurrentRow = false - } + let numberOfRows = max(Int(round(totalItemSize / gridLayout.size.width)), 1) + + let partition = linearPartitionForWeights(weights, numberOfPartitions:numberOfRows) + + var i = 0 + var offset = CGPoint(x: 0.0, y: 0.0) + var previousItemSize: CGFloat = 0.0 + var contentMaxValueInScrollDirection: CGFloat = 0.0 + let maxWidth = gridLayout.size.width + + let minimumInteritemSpacing: CGFloat = 1.0 + let minimumLineSpacing: CGFloat = 1.0 + + let viewportWidth: CGFloat = gridLayout.size.width + + let preferredRowSize = idealHeight + + var rowIndex = -1 + for row in partition { + rowIndex += 1 + + var summedRatios: CGFloat = 0.0 + + var j = i + var n = i + row.count + + while j < n { + summedRatios += self.items[j].aspectRatio + + j += 1 + } + + var rowSize = gridLayout.size.width - (CGFloat(row.count - 1) * minimumInteritemSpacing) + + if rowIndex == partition.count - 1 { + if row.count < 2 { + rowSize = floor(viewportWidth / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing) + } else if row.count < 3 { + rowSize = floor(viewportWidth * 2.0 / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing) + } + } + + j = i + n = i + row.count + + while j < n { + let preferredAspectRatio = self.items[j].aspectRatio + + let actualSize = CGSize(width: round(rowSize / summedRatios * (preferredAspectRatio)), height: preferredRowSize) + + var frame = CGRect(x: offset.x, y: offset.y, width: actualSize.width, height: actualSize.height) + if frame.origin.x + frame.size.width >= maxWidth - 2.0 { + frame.size.width = max(1.0, maxWidth - frame.origin.x) + } + + items.append(GridNodePresentationItem(index: j, frame: frame)) + + offset.x += actualSize.width + minimumInteritemSpacing + previousItemSize = actualSize.height + contentMaxValueInScrollDirection = frame.maxY + + j += 1 + } + + if row.count > 0 { + offset = CGPoint(x: 0.0, y: offset.y + previousItemSize + minimumLineSpacing) + } + + i += row.count + } + contentSize = CGSize(width: gridLayout.size.width, height: contentMaxValueInScrollDirection) } return GridNodeItemLayout(contentSize: contentSize, items: items, sections: sections) @@ -446,15 +568,17 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up var transition: ContainedViewLayoutTransition = .immediate let contentOffset: CGPoint - switch stationaryItems { + var updatedStationaryItems = stationaryItems + if scrollToItem != nil { + updatedStationaryItems = .none + } + switch updatedStationaryItems { case .none: if let scrollToItem = scrollToItem { let itemFrame = self.itemLayout.items[scrollToItem.index] var additionalOffset: CGFloat = 0.0 - if scrollToItem.adjustForTopInset { - additionalOffset = -gridLayout.insets.top - } else if scrollToItem.adjustForSection { + if scrollToItem.adjustForSection { var adjustForSection: GridSection? if scrollToItem.index == 0 { if let itemSection = self.items[scrollToItem.index].section { @@ -475,6 +599,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { if let adjustForSection = adjustForSection { additionalOffset = -adjustForSection.height } + + if scrollToItem.adjustForTopInset { + additionalOffset += -gridLayout.insets.top + } + } else if scrollToItem.adjustForTopInset { + additionalOffset = -gridLayout.insets.top } let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom) @@ -518,7 +648,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { var selectedContentOffset: CGPoint? for (index, itemNode) in self.itemNodes { if stationaryItemIndices.contains(index) { - let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y + //let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) break } @@ -532,7 +662,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { case .all: var selectedContentOffset: CGPoint? for (index, itemNode) in self.itemNodes { - let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y + //let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y) break } @@ -548,6 +678,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { let upperDisplayBound = contentOffset.y + self.gridLayout.size.height + self.gridLayout.preloadSize var presentationItems: [GridNodePresentationItem] = [] + + var validSections = Set() for item in self.itemLayout.items { if item.frame.origin.y < lowerDisplayBound { continue @@ -556,12 +688,19 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { break } presentationItems.append(item) + if self.floatingSections { + if let section = self.items[item.index].section { + validSections.insert(WrappedGridSection(section)) + } + } } var presentationSections: [GridNodePresentationSection] = [] for section in self.itemLayout.sections { if section.frame.origin.y < lowerDisplayBound { - continue + if !validSections.contains(WrappedGridSection(section.section)) { + continue + } } if section.frame.origin.y + section.frame.size.height > upperDisplayBound { break @@ -575,7 +714,21 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?) { + private func lowestSectionNode() -> ASDisplayNode? { + var lowestHeaderNode: ASDisplayNode? + var lowestHeaderNodeIndex: Int? + for (_, headerNode) in self.sectionNodes { + if let index = self.subnodes.index(of: headerNode) { + if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! { + lowestHeaderNodeIndex = index + lowestHeaderNode = headerNode + } + } + } + return lowestHeaderNode + } + + private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, completion: (GridNodeDisplayedItemRange) -> Void) { var previousItemFrames: ([WrappedGridItemNode: CGRect])? switch presentationLayoutTransition.transition { case .animated: @@ -603,29 +756,44 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } applyingContentOffset = false + let lowestSectionNode: ASDisplayNode? = self.lowestSectionNode() + var existingItemIndices = Set() for item in presentationLayoutTransition.layout.items { existingItemIndices.insert(item.index) if let itemNode = self.itemNodes[item.index] { - itemNode.frame = item.frame + if itemNode.frame != item.frame { + itemNode.frame = item.frame + } } else { let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout) itemNode.frame = item.frame - self.addItemNode(index: item.index, itemNode: itemNode) + self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode) } } var existingSections = Set() - for section in presentationLayoutTransition.layout.sections { + for i in 0 ..< presentationLayoutTransition.layout.sections.count { + let section = presentationLayoutTransition.layout.sections[i] + let wrappedSection = WrappedGridSection(section.section) existingSections.insert(wrappedSection) + var sectionFrame = section.frame + if self.floatingSections { + var maxY = CGFloat.greatestFiniteMagnitude + if i != presentationLayoutTransition.layout.sections.count - 1 { + maxY = presentationLayoutTransition.layout.sections[i + 1].frame.minY - sectionFrame.height + } + sectionFrame.origin.y = max(sectionFrame.minY, min(maxY, presentationLayoutTransition.layout.contentOffset.y + presentationLayoutTransition.layout.layout.insets.top)) + } + if let sectionNode = self.sectionNodes[wrappedSection] { - sectionNode.frame = section.frame + sectionNode.frame = sectionFrame } else { let sectionNode = section.section.node() - sectionNode.frame = section.frame + sectionNode.frame = sectionFrame self.addSectionNode(section: wrappedSection, sectionNode: sectionNode) } } @@ -698,7 +866,6 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) } for (wrappedSection, sectionNode) in self.sectionNodes where existingSections.contains(wrappedSection) { - let position = sectionNode.layer.position sectionNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true) } @@ -777,16 +944,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + completion(self.displayedItemRange()) + + self.updateItemNodeVisibilititesAndScrolling() + if let visibleItemsUpdated = self.visibleItemsUpdated { if presentationLayoutTransition.layout.items.count != 0 { let topIndex = presentationLayoutTransition.layout.items.first!.index let bottomIndex = presentationLayoutTransition.layout.items.last!.index var topVisible: (Int, GridItem) = (topIndex, self.items[topIndex]) - var bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) + let bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex]) let lowerDisplayBound = presentationLayoutTransition.layout.contentOffset.y - let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height + //let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height for item in presentationLayoutTransition.layout.items { if lowerDisplayBound.isLess(than: item.frame.maxY) { @@ -816,11 +987,15 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } - private func addItemNode(index: Int, itemNode: GridItemNode) { + private func addItemNode(index: Int, itemNode: GridItemNode, lowestSectionNode: ASDisplayNode?) { assert(self.itemNodes[index] == nil) self.itemNodes[index] = itemNode if itemNode.supernode == nil { - self.addSubnode(itemNode) + if let lowestSectionNode = lowestSectionNode { + self.insertSubnode(itemNode, belowSubnode: lowestSectionNode) + } else { + self.addSubnode(itemNode) + } } } @@ -848,6 +1023,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { } } + private func updateItemNodeVisibilititesAndScrolling() { + let visibleRect = self.scrollView.bounds + let isScrolling = self.scrollView.isDragging || self.scrollView.isDecelerating + for (_, itemNode) in self.itemNodes { + let visible = itemNode.frame.intersects(visibleRect) + if itemNode.isVisibleInGrid != visible { + itemNode.isVisibleInGrid = visible + } + if itemNode.isGridScrolling != isScrolling { + itemNode.isGridScrolling = isScrolling + } + } + } + public func forEachItemNode(_ f: (ASDisplayNode) -> Void) { for (_, node) in self.itemNodes { f(node) @@ -872,4 +1061,127 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate { f(row) } } + + public func itemNodeAtPoint(_ point: CGPoint) -> ASDisplayNode? { + for (_, node) in self.itemNodes { + if node.frame.contains(point) { + return node + } + } + return nil + } } + +private func NH_LP_TABLE_LOOKUP(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int) -> Int { + return table[i * rowsize + j] +} + +private func NH_LP_TABLE_LOOKUP_SET(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int, _ value: Int) { + table[i * rowsize + j] = value +} + +private func linearPartitionTable(_ weights: [Int], numberOfPartitions: Int) -> [Int] { + let n = weights.count + let k = numberOfPartitions + + let tableSize = n * k; + var tmpTable = Array(repeatElement(0, count: tableSize)) + + let solutionSize = (n - 1) * (k - 1) + var solution = Array(repeatElement(0, count: solutionSize)) + + for i in 0 ..< n { + let offset = i != 0 ? NH_LP_TABLE_LOOKUP(&tmpTable, i - 1, 0, k) : 0 + NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, 0, k, Int(weights[i]) + offset) + } + + for j in 0 ..< k { + NH_LP_TABLE_LOOKUP_SET(&tmpTable, 0, j, k, Int(weights[0])) + } + + for i in 1 ..< n { + for j in 1 ..< k { + var currentMin = 0 + var minX = Int.max + + for x in 0 ..< i { + let c1 = NH_LP_TABLE_LOOKUP(&tmpTable, x, j - 1, k) + let c2 = NH_LP_TABLE_LOOKUP(&tmpTable, i, 0, k) - NH_LP_TABLE_LOOKUP(&tmpTable, x, 0, k) + let cost = max(c1, c2) + + if x == 0 || cost < currentMin { + currentMin = cost; + minX = x + } + } + + NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, j, k, currentMin) + NH_LP_TABLE_LOOKUP_SET(&solution, i - 1, j - 1, k - 1, minX) + } + } + + return solution +} + +private func linearPartitionForWeights(_ weights: [Int], numberOfPartitions: Int) -> [[Int]] { + var n = weights.count + var k = numberOfPartitions + + if k <= 0 { + return [] + } + + if k >= n { + var partition: [[Int]] = [] + for weight in weights { + partition.append([weight]) + } + return partition + } + + if n == 1 { + return [weights] + } + + var solution = linearPartitionTable(weights, numberOfPartitions: numberOfPartitions) + let solutionRowSize = numberOfPartitions - 1 + + k = k - 2; + n = n - 1; + + var answer: [[Int]] = [] + + while k >= 0 { + if n < 1 { + answer.insert([], at: 0) + } else { + var currentAnswer: [Int] = [] + + var i = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) + 1 + let range = n + 1 + while i < range { + currentAnswer.append(weights[i]) + i += 1 + } + + answer.insert(currentAnswer, at: 0) + + n = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) + } + + k = k - 1 + } + + var currentAnswer: [Int] = [] + var i = 0 + let range = n + 1 + while i < range { + currentAnswer.append(weights[i]) + i += 1 + } + + answer.insert(currentAnswer, at: 0) + + return answer +} + diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift index eec8e3ada7..2a3bd70b83 100644 --- a/Display/LegacyPresentedController.swift +++ b/Display/LegacyPresentedController.swift @@ -126,7 +126,7 @@ open class LegacyPresentedController: ViewController { self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition) } - override open func dismiss() { + override open func dismiss(completion: (() -> Void)? = nil) { switch self.presentation { case .modal: self.controllerNode.animateModalOut { [weak self] in @@ -135,7 +135,7 @@ open class LegacyPresentedController: ViewController { } else if let controller = self?.legacyController as? TGNavigationController { controller.didDismiss() }*/ - self?.presentingViewController?.dismiss(animated: false, completion: nil) + self?.presentingViewController?.dismiss(animated: false, completion: completion) } case .custom: /*if let controller = self.legacyController as? TGViewController { @@ -143,7 +143,7 @@ open class LegacyPresentedController: ViewController { } else if let controller = self.legacyController as? TGNavigationController { controller.didDismiss() }*/ - self.presentingViewController?.dismiss(animated: false, completion: nil) + self.presentingViewController?.dismiss(animated: false, completion: completion) } } } diff --git a/Display/ListView.swift b/Display/ListView.swift index a1536f9862..12a64822e1 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -172,7 +172,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel public func reportStackTrace(stack: String!, withSlide slide: String!) { NSLog("reportStackTrace stack: \(stack)\n\nslide: \(slide)") } - + override public init() { class DisplayLinkProxy: NSObject { weak var target: ListView? @@ -459,7 +459,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateItemHeaders() - for (headerId, headerNode) in self.itemHeaderNodes { + for (_, headerNode) in self.itemHeaderNodes { //let position = headerNode.position //headerNode.position = CGPoint(x: position.x, y: position.y - deltaY) @@ -490,6 +490,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.updateVisibleContentOffset() self.updateVisibleItemRange() + self.updateItemNodesVisibilities() //CATransaction.commit() } @@ -2104,6 +2105,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + self.updateItemNodesVisibilities() + self.updateScroller() self.setNeedsAnimations() @@ -2117,6 +2120,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } else { self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty) + self.updateItemNodesVisibilities() if animated { self.setNeedsAnimations() @@ -2280,7 +2284,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes) } - var currentIds = Set(self.itemHeaderNodes.keys) + let currentIds = Set(self.itemHeaderNodes.keys) for id in currentIds.subtracting(visibleHeaderNodes) { if let headerNode = self.itemHeaderNodes.removeValue(forKey: id) { headerNode.removeFromSupernode() @@ -2288,6 +2292,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + private func updateItemNodesVisibilities() { + let visibilityRect = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height - self.insets.top - self.insets.bottom)) + for itemNode in self.itemNodes { + let itemFrame = itemNode.apparentFrame + var visibility: ListViewItemNodeVisibility = .none + if visibilityRect.intersects(itemFrame) { + visibility = .visible + } + if visibility != itemNode.visibility { + itemNode.visibility = visibility + } + } + } + private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) { var index = -1 let count = self.itemNodes.count diff --git a/Display/ListViewItemNode.swift b/Display/ListViewItemNode.swift index 59580cf9c8..40bf158e44 100644 --- a/Display/ListViewItemNode.swift +++ b/Display/ListViewItemNode.swift @@ -52,6 +52,12 @@ public struct ListViewItemNodeLayout { } } +public enum ListViewItemNodeVisibility { + case none + case nearlyVisible + case visible +} + open class ListViewItemNode: ASDisplayNode { let rotated: Bool final var index: Int? @@ -85,6 +91,8 @@ open class ListViewItemNode: ASDisplayNode { public final var canBeUsedAsScrollToItemAnchor: Bool = true + open var visibility: ListViewItemNodeVisibility = .none + open var canBeSelected: Bool { return true } diff --git a/Display/NativeWindowHostView.swift b/Display/NativeWindowHostView.swift new file mode 100644 index 0000000000..d2d78ae42a --- /dev/null +++ b/Display/NativeWindowHostView.swift @@ -0,0 +1,149 @@ +import Foundation +import SwiftSignalKit + +private let defaultOrientations: UIInterfaceOrientationMask = { + if UIDevice.current.userInterfaceIdiom == .pad { + return .all + } else { + return .allButUpsideDown + } +}() + +private class WindowRootViewController: UIViewController { + var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? + var orientations: UIInterfaceOrientationMask = defaultOrientations { + didSet { + if oldValue != self.orientations { + if self.orientations == .portrait { + if UIDevice.current.orientation != .portrait { + let value = UIInterfaceOrientation.portrait.rawValue + UIDevice.current.setValue(value, forKey: "orientation") + } + } else { + UIViewController.attemptRotationToDeviceOrientation() + } + } + } + } + + override var preferredStatusBarStyle: UIStatusBarStyle { + return .default + } + + override var prefersStatusBarHidden: Bool { + return false + } + + override var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return orientations + } +} + +private final class NativeWindow: UIWindow, WindowHost { + var updateSize: ((CGSize) -> Void)? + var layoutSubviewsEvent: (() -> Void)? + var updateIsUpdatingOrientationLayout: ((Bool) -> Void)? + var updateToInterfaceOrientation: (() -> Void)? + var presentController: ((ViewController) -> Void)? + var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)? + + override var frame: CGRect { + get { + return super.frame + } set(value) { + let sizeUpdated = super.frame.size != value.size + super.frame = value + + if sizeUpdated { + self.updateSize?(value.size) + } + } + } + + override var bounds: CGRect { + get { + return super.bounds + } + set(value) { + let sizeUpdated = super.bounds.size != value.size + super.bounds = value + + if sizeUpdated { + self.updateSize?(value.size) + } + } + } + + override func layoutSubviews() { + super.layoutSubviews() + + self.layoutSubviewsEvent?() + } + + override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { + self.updateIsUpdatingOrientationLayout?(true) + super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) + self.updateIsUpdatingOrientationLayout?(false) + + self.updateToInterfaceOrientation?() + } + + func present(_ controller: ViewController) { + self.presentController?(controller) + } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + return self.hitTestImpl?(point, event) + } +} + +public func nativeWindowHostView() -> WindowHostView { + let window = NativeWindow(frame: UIScreen.main.bounds) + + let rootViewController = WindowRootViewController() + window.rootViewController = rootViewController + rootViewController.viewWillAppear(false) + rootViewController.viewDidAppear(false) + rootViewController.view.isHidden = true + + let hostView = WindowHostView(view: window, isRotating: { + return window.isRotating() + }, updateSupportedInterfaceOrientations: { orientations in + rootViewController.orientations = orientations + }) + + window.updateSize = { [weak hostView] size in + hostView?.updateSize?(size) + } + + window.layoutSubviewsEvent = { [weak hostView] in + hostView?.layoutSubviews?() + } + + window.updateIsUpdatingOrientationLayout = { [weak hostView] value in + hostView?.isUpdatingOrientationLayout = value + } + + window.updateToInterfaceOrientation = { [weak hostView] in + hostView?.updateToInterfaceOrientation?() + } + + window.presentController = { [weak hostView] controller in + hostView?.present?(controller) + } + + window.hitTestImpl = { [weak hostView] point, event in + return hostView?.hitTest?(point, event) + } + + rootViewController.presentController = { [weak hostView] controller, animated, completion in + if let strongSelf = hostView { + strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom)) + if let completion = completion { + completion() + } + } + } + + return hostView +} diff --git a/Display/NavigationBar.swift b/Display/NavigationBar.swift index e4fdf024b3..b3dc8370c9 100644 --- a/Display/NavigationBar.swift +++ b/Display/NavigationBar.swift @@ -35,7 +35,7 @@ open class NavigationBar: ASDisplayNode { open var foregroundColor: UIColor = UIColor.black { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor) } } } @@ -53,7 +53,7 @@ open class NavigationBar: ASDisplayNode { private var collapsed: Bool { get { - return self.frame.size.height < (20.0 + 44.0) + return self.frame.size.height.isLess(than: 44.0) } } @@ -173,7 +173,7 @@ open class NavigationBar: ASDisplayNode { private var title: String? { didSet { if let title = self.title { - self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor) + self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor) if self.titleNode.supernode == nil { self.clippingNode.addSubnode(self.titleNode) } @@ -544,7 +544,7 @@ open class NavigationBar: ASDisplayNode { public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? { if let title = self.title { let node = ASTextNode() - node.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: foregroundColor) + node.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: foregroundColor) return node } else { return nil diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index 2fdffca162..d03e82a29b 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -504,4 +504,17 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle } return false } + + public final var window: WindowHost? { + if let window = self.view.window as? WindowHost { + return window + } else if let superwindow = self.view.window { + for subview in superwindow.subviews { + if let subview = subview as? WindowHost { + return subview + } + } + } + return nil + } } diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 02cb60bf55..5b94f1113e 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -33,6 +33,8 @@ final class PresentationContext { private var presentationDisposables = DisposableSet() + var topLevelSubview: UIView? + public func present(_ controller: ViewController) { let controllerReady = controller.ready.get() |> filter({ $0 }) @@ -40,7 +42,7 @@ final class PresentationContext { |> deliverOnMainQueue |> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true)) - if let view = self.view, let initialLayout = self.layout { + if let _ = self.view, let initialLayout = self.layout { controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size) controller.containerLayoutUpdated(initialLayout, transition: .immediate) @@ -60,10 +62,18 @@ final class PresentationContext { controller.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) - view.addSubview(controller.view) + if let topLevelSubview = strongSelf.topLevelSubview { + view.insertSubview(controller.view, belowSubview: topLevelSubview) + } else { + view.addSubview(controller.view) + } controller.containerLayoutUpdated(layout, transition: .immediate) } else { - view.addSubview(controller.view) + if let topLevelSubview = strongSelf.topLevelSubview { + view.insertSubview(controller.view, belowSubview: topLevelSubview) + } else { + view.addSubview(controller.view) + } } controller.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() @@ -115,7 +125,11 @@ final class PresentationContext { if let view = self.view, let layout = self.layout { for controller in self.controllers { controller.viewWillAppear(false) - view.addSubview(controller.view) + if let topLevelSubview = self.topLevelSubview { + view.insertSubview(controller.view, belowSubview: topLevelSubview) + } else { + view.addSubview(controller.view) + } controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) controller.containerLayoutUpdated(layout, transition: .immediate) controller.viewDidAppear(false) @@ -141,4 +155,14 @@ final class PresentationContext { } return nil } + + func combinedSupportedOrientations() -> UIInterfaceOrientationMask { + var mask: UIInterfaceOrientationMask = .all + + for controller in self.controllers { + mask = mask.intersection(controller.supportedInterfaceOrientations) + } + + return mask + } } diff --git a/Display/StatusBar.swift b/Display/StatusBar.swift index c5aa85610f..6bc02aec96 100644 --- a/Display/StatusBar.swift +++ b/Display/StatusBar.swift @@ -27,7 +27,7 @@ public class StatusBarSurface { public class StatusBar: ASDisplayNode { public var statusBarStyle: StatusBarStyle = .Black { didSet { - if self.statusBarStyle != statusBarStyle { + if self.statusBarStyle != oldValue { self.layer.invalidateUpTheTree() } } @@ -40,7 +40,7 @@ public class StatusBar: ASDisplayNode { return UITracingLayerView() }, didLoad: nil) - self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: Window.statusBarTracingTag)) + self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar)) self.clipsToBounds = true self.isUserInteractionEnabled = false diff --git a/Display/StatusBarManager.swift b/Display/StatusBarManager.swift index 69af044c0b..f75b236c13 100644 --- a/Display/StatusBarManager.swift +++ b/Display/StatusBarManager.swift @@ -18,7 +18,13 @@ private func mapStatusBar(_ statusBar: StatusBar) -> MappedStatusBar { } private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurface { - return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface) + var statusBars: [MappedStatusBar] = [] + for statusBar in surface.statusBars { + if statusBar.statusBarStyle != .Ignore { + statusBars.append(mapStatusBar(statusBar)) + } + } + return MappedStatusBarSurface(statusBars: statusBars, surface: surface) } private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface) -> MappedStatusBarSurface { @@ -70,12 +76,36 @@ class StatusBarManager { return } - var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) }) + var mappedSurfaces: [MappedStatusBarSurface] = [] + var mapIndex = 0 + var doNotOptimize = false + for surface in self.surfaces { + inner: for statusBar in surface.statusBars { + if statusBar.statusBarStyle == .Hide { + doNotOptimize = true + break inner + } + } + + let mapped = mappedSurface(surface) + + if doNotOptimize { + mappedSurfaces.append(mapped) + } else { + mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped)) + } + mapIndex += 1 + } var reduceSurfaces = true var reduceSurfacesStatusBarStyleAndAlpha: (StatusBarStyle, CGFloat)? + var reduceIndex = 0 outer: for surface in mappedSurfaces { for mappedStatusBar in surface.statusBars { + if reduceIndex == 0 && mappedStatusBar.style == .Hide { + reduceSurfaces = false + break outer + } if mappedStatusBar.frame.origin.equalTo(CGPoint()) { let statusBarAlpha = mappedStatusBar.statusBar?.alpha ?? 1.0 if let reduceSurfacesStatusBarStyleAndAlpha = reduceSurfacesStatusBarStyleAndAlpha { @@ -92,6 +122,7 @@ class StatusBarManager { } } } + reduceIndex += 1 } if reduceSurfaces { @@ -112,16 +143,19 @@ class StatusBarManager { var globalStatusBar: (StatusBarStyle, CGFloat)? var coveredIdentity = false + var statusBarIndex = 0 for i in 0 ..< mappedSurfaces.count { for mappedStatusBar in mappedSurfaces[i].statusBars { if let statusBar = mappedStatusBar.statusBar { if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() { if !coveredIdentity { - coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) - if i == 0 && globalStatusBar == nil { - globalStatusBar = (mappedStatusBar.style, statusBar.alpha) - } else { - visibleStatusBars.append(statusBar) + if statusBar.statusBarStyle != .Hide { + coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha) + if statusBarIndex == 0 && globalStatusBar == nil { + globalStatusBar = (mappedStatusBar.style, statusBar.alpha) + } else { + visibleStatusBars.append(statusBar) + } } } } else { @@ -130,11 +164,12 @@ class StatusBarManager { } else { if !coveredIdentity { coveredIdentity = true - if i == 0 && globalStatusBar == nil { + if statusBarIndex == 0 && globalStatusBar == nil { globalStatusBar = (mappedStatusBar.style, 1.0) } } } + statusBarIndex += 1 } } diff --git a/Display/StatusBarProxyNode.swift b/Display/StatusBarProxyNode.swift index 0f1e520996..e77e3d58e2 100644 --- a/Display/StatusBarProxyNode.swift +++ b/Display/StatusBarProxyNode.swift @@ -4,6 +4,8 @@ import AsyncDisplayKit public enum StatusBarStyle { case Black case White + case Ignore + case Hide } private enum StatusBarItemType { @@ -142,7 +144,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseColor: UInt32 switch style { - case .Black: + case .Black, .Ignore, .Hide: baseColor = 0x000000 case .White: baseColor = 0xffffff @@ -193,7 +195,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp let baseColor: UInt32 switch style { - case .Black: + case .Black, .Ignore, .Hide: baseColor = 0x000000 case .White: baseColor = 0xffffff diff --git a/Display/SwitchNode.swift b/Display/SwitchNode.swift index c1048ff36c..0a1a3c6797 100644 --- a/Display/SwitchNode.swift +++ b/Display/SwitchNode.swift @@ -27,6 +27,8 @@ open class SwitchNode: ASDisplayNode { override open func didLoad() { super.didLoad() + (self.view as! UISwitch).backgroundColor = .white + (self.view as! UISwitch).setOn(self._isOn, animated: false) (self.view as! UISwitch).addTarget(self, action: #selector(switchValueChanged(_:)), for: .valueChanged) diff --git a/Display/UIKitUtils.h b/Display/UIKitUtils.h index dd2807b5ed..292a972750 100644 --- a/Display/UIKitUtils.h +++ b/Display/UIKitUtils.h @@ -8,6 +8,6 @@ @end CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath); -CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity); +CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping); CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t); diff --git a/Display/UIKitUtils.m b/Display/UIKitUtils.m index e236dab2c7..195d4a7dcf 100644 --- a/Display/UIKitUtils.m +++ b/Display/UIKitUtils.m @@ -41,11 +41,11 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) { return springAnimation; } -CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity) { +CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) { CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath]; springAnimation.mass = 5.0f; springAnimation.stiffness = 900.0f; - springAnimation.damping = 88.0f; + springAnimation.damping = damping; static bool canSetInitialVelocity = true; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ diff --git a/Display/UIKitUtils.swift b/Display/UIKitUtils.swift index f49fafaef0..999fe5d312 100644 --- a/Display/UIKitUtils.swift +++ b/Display/UIKitUtils.swift @@ -118,11 +118,15 @@ public extension UIImage { private func makeSubtreeSnapshot(layer: CALayer) -> UIView? { let view = UIView() + //view.layer.isHidden = layer.isHidden + view.layer.opacity = layer.opacity view.layer.contents = layer.contents view.layer.contentsRect = layer.contentsRect view.layer.contentsScale = layer.contentsScale view.layer.contentsCenter = layer.contentsCenter view.layer.contentsGravity = layer.contentsGravity + view.layer.masksToBounds = layer.masksToBounds + view.layer.cornerRadius = layer.cornerRadius if let sublayers = layer.sublayers { for sublayer in sublayers { let subtree = makeSubtreeSnapshot(layer: sublayer) diff --git a/Display/ViewController.swift b/Display/ViewController.swift index daf8347c18..0dcfd25ff1 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -33,6 +33,11 @@ open class ViewControllerPresentationArguments { private var containerLayout = ContainerViewLayout() private let presentationContext: PresentationContext + public final var supportedOrientations: UIInterfaceOrientationMask = .all + override open var supportedInterfaceOrientations: UIInterfaceOrientationMask { + return self.supportedOrientations + } + public private(set) var presentationArguments: Any? private var _displayNode: ASDisplayNode? @@ -137,9 +142,9 @@ open class ViewControllerPresentationArguments { let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0 var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0)) - if statusBarHeight.isLessThanOrEqualTo(0.0) { - navigationBarFrame.origin.y -= 20.0 - navigationBarFrame.size.height = 20.0 + 32.0 + if layout.statusBarHeight == nil { + //navigationBarFrame.origin.y -= 20.0 + navigationBarFrame.size.height = 44.0 } if !self.displayNavigationBar { @@ -169,7 +174,7 @@ open class ViewControllerPresentationArguments { open func displayNodeDidLoad() { if let layer = self.displayNode.layer as? CATracingLayer { - layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: Window.keyboardTracingTag)) + layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard)) } self.updateScrollToTopView() } @@ -195,7 +200,14 @@ open class ViewControllerPresentationArguments { } override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { - preconditionFailure("use present(_:in)") + super.present(viewControllerToPresent, animated: flag, completion: completion) + return + + if let controller = viewControllerToPresent as? ViewController { + self.present(controller, in: .window) + } else { + preconditionFailure("use present(_:in) for \(viewControllerToPresent)") + } } override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { @@ -206,12 +218,12 @@ open class ViewControllerPresentationArguments { } } - private var window: Window? { - if let window = self.view.window as? Window { + public final var window: WindowHost? { + if let window = self.view.window as? WindowHost { return window } else if let superwindow = self.view.window { for subview in superwindow.subviews { - if let subview = subview as? Window { + if let subview = subview as? WindowHost { return subview } } @@ -247,6 +259,6 @@ open class ViewControllerPresentationArguments { super.viewDidAppear(animated) } - open func dismiss() { + open func dismiss(completion: (() -> Void)? = nil) { } } diff --git a/Display/Window.swift b/Display/WindowContent.swift similarity index 73% rename from Display/Window.swift rename to Display/WindowContent.swift index 60c55e5a8a..386d85fd3c 100644 --- a/Display/Window.swift +++ b/Display/WindowContent.swift @@ -11,12 +11,6 @@ private class WindowRootViewController: UIViewController { override var prefersStatusBarHidden: Bool { return false } - - /*override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { - if let presentController = self.presentController { - presentController(viewControllerToPresent, flag, completion) - } - }*/ } private struct WindowLayout: Equatable { @@ -115,9 +109,37 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: inputHeight) } -public class Window: UIWindow { - public static let statusBarTracingTag: Int32 = 0 - public static let keyboardTracingTag: Int32 = 1 +public final class WindowHostView { + public let view: UIView + public let isRotating: () -> Bool + + let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void + + var present: ((ViewController) -> Void)? + var updateSize: ((CGSize) -> Void)? + var layoutSubviews: (() -> Void)? + var updateToInterfaceOrientation: (() -> Void)? + var isUpdatingOrientationLayout = false + var hitTest: ((CGPoint, UIEvent?) -> UIView?)? + + init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void) { + self.view = view + self.isRotating = isRotating + self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations + } +} + +public struct WindowTracingTags { + public static let statusBar: Int32 = 0 + public static let keyboard: Int32 = 1 +} + +public protocol WindowHost { + func present(_ controller: ViewController) +} + +public class Window1 { + public let hostView: WindowHostView private let statusBarHost: StatusBarHost? private let statusBarManager: StatusBarManager? @@ -128,15 +150,15 @@ public class Window: UIWindow { private var windowLayout: WindowLayout private var updatingLayout: UpdatingLayout? - public var isUpdatingOrientationLayout = false - private let presentationContext: PresentationContext private var tracingStatusBarsInvalidated = false private var statusBarHidden = false - public init(frame: CGRect, statusBarHost: StatusBarHost?) { + public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) { + self.hostView = hostView + self.statusBarHost = statusBarHost let statusBarHeight: CGFloat if let statusBarHost = statusBarHost { @@ -156,15 +178,33 @@ public class Window: UIWindow { minimized = false } - self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized) + self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized) self.presentationContext = PresentationContext() - super.init(frame: frame) + self.hostView.present = { [weak self] controller in + self?.present(controller) + } - self.layer.setInvalidateTracingSublayers { [weak self] in + self.hostView.updateSize = { [weak self] size in + self?.updateSize(size) + } + + self.hostView.view.layer.setInvalidateTracingSublayers { [weak self] in self?.invalidateTracingStatusBars() } + self.hostView.layoutSubviews = { [weak self] in + self?.layoutSubviews() + } + + self.hostView.updateToInterfaceOrientation = { [weak self] in + self?.updateToInterfaceOrientation() + } + + self.hostView.hitTest = { [weak self] point, event in + return self?.hitTest(point, with: event) + } + self.keyboardManager?.minimizedUpdated = { [weak self] in if let strongSelf = self { strongSelf.updateLayout { current in @@ -173,24 +213,9 @@ public class Window: UIWindow { } } - self.presentationContext.view = self + self.presentationContext.view = self.hostView.view self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - let rootViewController = WindowRootViewController() - super.rootViewController = rootViewController - rootViewController.viewWillAppear(false) - rootViewController.viewDidAppear(false) - rootViewController.view.isHidden = true - - rootViewController.presentController = { [weak self] controller, animated, completion in - if let strongSelf = self { - strongSelf.present(LegacyPresentedController(legacyController: controller, presentation: .custom)) - if let completion = completion { - completion() - } - } - } - self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? 20.0) @@ -205,7 +230,7 @@ public class Window: UIWindow { let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect() let keyboardHeight = max(0.0, UIScreen.main.bounds.size.height - keyboardFrame.minY) var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0 - if duration > DBL_EPSILON { + if duration > Double.ulpOfOne { duration = 0.5 } let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7 @@ -237,54 +262,36 @@ public class Window: UIWindow { private func invalidateTracingStatusBars() { self.tracingStatusBarsInvalidated = true - self.setNeedsLayout() + self.hostView.view.setNeedsLayout() } - public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + for view in self.hostView.view.subviews.reversed() { + if NSStringFromClass(type(of: view)) == "UITransitionView" { + if let result = view.hitTest(point, with: event) { + return result + } + } + } + + if let result = self._topLevelOverlayController?.view.hitTest(point, with: event) { + return result + } + if let result = self.presentationContext.hitTest(point, with: event) { return result } return self.viewController?.view.hitTest(point, with: event) } - public override var frame: CGRect { - get { - return super.frame - } - set(value) { - let sizeUpdated = super.frame.size != value.size - super.frame = value - - if sizeUpdated { - let transition: ContainedViewLayoutTransition - if self.isRotating() { - transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) - } else { - transition = .immediate - } - self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) } - } - } - } - - public override var bounds: CGRect { - get { - return super.frame - } - set(value) { - let sizeUpdated = super.bounds.size != value.size - super.bounds = value - - if sizeUpdated { - let transition: ContainedViewLayoutTransition - if self.isRotating() { - transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) - } else { - transition = .immediate - } - self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) } - } + func updateSize(_ value: CGSize) { + let transition: ContainedViewLayoutTransition + if self.hostView.isRotating() { + transition = .animated(duration: orientationChangeDuration, curve: .easeInOut) + } else { + transition = .immediate } + self.updateLayout { $0.update(size: value, transition: transition, overrideTransition: true) } } private var _rootController: ContainableController? @@ -301,14 +308,33 @@ public class Window: UIWindow { if let rootController = self._rootController { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - self.addSubview(rootController.view) + self.hostView.view.addSubview(rootController.view) } } } - override public func layoutSubviews() { - super.layoutSubviews() - + private var _topLevelOverlayController: ContainableController? + public var topLevelOverlayController: ContainableController? { + get { + return _topLevelOverlayController + } + set(value) { + if let topLevelOverlayController = self._topLevelOverlayController { + topLevelOverlayController.view.removeFromSuperview() + } + self._topLevelOverlayController = value + + if let topLevelOverlayController = self._topLevelOverlayController { + topLevelOverlayController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) + + self.hostView.view.addSubview(topLevelOverlayController.view) + } + + self.presentationContext.topLevelSubview = self._topLevelOverlayController?.view + } + } + + private func layoutSubviews() { if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager { self.tracingStatusBarsInvalidated = false @@ -316,7 +342,7 @@ public class Window: UIWindow { statusBarManager.surfaces = [] } else { var statusBarSurfaces: [StatusBarSurface] = [] - for layers in self.layer.traceableLayerSurfaces(withTag: Window.statusBarTracingTag) { + for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) { let surface = StatusBarSurface() for layer in layers { let traceableInfo = layer.traceableInfo() @@ -326,12 +352,12 @@ public class Window: UIWindow { } statusBarSurfaces.append(surface) } - self.layer.adjustTraceableLayerTransforms(CGSize()) + self.hostView.view.layer.adjustTraceableLayerTransforms(CGSize()) statusBarManager.surfaces = statusBarSurfaces } var keyboardSurfaces: [KeyboardSurface] = [] - for layers in self.layer.traceableLayerSurfaces(withTag: Window.keyboardTracingTag) { + for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.keyboard) { for layer in layers { if let view = layer.delegate as? UITracingLayerView { keyboardSurfaces.append(KeyboardSurface(host: view)) @@ -339,22 +365,23 @@ public class Window: UIWindow { } } keyboardManager.surfaces = keyboardSurfaces + self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations()) } - if !Window.isDeviceRotating() { - if !self.isUpdatingOrientationLayout { + if !UIWindow.isDeviceRotating() { + if !self.hostView.isUpdatingOrientationLayout { self.commitUpdatingLayout() } else { self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in if let strongSelf = self { - strongSelf.setNeedsLayout() + strongSelf.hostView.view.setNeedsLayout() } }) } } else { - Window.addPostDeviceOrientationDidChange({ [weak self] in + UIWindow.addPostDeviceOrientationDidChange({ [weak self] in if let strongSelf = self { - strongSelf.setNeedsLayout() + strongSelf.hostView.view.setNeedsLayout() } }) } @@ -362,11 +389,7 @@ public class Window: UIWindow { var postUpdateToInterfaceOrientationBlocks: [(Void) -> Void] = [] - override public func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) { - self.isUpdatingOrientationLayout = true - super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3) - self.isUpdatingOrientationLayout = false - + private func updateToInterfaceOrientation() { let blocks = self.postUpdateToInterfaceOrientationBlocks self.postUpdateToInterfaceOrientationBlocks = [] for f in blocks { @@ -383,14 +406,14 @@ public class Window: UIWindow { self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate) } update(&self.updatingLayout!) - self.setNeedsLayout() + self.hostView.view.setNeedsLayout() } private func commitUpdatingLayout() { if let updatingLayout = self.updatingLayout { self.updatingLayout = nil if updatingLayout.layout != self.windowLayout { - var statusBarHeight: CGFloat + var statusBarHeight: CGFloat? if let statusBarHost = self.statusBarHost { statusBarHeight = statusBarHost.statusBarFrame.size.height } else { @@ -398,14 +421,14 @@ public class Window: UIWindow { } let statusBarWasHidden = self.statusBarHidden if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height { - statusBarHeight = 0.0 + statusBarHeight = nil self.statusBarHidden = true } else { self.statusBarHidden = false } if self.statusBarHidden != statusBarWasHidden { self.tracingStatusBarsInvalidated = true - self.setNeedsLayout() + self.hostView.view.setNeedsLayout() } self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized)