mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-29 17:31:56 +00:00
no message
This commit is contained in:
@@ -367,6 +367,14 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull
|
||||
|
||||
@implementation UITracingLayerView
|
||||
|
||||
- (void)setFrame:(CGRect)frame {
|
||||
[super setFrame:frame];
|
||||
}
|
||||
|
||||
- (void)setAutoresizingMask:(UIViewAutoresizing)autoresizingMask {
|
||||
[super setAutoresizingMask:0];
|
||||
}
|
||||
|
||||
+ (Class)layerClass {
|
||||
return [CATracingLayer class];
|
||||
}
|
||||
|
||||
@@ -45,25 +45,17 @@ public struct ContainerViewLayout: Equatable {
|
||||
public let safeInsets: UIEdgeInsets
|
||||
public let statusBarHeight: CGFloat?
|
||||
public let inputHeight: CGFloat?
|
||||
public let standardInputHeight: CGFloat
|
||||
public let inputHeightIsInteractivellyChanging: Bool
|
||||
|
||||
public init() {
|
||||
self.size = CGSize()
|
||||
self.metrics = LayoutMetrics()
|
||||
self.intrinsicInsets = UIEdgeInsets()
|
||||
self.safeInsets = UIEdgeInsets()
|
||||
self.statusBarHeight = nil
|
||||
self.inputHeight = nil
|
||||
self.inputHeightIsInteractivellyChanging = false
|
||||
}
|
||||
|
||||
public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, inputHeightIsInteractivellyChanging: Bool) {
|
||||
public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, standardInputHeight: CGFloat, inputHeightIsInteractivellyChanging: Bool) {
|
||||
self.size = size
|
||||
self.metrics = metrics
|
||||
self.intrinsicInsets = intrinsicInsets
|
||||
self.safeInsets = safeInsets
|
||||
self.statusBarHeight = statusBarHeight
|
||||
self.inputHeight = inputHeight
|
||||
self.standardInputHeight = standardInputHeight
|
||||
self.inputHeightIsInteractivellyChanging = inputHeightIsInteractivellyChanging
|
||||
}
|
||||
|
||||
@@ -79,15 +71,15 @@ public struct ContainerViewLayout: Equatable {
|
||||
}
|
||||
|
||||
public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout {
|
||||
return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
}
|
||||
|
||||
public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout {
|
||||
return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
}
|
||||
|
||||
public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout {
|
||||
return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
}
|
||||
|
||||
public static func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool {
|
||||
@@ -131,6 +123,10 @@ public struct ContainerViewLayout: Equatable {
|
||||
return false
|
||||
}
|
||||
|
||||
if !lhs.standardInputHeight.isEqual(to: rhs.standardInputHeight) {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.inputHeightIsInteractivellyChanging != rhs.inputHeightIsInteractivellyChanging {
|
||||
return false
|
||||
}
|
||||
|
||||
160
Display/GlobalOverlayPresentationContext.swift
Normal file
160
Display/GlobalOverlayPresentationContext.swift
Normal file
@@ -0,0 +1,160 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
|
||||
final class GlobalOverlayPresentationContext {
|
||||
private let statusBarHost: StatusBarHost?
|
||||
|
||||
private var controllers: [ViewController] = []
|
||||
|
||||
private var presentationDisposables = DisposableSet()
|
||||
private var layout: ContainerViewLayout?
|
||||
|
||||
private var ready: Bool {
|
||||
return self.currentPresentationView() != nil && self.layout != nil
|
||||
}
|
||||
|
||||
init(statusBarHost: StatusBarHost?) {
|
||||
self.statusBarHost = statusBarHost
|
||||
}
|
||||
|
||||
private func currentPresentationView() -> UIView? {
|
||||
if let statusBarHost = self.statusBarHost {
|
||||
if let keyboardWindow = statusBarHost.keyboardWindow {
|
||||
return keyboardWindow
|
||||
} else {
|
||||
return statusBarHost.statusBarWindow
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func present(_ controller: ViewController) {
|
||||
let controllerReady = controller.ready.get()
|
||||
|> filter({ $0 })
|
||||
|> take(1)
|
||||
|> deliverOnMainQueue
|
||||
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true))
|
||||
|
||||
if let _ = self.currentPresentationView(), let initialLayout = self.layout {
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
|
||||
controller.containerLayoutUpdated(initialLayout, transition: .immediate)
|
||||
|
||||
self.presentationDisposables.add(controllerReady.start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
if strongSelf.controllers.contains(where: { $0 === controller }) {
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.controllers.append(controller)
|
||||
if let view = strongSelf.currentPresentationView(), let layout = strongSelf.layout {
|
||||
controller.navigation_setDismiss({ [weak controller] in
|
||||
if let strongSelf = self, 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)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
} else {
|
||||
view.addSubview(controller.view)
|
||||
}
|
||||
controller.setIgnoreAppearanceMethodInvocations(false)
|
||||
view.layer.invalidateUpTheTree()
|
||||
controller.viewWillAppear(false)
|
||||
controller.viewDidAppear(false)
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.controllers.append(controller)
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.presentationDisposables.dispose()
|
||||
}
|
||||
|
||||
private func dismiss(_ controller: ViewController) {
|
||||
if let index = self.controllers.index(where: { $0 === controller }) {
|
||||
self.controllers.remove(at: index)
|
||||
controller.viewWillDisappear(false)
|
||||
controller.view.removeFromSuperview()
|
||||
controller.viewDidDisappear(false)
|
||||
}
|
||||
}
|
||||
|
||||
public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let wasReady = self.ready
|
||||
self.layout = layout
|
||||
|
||||
if wasReady != self.ready {
|
||||
self.readyChanged(wasReady: wasReady)
|
||||
} else if self.ready {
|
||||
for controller in self.controllers {
|
||||
controller.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func readyChanged(wasReady: Bool) {
|
||||
if !wasReady {
|
||||
self.addViews()
|
||||
} else {
|
||||
self.removeViews()
|
||||
}
|
||||
}
|
||||
|
||||
private func addViews() {
|
||||
if let view = self.currentPresentationView(), let layout = self.layout {
|
||||
for controller in self.controllers {
|
||||
controller.viewWillAppear(false)
|
||||
view.addSubview(controller.view)
|
||||
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
controller.containerLayoutUpdated(layout, transition: .immediate)
|
||||
controller.viewDidAppear(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func removeViews() {
|
||||
for controller in self.controllers {
|
||||
controller.viewWillDisappear(false)
|
||||
controller.view.removeFromSuperview()
|
||||
controller.viewDidDisappear(false)
|
||||
}
|
||||
}
|
||||
|
||||
func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
for controller in self.controllers.reversed() {
|
||||
if controller.isViewLoaded {
|
||||
if let result = controller.view.hitTest(point, with: event) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func combinedSupportedOrientations() -> UIInterfaceOrientationMask {
|
||||
var mask: UIInterfaceOrientationMask = .all
|
||||
|
||||
for controller in self.controllers {
|
||||
mask = mask.intersection(controller.supportedInterfaceOrientations)
|
||||
}
|
||||
|
||||
return mask
|
||||
}
|
||||
|
||||
func combinedDeferScreenEdgeGestures() -> UIRectEdge {
|
||||
var edges: UIRectEdge = []
|
||||
|
||||
for controller in self.controllers {
|
||||
edges = edges.union(controller.deferScreenEdgeGestures)
|
||||
}
|
||||
|
||||
return edges
|
||||
}
|
||||
}
|
||||
@@ -102,34 +102,6 @@ public struct GridNodeUpdateLayout {
|
||||
}
|
||||
}
|
||||
|
||||
/*private func binarySearch(_ inputArr: [GridNodePresentationItem], searchItem: CGFloat) -> Int? {
|
||||
if inputArr.isEmpty {
|
||||
return nil
|
||||
}
|
||||
|
||||
var lowerPosition = inputArr[0].frame.origin.y + inputArr[0].frame.size.height
|
||||
var upperPosition = inputArr[inputArr.count - 1].frame.origin.y
|
||||
|
||||
if lowerPosition > upperPosition {
|
||||
return nil
|
||||
}
|
||||
|
||||
while (true) {
|
||||
let currentPosition = (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
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
public enum GridNodeStationaryItems {
|
||||
case none
|
||||
case all
|
||||
@@ -263,6 +235,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
|
||||
public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)?
|
||||
public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)?
|
||||
public var scrollingCompleted: (() -> Void)?
|
||||
|
||||
public final var floatingSections = false
|
||||
|
||||
@@ -397,11 +370,13 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
if !decelerate {
|
||||
self.updateItemNodeVisibilititesAndScrolling()
|
||||
self.scrollingCompleted?()
|
||||
}
|
||||
}
|
||||
|
||||
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
||||
self.updateItemNodeVisibilititesAndScrolling()
|
||||
self.scrollingCompleted?()
|
||||
}
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
|
||||
@@ -1,10 +1,6 @@
|
||||
#if os(macOS)
|
||||
import SwiftSignalKitMac
|
||||
#else
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
import SwiftSignalKit
|
||||
#endif
|
||||
|
||||
private let useBackgroundDeallocation = false
|
||||
|
||||
@@ -48,7 +44,6 @@ final class ListViewBackingView: UIView {
|
||||
override func setNeedsDisplay() {
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
self.target?.touchesBegan(touches, with: event)
|
||||
}
|
||||
@@ -73,7 +68,6 @@ final class ListViewBackingView: UIView {
|
||||
}
|
||||
return super.hitTest(point, with: event)
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
private final class ListViewTimerProxy: NSObject {
|
||||
@@ -197,6 +191,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
public final var visibleContentOffsetChanged: (ListViewVisibleContentOffset) -> Void = { _ in }
|
||||
public final var beganInteractiveDragging: () -> Void = { }
|
||||
|
||||
public final var reorderItem: (Int, Int, Any?) -> Void = { _, _, _ in }
|
||||
|
||||
private final var animations: [ListViewAnimation] = []
|
||||
private final var actionsForVSync: [() -> ()] = []
|
||||
private final var inVSync = false
|
||||
@@ -211,6 +207,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
private var selectionLongTapDelayTimer: Foundation.Timer?
|
||||
private var flashNodesDelayTimer: Foundation.Timer?
|
||||
private var highlightedItemIndex: Int?
|
||||
private var reorderNode: ListViewReorderingItemNode?
|
||||
|
||||
private let waitingForNodesDisposable = MetaDisposable()
|
||||
|
||||
@@ -266,13 +263,37 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
trackingRecognizer.delegate = self
|
||||
self.view.addGestureRecognizer(trackingRecognizer)
|
||||
|
||||
self.view.addGestureRecognizer(ListViewReorderingGestureRecognizer(shouldBegin: { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
if let index = strongSelf.itemIndexAtPoint(point) {
|
||||
for i in 0 ..< strongSelf.itemNodes.count {
|
||||
if strongSelf.itemNodes[i].index == index {
|
||||
let itemNode = strongSelf.itemNodes[i]
|
||||
let itemNodeFrame = itemNode.frame
|
||||
let itemNodeBounds = itemNode.bounds
|
||||
if itemNode.isReorderable(at: point.offsetBy(dx: -itemNodeFrame.minX + itemNodeBounds.minX, dy: -itemNodeFrame.minY + itemNodeBounds.minY)) {
|
||||
strongSelf.beginReordering(itemNode: itemNode)
|
||||
return true
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}, ended: { [weak self] in
|
||||
self?.endReordering()
|
||||
}, moved: { [weak self] offset in
|
||||
self?.updateReordering(offset: offset)
|
||||
}))
|
||||
|
||||
self.displayLink = CADisplayLink(target: DisplayLinkProxy(target: self), selector: #selector(DisplayLinkProxy.displayLinkEvent))
|
||||
self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
|
||||
#if os(iOS)
|
||||
|
||||
if #available(iOS 10.0, *) {
|
||||
self.displayLink.preferredFramesPerSecond = 60
|
||||
}
|
||||
#endif
|
||||
|
||||
self.displayLink.isPaused = true
|
||||
}
|
||||
|
||||
@@ -334,6 +355,86 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
}
|
||||
|
||||
private func beginReordering(itemNode: ListViewItemNode) {
|
||||
if let reorderNode = self.reorderNode {
|
||||
reorderNode.removeFromSupernode()
|
||||
}
|
||||
let reorderNode = ListViewReorderingItemNode(itemNode: itemNode, initialLocation: itemNode.frame.origin)
|
||||
self.reorderNode = reorderNode
|
||||
self.addSubnode(reorderNode)
|
||||
itemNode.isHidden = true
|
||||
}
|
||||
|
||||
private func endReordering() {
|
||||
if let reorderNode = self.reorderNode {
|
||||
self.reorderNode = nil
|
||||
if let itemNode = reorderNode.itemNode, itemNode.supernode == self {
|
||||
self.view.bringSubview(toFront: itemNode.view)
|
||||
reorderNode.animateCompletion(completion: { [weak itemNode, weak reorderNode] in
|
||||
//itemNode?.isHidden = false
|
||||
reorderNode?.removeFromSupernode()
|
||||
})
|
||||
self.setNeedsAnimations()
|
||||
} else {
|
||||
reorderNode.removeFromSupernode()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func updateReordering(offset: CGFloat) {
|
||||
if let reorderNode = self.reorderNode {
|
||||
reorderNode.updateOffset(offset: offset)
|
||||
self.checkItemReordering()
|
||||
}
|
||||
}
|
||||
|
||||
private func checkItemReordering() {
|
||||
if let reorderNode = self.reorderNode, let reorderItemNode = reorderNode.itemNode, let reorderItemIndex = reorderItemNode.index, reorderItemNode.supernode == self {
|
||||
guard let verticalTopOffset = reorderNode.currentOffset() else {
|
||||
return
|
||||
}
|
||||
let verticalOffset = verticalTopOffset
|
||||
var closestIndex: (Int, CGFloat)?
|
||||
for i in 0 ..< self.itemNodes.count {
|
||||
if let itemNodeIndex = self.itemNodes[i].index, itemNodeIndex != reorderItemIndex {
|
||||
let itemOffset = self.itemNodes[i].frame.midY
|
||||
let deltaOffset = itemOffset - verticalOffset
|
||||
if let (_, closestOffset) = closestIndex {
|
||||
if abs(deltaOffset) < abs(closestOffset) {
|
||||
closestIndex = (itemNodeIndex, deltaOffset)
|
||||
}
|
||||
} else {
|
||||
closestIndex = (itemNodeIndex, deltaOffset)
|
||||
}
|
||||
}
|
||||
}
|
||||
if let (closestIndexValue, offset) = closestIndex {
|
||||
//print("closest \(closestIndexValue) offset \(offset)")
|
||||
var toIndex: Int
|
||||
if offset > 0 {
|
||||
toIndex = closestIndexValue
|
||||
if toIndex > reorderItemIndex {
|
||||
toIndex -= 1
|
||||
}
|
||||
} else {
|
||||
toIndex = closestIndexValue + 1
|
||||
if toIndex > reorderItemIndex {
|
||||
toIndex -= 1
|
||||
}
|
||||
}
|
||||
if toIndex != reorderItemNode.index {
|
||||
if reorderNode.currentState?.0 != reorderItemIndex || reorderNode.currentState?.1 != toIndex {
|
||||
reorderNode.currentState = (reorderItemIndex, toIndex)
|
||||
//print("reorder \(reorderItemIndex) to \(toIndex) offset \(offset)")
|
||||
self.reorderItem(reorderItemIndex, toIndex, self.opaqueTransactionState)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.setNeedsAnimations()
|
||||
}
|
||||
}
|
||||
|
||||
private func resetHeaderItemsFlashTimer(start: Bool) {
|
||||
if let flashNodesDelayTimer = self.flashNodesDelayTimer {
|
||||
flashNodesDelayTimer.invalidate()
|
||||
@@ -1524,7 +1625,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
var offsetHeight = node.apparentHeight
|
||||
var takenAnimation = false
|
||||
|
||||
if let _ = previousFrame , animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 {
|
||||
if let _ = previousFrame, animated && node.index != nil && nodeIndex != self.itemNodes.count - 1 {
|
||||
let nextNode = self.itemNodes[nodeIndex + 1]
|
||||
if nextNode.index == nil && nextNode.subnodes.isEmpty {
|
||||
let nextHeight = nextNode.apparentHeight
|
||||
@@ -1560,8 +1661,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
if node.index == nil {
|
||||
node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
if node.animationForKey("height") == nil || !(node is ListViewTempItemNode) {
|
||||
node.addHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
}
|
||||
if node.animationForKey("apparentHeight") == nil || !(node is ListViewTempItemNode) {
|
||||
node.addApparentHeightAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
}
|
||||
node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor())
|
||||
} else if animated {
|
||||
if takenAnimation {
|
||||
@@ -1656,7 +1761,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
}
|
||||
|
||||
private func lowestHeaderNode() -> ASDisplayNode? {
|
||||
private func lowestNodeToInsertBelow() -> ASDisplayNode? {
|
||||
if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self {
|
||||
//return itemNode
|
||||
}
|
||||
var lowestHeaderNode: ASDisplayNode?
|
||||
var lowestHeaderNodeIndex: Int?
|
||||
for (_, headerNode) in self.itemHeaderNodes {
|
||||
@@ -1709,6 +1817,18 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
private func replayOperations(animated: Bool, animateAlpha: Bool, animateCrossfade: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set<Int>, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) {
|
||||
/*if true {
|
||||
print("----------")
|
||||
for itemNode in self.itemNodes {
|
||||
var anim = ""
|
||||
if let animation = itemNode.animationForKey("apparentHeight") {
|
||||
anim = "\(animation.from)->\(animation.to)"
|
||||
}
|
||||
print("\(itemNode.index) \(itemNode.apparentFrame.height) \(anim)")
|
||||
}
|
||||
print("----------")
|
||||
}*/
|
||||
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
let listInsets = updateSizeAndInsets?.insets ?? self.insets
|
||||
@@ -1718,14 +1838,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
var previousTopItemVerticalOrigin: CGFloat?
|
||||
var previousBottomItemMaxY: CGFloat?
|
||||
var snapshotView: UIView?
|
||||
if animateCrossfade {
|
||||
snapshotView = self.view.snapshotView(afterScreenUpdates: false)
|
||||
}
|
||||
if animateTopItemVerticalOrigin {
|
||||
previousTopItemVerticalOrigin = self.topItemVerticalOrigin()
|
||||
previousBottomItemMaxY = self.bottomItemMaxY()
|
||||
}
|
||||
|
||||
var previousApparentFrames: [(ListViewItemNode, CGRect)] = []
|
||||
@@ -1740,7 +1858,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
}
|
||||
|
||||
let lowestHeaderNode = self.lowestHeaderNode()
|
||||
let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow()
|
||||
|
||||
for operation in operations {
|
||||
switch operation {
|
||||
@@ -1763,8 +1881,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
self.insertNodeAtIndex(animated: nodeAnimated, animateAlpha: animateAlpha, forceAnimateInsertion: forceAnimateInsertion, previousFrame: updatedPreviousFrame, nodeIndex: index, offsetDirection: offsetDirection, node: node, layout: layout, apply: apply, timestamp: timestamp, listInsets: listInsets)
|
||||
if let _ = updatedPreviousFrame {
|
||||
if let lowestHeaderNode = lowestHeaderNode {
|
||||
self.insertSubnode(node, belowSubnode: lowestHeaderNode)
|
||||
if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self {
|
||||
self.insertSubnode(node, belowSubnode: itemNode)
|
||||
} else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow {
|
||||
self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow)
|
||||
} else {
|
||||
self.addSubnode(node)
|
||||
}
|
||||
@@ -1776,8 +1896,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
self.insertSubnode(node, at: 0)
|
||||
}
|
||||
} else {
|
||||
if let lowestHeaderNode = lowestHeaderNode {
|
||||
self.insertSubnode(node, belowSubnode: lowestHeaderNode)
|
||||
if let itemNode = self.reorderNode?.itemNode, itemNode.supernode == self {
|
||||
self.insertSubnode(node, belowSubnode: itemNode)
|
||||
} else if let lowestNodeToInsertBelow = lowestNodeToInsertBelow {
|
||||
self.insertSubnode(node, belowSubnode: lowestNodeToInsertBelow)
|
||||
} else {
|
||||
self.addSubnode(node)
|
||||
}
|
||||
@@ -1797,7 +1919,9 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
if let height = height, let previousLayout = previousLayout {
|
||||
if takenPreviousNodes.contains(referenceNode) {
|
||||
self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: ListViewItemNode(layerBacked: true), layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets)
|
||||
let tempNode = ListViewTempItemNode(layerBacked: true)
|
||||
//referenceNode.copyHeightAndApparentHeightAnimations(to: tempNode)
|
||||
self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: tempNode, layout: ListViewItemNodeLayout(contentSize: CGSize(width: self.visibleSize.width, height: height), insets: UIEdgeInsets()), apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets)
|
||||
} else {
|
||||
referenceNode.index = nil
|
||||
self.insertNodeAtIndex(animated: false, animateAlpha: false, forceAnimateInsertion: false, previousFrame: nil, nodeIndex: index, offsetDirection: offsetDirection, node: referenceNode, layout: previousLayout, apply: { return (nil, {}) }, timestamp: timestamp, listInsets: listInsets)
|
||||
@@ -2212,11 +2336,11 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
self.updateItemHeaders(leftInset: listInsets.left, rightInset: listInsets.right, transition: headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty)
|
||||
|
||||
if let offset = offset , abs(offset) > CGFloat.ulpOfOne {
|
||||
let lowestHeaderNode = self.lowestHeaderNode()
|
||||
let lowestNodeToInsertBelow = self.lowestNodeToInsertBelow()
|
||||
for itemNode in temporaryPreviousNodes {
|
||||
itemNode.frame = itemNode.frame.offsetBy(dx: 0.0, dy: offset)
|
||||
if let lowestHeaderNode = lowestHeaderNode {
|
||||
self.insertSubnode(itemNode, belowSubnode: lowestHeaderNode)
|
||||
if let lowestNodeToInsertBelow = lowestNodeToInsertBelow {
|
||||
self.insertSubnode(itemNode, belowSubnode: lowestNodeToInsertBelow)
|
||||
} else {
|
||||
self.addSubnode(itemNode)
|
||||
}
|
||||
@@ -2714,7 +2838,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
private func immediateDisplayedItemRange() -> ListViewDisplayedItemRange {
|
||||
var loadedRange: ListViewItemRange?
|
||||
var visibleRange: ListViewItemRange?
|
||||
var visibleRange: ListViewVisibleItemRange?
|
||||
if self.itemNodes.count != 0 {
|
||||
var firstIndex: (nodeIndex: Int, index: Int)?
|
||||
var lastIndex: (nodeIndex: Int, index: Int)?
|
||||
@@ -2736,12 +2860,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
i -= 1
|
||||
}
|
||||
if let firstIndex = firstIndex, let lastIndex = lastIndex {
|
||||
var firstVisibleIndex: Int?
|
||||
var firstVisibleIndex: (Int, Bool)?
|
||||
for i in firstIndex.nodeIndex ... lastIndex.nodeIndex {
|
||||
if let index = self.itemNodes[i].index {
|
||||
let frame = self.itemNodes[i].apparentFrame
|
||||
if frame.maxY >= self.insets.top && frame.minY < self.visibleSize.height + self.insets.bottom {
|
||||
firstVisibleIndex = index
|
||||
firstVisibleIndex = (index, frame.minY >= self.insets.top)
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -2760,7 +2884,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
if let lastVisibleIndex = lastVisibleIndex {
|
||||
visibleRange = ListViewItemRange(firstIndex: firstVisibleIndex, lastIndex: lastVisibleIndex)
|
||||
visibleRange = ListViewVisibleItemRange(firstIndex: firstVisibleIndex.0, firstIndexFullyVisible: firstVisibleIndex.1, lastIndex: lastVisibleIndex)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2807,6 +2931,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
var offsetRanges = OffsetRanges()
|
||||
|
||||
if let reorderOffset = self.reorderNode?.currentOffset(), !self.itemNodes.isEmpty {
|
||||
if reorderOffset < self.insets.top + 10.0 {
|
||||
if self.itemNodes[0].apparentFrame.minY < self.insets.top {
|
||||
continueAnimations = true
|
||||
offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: 6.0)
|
||||
}
|
||||
} else if reorderOffset > self.visibleSize.height - self.insets.bottom - 10.0 {
|
||||
if self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY > self.visibleSize.height - self.insets.bottom {
|
||||
continueAnimations = true
|
||||
offsetRanges.offset(IndexRange(first: 0, last: Int.max), offset: -6.0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var requestUpdateVisibleItems = false
|
||||
var index = 0
|
||||
while index < self.itemNodes.count {
|
||||
@@ -2871,9 +3009,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
if requestUpdateVisibleItems {
|
||||
self.enqueueUpdateVisibleItems()
|
||||
}
|
||||
|
||||
self.checkItemReordering()
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
let touchesPosition = touches.first!.location(in: self.view)
|
||||
|
||||
@@ -2963,7 +3102,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
self.updateScroller(transition: .immediate)
|
||||
}
|
||||
#endif
|
||||
|
||||
public func clearHighlightAnimated(_ animated: Bool) {
|
||||
if let highlightedItemIndex = self.highlightedItemIndex {
|
||||
@@ -3016,8 +3154,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
override open func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
if let selectionTouchLocation = self.selectionTouchLocation {
|
||||
let location = touches.first!.location(in: self.view)
|
||||
@@ -3099,7 +3236,6 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
break
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
public func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
@@ -3111,8 +3247,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
#if os(iOS)
|
||||
|
||||
fileprivate func internalHitTest(_ point: CGPoint, with event: UIEvent?) -> Bool {
|
||||
if self.limitHitTestToNodes {
|
||||
var foundHit = false
|
||||
@@ -3128,5 +3263,4 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
return true
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -124,6 +124,19 @@ public final class ListViewAnimation {
|
||||
self.completed = completed
|
||||
}
|
||||
|
||||
init<T: Interpolatable>(copying: ListViewAnimation, update: @escaping (CGFloat, T) -> Void, completed: @escaping (Bool) -> Void = { _ in }) {
|
||||
self.from = copying.from
|
||||
self.to = copying.to
|
||||
self.duration = copying.duration
|
||||
self.curve = copying.curve
|
||||
self.startTime = copying.startTime
|
||||
self.interpolator = copying.interpolator
|
||||
self.update = { progress, value in
|
||||
update(progress, value as! T)
|
||||
}
|
||||
self.completed = completed
|
||||
}
|
||||
|
||||
public func completeAt(_ timestamp: Double) -> Bool {
|
||||
if timestamp >= self.startTime + self.duration {
|
||||
self.completed(true)
|
||||
|
||||
@@ -145,15 +145,25 @@ public struct ListViewUpdateSizeAndInsets {
|
||||
public struct ListViewItemRange: Equatable {
|
||||
public let firstIndex: Int
|
||||
public let lastIndex: Int
|
||||
|
||||
public static func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool {
|
||||
return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex
|
||||
}
|
||||
}
|
||||
|
||||
public func ==(lhs: ListViewItemRange, rhs: ListViewItemRange) -> Bool {
|
||||
return lhs.firstIndex == rhs.firstIndex && lhs.lastIndex == rhs.lastIndex
|
||||
public struct ListViewVisibleItemRange: Equatable {
|
||||
public let firstIndex: Int
|
||||
public let firstIndexFullyVisible: Bool
|
||||
public let lastIndex: Int
|
||||
|
||||
public static func ==(lhs: ListViewVisibleItemRange, rhs: ListViewVisibleItemRange) -> Bool {
|
||||
return lhs.firstIndex == rhs.firstIndex && lhs.firstIndexFullyVisible == rhs.firstIndexFullyVisible && lhs.lastIndex == rhs.lastIndex
|
||||
}
|
||||
}
|
||||
|
||||
public struct ListViewDisplayedItemRange: Equatable {
|
||||
public let loadedRange: ListViewItemRange?
|
||||
public let visibleRange: ListViewItemRange?
|
||||
public let visibleRange: ListViewVisibleItemRange?
|
||||
}
|
||||
|
||||
public func ==(lhs: ListViewDisplayedItemRange, rhs: ListViewDisplayedItemRange) -> Bool {
|
||||
|
||||
@@ -430,6 +430,27 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
self.setAnimationForKey("height", animation: animation)
|
||||
}
|
||||
|
||||
func copyHeightAndApparentHeightAnimations(to otherNode: ListViewItemNode) {
|
||||
if let animation = self.animationForKey("apparentHeight") {
|
||||
let updatedAnimation = ListViewAnimation(copying: animation, update: { [weak otherNode] (progress: CGFloat, currentValue: CGFloat) -> Void in
|
||||
if let strongSelf = otherNode {
|
||||
let frame = strongSelf.frame
|
||||
strongSelf.frame = CGRect(origin: frame.origin, size: CGSize(width: frame.width, height: currentValue))
|
||||
}
|
||||
})
|
||||
otherNode.setAnimationForKey("height", animation: updatedAnimation)
|
||||
}
|
||||
|
||||
if let animation = self.animationForKey("apparentHeight") {
|
||||
let updatedAnimation = ListViewAnimation(copying: animation, update: { [weak otherNode] (progress: CGFloat, currentValue: CGFloat) -> Void in
|
||||
if let strongSelf = otherNode {
|
||||
strongSelf.apparentHeight = currentValue
|
||||
}
|
||||
})
|
||||
otherNode.setAnimationForKey("apparentHeight", animation: updatedAnimation)
|
||||
}
|
||||
}
|
||||
|
||||
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
|
||||
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in
|
||||
if let strongSelf = self {
|
||||
@@ -484,6 +505,10 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
open func setHighlighted(_ highlighted: Bool, at point: CGPoint, animated: Bool) {
|
||||
}
|
||||
|
||||
open func isReorderable(at point: CGPoint) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
||||
|
||||
}
|
||||
|
||||
65
Display/ListViewReorderingGestureRecognizer.swift
Normal file
65
Display/ListViewReorderingGestureRecognizer.swift
Normal file
@@ -0,0 +1,65 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
|
||||
private let shouldBegin: (CGPoint) -> Bool
|
||||
private let ended: () -> Void
|
||||
private let moved: (CGFloat) -> Void
|
||||
|
||||
private var initialLocation: CGPoint?
|
||||
|
||||
init(shouldBegin: @escaping (CGPoint) -> Bool, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) {
|
||||
self.shouldBegin = shouldBegin
|
||||
self.ended = ended
|
||||
self.moved = moved
|
||||
|
||||
super.init(target: nil, action: nil)
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.initialLocation = nil
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if self.state == .possible {
|
||||
if let location = touches.first?.location(in: self.view), self.shouldBegin(location) {
|
||||
self.initialLocation = location
|
||||
self.state = .began
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
if self.state == .began || self.state == .changed {
|
||||
self.ended()
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
if self.state == .began || self.state == .changed {
|
||||
self.ended()
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
|
||||
self.state = .changed
|
||||
let offset = location.y - initialLocation.y
|
||||
self.moved(offset)
|
||||
}
|
||||
}
|
||||
}
|
||||
48
Display/ListViewReorderingItemNode.swift
Normal file
48
Display/ListViewReorderingItemNode.swift
Normal file
@@ -0,0 +1,48 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class ListViewReorderingItemNode: ASDisplayNode {
|
||||
weak var itemNode: ListViewItemNode?
|
||||
|
||||
var currentState: (Int, Int)?
|
||||
|
||||
private let copyView: UIView?
|
||||
private let initialLocation: CGPoint
|
||||
|
||||
init(itemNode: ListViewItemNode, initialLocation: CGPoint) {
|
||||
self.itemNode = itemNode
|
||||
self.copyView = itemNode.view.snapshotContentTree()
|
||||
self.initialLocation = initialLocation
|
||||
|
||||
super.init()
|
||||
|
||||
if let copyView = self.copyView {
|
||||
self.view.addSubview(copyView)
|
||||
copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: copyView.bounds.size)
|
||||
}
|
||||
}
|
||||
|
||||
func updateOffset(offset: CGFloat) {
|
||||
if let copyView = self.copyView {
|
||||
copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y + offset), size: copyView.bounds.size)
|
||||
}
|
||||
}
|
||||
|
||||
func currentOffset() -> CGFloat? {
|
||||
if let copyView = self.copyView {
|
||||
return copyView.center.y
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func animateCompletion(completion: @escaping () -> Void) {
|
||||
if let copyView = self.copyView, let itemNode = self.itemNode {
|
||||
itemNode.isHidden = false
|
||||
itemNode.transitionOffset = itemNode.apparentFrame.midY - copyView.frame.midY
|
||||
itemNode.addTransitionOffsetAnimation(0.0, duration: 0.2, beginAt: CACurrentMediaTime())
|
||||
completion()
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
}
|
||||
}
|
||||
5
Display/ListViewTempItemNode.swift
Normal file
5
Display/ListViewTempItemNode.swift
Normal file
@@ -0,0 +1,5 @@
|
||||
import Foundation
|
||||
|
||||
final class ListViewTempItemNode: ListViewItemNode {
|
||||
|
||||
}
|
||||
@@ -11,6 +11,7 @@ private let defaultOrientations: UIInterfaceOrientationMask = {
|
||||
|
||||
private class WindowRootViewController: UIViewController {
|
||||
var presentController: ((UIViewController, PresentationSurfaceLevel, Bool, (() -> Void)?) -> Void)?
|
||||
|
||||
var orientations: UIInterfaceOrientationMask = defaultOrientations {
|
||||
didSet {
|
||||
if oldValue != self.orientations {
|
||||
@@ -73,6 +74,7 @@ private final class NativeWindow: UIWindow, WindowHost {
|
||||
var updateIsUpdatingOrientationLayout: ((Bool) -> Void)?
|
||||
var updateToInterfaceOrientation: (() -> Void)?
|
||||
var presentController: ((ViewController, PresentationSurfaceLevel) -> Void)?
|
||||
var presentControllerInGlobalOverlay: ((_ controller: ViewController) -> Void)?
|
||||
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
||||
var presentNativeImpl: ((UIViewController) -> Void)?
|
||||
var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
|
||||
@@ -150,6 +152,10 @@ private final class NativeWindow: UIWindow, WindowHost {
|
||||
self.presentController?(controller, level)
|
||||
}
|
||||
|
||||
func presentInGlobalOverlay(_ controller: ViewController) {
|
||||
self.presentControllerInGlobalOverlay?(controller)
|
||||
}
|
||||
|
||||
func presentNative(_ controller: UIViewController) {
|
||||
self.presentNativeImpl?(controller)
|
||||
}
|
||||
@@ -226,6 +232,10 @@ public func nativeWindowHostView() -> WindowHostView {
|
||||
hostView?.present?(controller, level)
|
||||
}
|
||||
|
||||
window.presentControllerInGlobalOverlay = { [weak hostView] controller in
|
||||
hostView?.presentInGlobalOverlay?(controller)
|
||||
}
|
||||
|
||||
window.presentNativeImpl = { [weak hostView] controller in
|
||||
hostView?.presentNative?(controller)
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ private class NavigationControllerView: UIView {
|
||||
open class NavigationController: UINavigationController, ContainableController, UIGestureRecognizerDelegate {
|
||||
public private(set) weak var overlayPresentingController: ViewController?
|
||||
|
||||
private var containerLayout = ContainerViewLayout()
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var navigationTransitionCoordinator: NavigationTransitionCoordinator?
|
||||
|
||||
@@ -72,10 +72,10 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
if !self.isViewLoaded {
|
||||
self.loadView()
|
||||
}
|
||||
self.containerLayout = layout
|
||||
self.validLayout = layout
|
||||
transition.updateFrame(view: self.view, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
|
||||
|
||||
let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)
|
||||
let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, standardInputHeight: layout.standardInputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)
|
||||
|
||||
if let topViewController = self.topViewController {
|
||||
if let topViewController = topViewController as? ContainableController {
|
||||
@@ -101,6 +101,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
open override func loadView() {
|
||||
self.view = NavigationControllerView()
|
||||
self.view.clipsToBounds = true
|
||||
self.view.autoresizingMask = []
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
self.navigationBar.prefersLargeTitles = false
|
||||
@@ -224,17 +225,21 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
if !controller.hasActiveInput {
|
||||
self.view.endEditing(true)
|
||||
}
|
||||
let appliedLayout = self.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? self.containerLayout.inputHeight : nil)
|
||||
controller.containerLayoutUpdated(appliedLayout, transition: .immediate)
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
let containerLayout = strongSelf.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? strongSelf.containerLayout.inputHeight : nil)
|
||||
if containerLayout != appliedLayout {
|
||||
controller.containerLayoutUpdated(containerLayout, transition: .immediate)
|
||||
if let validLayout = self.validLayout {
|
||||
let appliedLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil)
|
||||
controller.containerLayoutUpdated(appliedLayout, transition: .immediate)
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self, let validLayout = strongSelf.validLayout {
|
||||
let containerLayout = validLayout.withUpdatedInputHeight(controller.hasActiveInput ? validLayout.inputHeight : nil)
|
||||
if containerLayout != appliedLayout {
|
||||
controller.containerLayoutUpdated(containerLayout, transition: .immediate)
|
||||
}
|
||||
strongSelf.pushViewController(controller, animated: true)
|
||||
}
|
||||
strongSelf.pushViewController(controller, animated: true)
|
||||
}
|
||||
}))
|
||||
}))
|
||||
} else {
|
||||
self.pushViewController(controller, animated: false)
|
||||
}
|
||||
}
|
||||
|
||||
open override func pushViewController(_ viewController: UIViewController, animated: Bool) {
|
||||
@@ -247,7 +252,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise<Bool>? = nil) {
|
||||
self.view.endEditing(true)
|
||||
controller.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
if let validLayout = self.validLayout {
|
||||
controller.containerLayoutUpdated(validLayout, transition: .immediate)
|
||||
}
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
ready?.set(true)
|
||||
@@ -261,7 +268,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise<Bool>? = nil) {
|
||||
self.view.endEditing(true)
|
||||
controller.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
if let validLayout = self.validLayout {
|
||||
controller.containerLayoutUpdated(validLayout, transition: .immediate)
|
||||
}
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
ready?.set(true)
|
||||
@@ -323,15 +332,17 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
let topViewController = viewControllers[viewControllers.count - 1] as UIViewController
|
||||
|
||||
if let controller = topViewController as? ContainableController {
|
||||
var layoutToApply = self.containerLayout
|
||||
var hasActiveInput = false
|
||||
if let controller = controller as? ViewController {
|
||||
hasActiveInput = controller.hasActiveInput
|
||||
if let validLayout = self.validLayout {
|
||||
var layoutToApply = validLayout
|
||||
var hasActiveInput = false
|
||||
if let controller = controller as? ViewController {
|
||||
hasActiveInput = controller.hasActiveInput
|
||||
}
|
||||
if !hasActiveInput {
|
||||
layoutToApply = layoutToApply.withUpdatedInputHeight(nil)
|
||||
}
|
||||
controller.containerLayoutUpdated(layoutToApply, transition: .immediate)
|
||||
}
|
||||
if !hasActiveInput {
|
||||
layoutToApply = layoutToApply.withUpdatedInputHeight(nil)
|
||||
}
|
||||
controller.containerLayoutUpdated(layoutToApply, transition: .immediate)
|
||||
} else {
|
||||
topViewController.view.frame = CGRect(origin: CGPoint(), size: self.view.bounds.size)
|
||||
}
|
||||
@@ -452,7 +463,9 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self._presentedViewController = controller
|
||||
|
||||
self.view.endEditing(true)
|
||||
controller.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
if let validLayout = self.validLayout {
|
||||
controller.containerLayoutUpdated(validLayout, transition: .immediate)
|
||||
}
|
||||
|
||||
var ready: Signal<Bool, Void> = .single(true)
|
||||
|
||||
|
||||
81
Display/PeekController.swift
Normal file
81
Display/PeekController.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public final class PeekControllerTheme {
|
||||
public let isDark: Bool
|
||||
public let menuBackgroundColor: UIColor
|
||||
public let menuItemHighligtedColor: UIColor
|
||||
public let menuItemSeparatorColor: UIColor
|
||||
public let accentColor: UIColor
|
||||
public let destructiveColor: UIColor
|
||||
|
||||
public init(isDark: Bool, menuBackgroundColor: UIColor, menuItemHighligtedColor: UIColor, menuItemSeparatorColor: UIColor, accentColor: UIColor, destructiveColor: UIColor) {
|
||||
self.isDark = isDark
|
||||
self.menuBackgroundColor = menuBackgroundColor
|
||||
self.menuItemHighligtedColor = menuItemHighligtedColor
|
||||
self.menuItemSeparatorColor = menuItemSeparatorColor
|
||||
self.accentColor = accentColor
|
||||
self.destructiveColor = destructiveColor
|
||||
}
|
||||
}
|
||||
|
||||
public final class PeekController: ViewController {
|
||||
private var controllerNode: PeekControllerNode {
|
||||
return self.displayNode as! PeekControllerNode
|
||||
}
|
||||
|
||||
private let theme: PeekControllerTheme
|
||||
private let content: PeekControllerContent
|
||||
private let sourceNode: () -> ASDisplayNode?
|
||||
|
||||
private var animatedIn = false
|
||||
|
||||
public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) {
|
||||
self.theme = theme
|
||||
self.content = content
|
||||
self.sourceNode = sourceNode
|
||||
|
||||
super.init(navigationBarTheme: nil)
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override public func loadDisplayNode() {
|
||||
self.displayNode = PeekControllerNode(theme: self.theme, content: self.content, requestDismiss: { [weak self] in
|
||||
self?.dismiss()
|
||||
})
|
||||
self.displayNodeDidLoad()
|
||||
}
|
||||
|
||||
private func getSourceRect() -> CGRect {
|
||||
if let sourceNode = self.sourceNode() {
|
||||
return sourceNode.view.convert(sourceNode.bounds, to: self.view)
|
||||
} else {
|
||||
let size = self.displayNode.bounds.size
|
||||
return CGRect(origin: CGPoint(x: floor((size.width - 10.0) / 2.0), y: floor((size.height - 10.0) / 2.0)), size: CGSize(width: 10.0, height: 10.0))
|
||||
}
|
||||
}
|
||||
|
||||
override public func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
if !self.animatedIn {
|
||||
self.animatedIn = true
|
||||
self.controllerNode.animateIn(from: self.getSourceRect())
|
||||
}
|
||||
}
|
||||
|
||||
override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
|
||||
override public func dismiss(completion: (() -> Void)? = nil) {
|
||||
self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in
|
||||
self?.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
})
|
||||
}
|
||||
}
|
||||
23
Display/PeekControllerContent.swift
Normal file
23
Display/PeekControllerContent.swift
Normal file
@@ -0,0 +1,23 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public enum PeekControllerContentPresentation {
|
||||
case contained
|
||||
case freeform
|
||||
}
|
||||
|
||||
public enum PeerkControllerMenuActivation {
|
||||
case drag
|
||||
case press
|
||||
}
|
||||
|
||||
public protocol PeekControllerContent {
|
||||
func presentation() -> PeekControllerContentPresentation
|
||||
func menuActivation() -> PeerkControllerMenuActivation
|
||||
func menuItems() -> [PeekControllerMenuItem]
|
||||
func node() -> PeekControllerContentNode & ASDisplayNode
|
||||
}
|
||||
|
||||
public protocol PeekControllerContentNode {
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
|
||||
}
|
||||
228
Display/PeekControllerGestureRecognizer.swift
Normal file
228
Display/PeekControllerGestureRecognizer.swift
Normal file
@@ -0,0 +1,228 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
|
||||
private func traceDeceleratingScrollView(_ view: UIView, at point: CGPoint) -> Bool {
|
||||
if view.bounds.contains(point), let view = view as? UIScrollView, view.isDecelerating {
|
||||
return true
|
||||
}
|
||||
for subview in view.subviews {
|
||||
let subviewPoint = view.convert(point, to: subview)
|
||||
if traceDeceleratingScrollView(subview, at: subviewPoint) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
public final class PeekControllerGestureRecognizer: UIPanGestureRecognizer {
|
||||
private let contentAtPoint: (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?
|
||||
private let present: (PeekControllerContent, ASDisplayNode) -> PeekController?
|
||||
|
||||
private var tapLocation: CGPoint?
|
||||
private var longTapTimer: SwiftSignalKit.Timer?
|
||||
private var pressTimer: SwiftSignalKit.Timer?
|
||||
|
||||
private let candidateContentDisposable = MetaDisposable()
|
||||
private var candidateContent: (ASDisplayNode, PeekControllerContent)?
|
||||
|
||||
private var menuActivation: PeerkControllerMenuActivation?
|
||||
private weak var presentedController: PeekController?
|
||||
|
||||
public init(contentAtPoint: @escaping (CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?, present: @escaping (PeekControllerContent, ASDisplayNode) -> PeekController?) {
|
||||
self.contentAtPoint = contentAtPoint
|
||||
self.present = present
|
||||
|
||||
super.init(target: nil, action: nil)
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.longTapTimer?.invalidate()
|
||||
self.pressTimer?.invalidate()
|
||||
self.candidateContentDisposable.dispose()
|
||||
}
|
||||
|
||||
private func startLongTapTimer() {
|
||||
self.longTapTimer?.invalidate()
|
||||
let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in
|
||||
self?.longTapTimerFired()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.longTapTimer = longTapTimer
|
||||
longTapTimer.start()
|
||||
}
|
||||
|
||||
private func startPressTimer() {
|
||||
self.pressTimer?.invalidate()
|
||||
let pressTimer = SwiftSignalKit.Timer(timeout: 1.0, repeat: false, completion: { [weak self] in
|
||||
self?.pressTimerFired()
|
||||
}, queue: Queue.mainQueue())
|
||||
self.pressTimer = pressTimer
|
||||
pressTimer.start()
|
||||
}
|
||||
|
||||
private func stopLongTapTimer() {
|
||||
self.longTapTimer?.invalidate()
|
||||
self.longTapTimer = nil
|
||||
}
|
||||
|
||||
private func stopPressTimer() {
|
||||
self.pressTimer?.invalidate()
|
||||
self.pressTimer = nil
|
||||
}
|
||||
|
||||
override public func reset() {
|
||||
super.reset()
|
||||
|
||||
self.stopLongTapTimer()
|
||||
self.stopPressTimer()
|
||||
self.tapLocation = nil
|
||||
self.candidateContent = nil
|
||||
self.menuActivation = nil
|
||||
self.presentedController = nil
|
||||
}
|
||||
|
||||
private func longTapTimerFired() {
|
||||
guard let _ = self.tapLocation, let (sourceNode, content) = self.candidateContent else {
|
||||
return
|
||||
}
|
||||
|
||||
self.state = .began
|
||||
|
||||
if let presentedController = self.present(content, sourceNode) {
|
||||
self.menuActivation = content.menuActivation()
|
||||
self.presentedController = presentedController
|
||||
|
||||
switch content.menuActivation() {
|
||||
case .drag:
|
||||
break
|
||||
case .press:
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
if presentedController.traitCollection.forceTouchCapability != .available {
|
||||
self.startPressTimer()
|
||||
}
|
||||
} else {
|
||||
self.startPressTimer()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func pressTimerFired() {
|
||||
if let _ = self.tapLocation, let menuActivation = self.menuActivation, case .press = menuActivation {
|
||||
if let presentedController = self.presentedController {
|
||||
if presentedController.isNodeLoaded {
|
||||
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
|
||||
}
|
||||
self.menuActivation = nil
|
||||
self.presentedController = nil
|
||||
self.state = .ended
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if let view = self.view, let tapLocation = touches.first?.location(in: view) {
|
||||
if traceDeceleratingScrollView(view, at: tapLocation) {
|
||||
self.candidateContent = nil
|
||||
self.state = .failed
|
||||
} else {
|
||||
if let contentSignal = self.contentAtPoint(tapLocation) {
|
||||
self.candidateContentDisposable.set((contentSignal |> deliverOnMainQueue).start(next: { [weak self] result in
|
||||
if let strongSelf = self {
|
||||
switch strongSelf.state {
|
||||
case .possible, .changed:
|
||||
if let (sourceNode, content) = result {
|
||||
strongSelf.tapLocation = tapLocation
|
||||
strongSelf.candidateContent = (sourceNode, content)
|
||||
strongSelf.menuActivation = content.menuActivation()
|
||||
strongSelf.startLongTapTimer()
|
||||
} else {
|
||||
strongSelf.state = .failed
|
||||
}
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
let velocity = self.velocity(in: self.view)
|
||||
|
||||
if let presentedController = self.presentedController, presentedController.isNodeLoaded {
|
||||
(presentedController.displayNode as? PeekControllerNode)?.endDraggingWithVelocity(velocity.y)
|
||||
self.presentedController = nil
|
||||
self.menuActivation = nil
|
||||
}
|
||||
|
||||
self.tapLocation = nil
|
||||
self.candidateContent = nil
|
||||
self.state = .failed
|
||||
}
|
||||
|
||||
override public func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.tapLocation = nil
|
||||
self.candidateContent = nil
|
||||
self.state = .failed
|
||||
|
||||
if let presentedController = self.presentedController {
|
||||
self.menuActivation = nil
|
||||
self.presentedController = nil
|
||||
presentedController.dismiss()
|
||||
}
|
||||
}
|
||||
|
||||
override public func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if let touch = touches.first, let initialTapLocation = self.tapLocation, let menuActivation = self.menuActivation {
|
||||
let touchLocation = touch.location(in: self.view)
|
||||
if let presentedController = self.presentedController {
|
||||
switch menuActivation {
|
||||
case .drag:
|
||||
var offset = touchLocation.y - initialTapLocation.y
|
||||
let delta = abs(offset)
|
||||
let factor: CGFloat = 60.0
|
||||
offset = (-((1.0 - (1.0 / (((delta) * 0.55 / (factor)) + 1.0))) * factor)) * (offset < 0.0 ? 1.0 : -1.0)
|
||||
|
||||
if presentedController.isNodeLoaded {
|
||||
(presentedController.displayNode as? PeekControllerNode)?.applyDraggingOffset(offset)
|
||||
}
|
||||
case .press:
|
||||
if #available(iOSApplicationExtension 9.0, *) {
|
||||
if touch.force >= 2.5 {
|
||||
if presentedController.isNodeLoaded {
|
||||
(presentedController.displayNode as? PeekControllerNode)?.activateMenu()
|
||||
self.menuActivation = nil
|
||||
self.presentedController = nil
|
||||
self.state = .ended
|
||||
}
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
} else {
|
||||
let dX = touchLocation.x - initialTapLocation.x
|
||||
let dY = touchLocation.y - initialTapLocation.y
|
||||
|
||||
if dX * dX + dY * dY > 3.0 * 3.0 {
|
||||
self.stopLongTapTimer()
|
||||
self.tapLocation = nil
|
||||
self.candidateContent = nil
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
91
Display/PeekControllerMenuItemNode.swift
Normal file
91
Display/PeekControllerMenuItemNode.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public enum PeekControllerMenuItemColor {
|
||||
case accent
|
||||
case destructive
|
||||
}
|
||||
|
||||
public struct PeekControllerMenuItem {
|
||||
public let title: String
|
||||
public let color: PeekControllerMenuItemColor
|
||||
public let action: () -> Void
|
||||
|
||||
public init(title: String, color: PeekControllerMenuItemColor, action: @escaping () -> Void) {
|
||||
self.title = title
|
||||
self.color = color
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
|
||||
private let item: PeekControllerMenuItem
|
||||
private let activatedAction: () -> Void
|
||||
|
||||
private let separatorNode: ASDisplayNode
|
||||
private let highlightedBackgroundNode: ASDisplayNode
|
||||
private let textNode: ASTextNode
|
||||
|
||||
init(theme: PeekControllerTheme, item: PeekControllerMenuItem, activatedAction: @escaping () -> Void) {
|
||||
self.item = item
|
||||
self.activatedAction = activatedAction
|
||||
|
||||
self.separatorNode = ASDisplayNode()
|
||||
self.separatorNode.isLayerBacked = true
|
||||
self.separatorNode.backgroundColor = theme.menuItemSeparatorColor
|
||||
|
||||
self.highlightedBackgroundNode = ASDisplayNode()
|
||||
self.highlightedBackgroundNode.isLayerBacked = true
|
||||
self.highlightedBackgroundNode.backgroundColor = theme.menuItemHighligtedColor
|
||||
self.highlightedBackgroundNode.alpha = 0.0
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.isLayerBacked = true
|
||||
self.textNode.displaysAsynchronously = false
|
||||
|
||||
let textColor: UIColor
|
||||
switch item.color {
|
||||
case .accent:
|
||||
textColor = theme.accentColor
|
||||
case .destructive:
|
||||
textColor = theme.destructiveColor
|
||||
}
|
||||
self.textNode.attributedText = NSAttributedString(string: item.title, font: Font.regular(20.0), textColor: textColor)
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.separatorNode)
|
||||
self.addSubnode(self.highlightedBackgroundNode)
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.view.superview?.bringSubview(toFront: strongSelf.view)
|
||||
strongSelf.highlightedBackgroundNode.alpha = 1.0
|
||||
} else {
|
||||
strongSelf.highlightedBackgroundNode.alpha = 0.0
|
||||
strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
let height: CGFloat = 57.0
|
||||
transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: height)))
|
||||
transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: width, height: UIScreenPixel)))
|
||||
|
||||
let textSize = self.textNode.measure(CGSize(width: width - 10.0, height: height))
|
||||
transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor((height - textSize.height) / 2.0)), size: textSize))
|
||||
|
||||
return height
|
||||
}
|
||||
|
||||
@objc func buttonPressed() {
|
||||
self.activatedAction()
|
||||
self.item.action()
|
||||
}
|
||||
}
|
||||
30
Display/PeekControllerMenuNode.swift
Normal file
30
Display/PeekControllerMenuNode.swift
Normal file
@@ -0,0 +1,30 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class PeekControllerMenuNode: ASDisplayNode {
|
||||
private let itemNodes: [PeekControllerMenuItemNode]
|
||||
|
||||
init(theme: PeekControllerTheme, items: [PeekControllerMenuItem], activatedAction: @escaping () -> Void) {
|
||||
self.itemNodes = items.map { PeekControllerMenuItemNode(theme: theme, item: $0, activatedAction: activatedAction) }
|
||||
|
||||
super.init()
|
||||
|
||||
self.backgroundColor = theme.menuBackgroundColor
|
||||
self.cornerRadius = 16.0
|
||||
self.clipsToBounds = true
|
||||
|
||||
for itemNode in self.itemNodes {
|
||||
self.addSubnode(itemNode)
|
||||
}
|
||||
}
|
||||
|
||||
func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
|
||||
var verticalOffset: CGFloat = 0.0
|
||||
for itemNode in self.itemNodes {
|
||||
let itemHeight = itemNode.updateLayout(width: width, transition: transition)
|
||||
transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: width, height: itemHeight)))
|
||||
verticalOffset += itemHeight
|
||||
}
|
||||
return verticalOffset - UIScreenPixel
|
||||
}
|
||||
}
|
||||
206
Display/PeekControllerNode.swift
Normal file
206
Display/PeekControllerNode.swift
Normal file
@@ -0,0 +1,206 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class PeekControllerNode: ViewControllerTracingNode {
|
||||
private let requestDismiss: () -> Void
|
||||
|
||||
private let theme: PeekControllerTheme
|
||||
|
||||
private let blurView: UIView
|
||||
private let dimNode: ASDisplayNode
|
||||
private let containerBackgroundNode: ASImageNode
|
||||
private let containerNode: ASDisplayNode
|
||||
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private var containerOffset: CGFloat = 0.0
|
||||
|
||||
private let content: PeekControllerContent
|
||||
private let contentNode: PeekControllerContentNode & ASDisplayNode
|
||||
|
||||
private let menuNode: PeekControllerMenuNode?
|
||||
private var displayingMenu = false
|
||||
|
||||
init(theme: PeekControllerTheme, content: PeekControllerContent, requestDismiss: @escaping () -> Void) {
|
||||
self.theme = theme
|
||||
self.requestDismiss = requestDismiss
|
||||
|
||||
self.dimNode = ASDisplayNode()
|
||||
self.blurView = UIVisualEffectView(effect: UIBlurEffect(style: theme.isDark ? .dark : .light))
|
||||
self.blurView.isUserInteractionEnabled = false
|
||||
|
||||
switch content.menuActivation() {
|
||||
case .drag:
|
||||
self.dimNode.backgroundColor = nil
|
||||
self.blurView.alpha = 1.0
|
||||
case .press:
|
||||
self.dimNode.backgroundColor = UIColor(white: theme.isDark ? 0.0 : 1.0, alpha: 0.5)
|
||||
self.blurView.alpha = 0.0
|
||||
}
|
||||
|
||||
self.containerBackgroundNode = ASImageNode()
|
||||
self.containerBackgroundNode.isLayerBacked = true
|
||||
self.containerBackgroundNode.displayWithoutProcessing = true
|
||||
self.containerBackgroundNode.displaysAsynchronously = false
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.clipsToBounds = true
|
||||
self.containerNode.cornerRadius = 16.0
|
||||
|
||||
self.content = content
|
||||
self.contentNode = content.node()
|
||||
|
||||
var activatedActionImpl: (() -> Void)?
|
||||
let menuItems = content.menuItems()
|
||||
if menuItems.isEmpty {
|
||||
self.menuNode = nil
|
||||
} else {
|
||||
self.menuNode = PeekControllerMenuNode(theme: theme, items: menuItems, activatedAction: {
|
||||
activatedActionImpl?()
|
||||
})
|
||||
}
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.dimNode)
|
||||
self.view.addSubview(self.blurView)
|
||||
self.containerNode.addSubnode(self.contentNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
|
||||
if let menuNode = self.menuNode {
|
||||
self.addSubnode(menuNode)
|
||||
}
|
||||
|
||||
activatedActionImpl = { [weak self] in
|
||||
self?.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.dimNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimNodeTap(_:))))
|
||||
self.view.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:))))
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.validLayout = layout
|
||||
|
||||
transition.updateFrame(node: self.dimNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
transition.updateFrame(view: self.blurView, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
let layoutInsets = layout.insets(options: [])
|
||||
let maxContainerSize = CGSize(width: layout.size.width - 14.0 * 2.0, height: layout.size.height - layoutInsets.top - layoutInsets.bottom - 90.0)
|
||||
|
||||
var menuSize: CGSize?
|
||||
|
||||
let contentSize = self.contentNode.updateLayout(size: maxContainerSize, transition: transition)
|
||||
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: contentSize))
|
||||
|
||||
var containerFrame: CGRect
|
||||
switch self.content.presentation() {
|
||||
case .contained:
|
||||
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: self.containerOffset + floor((layout.size.height - contentSize.height) / 2.0)), size: contentSize)
|
||||
case .freeform:
|
||||
containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - contentSize.width) / 2.0), y: self.containerOffset + floor((layout.size.height - contentSize.height) / 4.0)), size: contentSize)
|
||||
}
|
||||
|
||||
if let menuNode = self.menuNode {
|
||||
let menuWidth = layout.size.width - layoutInsets.left - layoutInsets.right - 14.0 * 2.0
|
||||
let menuHeight = menuNode.updateLayout(width: menuWidth, transition: transition)
|
||||
menuSize = CGSize(width: menuWidth, height: menuHeight)
|
||||
|
||||
if self.displayingMenu {
|
||||
containerFrame.origin.y = min(containerFrame.origin.y, layout.size.height - layoutInsets.bottom - menuHeight - 14.0 * 2.0 - containerFrame.height)
|
||||
|
||||
transition.updateAlpha(layer: self.blurView.layer, alpha: 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
transition.updateFrame(node: self.containerNode, frame: containerFrame)
|
||||
|
||||
if let menuNode = self.menuNode, let menuSize = menuSize {
|
||||
let menuY: CGFloat
|
||||
if self.displayingMenu {
|
||||
menuY = max(containerFrame.maxY + 14.0, layout.size.height - layoutInsets.bottom - 14.0 - menuSize.height)
|
||||
} else {
|
||||
menuY = layout.size.height + 14.0
|
||||
}
|
||||
|
||||
transition.updateFrame(node: menuNode, frame: CGRect(origin: CGPoint(x: floor((layout.size.width - menuSize.width) / 2.0), y: menuY), size: menuSize))
|
||||
}
|
||||
}
|
||||
|
||||
func animateIn(from rect: CGRect) {
|
||||
self.dimNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.blurView.layer.animateAlpha(from: 0.0, to: self.blurView.alpha, duration: 0.3)
|
||||
|
||||
self.containerNode.layer.animateSpring(from: NSValue(cgPoint: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y)), to: NSValue(cgPoint: CGPoint()), keyPath: "position", duration: 0.4, initialVelocity: 0.0, damping: 110.0, additive: true)
|
||||
self.containerNode.layer.animateSpring(from: 0.1 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.4, initialVelocity: 0.0, damping: 110.0)
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.15)
|
||||
}
|
||||
|
||||
func animateOut(to rect: CGRect, completion: @escaping () -> Void) {
|
||||
self.dimNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.blurView.layer.animateAlpha(from: self.blurView.alpha, to: 0.0, duration: 0.25, removeOnCompletion: false)
|
||||
self.containerNode.layer.animatePosition(from: CGPoint(), to: CGPoint(x: rect.midX - self.containerNode.position.x, y: rect.midY - self.containerNode.position.y), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, additive: true, force: true, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.2, removeOnCompletion: false)
|
||||
self.containerNode.layer.animateScale(from: 1.0, to: 0.1, duration: 0.25, removeOnCompletion: false)
|
||||
if let menuNode = self.menuNode {
|
||||
menuNode.layer.animatePosition(from: menuNode.position, to: CGPoint(x: menuNode.position.x, y: self.bounds.size.height + menuNode.bounds.size.height / 2.0), duration: 0.25, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false)
|
||||
}
|
||||
}
|
||||
|
||||
@objc func dimNodeTap(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .changed:
|
||||
break
|
||||
case .cancelled, .ended:
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
func applyDraggingOffset(_ offset: CGFloat) {
|
||||
self.containerOffset = min(0.0, offset)
|
||||
if self.containerOffset < -25.0 {
|
||||
//self.displayingMenu = true
|
||||
} else {
|
||||
//self.displayingMenu = false
|
||||
}
|
||||
if let layout = self.validLayout {
|
||||
self.containerLayoutUpdated(layout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
func activateMenu() {
|
||||
if let layout = self.validLayout {
|
||||
self.displayingMenu = true
|
||||
self.containerOffset = 0.0
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.18, curve: .spring))
|
||||
}
|
||||
}
|
||||
|
||||
func endDraggingWithVelocity(_ velocity: CGFloat) {
|
||||
if let _ = self.menuNode, velocity < -600.0 || self.containerOffset < -38.0 {
|
||||
if let layout = self.validLayout {
|
||||
self.displayingMenu = true
|
||||
self.containerOffset = 0.0
|
||||
self.containerLayoutUpdated(layout, transition: .animated(duration: 0.18, curve: .spring))
|
||||
}
|
||||
} else {
|
||||
self.requestDismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -226,15 +226,15 @@ class StatusBarManager {
|
||||
self.host.statusBarStyle = statusBarStyle
|
||||
}
|
||||
if let statusBarWindow = self.host.statusBarWindow {
|
||||
statusBarWindow.alpha = globalStatusBar.1
|
||||
statusBarView.alpha = globalStatusBar.1
|
||||
var statusBarBounds = statusBarWindow.bounds
|
||||
if !statusBarBounds.origin.y.isEqual(to: globalStatusBar.2) {
|
||||
statusBarBounds.origin.y = globalStatusBar.2
|
||||
statusBarWindow.bounds = statusBarBounds
|
||||
}
|
||||
}
|
||||
} else if let statusBarWindow = self.host.statusBarWindow {
|
||||
statusBarWindow.alpha = 0.0
|
||||
} else {
|
||||
statusBarView.alpha = 0.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ public enum StatusBarStyle {
|
||||
private enum StatusBarItemType {
|
||||
case Generic
|
||||
case Battery
|
||||
case Activity
|
||||
}
|
||||
|
||||
func makeStatusBarProxy(_ statusBarStyle: StatusBarStyle, statusBar: UIView) -> StatusBarProxyNode {
|
||||
@@ -90,7 +91,22 @@ private class StatusBarItemNode: ASDisplayNode {
|
||||
UIGraphicsPopContext()
|
||||
}
|
||||
}
|
||||
let type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic
|
||||
//dumpViews(self.targetView)
|
||||
var type: StatusBarItemType = self.targetView.checkIsKind(of: batteryItemClass!) ? .Battery : .Generic
|
||||
if case .Generic = type {
|
||||
var hasActivityBackground = false
|
||||
var hasText = false
|
||||
for subview in self.targetView.subviews {
|
||||
if let stringClass = stringClass, subview.checkIsKind(of: stringClass) {
|
||||
hasText = true
|
||||
} else if let activityClass = activityClass, subview.checkIsKind(of: activityClass) {
|
||||
hasActivityBackground = true
|
||||
}
|
||||
}
|
||||
if hasActivityBackground && hasText {
|
||||
type = .Activity
|
||||
}
|
||||
}
|
||||
tintStatusBarItem(context, type: type, style: statusBarStyle)
|
||||
self.contents = context.generateImage()?.cgImage
|
||||
|
||||
@@ -229,6 +245,8 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp
|
||||
}
|
||||
}
|
||||
}
|
||||
case .Activity:
|
||||
break
|
||||
case .Generic:
|
||||
var pixel = context.bytes.assumingMemoryBound(to: UInt32.self)
|
||||
let end = context.bytes.advanced(by: context.length).assumingMemoryBound(to: UInt32.self)
|
||||
@@ -259,7 +277,15 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp
|
||||
}
|
||||
}
|
||||
|
||||
private let batteryItemClass: AnyClass? = { () -> AnyClass? in
|
||||
private let foregroundClass: AnyClass? = {
|
||||
var nameString = "StatusBar"
|
||||
if CFAbsoluteTimeGetCurrent() > 0 {
|
||||
nameString += "ForegroundView"
|
||||
}
|
||||
return NSClassFromString("_UI" + nameString)
|
||||
}()
|
||||
|
||||
private let batteryItemClass: AnyClass? = {
|
||||
var nameString = "StatusBarBattery"
|
||||
if CFAbsoluteTimeGetCurrent() > 0 {
|
||||
nameString += "ItemView"
|
||||
@@ -267,6 +293,22 @@ private let batteryItemClass: AnyClass? = { () -> AnyClass? in
|
||||
return NSClassFromString("UI" + nameString)
|
||||
}()
|
||||
|
||||
private let activityClass: AnyClass? = {
|
||||
var nameString = "StatusBarBackground"
|
||||
if CFAbsoluteTimeGetCurrent() > 0 {
|
||||
nameString += "ActivityView"
|
||||
}
|
||||
return NSClassFromString("_UI" + nameString)
|
||||
}()
|
||||
|
||||
private let stringClass: AnyClass? = {
|
||||
var nameString = "StatusBar"
|
||||
if CFAbsoluteTimeGetCurrent() > 0 {
|
||||
nameString += "StringView"
|
||||
}
|
||||
return NSClassFromString("_UI" + nameString)
|
||||
}()
|
||||
|
||||
private class StatusBarProxyNodeTimerTarget: NSObject {
|
||||
let action: () -> Void
|
||||
|
||||
@@ -327,7 +369,15 @@ class StatusBarProxyNode: ASDisplayNode {
|
||||
self.clipsToBounds = true
|
||||
//self.backgroundColor = UIColor.blueColor().colorWithAlphaComponent(0.2)
|
||||
|
||||
var rootView: UIView = statusBar
|
||||
for subview in statusBar.subviews {
|
||||
if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) {
|
||||
rootView = subview
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
for subview in rootView.subviews {
|
||||
let itemNode = StatusBarItemNode(statusBarStyle: statusBarStyle, targetView: subview)
|
||||
self.itemNodes.append(itemNode)
|
||||
self.addSubnode(itemNode)
|
||||
@@ -343,10 +393,20 @@ class StatusBarProxyNode: ASDisplayNode {
|
||||
private func updateItems() {
|
||||
let statusBar = self.statusBar
|
||||
|
||||
var rootView: UIView = statusBar
|
||||
for subview in statusBar.subviews {
|
||||
if let foregroundClass = foregroundClass, subview.checkIsKind(of: foregroundClass) {
|
||||
rootView = subview
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
//dumpViews(self.statusBar)
|
||||
|
||||
var i = 0
|
||||
while i < self.itemNodes.count {
|
||||
var found = false
|
||||
for subview in statusBar.subviews {
|
||||
for subview in rootView.subviews {
|
||||
if self.itemNodes[i].targetView == subview {
|
||||
found = true
|
||||
break
|
||||
@@ -362,7 +422,7 @@ class StatusBarProxyNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
for subview in statusBar.subviews {
|
||||
for subview in rootView.subviews {
|
||||
var found = false
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.targetView == subview {
|
||||
|
||||
@@ -26,7 +26,7 @@ public final class TabBarControllerTheme {
|
||||
}
|
||||
|
||||
open class TabBarController: ViewController {
|
||||
private var containerLayout = ContainerViewLayout()
|
||||
private var validLayout: ContainerViewLayout?
|
||||
|
||||
private var tabBarControllerNode: TabBarControllerNode {
|
||||
get {
|
||||
@@ -88,10 +88,32 @@ open class TabBarController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private var debugTapCounter: (Double, Int) = (0.0, 0)
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = TabBarControllerNode(theme: self.theme, itemSelected: { [weak self] index in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllers[index].containerLayoutUpdated(strongSelf.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate)
|
||||
if strongSelf.selectedIndex == index {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
if strongSelf.debugTapCounter.0 < timestamp - 0.4 {
|
||||
strongSelf.debugTapCounter.0 = timestamp
|
||||
strongSelf.debugTapCounter.1 = 0
|
||||
}
|
||||
|
||||
if strongSelf.debugTapCounter.0 >= timestamp - 0.4 {
|
||||
strongSelf.debugTapCounter.0 = timestamp
|
||||
strongSelf.debugTapCounter.1 += 1
|
||||
}
|
||||
|
||||
if strongSelf.debugTapCounter.1 >= 10 {
|
||||
strongSelf.debugTapCounter.1 = 0
|
||||
|
||||
strongSelf.controllers[index].tabBarItemDebugTapAction?()
|
||||
}
|
||||
}
|
||||
if let validLayout = strongSelf.validLayout {
|
||||
strongSelf.controllers[index].containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate)
|
||||
}
|
||||
strongSelf.pendingControllerDisposable.set((strongSelf.controllers[index].ready.get() |> deliverOnMainQueue).start(next: { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.selectedIndex = index
|
||||
@@ -127,7 +149,9 @@ open class TabBarController: ViewController {
|
||||
var displayNavigationBar = false
|
||||
if let currentController = self.currentController {
|
||||
currentController.willMove(toParentViewController: self)
|
||||
currentController.containerLayoutUpdated(self.containerLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate)
|
||||
if let validLayout = self.validLayout {
|
||||
currentController.containerLayoutUpdated(validLayout.addedInsets(insets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: 49.0, right: 0.0)), transition: .immediate)
|
||||
}
|
||||
self.tabBarControllerNode.currentControllerView = currentController.view
|
||||
currentController.navigationBar?.isHidden = true
|
||||
self.addChildViewController(currentController)
|
||||
@@ -149,13 +173,15 @@ open class TabBarController: ViewController {
|
||||
self.setDisplayNavigationBar(displayNavigationBar)
|
||||
}
|
||||
|
||||
self.tabBarControllerNode.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
if let validLayout = self.validLayout {
|
||||
self.tabBarControllerNode.containerLayoutUpdated(validLayout, transition: .immediate)
|
||||
}
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.containerLayout = layout
|
||||
self.validLayout = layout
|
||||
|
||||
self.tabBarControllerNode.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
|
||||
@@ -197,6 +197,16 @@ class TabBarNode: ASDisplayNode {
|
||||
self.addSubnode(self.separatorNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.view.addGestureRecognizer(TabBarTapRecognizer(tap: { [weak self] point in
|
||||
if let strongSelf = self {
|
||||
strongSelf.tapped(at: point)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
func updateTheme(_ theme: TabBarControllerTheme) {
|
||||
if self.theme !== theme {
|
||||
self.theme = theme
|
||||
@@ -354,11 +364,8 @@ class TabBarNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if let touch = touches.first, let bottomInset = self.validLayout?.3 {
|
||||
let location = touch.location(in: self.view)
|
||||
private func tapped(at location: CGPoint) {
|
||||
if let bottomInset = self.validLayout?.3 {
|
||||
if location.y > self.bounds.size.height - bottomInset {
|
||||
return
|
||||
}
|
||||
|
||||
58
Display/TabBarTapRecognizer.swift
Normal file
58
Display/TabBarTapRecognizer.swift
Normal file
@@ -0,0 +1,58 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
final class TabBarTapRecognizer: UIGestureRecognizer {
|
||||
private let tap: (CGPoint) -> Void
|
||||
|
||||
private var initialLocation: CGPoint?
|
||||
|
||||
init(tap: @escaping (CGPoint) -> Void) {
|
||||
self.tap = tap
|
||||
|
||||
super.init(target: nil, action: nil)
|
||||
}
|
||||
|
||||
override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.initialLocation = nil
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if self.initialLocation == nil {
|
||||
self.initialLocation = touches.first?.location(in: self.view)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
if let initialLocation = self.initialLocation {
|
||||
self.initialLocation = nil
|
||||
self.tap(initialLocation)
|
||||
self.state = .ended
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
|
||||
let deltaX = initialLocation.x - location.x
|
||||
let deltaY = initialLocation.y - location.y
|
||||
if deltaX * deltaX + deltaY * deltaY > 4.0 {
|
||||
self.initialLocation = nil
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
self.initialLocation = nil
|
||||
self.state = .failed
|
||||
}
|
||||
}
|
||||
@@ -134,7 +134,7 @@ public extension UIImage {
|
||||
|
||||
private func makeSubtreeSnapshot(layer: CALayer) -> UIView? {
|
||||
let view = UIView()
|
||||
//view.layer.isHidden = layer.isHidden
|
||||
view.layer.isHidden = layer.isHidden
|
||||
view.layer.opacity = layer.opacity
|
||||
view.layer.contents = layer.contents
|
||||
view.layer.contentsRect = layer.contentsRect
|
||||
@@ -143,6 +143,7 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? {
|
||||
view.layer.contentsGravity = layer.contentsGravity
|
||||
view.layer.masksToBounds = layer.masksToBounds
|
||||
view.layer.cornerRadius = layer.cornerRadius
|
||||
view.layer.backgroundColor = layer.backgroundColor
|
||||
if let sublayers = layer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
let subtree = makeSubtreeSnapshot(layer: sublayer)
|
||||
@@ -160,7 +161,7 @@ private func makeSubtreeSnapshot(layer: CALayer) -> UIView? {
|
||||
|
||||
private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
|
||||
let view = CALayer()
|
||||
//view.layer.isHidden = layer.isHidden
|
||||
view.isHidden = layer.isHidden
|
||||
view.opacity = layer.opacity
|
||||
view.contents = layer.contents
|
||||
view.contentsRect = layer.contentsRect
|
||||
@@ -169,6 +170,7 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
|
||||
view.contentsGravity = layer.contentsGravity
|
||||
view.masksToBounds = layer.masksToBounds
|
||||
view.cornerRadius = layer.cornerRadius
|
||||
view.backgroundColor = layer.backgroundColor
|
||||
if let sublayers = layer.sublayers {
|
||||
for sublayer in sublayers {
|
||||
let subtree = makeLayerSubtreeSnapshot(layer: sublayer)
|
||||
@@ -185,24 +187,40 @@ private func makeLayerSubtreeSnapshot(layer: CALayer) -> CALayer? {
|
||||
}
|
||||
|
||||
public extension UIView {
|
||||
public func snapshotContentTree() -> UIView? {
|
||||
if let snapshot = makeSubtreeSnapshot(layer: self.layer) {
|
||||
public func snapshotContentTree(unhide: Bool = false) -> UIView? {
|
||||
let wasHidden = self.isHidden
|
||||
if unhide && wasHidden {
|
||||
self.isHidden = false
|
||||
}
|
||||
let snapshot = makeSubtreeSnapshot(layer: self.layer)
|
||||
if unhide && wasHidden {
|
||||
self.isHidden = true
|
||||
}
|
||||
if let snapshot = snapshot {
|
||||
snapshot.frame = self.frame
|
||||
return snapshot
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
public extension CALayer {
|
||||
public func snapshotContentTree() -> CALayer? {
|
||||
if let snapshot = makeLayerSubtreeSnapshot(layer: self) {
|
||||
public func snapshotContentTree(unhide: Bool = false) -> CALayer? {
|
||||
let wasHidden = self.isHidden
|
||||
if unhide && wasHidden {
|
||||
self.isHidden = false
|
||||
}
|
||||
let snapshot = makeLayerSubtreeSnapshot(layer: self)
|
||||
if unhide && wasHidden {
|
||||
self.isHidden = true
|
||||
}
|
||||
if let snapshot = snapshot {
|
||||
snapshot.frame = self.frame
|
||||
return snapshot
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@ typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
|
||||
|
||||
@interface UIViewController (Navigation)
|
||||
|
||||
- (BOOL)isPresentedInPreviewingContext;
|
||||
- (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations;
|
||||
- (BOOL)ignoreAppearanceMethodInvocations;
|
||||
- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller;
|
||||
|
||||
@@ -106,6 +106,10 @@ static bool notyfyingShiftState = false;
|
||||
});
|
||||
}
|
||||
|
||||
- (BOOL)isPresentedInPreviewingContext {
|
||||
return ![self.presentingViewController isKindOfClass:[UIViewControllerPresentingProxy class]];
|
||||
}
|
||||
|
||||
- (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations
|
||||
{
|
||||
[self setAssociatedObject:@(ignoreAppearanceMethodInvocations) forKey:UIViewControllerIgnoreAppearanceMethodInvocationsKey];
|
||||
|
||||
@@ -30,7 +30,7 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
|
||||
@objc open class ViewController: UIViewController, ContainableController {
|
||||
private var containerLayout = ContainerViewLayout()
|
||||
private var validLayout: ContainerViewLayout?
|
||||
private let presentationContext: PresentationContext
|
||||
|
||||
public final var supportedOrientations: UIInterfaceOrientationMask = .all
|
||||
@@ -60,6 +60,8 @@ open class ViewControllerPresentationArguments {
|
||||
|
||||
public private(set) var presentationArguments: Any?
|
||||
|
||||
public var tabBarItemDebugTapAction: (() -> Void)?
|
||||
|
||||
private var _displayNode: ASDisplayNode?
|
||||
public final var displayNode: ASDisplayNode {
|
||||
get {
|
||||
@@ -86,6 +88,8 @@ open class ViewControllerPresentationArguments {
|
||||
public let statusBar: StatusBar
|
||||
public let navigationBar: NavigationBar?
|
||||
|
||||
private var previewingContext: Any?
|
||||
|
||||
public var displayNavigationBar = true
|
||||
|
||||
private weak var activeInputViewCandidate: UIResponder?
|
||||
@@ -172,7 +176,7 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
|
||||
open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
self.containerLayout = layout
|
||||
self.validLayout = layout
|
||||
|
||||
if !self.isViewLoaded {
|
||||
self.loadView()
|
||||
@@ -221,6 +225,7 @@ open class ViewControllerPresentationArguments {
|
||||
self.displayNode.addSubnode(navigationBar)
|
||||
}
|
||||
}
|
||||
self.view.autoresizingMask = []
|
||||
self.view.addSubview(self.statusBar.view)
|
||||
self.presentationContext.view = self.view
|
||||
}
|
||||
@@ -238,8 +243,8 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
|
||||
public func requestLayout(transition: ContainedViewLayoutTransition) {
|
||||
if self.isViewLoaded {
|
||||
self.containerLayoutUpdated(self.containerLayout, transition: transition)
|
||||
if self.isViewLoaded, let validLayout = self.validLayout {
|
||||
self.containerLayoutUpdated(validLayout, transition: transition)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -293,6 +298,11 @@ open class ViewControllerPresentationArguments {
|
||||
}
|
||||
}
|
||||
|
||||
public func presentInGlobalOverlay(_ controller: ViewController, with arguments: Any? = nil) {
|
||||
controller.presentationArguments = arguments
|
||||
self.window?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
|
||||
open override func viewWillDisappear(_ animated: Bool) {
|
||||
self.activeInputViewCandidate = findCurrentResponder(self.view)
|
||||
|
||||
@@ -313,4 +323,27 @@ open class ViewControllerPresentationArguments {
|
||||
|
||||
open func dismiss(completion: (() -> Void)? = nil) {
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, *)
|
||||
open func registerForPreviewing(with delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, theme: PeekControllerTheme, onlyNative: Bool) {
|
||||
if self.traitCollection.forceTouchCapability == .available {
|
||||
let _ = super.registerForPreviewing(with: delegate, sourceView: sourceView)
|
||||
} else if !onlyNative {
|
||||
if self.previewingContext == nil {
|
||||
let previewingContext = SimulatedViewControllerPreviewing(theme: theme, delegate: delegate, sourceView: sourceView, node: self.displayNode, present: { [weak self] c, a in
|
||||
self?.presentInGlobalOverlay(c, with: a)
|
||||
})
|
||||
self.previewingContext = previewingContext
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, *)
|
||||
open override func unregisterForPreviewing(withContext previewing: UIViewControllerPreviewing) {
|
||||
if self.previewingContext != nil {
|
||||
self.previewingContext = nil
|
||||
} else {
|
||||
super.unregisterForPreviewing(withContext: previewing)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
118
Display/ViewControllerPreviewing.swift
Normal file
118
Display/ViewControllerPreviewing.swift
Normal file
@@ -0,0 +1,118 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftSignalKit
|
||||
|
||||
@available(iOSApplicationExtension 9.0, *)
|
||||
private final class ViewControllerPeekContent: PeekControllerContent {
|
||||
private let controller: ViewController
|
||||
private let menu: [PeekControllerMenuItem]
|
||||
|
||||
init(controller: ViewController) {
|
||||
self.controller = controller
|
||||
var menu: [PeekControllerMenuItem] = []
|
||||
for item in controller.previewActionItems {
|
||||
menu.append(PeekControllerMenuItem(title: item.title, color: .accent, action: { [weak controller] in
|
||||
if let controller = controller, let item = item as? UIPreviewAction {
|
||||
item.handler(item, controller)
|
||||
}
|
||||
}))
|
||||
}
|
||||
self.menu = menu
|
||||
}
|
||||
|
||||
func presentation() -> PeekControllerContentPresentation {
|
||||
return .contained
|
||||
}
|
||||
|
||||
func menuActivation() -> PeerkControllerMenuActivation {
|
||||
return .drag
|
||||
}
|
||||
|
||||
func menuItems() -> [PeekControllerMenuItem] {
|
||||
return self.menu
|
||||
}
|
||||
|
||||
func node() -> PeekControllerContentNode & ASDisplayNode {
|
||||
return ViewControllerPeekContentNode(controller: self.controller)
|
||||
}
|
||||
}
|
||||
|
||||
private final class ViewControllerPeekContentNode: ASDisplayNode, PeekControllerContentNode {
|
||||
private let controller: ViewController
|
||||
private var hasValidLayout = false
|
||||
|
||||
init(controller: ViewController) {
|
||||
self.controller = controller
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
if !self.hasValidLayout {
|
||||
self.hasValidLayout = true
|
||||
self.controller.view.frame = CGRect(origin: CGPoint(), size: size)
|
||||
self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: .immediate)
|
||||
self.controller.setIgnoreAppearanceMethodInvocations(true)
|
||||
self.view.addSubview(self.controller.view)
|
||||
self.controller.setIgnoreAppearanceMethodInvocations(false)
|
||||
self.controller.viewWillAppear(false)
|
||||
self.controller.viewDidAppear(false)
|
||||
} else {
|
||||
self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false), transition: transition)
|
||||
}
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
if self.bounds.contains(point) {
|
||||
return self.view
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, *)
|
||||
final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreviewing {
|
||||
weak var delegateImpl: UIViewControllerPreviewingDelegate?
|
||||
var delegate: UIViewControllerPreviewingDelegate {
|
||||
return self.delegateImpl!
|
||||
}
|
||||
let recognizer: PeekControllerGestureRecognizer
|
||||
var previewingGestureRecognizerForFailureRelationship: UIGestureRecognizer {
|
||||
return self.recognizer
|
||||
}
|
||||
let sourceView: UIView
|
||||
let node: ASDisplayNode
|
||||
|
||||
var sourceRect: CGRect = CGRect()
|
||||
|
||||
init(theme: PeekControllerTheme, delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, node: ASDisplayNode, present: @escaping (ViewController, Any?) -> Void) {
|
||||
self.delegateImpl = delegate
|
||||
self.sourceView = sourceView
|
||||
self.node = node
|
||||
var contentAtPointImpl: ((CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?)?
|
||||
self.recognizer = PeekControllerGestureRecognizer(contentAtPoint: { point in
|
||||
return contentAtPointImpl?(point)
|
||||
}, present: { content, sourceNode in
|
||||
let controller = PeekController(theme: theme, content: content, sourceNode: {
|
||||
return sourceNode
|
||||
})
|
||||
present(controller, nil)
|
||||
return controller
|
||||
})
|
||||
|
||||
node.view.addGestureRecognizer(self.recognizer)
|
||||
|
||||
super.init()
|
||||
|
||||
contentAtPointImpl = { [weak self] point in
|
||||
if let strongSelf = self, let delegate = strongSelf.delegateImpl {
|
||||
if let controller = delegate.previewingContext(strongSelf, viewControllerForLocation: point) as? ViewController {
|
||||
return .single((strongSelf.node, ViewControllerPeekContent(controller: controller)))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -162,7 +162,26 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container
|
||||
resolvedSafeInsets.right = 44.0
|
||||
}
|
||||
|
||||
return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil)
|
||||
var standardInputHeight: CGFloat = 216.0
|
||||
var predictiveHeight: CGFloat = 42.0
|
||||
|
||||
if layout.size.width.isEqual(to: 320.0) || layout.size.width.isEqual(to: 375.0) {
|
||||
standardInputHeight = 216.0
|
||||
predictiveHeight = 42.0
|
||||
} else if layout.size.width.isEqual(to: 414.0) {
|
||||
standardInputHeight = 226.0
|
||||
predictiveHeight = 42.0
|
||||
} else if layout.size.width.isEqual(to: 480.0) || layout.size.width.isEqual(to: 568.0) || layout.size.width.isEqual(to: 667.0) || layout.size.width.isEqual(to: 736.0) {
|
||||
standardInputHeight = 162.0
|
||||
predictiveHeight = 38.0
|
||||
} else if layout.size.width.isEqual(to: 768.0) || layout.size.width.isEqual(to: 1024.0) {
|
||||
standardInputHeight = 264.0
|
||||
predictiveHeight = 42.0
|
||||
}
|
||||
|
||||
standardInputHeight += predictiveHeight
|
||||
|
||||
return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(top: 0.0, left: 0.0, bottom: layout.onScreenNavigationHeight ?? 00, right: 0.0), safeInsets: resolvedSafeInsets, statusBarHeight: resolvedStatusBarHeight, inputHeight: updatedInputHeight, standardInputHeight: standardInputHeight, inputHeightIsInteractivellyChanging: layout.upperKeyboardInputPositionBound != nil && layout.upperKeyboardInputPositionBound != layout.size.height && layout.inputHeight != nil)
|
||||
}
|
||||
|
||||
private func encodeText(_ string: String, _ key: Int) -> String {
|
||||
@@ -250,6 +269,7 @@ public final class WindowHostView {
|
||||
let updatePreferNavigationUIHidden: (Bool) -> Void
|
||||
|
||||
var present: ((ViewController, PresentationSurfaceLevel) -> Void)?
|
||||
var presentInGlobalOverlay: ((_ controller: ViewController) -> Void)?
|
||||
var presentNative: ((UIViewController) -> Void)?
|
||||
var updateSize: ((CGSize) -> Void)?
|
||||
var layoutSubviews: (() -> Void)?
|
||||
@@ -276,6 +296,7 @@ public struct WindowTracingTags {
|
||||
|
||||
public protocol WindowHost {
|
||||
func present(_ controller: ViewController, on level: PresentationSurfaceLevel)
|
||||
func presentInGlobalOverlay(_ controller: ViewController)
|
||||
func invalidateDeferScreenEdgeGestures()
|
||||
func invalidatePreferNavigationUIHidden()
|
||||
func cancelInteractiveKeyboardGestures()
|
||||
@@ -324,6 +345,7 @@ public class Window1 {
|
||||
private var cachedHasPreview: Bool = false
|
||||
|
||||
private let presentationContext: PresentationContext
|
||||
private let overlayPresentationContext: GlobalOverlayPresentationContext
|
||||
|
||||
private var tracingStatusBarsInvalidated = false
|
||||
private var shouldUpdateDeferScreenEdgeGestures = false
|
||||
@@ -372,11 +394,16 @@ public class Window1 {
|
||||
|
||||
self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(boundsSize), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsetsForScreenSize(boundsSize), onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil)
|
||||
self.presentationContext = PresentationContext()
|
||||
self.overlayPresentationContext = GlobalOverlayPresentationContext(statusBarHost: statusBarHost)
|
||||
|
||||
self.hostView.present = { [weak self] controller, level in
|
||||
self?.present(controller, on: level)
|
||||
}
|
||||
|
||||
self.hostView.presentInGlobalOverlay = { [weak self] controller in
|
||||
self?.presentInGlobalOverlay(controller)
|
||||
}
|
||||
|
||||
self.hostView.presentNative = { [weak self] controller in
|
||||
self?.presentNative(controller)
|
||||
}
|
||||
@@ -415,6 +442,7 @@ public class Window1 {
|
||||
|
||||
self.presentationContext.view = self.hostView.view
|
||||
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate)
|
||||
self.overlayPresentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate)
|
||||
|
||||
self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in
|
||||
if let strongSelf = self {
|
||||
@@ -570,6 +598,10 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
|
||||
if let result = self.overlayPresentationContext.hitTest(point, with: event) {
|
||||
return result
|
||||
}
|
||||
|
||||
for controller in self._topLevelOverlayControllers.reversed() {
|
||||
if let result = controller.view.hitTest(point, with: event) {
|
||||
return result
|
||||
@@ -802,6 +834,7 @@ public class Window1 {
|
||||
if childLayoutUpdated {
|
||||
self._rootController?.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
self.overlayPresentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
|
||||
for controller in self.topLevelOverlayControllers {
|
||||
controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
@@ -833,6 +866,10 @@ public class Window1 {
|
||||
self.presentationContext.present(controller, on: level)
|
||||
}
|
||||
|
||||
public func presentInGlobalOverlay(_ controller: ViewController) {
|
||||
self.overlayPresentationContext.present(controller)
|
||||
}
|
||||
|
||||
public func presentNative(_ controller: UIViewController) {
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user