diff --git a/Display.xcodeproj/project.pbxproj b/Display.xcodeproj/project.pbxproj index a4bf33483d..747514cf83 100644 --- a/Display.xcodeproj/project.pbxproj +++ b/Display.xcodeproj/project.pbxproj @@ -17,6 +17,9 @@ D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */; }; D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE11D9049F60066BF65 /* GridItemNode.swift */; }; D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D01E2BE31D904A000066BF65 /* GridItem.swift */; }; + D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */; }; + D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */; }; + D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */; }; D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */; }; D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */; }; D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D03725C01D6DF594007FC290 /* ContextMenuNode.swift */; }; @@ -103,6 +106,7 @@ D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; }; D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; }; D0E1D6721CBC201E00B04029 /* AsyncDisplayKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */; }; + D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E35A021DE473B900BC6096 /* HighlightableButton.swift */; }; D0E49C881B83A3580099E553 /* ImageCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0E49C871B83A3580099E553 /* ImageCache.swift */; }; D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */; }; D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */; }; @@ -129,6 +133,9 @@ D01E2BDF1D90498E0066BF65 /* GridNodeScroller.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridNodeScroller.swift; sourceTree = ""; }; D01E2BE11D9049F60066BF65 /* GridItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItemNode.swift; sourceTree = ""; }; D01E2BE31D904A000066BF65 /* GridItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GridItem.swift; sourceTree = ""; }; + D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedController.swift; sourceTree = ""; }; + D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacyPresentedControllerNode.swift; sourceTree = ""; }; + D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewIntermediateState.swift; sourceTree = ""; }; D02957FF1D6F096000360E5E /* ContextMenuContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuContainerNode.swift; sourceTree = ""; }; D02BDB011B6AC703008AFAD2 /* RuntimeUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RuntimeUtils.swift; sourceTree = ""; }; D03725C01D6DF594007FC290 /* ContextMenuNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuNode.swift; sourceTree = ""; }; @@ -219,6 +226,7 @@ D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = ""; }; D0E1D6351CBC159C00B04029 /* AVFoundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AVFoundation.framework; path = System/Library/Frameworks/AVFoundation.framework; sourceTree = SDKROOT; }; D0E1D6711CBC201E00B04029 /* AsyncDisplayKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = AsyncDisplayKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + D0E35A021DE473B900BC6096 /* HighlightableButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightableButton.swift; sourceTree = ""; }; D0E49C871B83A3580099E553 /* ImageCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ImageCache.swift; sourceTree = ""; }; D0F1132E1D6F3C20008C3597 /* ContextMenuActionNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContextMenuActionNode.swift; sourceTree = ""; }; D0F7AB361DCFF6F8009AD9A1 /* ListViewItemHeader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListViewItemHeader.swift; sourceTree = ""; }; @@ -303,6 +311,7 @@ isa = PBXGroup; children = ( D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */, + D0E35A021DE473B900BC6096 /* HighlightableButton.swift */, ); name = Nodes; sourceTree = ""; @@ -504,6 +513,8 @@ D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */, D05BE4AA1D1F25E3002BD72C /* PresentationContext.swift */, D05CC2E21B69552C00E235A3 /* ViewController.swift */, + D023837F1DDF7916004018B6 /* LegacyPresentedController.swift */, + D02383811DDF798E004018B6 /* LegacyPresentedControllerNode.swift */, D05BE4A71D1F1DCC002BD72C /* Master */, D081229A1D19A9EB005F7395 /* Navigation */, D015F7551D1B142300E269B5 /* Tab Bar */, @@ -519,6 +530,7 @@ D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */, D0C2DFBD1CC4431D0044FF83 /* Spring.swift */, D0C2DFBE1CC4431D0044FF83 /* ListView.swift */, + D02383851DE0E3B4004018B6 /* ListViewIntermediateState.swift */, D0C2DFBF1CC4431D0044FF83 /* ListViewItem.swift */, D0C2DFBC1CC4431D0044FF83 /* ListViewItemNode.swift */, D0C2DFC01CC4431D0044FF83 /* ListViewAnimation.swift */, @@ -731,8 +743,10 @@ D08E903A1D24159200533158 /* ActionSheetItem.swift in Sources */, D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */, D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */, + D0E35A031DE473B900BC6096 /* HighlightableButton.swift in Sources */, D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */, D0C2DFCD1CC4431D0044FF83 /* ListViewTransactionQueue.swift in Sources */, + D02383821DDF798E004018B6 /* LegacyPresentedControllerNode.swift in Sources */, D05CC2FC1B6955D000E235A3 /* UIKitUtils.m in Sources */, D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */, D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */, @@ -742,11 +756,13 @@ D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */, D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */, D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */, + D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */, D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */, D05CC2A21B69326C00E235A3 /* Window.swift in Sources */, D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */, D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */, D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */, + D02383801DDF7916004018B6 /* LegacyPresentedController.swift in Sources */, D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */, D0F7AB371DCFF6F8009AD9A1 /* ListViewItemHeader.swift in Sources */, ); diff --git a/Display/HighlightTrackingButton.swift b/Display/HighlightTrackingButton.swift index 9f8d5bbfeb..f50385e127 100644 --- a/Display/HighlightTrackingButton.swift +++ b/Display/HighlightTrackingButton.swift @@ -1,22 +1,26 @@ import UIKit -public class HighlightTrackingButton: UIButton { +open class HighlightTrackingButton: UIButton { + public var internalHighligthedChanged: (Bool) -> Void = { _ in } public var highligthedChanged: (Bool) -> Void = { _ in } - public override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { + open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool { self.highligthedChanged(true) + self.internalHighligthedChanged(true) return super.beginTracking(touch, with: event) } - public override func endTracking(_ touch: UITouch?, with event: UIEvent?) { + open override func endTracking(_ touch: UITouch?, with event: UIEvent?) { self.highligthedChanged(false) + self.internalHighligthedChanged(false) super.endTracking(touch, with: event) } - public override func cancelTracking(with event: UIEvent?) { + open override func cancelTracking(with event: UIEvent?) { self.highligthedChanged(false) + self.internalHighligthedChanged(false) super.cancelTracking(with: event) } diff --git a/Display/HighlightableButton.swift b/Display/HighlightableButton.swift new file mode 100644 index 0000000000..44c0071958 --- /dev/null +++ b/Display/HighlightableButton.swift @@ -0,0 +1,26 @@ +import Foundation +import UIKit + +open class HighlightableButton: HighlightTrackingButton { + override public init(frame: CGRect) { + super.init(frame: frame) + + self.adjustsImageWhenHighlighted = false + self.adjustsImageWhenDisabled = false + self.internalHighligthedChanged = { [weak self] highlighted in + if let strongSelf = self { + if highlighted { + strongSelf.layer.removeAnimation(forKey: "opacity") + strongSelf.alpha = 0.4 + } else { + strongSelf.alpha = 1.0 + strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2) + } + } + } + } + + required public init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/Display/LegacyPresentedController.swift b/Display/LegacyPresentedController.swift new file mode 100644 index 0000000000..f1d399e6e5 --- /dev/null +++ b/Display/LegacyPresentedController.swift @@ -0,0 +1,149 @@ +import Foundation +import UIKit +import AsyncDisplayKit + +public enum LegacyPresentedControllerPresentation { + case custom + case modal +} + +private func passControllerAppearanceAnimated(presentation: LegacyPresentedControllerPresentation) -> Bool { + switch presentation { + case .custom: + return false + case .modal: + return true + } +} + +open class LegacyPresentedController: ViewController { + private let legacyController: UIViewController + private let presentation: LegacyPresentedControllerPresentation + + private var controllerNode: LegacyPresentedControllerNode { + return self.displayNode as! LegacyPresentedControllerNode + } + private var loadedController = false + + var controllerLoaded: (() -> Void)? + + private let asPresentable = true + + public init(legacyController: UIViewController, presentation: LegacyPresentedControllerPresentation) { + self.legacyController = legacyController + self.presentation = presentation + + super.init() + + self.navigationBar.isHidden = true + /*legacyController.navigation_setDismiss { [weak self] in + self?.dismiss() + }*/ + if !asPresentable { + self.addChildViewController(legacyController) + } + } + + required public init(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override open func loadDisplayNode() { + self.displayNode = LegacyPresentedControllerNode() + } + + override open func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + + if self.ignoreAppearanceMethodInvocations() { + return + } + + if !loadedController && !asPresentable { + loadedController = true + + self.controllerNode.controllerView = self.legacyController.view + self.controllerNode.view.addSubview(self.legacyController.view) + self.legacyController.didMove(toParentViewController: self) + + if let controllerLoaded = self.controllerLoaded { + controllerLoaded() + } + } + + if !asPresentable { + self.legacyController.viewWillAppear(animated && passControllerAppearanceAnimated(presentation: self.presentation)) + } + } + + override open func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + + if self.ignoreAppearanceMethodInvocations() { + return + } + + if !asPresentable { + self.legacyController.viewWillDisappear(animated && passControllerAppearanceAnimated(presentation: self.presentation)) + } + } + + override open func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if self.ignoreAppearanceMethodInvocations() { + return + } + + if asPresentable { + if !loadedController { + loadedController = true + //self.legacyController.modalPresentationStyle = .currentContext + self.present(self.legacyController, animated: false, completion: nil) + } + } else { + switch self.presentation { + case .modal: + self.controllerNode.animateModalIn() + self.legacyController.viewDidAppear(true) + case .custom: + self.legacyController.viewDidAppear(animated) + } + } + } + + override open func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + + if !self.asPresentable { + self.legacyController.viewDidDisappear(animated && passControllerAppearanceAnimated(presentation: self.presentation)) + } + } + + override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) { + super.containerLayoutUpdated(layout, transition: transition) + + self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationBar.frame.maxY, transition: transition) + } + + public func dismiss() { + switch self.presentation { + case .modal: + self.controllerNode.animateModalOut { [weak self] in + /*if let controller = self?.legacyController as? TGViewController { + controller.didDismiss() + } else if let controller = self?.legacyController as? TGNavigationController { + controller.didDismiss() + }*/ + self?.presentingViewController?.dismiss(animated: false, completion: nil) + } + case .custom: + /*if let controller = self.legacyController as? TGViewController { + controller.didDismiss() + } else if let controller = self.legacyController as? TGNavigationController { + controller.didDismiss() + }*/ + self.presentingViewController?.dismiss(animated: false, completion: nil) + } + } +} diff --git a/Display/LegacyPresentedControllerNode.swift b/Display/LegacyPresentedControllerNode.swift new file mode 100644 index 0000000000..f853701566 --- /dev/null +++ b/Display/LegacyPresentedControllerNode.swift @@ -0,0 +1,38 @@ +import Foundation +import AsyncDisplayKit +import Display + +final class LegacyPresentedControllerNode: ASDisplayNode { + private var containerLayout: ContainerViewLayout? + + var controllerView: UIView? { + didSet { + if let controllerView = self.controllerView, let containerLayout = self.containerLayout { + controllerView.frame = CGRect(origin: CGPoint(), size: containerLayout.size) + } + } + } + + override init() { + super.init(viewBlock: { + return UITracingLayerView() + }, didLoad: nil) + } + + func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) { + self.containerLayout = layout + if let controllerView = self.controllerView { + controllerView.frame = CGRect(origin: CGPoint(), size: layout.size) + } + } + + func animateModalIn() { + self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring) + } + + func animateModalOut(completion: @escaping () -> Void) { + self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in + completion() + }) + } +} diff --git a/Display/ListView.swift b/Display/ListView.swift index d92e52ed22..45e2611db7 100644 --- a/Display/ListView.swift +++ b/Display/ListView.swift @@ -5,875 +5,6 @@ import SwiftSignalKit private let usePerformanceTracker = false private let useDynamicTuning = false -public enum ListViewCenterScrollPositionOverflow { - case Top - case Bottom -} - -public enum ListViewScrollPosition: Equatable { - case Top - case Bottom - case Center(ListViewCenterScrollPositionOverflow) -} - -public func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { - switch lhs { - case .Top: - switch rhs { - case .Top: - return true - default: - return false - } - case .Bottom: - switch rhs { - case .Bottom: - return true - default: - return false - } - case let .Center(lhsOverflow): - switch rhs { - case let .Center(rhsOverflow) where lhsOverflow == rhsOverflow: - return true - default: - return false - } - } -} - -public enum ListViewScrollToItemDirectionHint { - case Up - case Down -} - -public enum ListViewAnimationCurve { - case Spring(duration: Double) - case Default -} - -public struct ListViewScrollToItem { - public let index: Int - public let position: ListViewScrollPosition - public let animated: Bool - public let curve: ListViewAnimationCurve - public let directionHint: ListViewScrollToItemDirectionHint - - public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) { - self.index = index - self.position = position - self.animated = animated - self.curve = curve - self.directionHint = directionHint - } -} - -public enum ListViewItemOperationDirectionHint { - case Up - case Down -} - -public struct ListViewDeleteItem { - public let index: Int - public let directionHint: ListViewItemOperationDirectionHint? - - public init(index: Int, directionHint: ListViewItemOperationDirectionHint?) { - self.index = index - self.directionHint = directionHint - } -} - -public struct ListViewInsertItem { - public let index: Int - public let previousIndex: Int? - public let item: ListViewItem - public let directionHint: ListViewItemOperationDirectionHint? - public let forceAnimateInsertion: Bool - - public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?, forceAnimateInsertion: Bool = false) { - self.index = index - self.previousIndex = previousIndex - self.item = item - self.directionHint = directionHint - self.forceAnimateInsertion = forceAnimateInsertion - } -} - -public struct ListViewUpdateItem { - public let index: Int - public let previousIndex: Int - public let item: ListViewItem - public let directionHint: ListViewItemOperationDirectionHint? - - public init(index: Int, previousIndex: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { - self.index = index - self.previousIndex = previousIndex - self.item = item - self.directionHint = directionHint - } -} - -public struct ListViewDeleteAndInsertOptions: OptionSet { - public let rawValue: Int - - public init(rawValue: Int) { - self.rawValue = rawValue - } - - public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1) - public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) - public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) - public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) - public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) -} - -public struct ListViewUpdateSizeAndInsets { - public let size: CGSize - public let insets: UIEdgeInsets - public let duration: Double - public let curve: ListViewAnimationCurve - - public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve) { - self.size = size - self.insets = insets - self.duration = duration - self.curve = curve - } -} - -public struct ListViewItemRange: Equatable { - public let firstIndex: Int - public let lastIndex: Int -} - -public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { - return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex -} - -public struct ListViewDisplayedItemRange: Equatable { - public let loadedRange: ListViewItemRange? - public let visibleRange: ListViewItemRange? -} - -public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { - return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange -} - -private struct IndexRange { - let first: Int - let last: Int - - func contains(_ index: Int) -> Bool { - return index >= first && index <= last - } - - var empty: Bool { - return first > last - } -} - -private struct OffsetRanges { - var offsets: [(IndexRange, CGFloat)] = [] - - mutating func append(_ other: OffsetRanges) { - self.offsets.append(contentsOf: other.offsets) - } - - mutating func offset(_ indexRange: IndexRange, offset: CGFloat) { - self.offsets.append((indexRange, offset)) - } - - func offsetForIndex(_ index: Int) -> CGFloat { - var result: CGFloat = 0.0 - for offset in self.offsets { - if offset.0.contains(index) { - result += offset.1 - } - } - return result - } -} - -private func binarySearch(_ inputArr: [Int], searchItem: Int) -> Int? { - var lowerIndex = 0; - var upperIndex = inputArr.count - 1 - - if lowerIndex > upperIndex { - return nil - } - - while (true) { - let currentIndex = (lowerIndex + upperIndex) / 2 - if (inputArr[currentIndex] == searchItem) { - return currentIndex - } else if (lowerIndex > upperIndex) { - return nil - } else { - if (inputArr[currentIndex] > searchItem) { - upperIndex = currentIndex - 1 - } else { - lowerIndex = currentIndex + 1 - } - } - } -} - -private struct TransactionState { - let visibleSize: CGSize - let items: [ListViewItem] -} - -private struct PendingNode { - let index: Int - let node: ListViewItemNode - let apply: () -> () - let frame: CGRect - let apparentHeight: CGFloat -} - -private enum ListViewStateNode { - case Node(index: Int, frame: CGRect, referenceNode: ListViewItemNode?) - case Placeholder(frame: CGRect) - - var index: Int? { - switch self { - case .Node(let index, _, _): - return index - case .Placeholder(_): - return nil - } - } - - var frame: CGRect { - get { - switch self { - case .Node(_, let frame, _): - return frame - case .Placeholder(let frame): - return frame - } - } set(value) { - switch self { - case let .Node(index, _, referenceNode): - self = .Node(index: index, frame: value, referenceNode: referenceNode) - case .Placeholder(_): - self = .Placeholder(frame: value) - } - } - } -} - -private enum ListViewInsertionOffsetDirection { - case Up - case Down - - init(_ hint: ListViewItemOperationDirectionHint) { - switch hint { - case .Up: - self = .Up - case .Down: - self = .Down - } - } - - func inverted() -> ListViewInsertionOffsetDirection { - switch self { - case .Up: - return .Down - case .Down: - return .Up - } - } -} - -private struct ListViewInsertionPoint { - let index: Int - let point: CGPoint - let direction: ListViewInsertionOffsetDirection -} - -private struct ListViewState { - var insets: UIEdgeInsets - var visibleSize: CGSize - let invisibleInset: CGFloat - var nodes: [ListViewStateNode] - var scrollPosition: (Int, ListViewScrollPosition)? - var stationaryOffset: (Int, CGFloat)? - - mutating func fixScrollPostition(_ itemCount: Int) { - if let (fixedIndex, fixedPosition) = self.scrollPosition { - for node in self.nodes { - if let index = node.index , index == fixedIndex { - let offset: CGFloat - switch fixedPosition { - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY - case .Top: - offset = self.insets.top - node.frame.minY - case let .Center(overflow): - let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top - if node.frame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { - offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY - } else { - switch overflow { - case .Top: - offset = self.insets.top - node.frame.minY - case .Bottom: - offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY - } - } - } - - var minY: CGFloat = CGFloat.greatestFiniteMagnitude - var maxY: CGFloat = 0.0 - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame = frame.offsetBy(dx: 0.0, dy: offset) - self.nodes[i].frame = frame - - minY = min(minY, frame.minY) - maxY = max(maxY, frame.maxY) - } - - var additionalOffset: CGFloat = 0.0 - if minY > self.insets.top { - additionalOffset = self.insets.top - minY - } - - if abs(additionalOffset) > CGFloat(FLT_EPSILON) { - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame = frame.offsetBy(dx: 0.0, dy: additionalOffset) - self.nodes[i].frame = frame - } - } - - self.snapToBounds(itemCount, snapTopItem: true) - - break - } - } - } else if let (stationaryIndex, stationaryOffset) = self.stationaryOffset { - for node in self.nodes { - if node.index == stationaryIndex { - let offset = stationaryOffset - node.frame.minY - - if abs(offset) > CGFloat(FLT_EPSILON) { - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame = frame.offsetBy(dx: 0.0, dy: offset) - self.nodes[i].frame = frame - } - } - - break - } - } - - //self.snapToBounds(itemCount, snapTopItem: true) - } - } - - mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) { - if index < boundary { - for node in self.nodes { - if let nodeIndex = node.index , nodeIndex >= index { - if let frame = frames[nodeIndex] { - self.stationaryOffset = (nodeIndex, frame.minY) - break - } - } - } - } else { - for node in self.nodes.reversed() { - if let nodeIndex = node.index , nodeIndex <= index { - if let frame = frames[nodeIndex] { - self.stationaryOffset = (nodeIndex, frame.minY) - break - } - } - } - } - } - - mutating func snapToBounds(_ itemCount: Int, snapTopItem: Bool) { - var completeHeight: CGFloat = 0.0 - var topItemFound = false - var bottomItemFound = false - var topItemEdge: CGFloat = 0.0 - var bottomItemEdge: CGFloat = 0.0 - - for node in self.nodes { - if let index = node.index { - if index == 0 { - topItemFound = true - topItemEdge = node.frame.minY - } - break - } - } - - for node in self.nodes.reversed() { - if let index = node.index { - if index == itemCount - 1 { - bottomItemFound = true - bottomItemEdge = node.frame.maxY - } - break - } - } - - if topItemFound && bottomItemFound { - for node in self.nodes { - completeHeight += node.frame.size.height - } - } - - let overscroll: CGFloat = 0.0 - - var offset: CGFloat = 0.0 - if topItemFound && bottomItemFound { - let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) - if bottomItemEdge < self.insets.top + areaHeight - overscroll { - offset = self.insets.top + areaHeight - overscroll - bottomItemEdge - } else if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - } - } else if topItemFound { - if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - } - } else if bottomItemFound { - if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { - offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge - } - } - - if abs(offset) > CGFloat(FLT_EPSILON) { - for i in 0 ..< self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y += offset - self.nodes[i].frame = frame - } - } - } - - func insertionPoint(_ insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { - var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)? - - if let (fixedIndex, _) = self.scrollPosition { - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , index == fixedIndex { - fixedNode = (i, index, node.frame) - break - } - } - - if fixedNode == nil { - return ListViewInsertionPoint(index: fixedIndex, point: CGPoint(), direction: .Down) - } - } - - if fixedNode == nil { - if let (fixedIndex, _) = self.stationaryOffset { - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , index == fixedIndex { - fixedNode = (i, index, node.frame) - break - } - } - } - } - - if fixedNode == nil { - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , node.frame.maxY >= self.insets.top { - fixedNode = (i, index, node.frame) - break - } - } - } - - if fixedNode == nil && self.nodes.count != 0 { - for i in (0 ..< self.nodes.count).reversed() { - let node = self.nodes[i] - if let index = node.index { - fixedNode = (i, index, node.frame) - break - } - } - } - - if let fixedNode = fixedNode { - var currentUpperNode = fixedNode - for i in (0 ..< fixedNode.nodeIndex).reversed() { - let node = self.nodes[i] - if let index = node.index { - if index != currentUpperNode.index - 1 { - if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) - } else { - break - } - } - currentUpperNode = (i, index, node.frame) - } - } - - if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - - return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) - } - - var currentLowerNode = fixedNode - if fixedNode.nodeIndex + 1 < self.nodes.count { - for i in (fixedNode.nodeIndex + 1) ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index { - if index != currentLowerNode.index + 1 { - if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) - } else { - break - } - } - currentLowerNode = (i, index, node.frame) - } - } - } - - if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { - var directionHint: ListViewInsertionOffsetDirection? - if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - directionHint = ListViewInsertionOffsetDirection(hint) - } - return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) - } - } else if itemCount != 0 { - return ListViewInsertionPoint(index: 0, point: CGPoint(x: 0.0, y: self.insets.top), direction: .Down) - } - - return nil - } - - mutating func removeInvisibleNodes(_ operations: inout [ListViewStateOperation]) { - var i = 0 - var visibleItemNodeHeight: CGFloat = 0.0 - while i < self.nodes.count { - visibleItemNodeHeight += self.nodes[i].frame.height - i += 1 - } - - if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { - i = self.nodes.count - 1 - while i >= 0 { - let itemNode = self.nodes[i] - let frame = itemNode.frame - //print("node \(i) frame \(frame)") - if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { - //print("remove invisible 1 \(i) frame \(frame)") - operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) - self.nodes.remove(at: i) - } - - i -= 1 - } - } - - let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) - for i in 0 ..< self.nodes.count { - let node = self.nodes[i] - if let index = node.index , node.frame.maxY > upperBound { - if i != 0 { - var previousIndex = index - for j in (0 ..< i).reversed() { - if self.nodes[j].frame.maxY < upperBound { - if let index = self.nodes[j].index { - if index != previousIndex - 1 { - //print("remove monotonity \(j) (\(index))") - operations.append(.Remove(index: j, offsetDirection: .Down)) - self.nodes.remove(at: j) - } else { - previousIndex = index - } - } - } - } - } - break - } - } - - let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) - for i in (0 ..< self.nodes.count).reversed() { - let node = self.nodes[i] - if let index = node.index , node.frame.minY < lowerBound { - if i != self.nodes.count - 1 { - var previousIndex = index - var removeIndices: [Int] = [] - for j in (i + 1) ..< self.nodes.count { - if self.nodes[j].frame.minY > lowerBound { - if let index = self.nodes[j].index { - if index != previousIndex + 1 { - removeIndices.append(j) - } else { - previousIndex = index - } - } - } - } - if !removeIndices.isEmpty { - for i in removeIndices.reversed() { - //print("remove monotonity \(i) (\(self.nodes[i].index!))") - operations.append(.Remove(index: i, offsetDirection: .Up)) - self.nodes.remove(at: i) - } - } - } - break - } - } - } - - func nodeInsertionPointAndIndex(_ itemIndex: Int) -> (CGPoint, Int) { - if self.nodes.count == 0 { - return (CGPoint(x: 0.0, y: self.insets.top), 0) - } else { - var index = 0 - var lastNodeWithIndex = -1 - for node in self.nodes { - if let nodeItemIndex = node.index { - if nodeItemIndex > itemIndex { - break - } - lastNodeWithIndex = index - } - index += 1 - } - lastNodeWithIndex += 1 - return (CGPoint(x: 0.0, y: lastNodeWithIndex == 0 ? self.nodes[0].frame.minY : self.nodes[lastNodeWithIndex - 1].frame.maxY), lastNodeWithIndex) - } - } - - func continuousHeightRelativeToNodeIndex(_ fixedNodeIndex: Int) -> CGFloat { - let fixedIndex = self.nodes[fixedNodeIndex].index! - - var height: CGFloat = 0.0 - - if fixedNodeIndex != 0 { - var upperIndex = fixedIndex - for i in (0 ..< fixedNodeIndex).reversed() { - if let index = self.nodes[i].index { - if index == upperIndex - 1 { - height += self.nodes[i].frame.size.height - upperIndex = index - } else { - break - } - } - } - } - - if fixedNodeIndex != self.nodes.count - 1 { - var lowerIndex = fixedIndex - for i in (fixedNodeIndex + 1) ..< self.nodes.count { - if let index = self.nodes[i].index { - if index == lowerIndex + 1 { - height += self.nodes[i].frame.size.height - lowerIndex = index - } else { - break - } - } - } - } - - return height - } - - mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { - let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) - - let nodeOrigin: CGPoint - switch offsetDirection { - case .Up: - nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height)) - case .Down: - nodeOrigin = insertionOrigin - } - - let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) - - operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) - self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex) - - if !animated { - switch offsetDirection { - case .Up: - var i = insertionIndex - 1 - while i >= 0 { - var frame = self.nodes[i].frame - frame.origin.y -= nodeFrame.size.height - self.nodes[i].frame = frame - i -= 1 - } - case .Down: - var i = insertionIndex + 1 - while i < self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y += nodeFrame.size.height - self.nodes[i].frame = frame - i += 1 - } - } - } - - var previousIndex: Int? - for node in self.nodes { - if let index = node.index { - if let currentPreviousIndex = previousIndex { - if index <= currentPreviousIndex { - print("index <= previousIndex + 1") - break - } - previousIndex = index - } else { - previousIndex = index - } - } - } - - if let _ = self.scrollPosition { - self.fixScrollPostition(itemCount) - } - } - - mutating func removeNodeAtIndex(_ index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, operations: inout [ListViewStateOperation]) { - let node = self.nodes[index] - if case let .Node(_, _, referenceNode) = node { - let nodeFrame = node.frame - self.nodes.remove(at: index) - let offsetDirection: ListViewInsertionOffsetDirection - if let direction = direction { - offsetDirection = ListViewInsertionOffsetDirection(direction) - } else { - if nodeFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { - offsetDirection = .Down - } else { - offsetDirection = .Up - } - } - operations.append(.Remove(index: index, offsetDirection: offsetDirection)) - - if let referenceNode = referenceNode , animated { - self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) - operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) - } else { - if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { - if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { - for i in (0 ..< index).reversed() { - var frame = self.nodes[i].frame - frame.origin.y += nodeFrame.size.height - self.nodes[i].frame = frame - } - } else { - for i in index ..< self.nodes.count { - var frame = self.nodes[i].frame - frame.origin.y -= nodeFrame.size.height - self.nodes[i].frame = frame - } - } - } else if index != 0 { - for i in (0 ..< index).reversed() { - var frame = self.nodes[i].frame - frame.origin.y += nodeFrame.size.height - self.nodes[i].frame = frame - } - } - } - } else { - assertionFailure() - } - } - - mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> Void, operations: inout [ListViewStateOperation]) { - var i = -1 - for node in self.nodes { - i += 1 - if node.index == itemIndex { - switch animation { - case .None: - let offsetDirection: ListViewInsertionOffsetDirection - if let direction = direction { - offsetDirection = ListViewInsertionOffsetDirection(direction) - } else { - if node.frame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { - offsetDirection = .Down - } else { - offsetDirection = .Up - } - } - - switch offsetDirection { - case .Up: - let offsetDelta = -(layout.size.height - node.frame.size.height) - var updatedFrame = node.frame - updatedFrame.origin.y += offsetDelta - updatedFrame.size.height = layout.size.height - self.nodes[i].frame = updatedFrame - - for j in 0 ..< i { - var frame = self.nodes[j].frame - frame.origin.y += offsetDelta - self.nodes[j].frame = frame - } - case .Down: - let offsetDelta = layout.size.height - node.frame.size.height - var updatedFrame = node.frame - updatedFrame.size.height = layout.size.height - self.nodes[i].frame = updatedFrame - - for j in i + 1 ..< self.nodes.count { - var frame = self.nodes[j].frame - frame.origin.y += offsetDelta - self.nodes[j].frame = frame - } - } - - operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) - case .System: - operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) - } - - break - } - } - } -} - -private enum ListViewStateOperation { - case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) - case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) - case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) - case Remap([Int: Int]) - case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) -} - private let infiniteScrollSize: CGFloat = 10000.0 private let insertionAnimationDuration: Double = 0.4 @@ -886,7 +17,7 @@ private final class ListViewBackingLayer: CALayer { } final class ListViewBackingView: UIView { - weak var target: ASDisplayNode? + weak var target: ListView? override class var layerClass: AnyClass { return ListViewBackingLayer.self @@ -913,6 +44,15 @@ final class ListViewBackingView: UIView { override func touchesEnded(_ touches: Set, with event: UIEvent?) { self.target?.touchesEnded(touches, with: event) } + + override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { + if let target = target, target.limitHitTestToNodes { + if !target.internalHitTest(point, with: event) { + return nil + } + } + return super.hitTest(point, with: event) + } } private final class ListViewTimerProxy: NSObject { @@ -958,6 +98,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + public final var stackFromBottom: Bool = false + public final var stackFromBottomInsetItemFactor: CGFloat = 0.0 + public final var limitHitTestToNodes: Bool = false + private var touchesPosition = CGPoint() private var isTracking = false private var isDeceleratingAfterTracking = false @@ -1273,7 +417,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } - self.snapToBounds() + if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } self.updateScroller() self.updateItemHeaders() @@ -1313,9 +459,33 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel //CATransaction.commit() } - private func snapToBounds(_ snapTopItem: Bool = false) { + private func calculateAdditionalTopInverseInset() -> CGFloat { + var additionalInverseTopInset: CGFloat = 0.0 + if !self.stackFromBottomInsetItemFactor.isZero { + var remainingFactor = self.stackFromBottomInsetItemFactor + for itemNode in self.itemNodes { + if remainingFactor.isLessThanOrEqualTo(0.0) { + break + } + + let itemFactor: CGFloat + if CGFloat(1.0).isLessThanOrEqualTo(remainingFactor) { + itemFactor = 1.0 + } else { + itemFactor = remainingFactor + } + + additionalInverseTopInset += floor(itemNode.apparentBounds.height * itemFactor) + + remainingFactor -= 1.0 + } + } + return additionalInverseTopInset + } + + private func snapToBounds(snapTopItem: Bool, stackFromBottom: Bool) -> (snappedTopInset: CGFloat, offset: CGFloat) { if self.itemNodes.count == 0 { - return + return (0.0, 0.0) } var overscroll: CGFloat = 0.0 @@ -1340,6 +510,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel } } + var effectiveInsets = self.insets + if topItemFound && !self.stackFromBottomInsetItemFactor.isZero { + let additionalInverseTopInset = self.calculateAdditionalTopInverseInset() + effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset) + } + if topItemFound { topItemEdge = itemNodes[0].apparentFrame.origin.y } @@ -1365,23 +541,38 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var offset: CGFloat = 0.0 if topItemFound && bottomItemFound { - let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) - if bottomItemEdge < self.insets.top + areaHeight - overscroll { - offset = self.insets.top + areaHeight - overscroll - bottomItemEdge - //print("bottom edge offset \(offset) = \(self.insets.top) + \(areaHeight) - \(overscroll) - \(bottomItemEdge)") - } else if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - //print("top edge offset \(offset)") + let visibleAreaHeight = self.visibleSize.height - effectiveInsets.bottom - effectiveInsets.top + if self.stackFromBottom { + if visibleAreaHeight > completeHeight { + let areaHeight = completeHeight + if topItemEdge < self.visibleSize.height - effectiveInsets.bottom - areaHeight - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - areaHeight - overscroll - topItemEdge + } else if bottomItemEdge > self.visibleSize.height - effectiveInsets.bottom - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - overscroll - bottomItemEdge + } + } else { + let areaHeight = min(completeHeight, visibleAreaHeight) + if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll { + offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > effectiveInsets.top - overscroll { + offset = (effectiveInsets.top - overscroll) - topItemEdge + } + } + } else { + let areaHeight = min(completeHeight, visibleAreaHeight) + if bottomItemEdge < effectiveInsets.top + areaHeight - overscroll { + offset = effectiveInsets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { + offset = (effectiveInsets.top - overscroll) - topItemEdge + } } } else if topItemFound { - if topItemEdge > self.insets.top - overscroll && snapTopItem { - offset = (self.insets.top - overscroll) - topItemEdge - //print("top only edge offset \(offset)") + if topItemEdge > effectiveInsets.top - overscroll && /*snapTopItem*/ true { + offset = (effectiveInsets.top - overscroll) - topItemEdge } } else if bottomItemFound { - if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { - offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge - //print("bottom only edge offset \(offset)") + if bottomItemEdge < self.visibleSize.height - effectiveInsets.bottom - overscroll { + offset = self.visibleSize.height - effectiveInsets.bottom - overscroll - bottomItemEdge } } @@ -1391,9 +582,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel frame.origin.y += offset itemNode.frame = frame } - - self.updateVisibleContentOffset() } + + var snappedTopInset: CGFloat = 0.0 + if !self.stackFromBottomInsetItemFactor.isZero && topItemFound { + snappedTopInset = max(0.0, (effectiveInsets.top - self.insets.top) - (topItemEdge + offset)) + } + + return (snappedTopInset, offset) } private func updateVisibleContentOffset() { @@ -1426,36 +622,61 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel return } - var completeHeight = self.insets.top + self.insets.bottom var topItemFound = false var bottomItemFound = false var topItemEdge: CGFloat = 0.0 var bottomItemEdge: CGFloat = 0.0 - if itemNodes[0].index == 0 { - topItemFound = true - topItemEdge = itemNodes[0].apparentFrame.origin.y + for i in 0 ..< self.itemNodes.count { + if let index = itemNodes[i].index { + if index == 0 { + topItemFound = true + topItemEdge = itemNodes[0].apparentFrame.origin.y + break + } + } } + var effectiveInsets = self.insets + if topItemFound && !self.stackFromBottomInsetItemFactor.isZero { + let additionalInverseTopInset = self.calculateAdditionalTopInverseInset() + effectiveInsets.top = max(effectiveInsets.top, self.visibleSize.height - additionalInverseTopInset) + } + + var completeHeight = effectiveInsets.top + effectiveInsets.bottom + if itemNodes[itemNodes.count - 1].index == self.items.count - 1 { bottomItemFound = true bottomItemEdge = itemNodes[itemNodes.count - 1].apparentFrame.maxY } + topItemEdge -= effectiveInsets.top + bottomItemEdge += effectiveInsets.bottom + if topItemFound && bottomItemFound { for itemNode in self.itemNodes { completeHeight += itemNode.apparentBounds.height } + + if self.stackFromBottom { + let updatedCompleteHeight = max(completeHeight, self.visibleSize.height) + let deltaCompleteHeight = updatedCompleteHeight - completeHeight + topItemEdge -= deltaCompleteHeight + bottomItemEdge -= deltaCompleteHeight + completeHeight = updatedCompleteHeight + } } - topItemEdge -= self.insets.top - bottomItemEdge += self.insets.bottom - + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true if topItemFound && bottomItemFound { + if self.stackFromBottom { + self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) + } else { + self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) + } self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: completeHeight) - self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) - self.scroller.contentOffset = self.lastContentOffset; + self.scroller.contentOffset = self.lastContentOffset } else if topItemFound { self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) self.lastContentOffset = CGPoint(x: 0.0, y: -topItemEdge) @@ -1471,8 +692,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset } - - self.ignoreScrollingEvents = false + self.ignoreScrollingEvents = wasIgnoringScrollingEvents } private func async(_ f: @escaping () -> Void) { @@ -1533,7 +753,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel nodes.append(.Placeholder(frame: node.apparentFrame)) } } - return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil) + return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom) } public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) { @@ -1556,7 +776,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping (Void) -> Void) { if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil { - if let updateSizeAndInsets = updateSizeAndInsets , self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets) { + if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) { self.visibleSize = updateSizeAndInsets.size self.insets = updateSizeAndInsets.insets @@ -1568,11 +788,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.scrollingResistanceSlider.frame = CGRect(x: 10.0, y: self.freeResistanceSlider.frame.minY - self.scrollingResistanceSlider.bounds.height, width: size.width - 20.0, height: self.scrollingResistanceSlider.bounds.height) } + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: updateSizeAndInsets.size) self.scroller.contentSize = CGSize(width: updateSizeAndInsets.size.width, height: infiniteScrollSize * 2.0) self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset + self.ignoreScrollingEvents = wasIgnoringScrollingEvents self.updateScroller() @@ -2393,21 +1615,40 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel var headerNodesTransition: (ContainedViewLayoutTransition, Bool, CGFloat) = (.immediate, false, 0.0) if let updateSizeAndInsets = updateSizeAndInsets { - self.visibleSize = updateSizeAndInsets.size - - if self.insets != updateSizeAndInsets.insets { + if self.insets != updateSizeAndInsets.insets || !self.visibleSize.height.isEqual(to: updateSizeAndInsets.size.height) { + let previousVisibleSize = self.visibleSize + self.visibleSize = updateSizeAndInsets.size + var offsetFix = updateSizeAndInsets.insets.top - self.insets.top self.insets = updateSizeAndInsets.insets - - var completeOffset = offsetFix - sizeAndInsetsOffset = offsetFix + self.visibleSize = updateSizeAndInsets.size for itemNode in self.itemNodes { let position = itemNode.position itemNode.position = CGPoint(x: position.x, y: position.y + offsetFix) } + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom) + + if !snappedTopInset.isZero && (previousVisibleSize.height.isZero || previousApparentFrames.isEmpty) { + offsetFix += snappedTopInset + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) + } + } + + var completeOffset = offsetFix + + if !snapToBoundsOffset.isZero { + self.updateVisibleContentOffset() + } + + sizeAndInsetsOffset = offsetFix + completeOffset += snapToBoundsOffset + if updateSizeAndInsets.duration > DBL_EPSILON { let animation: CABasicAnimation switch updateSizeAndInsets.curve { @@ -2441,17 +1682,38 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel self.layer.add(animation, forKey: nil) } + } else { + self.visibleSize = updateSizeAndInsets.size + + if !self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } } + let wasIgnoringScrollingEvents = self.ignoreScrollingEvents self.ignoreScrollingEvents = true self.scroller.frame = CGRect(origin: CGPoint(), size: self.visibleSize) self.scroller.contentSize = CGSize(width: self.visibleSize.width, height: infiniteScrollSize * 2.0) self.lastContentOffset = CGPoint(x: 0.0, y: infiniteScrollSize) self.scroller.contentOffset = self.lastContentOffset + self.ignoreScrollingEvents = wasIgnoringScrollingEvents + } else { + let (snappedTopInset, snapToBoundsOffset) = self.snapToBounds(snapTopItem: scrollToItem != nil, stackFromBottom: self.stackFromBottom) + + if !snappedTopInset.isZero && previousApparentFrames.isEmpty { + let offsetFix = snappedTopInset + + for itemNode in self.itemNodes { + let position = itemNode.position + itemNode.position = CGPoint(x: position.x, y: position.y + snappedTopInset) + } + } + + if !snapToBoundsOffset.isZero { + self.updateVisibleContentOffset() + } } - self.snapToBounds(scrollToItem != nil) - self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp) self.updateFloatingAccessoryNodes(animated: animated, currentTimestamp: timestamp) @@ -3090,7 +2352,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel index += 1 } - self.snapToBounds() + if !self.snapToBounds(snapTopItem: false, stackFromBottom: self.stackFromBottom).offset.isZero { + self.updateVisibleContentOffset() + } } self.debugCheckMonotonity() @@ -3256,4 +2520,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel completion() } } + + fileprivate func internalHitTest(_ point: CGPoint, with event: UIEvent?) -> Bool { + if self.limitHitTestToNodes { + var foundHit = false + for itemNode in self.itemNodes { + if itemNode.frame.contains(point) { + foundHit = true + break + } + } + if !foundHit { + return false + } + } + return true + } } diff --git a/Display/ListViewIntermediateState.swift b/Display/ListViewIntermediateState.swift new file mode 100644 index 0000000000..ae42d2562b --- /dev/null +++ b/Display/ListViewIntermediateState.swift @@ -0,0 +1,869 @@ +import Foundation + +public enum ListViewCenterScrollPositionOverflow { + case Top + case Bottom +} + +public enum ListViewScrollPosition: Equatable { + case Top + case Bottom + case Center(ListViewCenterScrollPositionOverflow) +} + +public func ==(lhs: ListViewScrollPosition, rhs: ListViewScrollPosition) -> Bool { + switch lhs { + case .Top: + switch rhs { + case .Top: + return true + default: + return false + } + case .Bottom: + switch rhs { + case .Bottom: + return true + default: + return false + } + case let .Center(lhsOverflow): + switch rhs { + case let .Center(rhsOverflow) where lhsOverflow == rhsOverflow: + return true + default: + return false + } + } +} + +public enum ListViewScrollToItemDirectionHint { + case Up + case Down +} + +public enum ListViewAnimationCurve { + case Spring(duration: Double) + case Default +} + +public struct ListViewScrollToItem { + public let index: Int + public let position: ListViewScrollPosition + public let animated: Bool + public let curve: ListViewAnimationCurve + public let directionHint: ListViewScrollToItemDirectionHint + + public init(index: Int, position: ListViewScrollPosition, animated: Bool, curve: ListViewAnimationCurve, directionHint: ListViewScrollToItemDirectionHint) { + self.index = index + self.position = position + self.animated = animated + self.curve = curve + self.directionHint = directionHint + } +} + +public enum ListViewItemOperationDirectionHint { + case Up + case Down +} + +public struct ListViewDeleteItem { + public let index: Int + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.directionHint = directionHint + } +} + +public struct ListViewInsertItem { + public let index: Int + public let previousIndex: Int? + public let item: ListViewItem + public let directionHint: ListViewItemOperationDirectionHint? + public let forceAnimateInsertion: Bool + + public init(index: Int, previousIndex: Int?, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?, forceAnimateInsertion: Bool = false) { + self.index = index + self.previousIndex = previousIndex + self.item = item + self.directionHint = directionHint + self.forceAnimateInsertion = forceAnimateInsertion + } +} + +public struct ListViewUpdateItem { + public let index: Int + public let previousIndex: Int + public let item: ListViewItem + public let directionHint: ListViewItemOperationDirectionHint? + + public init(index: Int, previousIndex: Int, item: ListViewItem, directionHint: ListViewItemOperationDirectionHint?) { + self.index = index + self.previousIndex = previousIndex + self.item = item + self.directionHint = directionHint + } +} + +public struct ListViewDeleteAndInsertOptions: OptionSet { + public let rawValue: Int + + public init(rawValue: Int) { + self.rawValue = rawValue + } + + public static let AnimateInsertion = ListViewDeleteAndInsertOptions(rawValue: 1) + public static let AnimateAlpha = ListViewDeleteAndInsertOptions(rawValue: 2) + public static let LowLatency = ListViewDeleteAndInsertOptions(rawValue: 4) + public static let Synchronous = ListViewDeleteAndInsertOptions(rawValue: 8) + public static let RequestItemInsertionAnimations = ListViewDeleteAndInsertOptions(rawValue: 16) +} + +public struct ListViewUpdateSizeAndInsets { + public let size: CGSize + public let insets: UIEdgeInsets + public let duration: Double + public let curve: ListViewAnimationCurve + + public init(size: CGSize, insets: UIEdgeInsets, duration: Double, curve: ListViewAnimationCurve) { + self.size = size + self.insets = insets + self.duration = duration + self.curve = curve + } +} + +public struct ListViewItemRange: Equatable { + public let firstIndex: Int + public let lastIndex: Int +} + +public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool { + return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex +} + +public struct ListViewDisplayedItemRange: Equatable { + public let loadedRange: ListViewItemRange? + public let visibleRange: ListViewItemRange? +} + +public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool { + return lhs.loadedRange == rhs.loadedRange && lhs.visibleRange == rhs.visibleRange +} + +struct IndexRange { + let first: Int + let last: Int + + func contains(_ index: Int) -> Bool { + return index >= first && index <= last + } + + var empty: Bool { + return first > last + } +} + +struct OffsetRanges { + var offsets: [(IndexRange, CGFloat)] = [] + + mutating func append(_ other: OffsetRanges) { + self.offsets.append(contentsOf: other.offsets) + } + + mutating func offset(_ indexRange: IndexRange, offset: CGFloat) { + self.offsets.append((indexRange, offset)) + } + + func offsetForIndex(_ index: Int) -> CGFloat { + var result: CGFloat = 0.0 + for offset in self.offsets { + if offset.0.contains(index) { + result += offset.1 + } + } + return result + } +} + +func binarySearch(_ inputArr: [Int], searchItem: Int) -> Int? { + var lowerIndex = 0; + var upperIndex = inputArr.count - 1 + + if lowerIndex > upperIndex { + return nil + } + + while (true) { + let currentIndex = (lowerIndex + upperIndex) / 2 + if (inputArr[currentIndex] == searchItem) { + return currentIndex + } else if (lowerIndex > upperIndex) { + return nil + } else { + if (inputArr[currentIndex] > searchItem) { + upperIndex = currentIndex - 1 + } else { + lowerIndex = currentIndex + 1 + } + } + } +} + +struct TransactionState { + let visibleSize: CGSize + let items: [ListViewItem] +} + +struct PendingNode { + let index: Int + let node: ListViewItemNode + let apply: () -> () + let frame: CGRect + let apparentHeight: CGFloat +} + +enum ListViewStateNode { + case Node(index: Int, frame: CGRect, referenceNode: ListViewItemNode?) + case Placeholder(frame: CGRect) + + var index: Int? { + switch self { + case .Node(let index, _, _): + return index + case .Placeholder(_): + return nil + } + } + + var frame: CGRect { + get { + switch self { + case .Node(_, let frame, _): + return frame + case .Placeholder(let frame): + return frame + } + } set(value) { + switch self { + case let .Node(index, _, referenceNode): + self = .Node(index: index, frame: value, referenceNode: referenceNode) + case .Placeholder(_): + self = .Placeholder(frame: value) + } + } + } +} + +enum ListViewInsertionOffsetDirection { + case Up + case Down + + init(_ hint: ListViewItemOperationDirectionHint) { + switch hint { + case .Up: + self = .Up + case .Down: + self = .Down + } + } + + func inverted() -> ListViewInsertionOffsetDirection { + switch self { + case .Up: + return .Down + case .Down: + return .Up + } + } +} + +struct ListViewInsertionPoint { + let index: Int + let point: CGPoint + let direction: ListViewInsertionOffsetDirection +} + +struct ListViewState { + var insets: UIEdgeInsets + var visibleSize: CGSize + let invisibleInset: CGFloat + var nodes: [ListViewStateNode] + var scrollPosition: (Int, ListViewScrollPosition)? + var stationaryOffset: (Int, CGFloat)? + let stackFromBottom: Bool + + mutating func fixScrollPostition(_ itemCount: Int) { + if let (fixedIndex, fixedPosition) = self.scrollPosition { + for node in self.nodes { + if let index = node.index , index == fixedIndex { + let offset: CGFloat + switch fixedPosition { + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + case .Top: + offset = self.insets.top - node.frame.minY + case let .Center(overflow): + let contentAreaHeight = self.visibleSize.height - self.insets.bottom - self.insets.top + if node.frame.size.height <= contentAreaHeight + CGFloat(FLT_EPSILON) { + offset = self.insets.top + floor((contentAreaHeight - node.frame.size.height) / 2.0) - node.frame.minY + } else { + switch overflow { + case .Top: + offset = self.insets.top - node.frame.minY + case .Bottom: + offset = (self.visibleSize.height - self.insets.bottom) - node.frame.maxY + } + } + } + + var minY: CGFloat = CGFloat.greatestFiniteMagnitude + var maxY: CGFloat = 0.0 + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame = frame.offsetBy(dx: 0.0, dy: offset) + self.nodes[i].frame = frame + + minY = min(minY, frame.minY) + maxY = max(maxY, frame.maxY) + } + + var additionalOffset: CGFloat = 0.0 + if minY > self.insets.top { + additionalOffset = self.insets.top - minY + } + + if abs(additionalOffset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame = frame.offsetBy(dx: 0.0, dy: additionalOffset) + self.nodes[i].frame = frame + } + } + + self.snapToBounds(itemCount, snapTopItem: true, stackFromBottom: self.stackFromBottom) + + break + } + } + } else if let (stationaryIndex, stationaryOffset) = self.stationaryOffset { + for node in self.nodes { + if node.index == stationaryIndex { + let offset = stationaryOffset - node.frame.minY + + if abs(offset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame = frame.offsetBy(dx: 0.0, dy: offset) + self.nodes[i].frame = frame + } + } + + break + } + } + } + } + + mutating func setupStationaryOffset(_ index: Int, boundary: Int, frames: [Int: CGRect]) { + if index < boundary { + for node in self.nodes { + if let nodeIndex = node.index , nodeIndex >= index { + if let frame = frames[nodeIndex] { + self.stationaryOffset = (nodeIndex, frame.minY) + break + } + } + } + } else { + for node in self.nodes.reversed() { + if let nodeIndex = node.index , nodeIndex <= index { + if let frame = frames[nodeIndex] { + self.stationaryOffset = (nodeIndex, frame.minY) + break + } + } + } + } + } + + mutating func snapToBounds(_ itemCount: Int, snapTopItem: Bool, stackFromBottom: Bool) { + var completeHeight: CGFloat = 0.0 + var topItemFound = false + var bottomItemFound = false + var topItemEdge: CGFloat = 0.0 + var bottomItemEdge: CGFloat = 0.0 + + for node in self.nodes { + if let index = node.index { + if index == 0 { + topItemFound = true + topItemEdge = node.frame.minY + } + break + } + } + + for node in self.nodes.reversed() { + if let index = node.index { + if index == itemCount - 1 { + bottomItemFound = true + bottomItemEdge = node.frame.maxY + } + break + } + } + + if topItemFound && bottomItemFound { + for node in self.nodes { + completeHeight += node.frame.size.height + } + } + + let overscroll: CGFloat = 0.0 + + var offset: CGFloat = 0.0 + if topItemFound && bottomItemFound { + let areaHeight = min(completeHeight, self.visibleSize.height - self.insets.bottom - self.insets.top) + if bottomItemEdge < self.insets.top + areaHeight - overscroll { + offset = self.insets.top + areaHeight - overscroll - bottomItemEdge + } else if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge + } + } else if topItemFound { + if topItemEdge > self.insets.top - overscroll && snapTopItem { + offset = (self.insets.top - overscroll) - topItemEdge + } + } else if bottomItemFound { + if bottomItemEdge < self.visibleSize.height - self.insets.bottom - overscroll { + offset = self.visibleSize.height - self.insets.bottom - overscroll - bottomItemEdge + } + } + + if abs(offset) > CGFloat(FLT_EPSILON) { + for i in 0 ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y += offset + self.nodes[i].frame = frame + } + } + } + + func insertionPoint(_ insertDirectionHints: [Int: ListViewItemOperationDirectionHint], itemCount: Int) -> ListViewInsertionPoint? { + var fixedNode: (nodeIndex: Int, index: Int, frame: CGRect)? + + if let (fixedIndex, _) = self.scrollPosition { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , index == fixedIndex { + fixedNode = (i, index, node.frame) + break + } + } + + if fixedNode == nil { + return ListViewInsertionPoint(index: fixedIndex, point: CGPoint(), direction: .Down) + } + } + + if fixedNode == nil { + if let (fixedIndex, _) = self.stationaryOffset { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , index == fixedIndex { + fixedNode = (i, index, node.frame) + break + } + } + } + } + + if fixedNode == nil { + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , node.frame.maxY >= self.insets.top { + fixedNode = (i, index, node.frame) + break + } + } + } + + if fixedNode == nil && self.nodes.count != 0 { + for i in (0 ..< self.nodes.count).reversed() { + let node = self.nodes[i] + if let index = node.index { + fixedNode = (i, index, node.frame) + break + } + } + } + + if let fixedNode = fixedNode { + var currentUpperNode = fixedNode + for i in (0 ..< fixedNode.nodeIndex).reversed() { + let node = self.nodes[i] + if let index = node.index { + if index != currentUpperNode.index - 1 { + if currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) + } else { + break + } + } + currentUpperNode = (i, index, node.frame) + } + } + + if currentUpperNode.index != 0 && currentUpperNode.frame.minY > -self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentUpperNode.index - 1] , currentUpperNode.frame.minY > self.insets.top - CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + + return ListViewInsertionPoint(index: currentUpperNode.index - 1, point: CGPoint(x: 0.0, y: currentUpperNode.frame.minY), direction: directionHint ?? .Up) + } + + var currentLowerNode = fixedNode + if fixedNode.nodeIndex + 1 < self.nodes.count { + for i in (fixedNode.nodeIndex + 1) ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index { + if index != currentLowerNode.index + 1 { + if currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) + } else { + break + } + } + currentLowerNode = (i, index, node.frame) + } + } + } + + if currentLowerNode.index != itemCount - 1 && currentLowerNode.frame.maxY < self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) { + var directionHint: ListViewInsertionOffsetDirection? + if let hint = insertDirectionHints[currentLowerNode.index + 1] , currentLowerNode.frame.maxY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + directionHint = ListViewInsertionOffsetDirection(hint) + } + return ListViewInsertionPoint(index: currentLowerNode.index + 1, point: CGPoint(x: 0.0, y: currentLowerNode.frame.maxY), direction: directionHint ?? .Down) + } + } else if itemCount != 0 { + return ListViewInsertionPoint(index: 0, point: CGPoint(x: 0.0, y: self.insets.top), direction: .Down) + } + + return nil + } + + mutating func removeInvisibleNodes(_ operations: inout [ListViewStateOperation]) { + var i = 0 + var visibleItemNodeHeight: CGFloat = 0.0 + while i < self.nodes.count { + visibleItemNodeHeight += self.nodes[i].frame.height + i += 1 + } + + if visibleItemNodeHeight > (self.visibleSize.height + self.invisibleInset + self.invisibleInset) { + i = self.nodes.count - 1 + while i >= 0 { + let itemNode = self.nodes[i] + let frame = itemNode.frame + //print("node \(i) frame \(frame)") + if frame.maxY < -self.invisibleInset || frame.origin.y > self.visibleSize.height + self.invisibleInset { + //print("remove invisible 1 \(i) frame \(frame)") + operations.append(.Remove(index: i, offsetDirection: frame.maxY < -self.invisibleInset ? .Down : .Up)) + self.nodes.remove(at: i) + } + + i -= 1 + } + } + + let upperBound = -self.invisibleInset + CGFloat(FLT_EPSILON) + for i in 0 ..< self.nodes.count { + let node = self.nodes[i] + if let index = node.index , node.frame.maxY > upperBound { + if i != 0 { + var previousIndex = index + for j in (0 ..< i).reversed() { + if self.nodes[j].frame.maxY < upperBound { + if let index = self.nodes[j].index { + if index != previousIndex - 1 { + //print("remove monotonity \(j) (\(index))") + operations.append(.Remove(index: j, offsetDirection: .Down)) + self.nodes.remove(at: j) + } else { + previousIndex = index + } + } + } + } + } + break + } + } + + let lowerBound = self.visibleSize.height + self.invisibleInset - CGFloat(FLT_EPSILON) + for i in (0 ..< self.nodes.count).reversed() { + let node = self.nodes[i] + if let index = node.index , node.frame.minY < lowerBound { + if i != self.nodes.count - 1 { + var previousIndex = index + var removeIndices: [Int] = [] + for j in (i + 1) ..< self.nodes.count { + if self.nodes[j].frame.minY > lowerBound { + if let index = self.nodes[j].index { + if index != previousIndex + 1 { + removeIndices.append(j) + } else { + previousIndex = index + } + } + } + } + if !removeIndices.isEmpty { + for i in removeIndices.reversed() { + //print("remove monotonity \(i) (\(self.nodes[i].index!))") + operations.append(.Remove(index: i, offsetDirection: .Up)) + self.nodes.remove(at: i) + } + } + } + break + } + } + } + + func nodeInsertionPointAndIndex(_ itemIndex: Int) -> (CGPoint, Int) { + if self.nodes.count == 0 { + return (CGPoint(x: 0.0, y: self.insets.top), 0) + } else { + var index = 0 + var lastNodeWithIndex = -1 + for node in self.nodes { + if let nodeItemIndex = node.index { + if nodeItemIndex > itemIndex { + break + } + lastNodeWithIndex = index + } + index += 1 + } + lastNodeWithIndex += 1 + return (CGPoint(x: 0.0, y: lastNodeWithIndex == 0 ? self.nodes[0].frame.minY : self.nodes[lastNodeWithIndex - 1].frame.maxY), lastNodeWithIndex) + } + } + + func continuousHeightRelativeToNodeIndex(_ fixedNodeIndex: Int) -> CGFloat { + let fixedIndex = self.nodes[fixedNodeIndex].index! + + var height: CGFloat = 0.0 + + if fixedNodeIndex != 0 { + var upperIndex = fixedIndex + for i in (0 ..< fixedNodeIndex).reversed() { + if let index = self.nodes[i].index { + if index == upperIndex - 1 { + height += self.nodes[i].frame.size.height + upperIndex = index + } else { + break + } + } + } + } + + if fixedNodeIndex != self.nodes.count - 1 { + var lowerIndex = fixedIndex + for i in (fixedNodeIndex + 1) ..< self.nodes.count { + if let index = self.nodes[i].index { + if index == lowerIndex + 1 { + height += self.nodes[i].frame.size.height + lowerIndex = index + } else { + break + } + } + } + } + + return height + } + + mutating func insertNode(_ itemIndex: Int, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: @escaping () -> (), offsetDirection: ListViewInsertionOffsetDirection, animated: Bool, operations: inout [ListViewStateOperation], itemCount: Int) { + let (insertionOrigin, insertionIndex) = self.nodeInsertionPointAndIndex(itemIndex) + + let nodeOrigin: CGPoint + switch offsetDirection { + case .Up: + nodeOrigin = CGPoint(x: insertionOrigin.x, y: insertionOrigin.y - (animated ? 0.0 : layout.size.height)) + case .Down: + nodeOrigin = insertionOrigin + } + + let nodeFrame = CGRect(origin: nodeOrigin, size: CGSize(width: layout.size.width, height: animated ? 0.0 : layout.size.height)) + + operations.append(.InsertNode(index: insertionIndex, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply)) + self.nodes.insert(.Node(index: itemIndex, frame: nodeFrame, referenceNode: nil), at: insertionIndex) + + if !animated { + switch offsetDirection { + case .Up: + var i = insertionIndex - 1 + while i >= 0 { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + i -= 1 + } + case .Down: + var i = insertionIndex + 1 + while i < self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + i += 1 + } + } + } + + var previousIndex: Int? + for node in self.nodes { + if let index = node.index { + if let currentPreviousIndex = previousIndex { + if index <= currentPreviousIndex { + print("index <= previousIndex + 1") + break + } + previousIndex = index + } else { + previousIndex = index + } + } + } + + if let _ = self.scrollPosition { + self.fixScrollPostition(itemCount) + } + } + + mutating func removeNodeAtIndex(_ index: Int, direction: ListViewItemOperationDirectionHint?, animated: Bool, operations: inout [ListViewStateOperation]) { + let node = self.nodes[index] + if case let .Node(_, _, referenceNode) = node { + let nodeFrame = node.frame + self.nodes.remove(at: index) + let offsetDirection: ListViewInsertionOffsetDirection + if let direction = direction { + offsetDirection = ListViewInsertionOffsetDirection(direction) + } else { + if nodeFrame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetDirection = .Down + } else { + offsetDirection = .Up + } + } + operations.append(.Remove(index: index, offsetDirection: offsetDirection)) + + if let referenceNode = referenceNode , animated { + self.nodes.insert(.Placeholder(frame: nodeFrame), at: index) + operations.append(.InsertDisappearingPlaceholder(index: index, referenceNode: referenceNode, offsetDirection: offsetDirection.inverted())) + } else { + if nodeFrame.maxY > self.insets.top - CGFloat(FLT_EPSILON) { + if let direction = direction , direction == .Down && node.frame.minY < self.visibleSize.height - self.insets.bottom + CGFloat(FLT_EPSILON) { + for i in (0 ..< index).reversed() { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + } + } else { + for i in index ..< self.nodes.count { + var frame = self.nodes[i].frame + frame.origin.y -= nodeFrame.size.height + self.nodes[i].frame = frame + } + } + } else if index != 0 { + for i in (0 ..< index).reversed() { + var frame = self.nodes[i].frame + frame.origin.y += nodeFrame.size.height + self.nodes[i].frame = frame + } + } + } + } else { + assertionFailure() + } + } + + mutating func updateNodeAtItemIndex(_ itemIndex: Int, layout: ListViewItemNodeLayout, direction: ListViewItemOperationDirectionHint?, animation: ListViewItemUpdateAnimation, apply: @escaping () -> Void, operations: inout [ListViewStateOperation]) { + var i = -1 + for node in self.nodes { + i += 1 + if node.index == itemIndex { + switch animation { + case .None: + let offsetDirection: ListViewInsertionOffsetDirection + if let direction = direction { + offsetDirection = ListViewInsertionOffsetDirection(direction) + } else { + if node.frame.maxY < self.insets.top + CGFloat(FLT_EPSILON) { + offsetDirection = .Down + } else { + offsetDirection = .Up + } + } + + switch offsetDirection { + case .Up: + let offsetDelta = -(layout.size.height - node.frame.size.height) + var updatedFrame = node.frame + updatedFrame.origin.y += offsetDelta + updatedFrame.size.height = layout.size.height + self.nodes[i].frame = updatedFrame + + for j in 0 ..< i { + var frame = self.nodes[j].frame + frame.origin.y += offsetDelta + self.nodes[j].frame = frame + } + case .Down: + let offsetDelta = layout.size.height - node.frame.size.height + var updatedFrame = node.frame + updatedFrame.size.height = layout.size.height + self.nodes[i].frame = updatedFrame + + for j in i + 1 ..< self.nodes.count { + var frame = self.nodes[j].frame + frame.origin.y += offsetDelta + self.nodes[j].frame = frame + } + } + + operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) + case .System: + operations.append(.UpdateLayout(index: i, layout: layout, apply: apply)) + } + + break + } + } + } +} + +enum ListViewStateOperation { + case InsertNode(index: Int, offsetDirection: ListViewInsertionOffsetDirection, node: ListViewItemNode, layout: ListViewItemNodeLayout, apply: () -> ()) + case InsertDisappearingPlaceholder(index: Int, referenceNode: ListViewItemNode, offsetDirection: ListViewInsertionOffsetDirection) + case Remove(index: Int, offsetDirection: ListViewInsertionOffsetDirection) + case Remap([Int: Int]) + case UpdateLayout(index: Int, layout: ListViewItemNodeLayout, apply: () -> ()) +} diff --git a/Display/NavigationController.swift b/Display/NavigationController.swift index ccac1ccfcc..bc081974b9 100644 --- a/Display/NavigationController.swift +++ b/Display/NavigationController.swift @@ -368,11 +368,11 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { if let controller = viewControllerToPresent as? NavigationController { - controller.navigation_setDismiss { [weak self] in + controller.navigation_setDismiss({ [weak self] in if let strongSelf = self { strongSelf.dismiss(animated: false, completion: nil) } - } + }, rootController: self.view!.window!.rootViewController) self._presentedViewController = controller self.view.endEditing(true) diff --git a/Display/PresentationContext.swift b/Display/PresentationContext.swift index 85df924276..02cb60bf55 100644 --- a/Display/PresentationContext.swift +++ b/Display/PresentationContext.swift @@ -52,11 +52,12 @@ final class PresentationContext { strongSelf.controllers.append(controller) if let view = strongSelf.view, let layout = strongSelf.layout { - controller.navigation_setDismiss { [weak strongSelf, weak controller] in + controller.navigation_setDismiss({ [weak strongSelf, weak controller] in if let strongSelf = strongSelf, let controller = controller { strongSelf.dismiss(controller) } - } + }, rootController: nil) + controller.setIgnoreAppearanceMethodInvocations(true) if layout != initialLayout { controller.view.frame = CGRect(origin: CGPoint(), size: layout.size) view.addSubview(controller.view) @@ -64,6 +65,7 @@ final class PresentationContext { } else { view.addSubview(controller.view) } + controller.setIgnoreAppearanceMethodInvocations(false) view.layer.invalidateUpTheTree() controller.viewWillAppear(false) controller.viewDidAppear(false) diff --git a/Display/UIViewController+Navigation.h b/Display/UIViewController+Navigation.h index 474b3a6858..8f9f226ef0 100644 --- a/Display/UIViewController+Navigation.h +++ b/Display/UIViewController+Navigation.h @@ -3,9 +3,10 @@ @interface UIViewController (Navigation) - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations; +- (BOOL)ignoreAppearanceMethodInvocations; - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller; - (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController; -- (void)navigation_setDismiss:(void (^_Nullable)())dismiss; +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:(UIViewController *)rootController; @end diff --git a/Display/UIViewController+Navigation.m b/Display/UIViewController+Navigation.m index 128296b26d..c1a81a4c5c 100644 --- a/Display/UIViewController+Navigation.m +++ b/Display/UIViewController+Navigation.m @@ -8,12 +8,14 @@ @interface UIViewControllerPresentingProxy : UIViewController @property (nonatomic, copy) void (^dismiss)(); +@property (nonatomic, strong, readonly) UIViewController *rootController; @end @implementation UIViewControllerPresentingProxy -- (instancetype)init { +- (instancetype)initWithRootController:(UIViewController *)rootController { + _rootController = rootController; return self; } @@ -92,6 +94,7 @@ static bool notyfyingShiftState = false; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(viewDidDisappear:) newSelector:@selector(_65087dc8_viewDidDisappear:)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(navigationController) newSelector:@selector(_65087dc8_navigationController)]; [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)]; + [RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)]; //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)]; //[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)]; @@ -156,8 +159,8 @@ static bool notyfyingShiftState = false; [self setAssociatedObject:[[NSWeakReference alloc] initWithValue:presentingViewController] forKey:UIViewControllerPresentingControllerKey]; } -- (void)navigation_setDismiss:(void (^_Nullable)())dismiss { - UIViewControllerPresentingProxy *proxy = [[UIViewControllerPresentingProxy alloc] init]; +- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:(UIViewController *)rootController { + UIViewControllerPresentingProxy *proxy = [[UIViewControllerPresentingProxy alloc] initWithRootController:rootController]; proxy.dismiss = dismiss; [self setAssociatedObject:proxy forKey:UIViewControllerPresentingProxyControllerKey]; } @@ -173,7 +176,16 @@ static bool notyfyingShiftState = false; return controller; } - return [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; + UIView *result = [self associatedObjectForKey:UIViewControllerPresentingProxyControllerKey]; + if (result != nil) { + return result; + } + + return [self _65087dc8_presentingViewController]; +} + +- (void)_65087dc8_presentViewController:(UIViewController *)viewControllerToPresent animated:(BOOL)flag completion:(void (^)(void))completion { + [self _65087dc8_presentViewController:viewControllerToPresent animated:flag completion:completion]; } @end diff --git a/Display/ViewController.swift b/Display/ViewController.swift index aaa22f1f77..4fd5e3d7ca 100644 --- a/Display/ViewController.swift +++ b/Display/ViewController.swift @@ -174,9 +174,9 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? { } } - override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + /*override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { preconditionFailure("use present(_:in)") - } + }*/ override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) { if let navigationController = self.navigationController as? NavigationController { diff --git a/Display/Window.swift b/Display/Window.swift index 9589de34a5..4ab3fd3c2b 100644 --- a/Display/Window.swift +++ b/Display/Window.swift @@ -2,6 +2,8 @@ import Foundation import AsyncDisplayKit private class WindowRootViewController: UIViewController { + var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)? + override var preferredStatusBarStyle: UIStatusBarStyle { return .default } @@ -9,6 +11,12 @@ private class WindowRootViewController: UIViewController { override var prefersStatusBarHidden: Bool { return false } + + /*override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) { + if let presentController = self.presentController { + presentController(viewControllerToPresent, flag, completion) + } + }*/ } private struct WindowLayout: Equatable { @@ -130,9 +138,20 @@ public class Window: UIWindow { self.presentationContext.view = self self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) - super.rootViewController = WindowRootViewController() - super.rootViewController?.viewWillAppear(false) - super.rootViewController?.viewDidAppear(false) + let rootViewController = WindowRootViewController() + super.rootViewController = rootViewController + rootViewController.viewWillAppear(false) + rootViewController.viewDidAppear(false) + rootViewController.view.isHidden = true + + rootViewController.presentController = { [weak self] controller, animated, completion in + if let strongSelf = self { + strongSelf.present(LegacyPresentedController(legacyController: controller, presentation: .custom)) + if let completion = completion { + completion() + } + } + } self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in if let strongSelf = self { @@ -230,18 +249,18 @@ public class Window: UIWindow { } } - private var rootController: ContainableController? + private var _rootController: ContainableController? public var viewController: ContainableController? { get { - return rootController + return _rootController } set(value) { - if let rootController = self.rootController { + if let rootController = self._rootController { rootController.view.removeFromSuperview() } - self.rootController = value + self._rootController = value - if let rootController = self.rootController { + if let rootController = self._rootController { rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate) self.addSubview(rootController.view) @@ -343,14 +362,13 @@ public class Window: UIWindow { } self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight) - self.rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) - + self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition) } } } - func present(_ controller: ViewController) { + public func present(_ controller: ViewController) { self.presentationContext.present(controller) } }