mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-22 14:20:20 +00:00
no message
This commit is contained in:
@@ -93,6 +93,7 @@
|
||||
D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; };
|
||||
D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; };
|
||||
D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; };
|
||||
D0BB4EBA1F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */; };
|
||||
D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */; };
|
||||
D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */; };
|
||||
D0C0B59D1EE022CC000F4D2C /* NavigationBarContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */; };
|
||||
@@ -113,7 +114,9 @@
|
||||
D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; };
|
||||
D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; };
|
||||
D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; };
|
||||
D0CB78901F9822F8004AB79B /* WindowPanRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */; };
|
||||
D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; };
|
||||
D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */; };
|
||||
D0CE8CE91F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */; };
|
||||
D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; };
|
||||
D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; };
|
||||
@@ -233,6 +236,7 @@
|
||||
D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GenerateImage.swift; sourceTree = "<group>"; };
|
||||
D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarTransitionState.swift; sourceTree = "<group>"; };
|
||||
D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBarProxyNode.swift; sourceTree = "<group>"; };
|
||||
D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowInputAccessoryHeightProvider.swift; sourceTree = "<group>"; };
|
||||
D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWindowHostView.swift; sourceTree = "<group>"; };
|
||||
D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetTextItem.swift; sourceTree = "<group>"; };
|
||||
D0C0B59C1EE022CC000F4D2C /* NavigationBarContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationBarContentNode.swift; sourceTree = "<group>"; };
|
||||
@@ -253,7 +257,9 @@
|
||||
D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = "<group>"; };
|
||||
D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = "<group>"; };
|
||||
D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = "<group>"; };
|
||||
D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WindowPanRecognizer.swift; sourceTree = "<group>"; };
|
||||
D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.swift; sourceTree = "<group>"; };
|
||||
D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActionSheetTheme.swift; sourceTree = "<group>"; };
|
||||
D0CE8CE81F6FC7EC00AA2DB0 /* NavigationBarTitleView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NavigationBarTitleView.swift; sourceTree = "<group>"; };
|
||||
D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UniversalTapRecognizer.swift; sourceTree = "<group>"; };
|
||||
D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertController.swift; sourceTree = "<group>"; };
|
||||
@@ -315,6 +321,8 @@
|
||||
D05CC2A11B69326C00E235A3 /* WindowContent.swift */,
|
||||
D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */,
|
||||
D087BFB41F75181D003FD209 /* ChildWindowHostView.swift */,
|
||||
D0BB4EB91F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift */,
|
||||
D0CB788F1F9822F8004AB79B /* WindowPanRecognizer.swift */,
|
||||
);
|
||||
name = Window;
|
||||
sourceTree = "<group>";
|
||||
@@ -342,6 +350,7 @@
|
||||
D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */,
|
||||
D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */,
|
||||
D0C0B5981EDF3BC9000F4D2C /* ActionSheetTextItem.swift */,
|
||||
D0CE67911F7DA11700FFB557 /* ActionSheetTheme.swift */,
|
||||
);
|
||||
name = "Action Sheet";
|
||||
sourceTree = "<group>";
|
||||
@@ -822,6 +831,7 @@
|
||||
D05CC2F81B6955D000E235A3 /* UIViewController+Navigation.m in Sources */,
|
||||
D0F1132F1D6F3C20008C3597 /* ContextMenuActionNode.swift in Sources */,
|
||||
D02BDB021B6AC703008AFAD2 /* RuntimeUtils.swift in Sources */,
|
||||
D0BB4EBA1F96DCC60036D9DE /* WindowInputAccessoryHeightProvider.swift in Sources */,
|
||||
D05CC31F1B695A9600E235A3 /* NavigationControllerProxy.m in Sources */,
|
||||
D05CC3031B69568600E235A3 /* NotificationCenterUtils.m in Sources */,
|
||||
D02958001D6F096000360E5E /* ContextMenuContainerNode.swift in Sources */,
|
||||
@@ -894,10 +904,12 @@
|
||||
D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */,
|
||||
D05CC3291B69750D00E235A3 /* InteractiveTransitionGestureRecognizer.swift in Sources */,
|
||||
D077B8E91F4637040046D27A /* NavigationBarBadge.swift in Sources */,
|
||||
D0CE67921F7DA11700FFB557 /* ActionSheetTheme.swift in Sources */,
|
||||
D05BE4AE1D217F6B002BD72C /* MergedLayoutEvents.swift in Sources */,
|
||||
D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */,
|
||||
D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */,
|
||||
D036574B1E71C44D00BB1EE4 /* MinimizeKeyboardGestureRecognizer.swift in Sources */,
|
||||
D0CB78901F9822F8004AB79B /* WindowPanRecognizer.swift in Sources */,
|
||||
D05CC2FE1B6955D000E235A3 /* UIWindow+OrientationChange.m in Sources */,
|
||||
D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */,
|
||||
D0C0B5991EDF3BC9000F4D2C /* ActionSheetTextItem.swift in Sources */,
|
||||
|
||||
@@ -20,8 +20,8 @@ public class ActionSheetButtonItem: ActionSheetItem {
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func node() -> ActionSheetItemNode {
|
||||
let node = ActionSheetButtonNode()
|
||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
let node = ActionSheetButtonNode(theme: theme)
|
||||
node.setItem(self)
|
||||
return node
|
||||
}
|
||||
@@ -37,6 +37,8 @@ public class ActionSheetButtonItem: ActionSheetItem {
|
||||
}
|
||||
|
||||
public class ActionSheetButtonNode: ActionSheetItemNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
public static let defaultFont: UIFont = Font.regular(20.0)
|
||||
|
||||
private var item: ActionSheetButtonItem?
|
||||
@@ -44,7 +46,9 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
|
||||
private let button: HighlightTrackingButton
|
||||
private let label: ASTextNode
|
||||
|
||||
override public init() {
|
||||
override public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.button = HighlightTrackingButton()
|
||||
|
||||
self.label = ASTextNode()
|
||||
@@ -52,7 +56,7 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
|
||||
self.label.maximumNumberOfLines = 1
|
||||
self.label.displaysAsynchronously = false
|
||||
|
||||
super.init()
|
||||
super.init(theme: theme)
|
||||
|
||||
self.view.addSubview(self.button)
|
||||
|
||||
@@ -62,10 +66,10 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
|
||||
self.button.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor
|
||||
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor
|
||||
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -80,11 +84,11 @@ public class ActionSheetButtonNode: ActionSheetItemNode {
|
||||
let textColor: UIColor
|
||||
switch item.color {
|
||||
case .accent:
|
||||
textColor = UIColor(rgb: 0x007ee5)
|
||||
textColor = self.theme.standardActionTextColor
|
||||
case .destructive:
|
||||
textColor = .red
|
||||
textColor = self.theme.destructiveActionTextColor
|
||||
case .disabled:
|
||||
textColor = .gray
|
||||
textColor = self.theme.disabledActionTextColor
|
||||
}
|
||||
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: textColor)
|
||||
|
||||
|
||||
@@ -1,16 +1,6 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(UIColor(rgb: 0x007ee5).cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.move(to: CGPoint(x: 12.0, y: 1.0))
|
||||
context.addLine(to: CGPoint(x: 4.16482734, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
|
||||
context.strokePath()
|
||||
})
|
||||
|
||||
public class ActionSheetCheckboxItem: ActionSheetItem {
|
||||
public let title: String
|
||||
public let label: String
|
||||
@@ -24,8 +14,8 @@ public class ActionSheetCheckboxItem: ActionSheetItem {
|
||||
self.action = action
|
||||
}
|
||||
|
||||
public func node() -> ActionSheetItemNode {
|
||||
let node = ActionSheetCheckboxItemNode()
|
||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
let node = ActionSheetCheckboxItemNode(theme: theme)
|
||||
node.setItem(self)
|
||||
return node
|
||||
}
|
||||
@@ -43,6 +33,8 @@ public class ActionSheetCheckboxItem: ActionSheetItem {
|
||||
public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
|
||||
public static let defaultFont: UIFont = Font.regular(20.0)
|
||||
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private var item: ActionSheetCheckboxItem?
|
||||
|
||||
private let button: HighlightTrackingButton
|
||||
@@ -50,7 +42,9 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
|
||||
private let labelNode: ASTextNode
|
||||
private let checkNode: ASImageNode
|
||||
|
||||
public override init() {
|
||||
override public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.button = HighlightTrackingButton()
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
@@ -67,9 +61,17 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
|
||||
self.checkNode.isUserInteractionEnabled = false
|
||||
self.checkNode.displayWithoutProcessing = true
|
||||
self.checkNode.displaysAsynchronously = false
|
||||
self.checkNode.image = checkIcon
|
||||
self.checkNode.image = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setStrokeColor(theme.controlAccentColor.cgColor)
|
||||
context.setLineWidth(2.0)
|
||||
context.move(to: CGPoint(x: 12.0, y: 1.0))
|
||||
context.addLine(to: CGPoint(x: 4.16482734, y: 9.0))
|
||||
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
|
||||
context.strokePath()
|
||||
})
|
||||
|
||||
super.init()
|
||||
super.init(theme: theme)
|
||||
|
||||
self.view.addSubview(self.button)
|
||||
self.addSubnode(self.titleNode)
|
||||
@@ -79,10 +81,10 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
|
||||
self.button.highligthedChanged = { [weak self] highlighted in
|
||||
if let strongSelf = self {
|
||||
if highlighted {
|
||||
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor
|
||||
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
|
||||
} else {
|
||||
UIView.animate(withDuration: 0.3, animations: {
|
||||
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor
|
||||
strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -94,8 +96,8 @@ public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
|
||||
func setItem(_ item: ActionSheetCheckboxItem) {
|
||||
self.item = item
|
||||
|
||||
self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: .black)
|
||||
self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: UIColor(rgb: 0x8e8e93))
|
||||
self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.primaryTextColor)
|
||||
self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: self.theme.secondaryTextColor)
|
||||
self.checkNode.isHidden = !item.value
|
||||
|
||||
self.setNeedsLayout()
|
||||
|
||||
@@ -5,9 +5,13 @@ open class ActionSheetController: ViewController {
|
||||
return self.displayNode as! ActionSheetControllerNode
|
||||
}
|
||||
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private var groups: [ActionSheetItemGroup] = []
|
||||
|
||||
public init() {
|
||||
public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
super.init(navigationBarTheme: nil)
|
||||
}
|
||||
|
||||
@@ -20,7 +24,7 @@ open class ActionSheetController: ViewController {
|
||||
}
|
||||
|
||||
open override func loadDisplayNode() {
|
||||
self.displayNode = ActionSheetControllerNode()
|
||||
self.displayNode = ActionSheetControllerNode(theme: self.theme)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.actionSheetNode.dismiss = { [weak self] in
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
private let containerInsets = UIEdgeInsets(top: 16.0, left: 16.0, bottom: 8.0, right: 16.0)
|
||||
private let containerInsets = UIEdgeInsets(top: 10.0, left: 10.0, bottom: 10.0, right: 10.0)
|
||||
|
||||
private class ActionSheetControllerNodeScrollView: UIScrollView {
|
||||
override func touchesShouldCancel(in view: UIView) -> Bool {
|
||||
@@ -10,7 +10,7 @@ private class ActionSheetControllerNodeScrollView: UIScrollView {
|
||||
}
|
||||
|
||||
final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
static let dimColor: UIColor = UIColor(white: 0.0, alpha: 0.4)
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private let dismissTapView: UIView
|
||||
|
||||
@@ -25,7 +25,9 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var dismiss: () -> Void = { }
|
||||
|
||||
override init() {
|
||||
init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.scrollView = ActionSheetControllerNodeScrollView()
|
||||
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
@@ -38,22 +40,22 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
self.dismissTapView = UIView()
|
||||
|
||||
self.leftDimView = UIView()
|
||||
self.leftDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.leftDimView.backgroundColor = self.theme.dimColor
|
||||
self.leftDimView.isUserInteractionEnabled = false
|
||||
|
||||
self.rightDimView = UIView()
|
||||
self.rightDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.rightDimView.backgroundColor = self.theme.dimColor
|
||||
self.rightDimView.isUserInteractionEnabled = false
|
||||
|
||||
self.topDimView = UIView()
|
||||
self.topDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.topDimView.backgroundColor = self.theme.dimColor
|
||||
self.topDimView.isUserInteractionEnabled = false
|
||||
|
||||
self.bottomDimView = UIView()
|
||||
self.bottomDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.bottomDimView.backgroundColor = self.theme.dimColor
|
||||
self.bottomDimView.isUserInteractionEnabled = false
|
||||
|
||||
self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode()
|
||||
self.itemGroupsContainerNode = ActionSheetItemGroupsContainerNode(theme: self.theme)
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -74,7 +76,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
var insets = layout.insets(options: [.statusBar])
|
||||
let insets = layout.insets(options: [.statusBar])
|
||||
|
||||
self.scrollView.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
self.dismissTapView.frame = CGRect(origin: CGPoint(), size: layout.size)
|
||||
@@ -88,7 +90,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
func animateIn() {
|
||||
let tempDimView = UIView()
|
||||
tempDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
tempDimView.backgroundColor = self.theme.dimColor
|
||||
tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height)
|
||||
self.view.addSubview(tempDimView)
|
||||
|
||||
@@ -105,7 +107,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
func animateOut() {
|
||||
let tempDimView = UIView()
|
||||
tempDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
tempDimView.backgroundColor = self.theme.dimColor
|
||||
tempDimView.frame = self.bounds.offsetBy(dx: 0.0, dy: -self.bounds.size.height)
|
||||
self.view.addSubview(tempDimView)
|
||||
|
||||
@@ -138,7 +140,7 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
|
||||
let contentOffset = self.scrollView.contentOffset
|
||||
var additionalTopHeight = max(0.0, -contentOffset.y)
|
||||
let additionalTopHeight = max(0.0, -contentOffset.y)
|
||||
|
||||
if additionalTopHeight >= 30.0 {
|
||||
self.animateOut()
|
||||
@@ -146,8 +148,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
}
|
||||
|
||||
func updateScrollDimViews(size: CGSize) {
|
||||
var additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y)
|
||||
var additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y)
|
||||
let additionalTopHeight = max(0.0, -self.scrollView.contentOffset.y)
|
||||
let additionalBottomHeight = -min(0.0, -self.scrollView.contentOffset.y)
|
||||
|
||||
self.topDimView.frame = CGRect(x: containerInsets.left, y: -additionalTopHeight, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, self.itemGroupsContainerNode.frame.minY + additionalTopHeight))
|
||||
self.bottomDimView.frame = CGRect(x: containerInsets.left, y: self.itemGroupsContainerNode.frame.maxY, width: size.width - containerInsets.left - containerInsets.right, height: max(0.0, size.height - self.itemGroupsContainerNode.frame.maxY + additionalBottomHeight))
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import Foundation
|
||||
|
||||
public protocol ActionSheetItem {
|
||||
func node() -> ActionSheetItemNode
|
||||
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode
|
||||
func updateNode(_ node: ActionSheetItemNode) -> Void
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ private class ActionSheetItemGroupNodeScrollView: UIScrollView {
|
||||
}
|
||||
|
||||
final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private let centerDimView: UIImageView
|
||||
private let topDimView: UIView
|
||||
private let bottomDimView: UIView
|
||||
@@ -22,26 +24,28 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
|
||||
var respectInputHeight = true
|
||||
|
||||
override init() {
|
||||
init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.centerDimView = UIImageView()
|
||||
self.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: ActionSheetControllerNode.dimColor)
|
||||
self.centerDimView.image = generateStretchableFilledCircleImage(radius: 16.0, color: nil, backgroundColor: self.theme.dimColor)
|
||||
|
||||
self.topDimView = UIView()
|
||||
self.topDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.topDimView.backgroundColor = self.theme.dimColor
|
||||
self.topDimView.isUserInteractionEnabled = false
|
||||
|
||||
self.bottomDimView = UIView()
|
||||
self.bottomDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.bottomDimView.backgroundColor = self.theme.dimColor
|
||||
self.bottomDimView.isUserInteractionEnabled = false
|
||||
|
||||
self.trailingDimView = UIView()
|
||||
self.trailingDimView.backgroundColor = ActionSheetControllerNode.dimColor
|
||||
self.trailingDimView.backgroundColor = self.theme.dimColor
|
||||
|
||||
self.clippingNode = ASDisplayNode()
|
||||
self.clippingNode.clipsToBounds = true
|
||||
self.clippingNode.cornerRadius = 16.0
|
||||
|
||||
self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||
self.backgroundEffectView = UIVisualEffectView(effect: UIBlurEffect(style: self.theme.backgroundType == .light ? .light : .dark))
|
||||
|
||||
self.scrollView = ActionSheetItemGroupNodeScrollView()
|
||||
if #available(iOSApplicationExtension 11.0, *) {
|
||||
@@ -107,7 +111,7 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
|
||||
i += 1
|
||||
}
|
||||
|
||||
return CGSize(width: constrainedSize.width, height: min(itemNodesHeight, constrainedSize.height))
|
||||
return CGSize(width: constrainedSize.width, height: min(floorToScreenPixels(itemNodesHeight), constrainedSize.height))
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
|
||||
@@ -1,13 +1,17 @@
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
private let groupSpacing: CGFloat = 16.0
|
||||
private let groupSpacing: CGFloat = 8.0
|
||||
|
||||
final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private var groups: [ActionSheetItemGroup] = []
|
||||
private var groupNodes: [ActionSheetItemGroupNode] = []
|
||||
|
||||
override init() {
|
||||
init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
@@ -20,8 +24,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
|
||||
self.groupNodes.removeAll()
|
||||
|
||||
for group in groups {
|
||||
let groupNode = ActionSheetItemGroupNode()
|
||||
groupNode.updateItemNodes(group.items.map({ $0.node() }), leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0)
|
||||
let groupNode = ActionSheetItemGroupNode(theme: self.theme)
|
||||
groupNode.updateItemNodes(group.items.map({ $0.node(theme: self.theme) }), leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0)
|
||||
self.groupNodes.append(groupNode)
|
||||
self.addSubnode(groupNode)
|
||||
}
|
||||
|
||||
@@ -2,18 +2,19 @@ import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
open class ActionSheetItemNode: ASDisplayNode {
|
||||
public static let defaultBackgroundColor: UIColor = UIColor(white: 1.0, alpha: 0.8)
|
||||
public static let highlightedBackgroundColor: UIColor = UIColor(white: 0.9, alpha: 0.7)
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
public let backgroundNode: ASDisplayNode
|
||||
private let overflowSeparatorNode: ASDisplayNode
|
||||
|
||||
public override init() {
|
||||
public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor
|
||||
self.backgroundNode.backgroundColor = self.theme.itemBackgroundColor
|
||||
|
||||
self.overflowSeparatorNode = ASDisplayNode()
|
||||
self.overflowSeparatorNode.backgroundColor = UIColor(white: 0.5, alpha: 0.3)
|
||||
self.overflowSeparatorNode.backgroundColor = self.theme.itemHighlightedBackgroundColor
|
||||
|
||||
super.init()
|
||||
|
||||
|
||||
@@ -8,8 +8,8 @@ public class ActionSheetTextItem: ActionSheetItem {
|
||||
self.title = title
|
||||
}
|
||||
|
||||
public func node() -> ActionSheetItemNode {
|
||||
let node = ActionSheetTextNode()
|
||||
public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
|
||||
let node = ActionSheetTextNode(theme: theme)
|
||||
node.setItem(self)
|
||||
return node
|
||||
}
|
||||
@@ -27,18 +27,22 @@ public class ActionSheetTextItem: ActionSheetItem {
|
||||
public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
public static let defaultFont: UIFont = Font.regular(13.0)
|
||||
|
||||
private let theme: ActionSheetControllerTheme
|
||||
|
||||
private var item: ActionSheetTextItem?
|
||||
|
||||
private let label: ASTextNode
|
||||
|
||||
override public init() {
|
||||
override public init(theme: ActionSheetControllerTheme) {
|
||||
self.theme = theme
|
||||
|
||||
self.label = ASTextNode()
|
||||
self.label.isLayerBacked = true
|
||||
self.label.maximumNumberOfLines = 1
|
||||
self.label.displaysAsynchronously = false
|
||||
self.label.truncationMode = .byTruncatingTail
|
||||
|
||||
super.init()
|
||||
super.init(theme: theme)
|
||||
|
||||
self.label.isUserInteractionEnabled = false
|
||||
self.addSubnode(self.label)
|
||||
@@ -47,9 +51,7 @@ public class ActionSheetTextNode: ActionSheetItemNode {
|
||||
func setItem(_ item: ActionSheetTextItem) {
|
||||
self.item = item
|
||||
|
||||
let textColor = UIColor(rgb: 0x7c7c7c)
|
||||
|
||||
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: textColor)
|
||||
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor)
|
||||
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
|
||||
33
Display/ActionSheetTheme.swift
Normal file
33
Display/ActionSheetTheme.swift
Normal file
@@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public enum ActionSheetControllerThemeBackgroundType {
|
||||
case light
|
||||
case dark
|
||||
}
|
||||
|
||||
public final class ActionSheetControllerTheme {
|
||||
public let dimColor: UIColor
|
||||
public let backgroundType: ActionSheetControllerThemeBackgroundType
|
||||
public let itemBackgroundColor: UIColor
|
||||
public let itemHighlightedBackgroundColor: UIColor
|
||||
public let standardActionTextColor: UIColor
|
||||
public let destructiveActionTextColor: UIColor
|
||||
public let disabledActionTextColor: UIColor
|
||||
public let primaryTextColor: UIColor
|
||||
public let secondaryTextColor: UIColor
|
||||
public let controlAccentColor: UIColor
|
||||
|
||||
public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor) {
|
||||
self.dimColor = dimColor
|
||||
self.backgroundType = backgroundType
|
||||
self.itemBackgroundColor = itemBackgroundColor
|
||||
self.itemHighlightedBackgroundColor = itemHighlightedBackgroundColor
|
||||
self.standardActionTextColor = standardActionTextColor
|
||||
self.destructiveActionTextColor = destructiveActionTextColor
|
||||
self.disabledActionTextColor = disabledActionTextColor
|
||||
self.primaryTextColor = primaryTextColor
|
||||
self.secondaryTextColor = secondaryTextColor
|
||||
self.controlAccentColor = controlAccentColor
|
||||
}
|
||||
}
|
||||
@@ -39,7 +39,7 @@ public extension CAAnimation {
|
||||
}
|
||||
|
||||
public extension CALayer {
|
||||
public func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation {
|
||||
public func makeAnimation(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) -> CAAnimation {
|
||||
if timingFunction == kCAMediaTimingFunctionSpring {
|
||||
let animation = makeSpringAnimation(keyPath)
|
||||
animation.fromValue = from
|
||||
@@ -59,6 +59,11 @@ public extension CALayer {
|
||||
animation.speed = speed * Float(animation.duration / duration)
|
||||
animation.isAdditive = additive
|
||||
|
||||
if !delay.isZero {
|
||||
animation.beginTime = CACurrentMediaTime() + delay
|
||||
animation.fillMode = kCAFillModeBoth
|
||||
}
|
||||
|
||||
return animation
|
||||
} else {
|
||||
let k = Float(UIView.animationDurationFactor())
|
||||
@@ -84,12 +89,17 @@ public extension CALayer {
|
||||
animation.delegate = CALayerAnimationDelegate(completion: completion)
|
||||
}
|
||||
|
||||
if !delay.isZero {
|
||||
animation.beginTime = CACurrentMediaTime() + delay
|
||||
animation.fillMode = kCAFillModeBoth
|
||||
}
|
||||
|
||||
return animation
|
||||
}
|
||||
}
|
||||
|
||||
public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
public func animate(from: AnyObject, to: AnyObject, keyPath: String, timingFunction: String, duration: Double, delay: Double = 0.0, mediaTimingFunction: CAMediaTimingFunction? = nil, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let animation = self.makeAnimation(from: from, to: to, keyPath: keyPath, timingFunction: timingFunction, duration: duration, delay: delay, mediaTimingFunction: mediaTimingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
|
||||
self.add(animation, forKey: additive ? nil : keyPath)
|
||||
}
|
||||
|
||||
@@ -184,8 +194,8 @@ public extension CALayer {
|
||||
self.add(animation, forKey: key)
|
||||
}
|
||||
|
||||
public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion)
|
||||
public func animateAlpha(from: CGFloat, to: CGFloat, duration: Double, delay: Double = 0.0, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> ())? = nil) {
|
||||
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "opacity", timingFunction: timingFunction, duration: duration, delay: delay, removeOnCompletion: removeOnCompletion, completion: completion)
|
||||
}
|
||||
|
||||
public func animateScale(from: CGFloat, to: CGFloat, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
|
||||
@@ -62,8 +62,8 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateBounds(node: ASDisplayNode, bounds: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.bounds.equalTo(bounds) {
|
||||
func updateBounds(node: ASDisplayNode, bounds: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if node.bounds.equalTo(bounds) && !force {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
@@ -75,7 +75,7 @@ public extension ContainedViewLayoutTransition {
|
||||
case let .animated(duration, curve):
|
||||
let previousBounds = node.bounds
|
||||
node.bounds = bounds
|
||||
node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, completion: { result in
|
||||
node.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
@@ -140,6 +140,21 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func animateFrame(node: ASDisplayNode, from frame: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
node.layer.animateFrame(from: frame, to: node.layer.frame, duration: duration, timingFunction: curve.timingFunction, removeOnCompletion: removeOnCompletion, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func animateBounds(layer: CALayer, from bounds: CGRect, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
@@ -171,10 +186,10 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func animateOffsetAdditive(layer: CALayer, offset: CGFloat) {
|
||||
func animateOffsetAdditive(layer: CALayer, offset: CGFloat, completion: (() -> Void)? = nil) {
|
||||
switch self {
|
||||
case .immediate:
|
||||
break
|
||||
completion?()
|
||||
case let .animated(duration, curve):
|
||||
let timingFunction: String
|
||||
switch curve {
|
||||
@@ -183,7 +198,9 @@ public extension ContainedViewLayoutTransition {
|
||||
case .spring:
|
||||
timingFunction = kCAMediaTimingFunctionSpring
|
||||
}
|
||||
layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction)
|
||||
layer.animateBoundsOriginYAdditive(from: offset, to: 0.0, duration: duration, timingFunction: timingFunction, completion: { _ in
|
||||
completion?()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,6 +220,28 @@ public extension ContainedViewLayoutTransition {
|
||||
}
|
||||
}
|
||||
|
||||
func updateFrame(view: UIView, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if view.frame.equalTo(frame) && !force {
|
||||
completion?(true)
|
||||
} else {
|
||||
switch self {
|
||||
case .immediate:
|
||||
view.frame = frame
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
let previousFrame = view.frame
|
||||
view.frame = frame
|
||||
view.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func updateFrame(layer: CALayer, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
|
||||
if layer.frame.equalTo(frame) {
|
||||
completion?(true)
|
||||
@@ -331,6 +370,34 @@ public extension ContainedViewLayoutTransition {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func updateSublayerTransformOffset(layer: CALayer, offset: CGPoint, completion: ((Bool) -> Void)? = nil) {
|
||||
print("update to \(offset) animated: \(self.isAnimated)")
|
||||
let t = layer.transform
|
||||
let currentOffset = CGPoint(x: t.m41, y: t.m42)
|
||||
if currentOffset == offset {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0)
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
layer.sublayerTransform = CATransform3DMakeTranslation(offset.x, offset.y, 0.0)
|
||||
layer.animate(from: NSValue(caTransform3D: t), to: NSValue(caTransform3D: layer.sublayerTransform), keyPath: "sublayerTransform", timingFunction: curve.timingFunction, duration: duration, delay: 0.0, mediaTimingFunction: nil, removeOnCompletion: true, additive: false, completion: {
|
||||
result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public extension ContainedViewLayoutTransition {
|
||||
|
||||
@@ -42,23 +42,29 @@ public struct ContainerViewLayout: Equatable {
|
||||
public let size: CGSize
|
||||
public let metrics: LayoutMetrics
|
||||
public let intrinsicInsets: UIEdgeInsets
|
||||
public let safeInsets: UIEdgeInsets
|
||||
public let statusBarHeight: CGFloat?
|
||||
public let inputHeight: 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, statusBarHeight: CGFloat?, inputHeight: CGFloat?) {
|
||||
public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, inputHeightIsInteractivellyChanging: Bool) {
|
||||
self.size = size
|
||||
self.metrics = metrics
|
||||
self.intrinsicInsets = intrinsicInsets
|
||||
self.safeInsets = safeInsets
|
||||
self.statusBarHeight = statusBarHeight
|
||||
self.inputHeight = inputHeight
|
||||
self.inputHeightIsInteractivellyChanging = inputHeightIsInteractivellyChanging
|
||||
}
|
||||
|
||||
public func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets {
|
||||
@@ -73,19 +79,18 @@ 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), statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight)
|
||||
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)
|
||||
}
|
||||
|
||||
public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout {
|
||||
return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight)
|
||||
return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
}
|
||||
|
||||
public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout {
|
||||
return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight)
|
||||
}
|
||||
return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging)
|
||||
}
|
||||
|
||||
public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool {
|
||||
public static func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool {
|
||||
if !lhs.size.equalTo(rhs.size) {
|
||||
return false
|
||||
}
|
||||
@@ -98,6 +103,10 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.safeInsets != rhs.safeInsets {
|
||||
return false
|
||||
}
|
||||
|
||||
if let lhsStatusBarHeight = lhs.statusBarHeight {
|
||||
if let rhsStatusBarHeight = rhs.statusBarHeight {
|
||||
if !lhsStatusBarHeight.isEqual(to: rhsStatusBarHeight) {
|
||||
@@ -122,5 +131,10 @@ public func ==(lhs: ContainerViewLayout, rhs: ContainerViewLayout) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.inputHeightIsInteractivellyChanging != rhs.inputHeightIsInteractivellyChanging {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,11 +2,139 @@ import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
private func generateShadowImage() -> UIImage? {
|
||||
return generateImage(CGSize(width: 30.0, height: 1.0), rotatedContext: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setShadow(offset: CGSize(), blur: 10.0, color: UIColor(white: 0.18, alpha: 1.0).cgColor)
|
||||
context.setFillColor(UIColor(white: 0.18, alpha: 1.0).cgColor)
|
||||
context.fill(CGRect(origin: CGPoint(x: -15.0, y: 0.0), size: CGSize(width: 30.0, height: 1.0)))
|
||||
})
|
||||
}
|
||||
|
||||
private final class ContextMenuContentScrollNode: ASDisplayNode {
|
||||
var contentWidth: CGFloat = 0.0
|
||||
|
||||
private var initialOffset: CGFloat = 0.0
|
||||
|
||||
private let leftShadow: ASImageNode
|
||||
private let rightShadow: ASImageNode
|
||||
private let leftOverscrollNode: ASDisplayNode
|
||||
private let rightOverscrollNode: ASDisplayNode
|
||||
let contentNode: ASDisplayNode
|
||||
|
||||
override init() {
|
||||
self.contentNode = ASDisplayNode()
|
||||
|
||||
let shadowImage = generateShadowImage()
|
||||
|
||||
self.leftShadow = ASImageNode()
|
||||
self.leftShadow.displayWithoutProcessing = true
|
||||
self.leftShadow.displaysAsynchronously = false
|
||||
self.leftShadow.image = shadowImage
|
||||
self.rightShadow = ASImageNode()
|
||||
self.rightShadow.displayWithoutProcessing = true
|
||||
self.rightShadow.displaysAsynchronously = false
|
||||
self.rightShadow.image = shadowImage
|
||||
self.rightShadow.transform = CATransform3DMakeScale(-1.0, 1.0, 1.0)
|
||||
|
||||
self.leftOverscrollNode = ASDisplayNode()
|
||||
self.leftOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
self.rightOverscrollNode = ASDisplayNode()
|
||||
self.rightOverscrollNode.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
|
||||
|
||||
super.init()
|
||||
|
||||
self.contentNode.addSubnode(self.leftOverscrollNode)
|
||||
self.contentNode.addSubnode(self.rightOverscrollNode)
|
||||
self.addSubnode(self.contentNode)
|
||||
|
||||
self.addSubnode(self.leftShadow)
|
||||
self.addSubnode(self.rightShadow)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
let panRecognizer = UIPanGestureRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
self.view.addGestureRecognizer(panRecognizer)
|
||||
}
|
||||
|
||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
self.initialOffset = self.contentNode.bounds.origin.x
|
||||
case .changed:
|
||||
var bounds = self.contentNode.bounds
|
||||
bounds.origin.x = self.initialOffset - recognizer.translation(in: self.view).x
|
||||
if bounds.origin.x > self.contentWidth - bounds.size.width {
|
||||
let delta = bounds.origin.x - (self.contentWidth - bounds.size.width)
|
||||
bounds.origin.x = self.contentWidth - bounds.size.width + ((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0)
|
||||
}
|
||||
if bounds.origin.x < 0.0 {
|
||||
let delta = -bounds.origin.x
|
||||
bounds.origin.x = -((1.0 - (1.0 / (((delta) * 0.55 / (50.0)) + 1.0))) * 50.0)
|
||||
}
|
||||
self.contentNode.bounds = bounds
|
||||
self.updateShadows(.immediate)
|
||||
case .ended, .cancelled:
|
||||
var bounds = self.contentNode.bounds
|
||||
bounds.origin.x = self.initialOffset - recognizer.translation(in: self.view).x
|
||||
|
||||
var duration = 0.4
|
||||
|
||||
if abs(bounds.origin.x - self.initialOffset) > 10.0 || abs(recognizer.velocity(in: self.view).x) > 100.0 {
|
||||
duration = 0.2
|
||||
if bounds.origin.x < self.initialOffset {
|
||||
bounds.origin.x = 0.0
|
||||
} else {
|
||||
bounds.origin.x = self.contentWidth - bounds.size.width
|
||||
}
|
||||
} else {
|
||||
bounds.origin.x = self.initialOffset
|
||||
}
|
||||
|
||||
if bounds.origin.x > self.contentWidth - bounds.size.width {
|
||||
bounds.origin.x = self.contentWidth - bounds.size.width
|
||||
}
|
||||
if bounds.origin.x < 0.0 {
|
||||
bounds.origin.x = 0.0
|
||||
}
|
||||
let previousBounds = self.contentNode.bounds
|
||||
self.contentNode.bounds = bounds
|
||||
self.contentNode.layer.animateBounds(from: previousBounds, to: bounds, duration: duration, timingFunction: kCAMediaTimingFunctionSpring)
|
||||
self.updateShadows(.animated(duration: duration, curve: .spring))
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
let bounds = self.bounds
|
||||
self.contentNode.frame = bounds
|
||||
self.leftShadow.frame = CGRect(origin: CGPoint(), size: CGSize(width: 30.0, height: bounds.height))
|
||||
self.rightShadow.frame = CGRect(origin: CGPoint(x: bounds.size.width - 30.0, y: 0.0), size: CGSize(width: 30.0, height: bounds.height))
|
||||
self.leftOverscrollNode.frame = bounds.offsetBy(dx: -bounds.width, dy: 0.0)
|
||||
self.rightOverscrollNode.frame = bounds.offsetBy(dx: self.contentWidth, dy: 0.0)
|
||||
self.updateShadows(.immediate)
|
||||
}
|
||||
|
||||
private func updateShadows(_ transition: ContainedViewLayoutTransition) {
|
||||
let bounds = self.contentNode.bounds
|
||||
|
||||
let leftAlpha = max(0.0, min(1.0, bounds.minX / 20.0))
|
||||
transition.updateAlpha(node: self.leftShadow, alpha: leftAlpha)
|
||||
|
||||
let rightAlpha = max(0.0, min(1.0, (self.contentWidth - bounds.maxX) / 20.0))
|
||||
transition.updateAlpha(node: self.rightShadow, alpha: rightAlpha)
|
||||
}
|
||||
}
|
||||
|
||||
final class ContextMenuNode: ASDisplayNode {
|
||||
private let actions: [ContextMenuAction]
|
||||
private let dismiss: () -> Void
|
||||
|
||||
private let containerNode: ContextMenuContainerNode
|
||||
private let scrollNode: ContextMenuContentScrollNode
|
||||
private let actionNodes: [ContextMenuActionNode]
|
||||
|
||||
var sourceRect: CGRect?
|
||||
@@ -19,6 +147,7 @@ final class ContextMenuNode: ASDisplayNode {
|
||||
self.dismiss = dismiss
|
||||
|
||||
self.containerNode = ContextMenuContainerNode()
|
||||
self.scrollNode = ContextMenuContentScrollNode()
|
||||
|
||||
self.actionNodes = actions.map { action in
|
||||
return ContextMenuActionNode(action: action)
|
||||
@@ -26,28 +155,33 @@ final class ContextMenuNode: ASDisplayNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.containerNode.addSubnode(self.scrollNode)
|
||||
|
||||
self.addSubnode(self.containerNode)
|
||||
let dismissNode = {
|
||||
dismiss()
|
||||
}
|
||||
for actionNode in self.actionNodes {
|
||||
actionNode.dismiss = dismissNode
|
||||
self.containerNode.addSubnode(actionNode)
|
||||
self.scrollNode.contentNode.addSubnode(actionNode)
|
||||
}
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
var actionsWidth: CGFloat = 0.0
|
||||
var unboundActionsWidth: CGFloat = 0.0
|
||||
let actionSeparatorWidth: CGFloat = UIScreenPixel
|
||||
for actionNode in self.actionNodes {
|
||||
if !actionsWidth.isZero {
|
||||
actionsWidth += actionSeparatorWidth
|
||||
if !unboundActionsWidth.isZero {
|
||||
unboundActionsWidth += actionSeparatorWidth
|
||||
}
|
||||
let actionSize = actionNode.measure(CGSize(width: layout.size.width, height: 54.0))
|
||||
actionNode.frame = CGRect(origin: CGPoint(x: actionsWidth, y: 0.0), size: actionSize)
|
||||
actionsWidth += actionSize.width
|
||||
actionNode.frame = CGRect(origin: CGPoint(x: unboundActionsWidth, y: 0.0), size: actionSize)
|
||||
unboundActionsWidth += actionSize.width
|
||||
}
|
||||
|
||||
let maxActionsWidth = layout.size.width - 20.0
|
||||
let actionsWidth = min(unboundActionsWidth, maxActionsWidth)
|
||||
|
||||
let sourceRect: CGRect = self.sourceRect ?? CGRect(origin: CGPoint(x: layout.size.width / 2.0, y: layout.size.height / 2.0), size: CGSize())
|
||||
|
||||
let insets = layout.insets(options: [.statusBar, .input])
|
||||
@@ -67,7 +201,11 @@ final class ContextMenuNode: ASDisplayNode {
|
||||
self.containerNode.frame = CGRect(origin: CGPoint(x: horizontalOrigin, y: verticalOrigin), size: CGSize(width: actionsWidth, height: 54.0))
|
||||
self.containerNode.relativeArrowPosition = (sourceRect.midX - horizontalOrigin, arrowOnBottom)
|
||||
|
||||
self.scrollNode.frame = CGRect(origin: CGPoint(), size: CGSize(width: actionsWidth, height: 54.0))
|
||||
self.scrollNode.contentWidth = unboundActionsWidth
|
||||
|
||||
self.containerNode.layout()
|
||||
self.scrollNode.layout()
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
|
||||
@@ -300,6 +300,9 @@ public class DrawingContext {
|
||||
}
|
||||
|
||||
public func generateImage() -> UIImage? {
|
||||
if self.scaledSize.width.isZero || self.scaledSize.height.isZero {
|
||||
return nil
|
||||
}
|
||||
if let image = CGImage(width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bitsPerPixel: 32, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo, provider: provider!, decode: nil, shouldInterpolate: false, intent: .defaultIntent) {
|
||||
return UIImage(cgImage: image, scale: scale, orientation: .up)
|
||||
} else {
|
||||
|
||||
@@ -373,16 +373,21 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
|
||||
self.itemLayout = self.generateItemLayout()
|
||||
|
||||
var updateLayoutTransition = transaction.updateLayout?.transition
|
||||
|
||||
let generatedScrollToItem: GridNodeScrollToItem?
|
||||
if let scrollToItem = transaction.scrollToItem {
|
||||
generatedScrollToItem = scrollToItem
|
||||
if updateLayoutTransition == nil {
|
||||
updateLayoutTransition = scrollToItem.transition
|
||||
}
|
||||
} else if previousLayoutWasEmpty {
|
||||
generatedScrollToItem = GridNodeScrollToItem(index: 0, position: .top, transition: .immediate, directionHint: .up, adjustForSection: true, adjustForTopInset: true)
|
||||
} else {
|
||||
generatedScrollToItem = nil
|
||||
}
|
||||
|
||||
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, itemTransition: transaction.itemTransition, completion: completion)
|
||||
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: updateLayoutTransition, customScrollToItem: transaction.scrollToItem != nil, itemTransition: transaction.itemTransition, completion: completion)
|
||||
}
|
||||
|
||||
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
||||
@@ -401,7 +406,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
|
||||
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
|
||||
if !self.applyingContentOffset {
|
||||
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, itemTransition: .immediate, completion: { _ in })
|
||||
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, customScrollToItem: false, itemTransition: .immediate, completion: { _ in })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -764,7 +769,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
return lowestHeaderNode
|
||||
}
|
||||
|
||||
private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) {
|
||||
private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, customScrollToItem: Bool, itemTransition: ContainedViewLayoutTransition, completion: (GridNodeDisplayedItemRange) -> Void) {
|
||||
let boundsTransition: ContainedViewLayoutTransition = updateLayoutTransition ?? .immediate
|
||||
|
||||
var previousItemFrames: [WrappedGridItemNode: CGRect]?
|
||||
@@ -806,14 +811,14 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
self.scrollView.scrollIndicatorInsets = presentationLayoutTransition.layout.layout.insets
|
||||
}
|
||||
var boundsOffset: CGFloat = 0.0
|
||||
var shouldAnimateBounds = false
|
||||
if !self.scrollView.contentOffset.equalTo(presentationLayoutTransition.layout.contentOffset) || self.bounds.size != presentationLayoutTransition.layout.layout.size {
|
||||
let updatedBounds = CGRect(origin: presentationLayoutTransition.layout.contentOffset, size: presentationLayoutTransition.layout.layout.size)
|
||||
boundsOffset = updatedBounds.origin.y - previousBounds.origin.y
|
||||
self.bounds = updatedBounds
|
||||
//boundsTransition.animateOffsetAdditive(layer: self.layer, offset: -boundsOffset - insetsOffset)
|
||||
boundsTransition.animateBounds(layer: self.layer, from: previousBounds)
|
||||
shouldAnimateBounds = true
|
||||
}
|
||||
applyingContentOffset = false
|
||||
self.applyingContentOffset = false
|
||||
|
||||
let lowestSectionNode: ASDisplayNode? = self.lowestSectionNode()
|
||||
|
||||
@@ -860,6 +865,9 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
if let previousItemFrames = previousItemFrames, case let .animated(duration, curve) = presentationLayoutTransition.transition {
|
||||
let contentOffset = presentationLayoutTransition.layout.contentOffset
|
||||
|
||||
boundsOffset = 0.0
|
||||
shouldAnimateBounds = false
|
||||
|
||||
var offset: CGFloat?
|
||||
for (index, itemNode) in self.itemNodes {
|
||||
if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)], existingItemIndices.contains(index) {
|
||||
@@ -934,7 +942,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] {
|
||||
self.removeItemNodeWithIndex(index, removeNode: false)
|
||||
let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY)
|
||||
itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak itemNode] _ in
|
||||
itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in
|
||||
itemNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
@@ -946,7 +954,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
for itemNode in removedNodes {
|
||||
if let previousFrame = previousItemFrames[WrappedGridItemNode(node: itemNode)] {
|
||||
let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY)
|
||||
itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak itemNode] _ in
|
||||
itemNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak itemNode] _ in
|
||||
itemNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
@@ -960,7 +968,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
if let previousFrame = previousItemFrames[WrappedGridItemNode(node: sectionNode)] {
|
||||
self.removeSectionNodeWithSection(wrappedSection, removeNode: false)
|
||||
let position = CGPoint(x: previousFrame.midX, y: previousFrame.midY)
|
||||
sectionNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, completion: { [weak sectionNode] _ in
|
||||
sectionNode.layer.animatePosition(from: CGPoint(x: position.x, y: position.y + contentOffset.y), to: CGPoint(x: position.x, y: position.y + contentOffset.y - offset), duration: duration, timingFunction: timingFunction, removeOnCompletion: false, force: true, completion: { [weak sectionNode] _ in
|
||||
sectionNode?.removeFromSupernode()
|
||||
})
|
||||
} else {
|
||||
@@ -1062,6 +1070,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
}
|
||||
}
|
||||
|
||||
if shouldAnimateBounds {
|
||||
boundsTransition.animateBounds(layer: self.layer, from: previousBounds)
|
||||
}
|
||||
|
||||
completion(self.displayedItemRange())
|
||||
|
||||
self.updateItemNodeVisibilititesAndScrolling()
|
||||
|
||||
@@ -5,43 +5,25 @@ struct KeyboardSurface {
|
||||
let host: UIView
|
||||
}
|
||||
|
||||
private func hasFirstResponder(_ view: UIView) -> Bool {
|
||||
private func getFirstResponder(_ view: UIView) -> UIView? {
|
||||
if view.isFirstResponder {
|
||||
return true
|
||||
return view
|
||||
} else {
|
||||
for subview in view.subviews {
|
||||
if hasFirstResponder(subview) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
private func findKeyboardBackdrop(_ view: UIView) -> UIView? {
|
||||
if NSStringFromClass(type(of: view)) == "UIKBInputBackdropView" {
|
||||
return view
|
||||
}
|
||||
for subview in view.subviews {
|
||||
if let result = findKeyboardBackdrop(subview) {
|
||||
if let result = getFirstResponder(subview) {
|
||||
return result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
class KeyboardManager {
|
||||
private let host: StatusBarHost
|
||||
|
||||
private weak var previousPositionAnimationMirrorSource: CATracingLayer?
|
||||
private weak var previousFirstResponderView: UIView?
|
||||
|
||||
var gestureRecognizer: MinimizeKeyboardGestureRecognizer? = nil
|
||||
|
||||
var minimized: Bool = false
|
||||
var minimizedUpdated: (() -> Void)?
|
||||
|
||||
var updatedMinimizedBackdrop = false
|
||||
private var interactiveInputOffset: CGFloat = 0.0
|
||||
|
||||
var surfaces: [KeyboardSurface] = [] {
|
||||
didSet {
|
||||
@@ -53,63 +35,49 @@ class KeyboardManager {
|
||||
self.host = host
|
||||
}
|
||||
|
||||
func updateInteractiveInputOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
|
||||
guard let keyboardView = self.host.keyboardView else {
|
||||
return
|
||||
}
|
||||
|
||||
self.interactiveInputOffset = offset
|
||||
|
||||
let previousBounds = keyboardView.bounds
|
||||
let updatedBounds = CGRect(origin: CGPoint(x: 0.0, y: -offset), size: previousBounds.size)
|
||||
keyboardView.layer.bounds = updatedBounds
|
||||
if transition.isAnimated {
|
||||
transition.animateOffsetAdditive(layer: keyboardView.layer, offset: previousBounds.minY - updatedBounds.minY, completion: completion)
|
||||
} else {
|
||||
completion()
|
||||
}
|
||||
|
||||
//transition.updateSublayerTransformOffset(layer: keyboardView.layer, offset: CGPoint(x: 0.0, y: offset))
|
||||
}
|
||||
|
||||
private func updateSurfaces(_ previousSurfaces: [KeyboardSurface]) {
|
||||
guard let keyboardWindow = self.host.keyboardWindow else {
|
||||
return
|
||||
}
|
||||
|
||||
if let keyboardView = self.host.keyboardView {
|
||||
if self.minimized {
|
||||
let normalizedHeight = floor(0.85 * keyboardView.frame.size.height)
|
||||
let factor = normalizedHeight / keyboardView.frame.size.height
|
||||
let scaleTransform = CATransform3DMakeScale(factor, factor, 1.0)
|
||||
let horizontalOffset = (keyboardView.frame.size.width - keyboardView.frame.size.width * factor) / 2.0
|
||||
let verticalOffset = (keyboardView.frame.size.height - keyboardView.frame.size.height * factor) / 2.0
|
||||
let translate = CATransform3DMakeTranslation(horizontalOffset, verticalOffset, 0.0)
|
||||
keyboardView.layer.sublayerTransform = CATransform3DConcat(scaleTransform, translate)
|
||||
|
||||
self.updatedMinimizedBackdrop = false
|
||||
|
||||
if let backdrop = findKeyboardBackdrop(keyboardView) {
|
||||
let scale = CATransform3DMakeScale(1.0 / factor, 1.0, 0.0)
|
||||
let translate = CATransform3DMakeTranslation(-horizontalOffset * (1.0 / factor), 0.0, 0.0)
|
||||
backdrop.layer.sublayerTransform = CATransform3DConcat(scale, translate)
|
||||
}
|
||||
} else {
|
||||
keyboardView.layer.sublayerTransform = CATransform3DIdentity
|
||||
if !self.updatedMinimizedBackdrop {
|
||||
if let backdrop = findKeyboardBackdrop(keyboardView) {
|
||||
backdrop.layer.sublayerTransform = CATransform3DIdentity
|
||||
}
|
||||
|
||||
self.updatedMinimizedBackdrop = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let gestureRecognizer = self.gestureRecognizer {
|
||||
if keyboardWindow.gestureRecognizers == nil || !keyboardWindow.gestureRecognizers!.contains(gestureRecognizer) {
|
||||
keyboardWindow.addGestureRecognizer(gestureRecognizer)
|
||||
}
|
||||
} else {
|
||||
let gestureRecognizer = MinimizeKeyboardGestureRecognizer(target: self, action: #selector(self.minimizeGesture(_:)))
|
||||
self.gestureRecognizer = gestureRecognizer
|
||||
keyboardWindow.addGestureRecognizer(gestureRecognizer)
|
||||
}
|
||||
|
||||
var firstResponderView: UIView?
|
||||
var firstResponderDisablesAutomaticKeyboardHandling = false
|
||||
for surface in self.surfaces {
|
||||
if hasFirstResponder(surface.host) {
|
||||
if let view = getFirstResponder(surface.host) {
|
||||
firstResponderView = surface.host
|
||||
firstResponderDisablesAutomaticKeyboardHandling = view.disablesAutomaticKeyboardHandling
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if let firstResponderView = firstResponderView {
|
||||
let containerOrigin = firstResponderView.convert(CGPoint(), to: nil)
|
||||
let horizontalTranslation = CATransform3DMakeTranslation(containerOrigin.x, 0.0, 0.0)
|
||||
let horizontalTranslation = CATransform3DMakeTranslation(firstResponderDisablesAutomaticKeyboardHandling ? 0.0 : containerOrigin.x, 0.0, 0.0)
|
||||
let currentTransform = keyboardWindow.layer.sublayerTransform
|
||||
if !CATransform3DEqualToTransform(horizontalTranslation, currentTransform) {
|
||||
//print("set to \(CGPoint(x: containerOrigin.x, y: self.interactiveInputOffset))")
|
||||
keyboardWindow.layer.sublayerTransform = horizontalTranslation
|
||||
if let tracingLayer = firstResponderView.layer as? CATracingLayer {
|
||||
}
|
||||
if let tracingLayer = firstResponderView.layer as? CATracingLayer, !firstResponderDisablesAutomaticKeyboardHandling {
|
||||
if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer {
|
||||
previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil)
|
||||
}
|
||||
@@ -137,11 +105,4 @@ class KeyboardManager {
|
||||
|
||||
self.previousFirstResponderView = firstResponderView
|
||||
}
|
||||
|
||||
@objc func minimizeGesture(_ recognizer: UISwipeGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.minimized = !self.minimized
|
||||
self.minimizedUpdated?()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -146,8 +146,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
private var bottomItemOverscrollBackground: ASDisplayNode?
|
||||
|
||||
private var touchesPosition = CGPoint()
|
||||
private var isTracking = false
|
||||
private var isDeceleratingAfterTracking = false
|
||||
public private(set) var isTracking = false
|
||||
public private(set) var trackingOffset: CGFloat = 0.0
|
||||
public private(set) var beganTrackingAtTopOrigin = false
|
||||
public private(set) var isDeceleratingAfterTracking = false
|
||||
|
||||
private final var transactionQueue: ListViewTransactionQueue
|
||||
private final var transactionOffset: CGFloat = 0.0
|
||||
@@ -447,6 +449,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
self.transactionOffset += -deltaY
|
||||
|
||||
if self.isTracking {
|
||||
self.trackingOffset += -deltaY
|
||||
}
|
||||
|
||||
self.enqueueUpdateVisibleItems()
|
||||
|
||||
var useScrollDynamics = false
|
||||
@@ -612,7 +618,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
var transition: ContainedViewLayoutTransition = .immediate
|
||||
if let updateSizeAndInsets = updateSizeAndInsets {
|
||||
if updateSizeAndInsets.duration > DBL_EPSILON {
|
||||
if !updateSizeAndInsets.duration.isZero {
|
||||
switch updateSizeAndInsets.curve {
|
||||
case let .Spring(duration):
|
||||
transition = .animated(duration: duration, curve: .spring)
|
||||
@@ -954,8 +960,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
return ListViewState(insets: self.insets, visibleSize: self.visibleSize, invisibleInset: self.invisibleInset, nodes: nodes, scrollPosition: nil, stationaryOffset: nil, stackFromBottom: self.stackFromBottom)
|
||||
}
|
||||
|
||||
public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) {
|
||||
if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil {
|
||||
public func transaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem? = nil, additionalScrollDistance: CGFloat = 0.0, updateSizeAndInsets: ListViewUpdateSizeAndInsets? = nil, stationaryItemRange: (Int, Int)? = nil, updateOpaqueState: Any?, completion: @escaping (ListViewDisplayedItemRange) -> Void = { _ in }) {
|
||||
if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil && updateSizeAndInsets == nil && additionalScrollDistance.isZero {
|
||||
completion(self.immediateDisplayedItemRange())
|
||||
return
|
||||
}
|
||||
@@ -963,7 +969,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
self.transactionQueue.addTransaction({ [weak self] transactionCompletion in
|
||||
if let strongSelf = self {
|
||||
strongSelf.transactionOffset = 0.0
|
||||
strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, completion: { [weak strongSelf] in
|
||||
strongSelf.deleteAndInsertItemsTransaction(deleteIndices: deleteIndices, insertIndicesAndItems: insertIndicesAndItems, updateIndicesAndItems: updateIndicesAndItems, options: options, scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemRange: stationaryItemRange, updateOpaqueState: updateOpaqueState, completion: { [weak strongSelf] in
|
||||
completion(strongSelf?.immediateDisplayedItemRange() ?? ListViewDisplayedItemRange(loadedRange: nil, visibleRange: nil))
|
||||
|
||||
transactionCompletion()
|
||||
@@ -972,7 +978,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
})
|
||||
}
|
||||
|
||||
private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) {
|
||||
private func deleteAndInsertItemsTransaction(deleteIndices: [ListViewDeleteItem], insertIndicesAndItems: [ListViewInsertItem], updateIndicesAndItems: [ListViewUpdateItem], options: ListViewDeleteAndInsertOptions, scrollToItem: ListViewScrollToItem?, additionalScrollDistance: CGFloat, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemRange: (Int, Int)?, updateOpaqueState: Any?, completion: @escaping () -> Void) {
|
||||
if deleteIndices.isEmpty && insertIndicesAndItems.isEmpty && updateIndicesAndItems.isEmpty && scrollToItem == nil {
|
||||
if let updateSizeAndInsets = updateSizeAndInsets , (self.items.count == 0 || (updateSizeAndInsets.size == self.visibleSize && updateSizeAndInsets.insets == self.insets)) {
|
||||
self.visibleSize = updateSizeAndInsets.size
|
||||
@@ -1249,7 +1255,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
let beginReplay = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: {
|
||||
strongSelf.replayOperations(animated: animated, animateAlpha: options.contains(.AnimateAlpha), animateCrossfade: options.contains(.AnimateCrossfade), animateTopItemVerticalOrigin: options.contains(.AnimateTopItemPosition), operations: updatedOperations, requestItemInsertionAnimationsIndices: options.contains(.RequestItemInsertionAnimations) ? insertedIndexSet : Set(), scrollToItem: scrollToItem, additionalScrollDistance: additionalScrollDistance, updateSizeAndInsets: updateSizeAndInsets, stationaryItemIndex: stationaryItemIndex, updateOpaqueState: updateOpaqueState, completion: {
|
||||
if options.contains(.PreferSynchronousDrawing) {
|
||||
let startTime = CACurrentMediaTime()
|
||||
self?.recursivelyEnsureDisplaySynchronously(true)
|
||||
@@ -1656,7 +1662,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
}
|
||||
|
||||
private func replayOperations(animated: Bool, animateAlpha: Bool, animateTopItemVerticalOrigin: Bool, operations: [ListViewStateOperation], requestItemInsertionAnimationsIndices: Set<Int>, scrollToItem: ListViewScrollToItem?, updateSizeAndInsets: ListViewUpdateSizeAndInsets?, stationaryItemIndex: Int?, updateOpaqueState: Any?, completion: () -> Void) {
|
||||
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) {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
|
||||
if let updateOpaqueState = updateOpaqueState {
|
||||
@@ -1666,10 +1672,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()
|
||||
snapshotView = self.view.snapshotView(afterScreenUpdates: false)
|
||||
}
|
||||
|
||||
var previousApparentFrames: [(ListViewItemNode, CGRect)] = []
|
||||
@@ -1913,6 +1921,13 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if !additionalScrollDistance.isZero {
|
||||
self.stopScrolling()
|
||||
/*for itemNode in self.itemNodes {
|
||||
var frame = itemNode.frame
|
||||
frame.origin.y += additionalScrollDistance
|
||||
itemNode.frame = frame
|
||||
}*/
|
||||
}
|
||||
|
||||
self.insertNodesInBatches(nodes: [], completion: {
|
||||
@@ -1928,12 +1943,16 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
self.visibleSize = updateSizeAndInsets.size
|
||||
|
||||
var offsetFix: CGFloat
|
||||
if self.snapToBottomInsetUntilFirstInteraction {
|
||||
if self.isTracking {
|
||||
offsetFix = 0.0
|
||||
} else if self.snapToBottomInsetUntilFirstInteraction {
|
||||
offsetFix = -updateSizeAndInsets.insets.bottom + self.insets.bottom
|
||||
} else {
|
||||
offsetFix = updateSizeAndInsets.insets.top - self.insets.top
|
||||
}
|
||||
|
||||
offsetFix += additionalScrollDistance
|
||||
|
||||
self.insets = updateSizeAndInsets.insets
|
||||
self.visibleSize = updateSizeAndInsets.size
|
||||
|
||||
@@ -1962,7 +1981,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
sizeAndInsetsOffset = offsetFix
|
||||
completeOffset += snapToBoundsOffset
|
||||
|
||||
if updateSizeAndInsets.duration > DBL_EPSILON {
|
||||
if !updateSizeAndInsets.duration.isZero {
|
||||
let animation: CABasicAnimation
|
||||
switch updateSizeAndInsets.curve {
|
||||
case let .Spring(duration):
|
||||
@@ -2063,6 +2082,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
if !snapToBoundsOffset.isZero {
|
||||
self.updateVisibleContentOffset()
|
||||
}
|
||||
|
||||
if let snapshotView = snapshotView {
|
||||
snapshotView.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: snapshotView.frame.size)
|
||||
self.view.addSubview(snapshotView)
|
||||
snapshotView.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.12, removeOnCompletion: false, completion: { [weak snapshotView] _ in
|
||||
snapshotView?.removeFromSuperview()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
self.updateAccessoryNodes(animated: animated, currentTimestamp: timestamp)
|
||||
@@ -2586,7 +2613,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
var updatedOperations = operations
|
||||
updatedState.removeInvisibleNodes(&updatedOperations)
|
||||
self.dispatchOnVSync {
|
||||
self.replayOperations(animated: false, animateAlpha: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
|
||||
self.replayOperations(animated: false, animateAlpha: false, animateCrossfade: false, animateTopItemVerticalOrigin: false, operations: updatedOperations, requestItemInsertionAnimationsIndices: Set(), scrollToItem: nil, additionalScrollDistance: 0.0, updateSizeAndInsets: nil, stationaryItemIndex: nil, updateOpaqueState: nil, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2759,6 +2786,14 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
}
|
||||
|
||||
override open func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
let offset = self.visibleContentOffset()
|
||||
switch offset {
|
||||
case let .known(value) where value <= 10.0:
|
||||
self.beganTrackingAtTopOrigin = true
|
||||
default:
|
||||
self.beganTrackingAtTopOrigin = false
|
||||
}
|
||||
|
||||
self.touchesPosition = touches.first!.location(in: self.view)
|
||||
self.selectionTouchLocation = touches.first!.location(in: self.view)
|
||||
|
||||
@@ -2915,7 +2950,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
self.isTracking = true
|
||||
break
|
||||
self.trackingOffset = 0.0
|
||||
case .changed:
|
||||
self.touchesPosition = recognizer.location(in: self.view)
|
||||
case .ended, .cancelled:
|
||||
|
||||
@@ -121,6 +121,7 @@ public struct ListViewDeleteAndInsertOptions: OptionSet {
|
||||
public static let AnimateTopItemPosition = ListViewDeleteAndInsertOptions(rawValue: 32)
|
||||
public static let PreferSynchronousDrawing = ListViewDeleteAndInsertOptions(rawValue: 64)
|
||||
public static let PreferSynchronousResourceLoading = ListViewDeleteAndInsertOptions(rawValue: 128)
|
||||
public static let AnimateCrossfade = ListViewDeleteAndInsertOptions(rawValue: 256)
|
||||
}
|
||||
|
||||
public struct ListViewUpdateSizeAndInsets {
|
||||
|
||||
@@ -61,13 +61,22 @@ private final class NativeWindow: UIWindow, WindowHost {
|
||||
var presentController: ((ViewController, PresentationSurfaceLevel) -> Void)?
|
||||
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
|
||||
var presentNativeImpl: ((UIViewController) -> Void)?
|
||||
var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
|
||||
|
||||
private var frameTransition: ContainedViewLayoutTransition?
|
||||
|
||||
override var frame: CGRect {
|
||||
get {
|
||||
return super.frame
|
||||
} set(value) {
|
||||
let sizeUpdated = super.frame.size != value.size
|
||||
if sizeUpdated, let transition = self.frameTransition, case let .animated(duration, curve) = transition {
|
||||
let previousFrame = super.frame
|
||||
super.frame = value
|
||||
self.layer.animateFrame(from: previousFrame, to: value, duration: duration, timingFunction: curve.timingFunction)
|
||||
} else {
|
||||
super.frame = value
|
||||
}
|
||||
|
||||
if sizeUpdated {
|
||||
self.updateSize?(value.size)
|
||||
@@ -97,7 +106,11 @@ private final class NativeWindow: UIWindow, WindowHost {
|
||||
|
||||
override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) {
|
||||
self.updateIsUpdatingOrientationLayout?(true)
|
||||
if !arg2.isZero {
|
||||
self.frameTransition = .animated(duration: arg2, curve: .easeInOut)
|
||||
}
|
||||
super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3)
|
||||
self.frameTransition = nil
|
||||
self.updateIsUpdatingOrientationLayout?(false)
|
||||
|
||||
self.updateToInterfaceOrientation?()
|
||||
@@ -114,6 +127,26 @@ private final class NativeWindow: UIWindow, WindowHost {
|
||||
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
return self.hitTestImpl?(point, event)
|
||||
}
|
||||
|
||||
override func insertSubview(_ view: UIView, at index: Int) {
|
||||
super.insertSubview(view, at: index)
|
||||
}
|
||||
|
||||
override func addSubview(_ view: UIView) {
|
||||
super.addSubview(view)
|
||||
}
|
||||
|
||||
override func insertSubview(_ view: UIView, aboveSubview siblingSubview: UIView) {
|
||||
if let transitionClass = NSClassFromString("UITransitionView"), view.isKind(of: transitionClass) {
|
||||
super.insertSubview(view, aboveSubview: self.subviews.last!)
|
||||
} else {
|
||||
super.insertSubview(view, aboveSubview: siblingSubview)
|
||||
}
|
||||
}
|
||||
|
||||
func invalidateDeferScreenEdgeGestures() {
|
||||
self.invalidateDeferScreenEdgeGestureImpl?()
|
||||
}
|
||||
}
|
||||
|
||||
public func nativeWindowHostView() -> WindowHostView {
|
||||
@@ -161,6 +194,10 @@ public func nativeWindowHostView() -> WindowHostView {
|
||||
return hostView?.hitTest?(point, event)
|
||||
}
|
||||
|
||||
window.invalidateDeferScreenEdgeGestureImpl = { [weak hostView] in
|
||||
return hostView?.invalidateDeferScreenEdgeGesture?()
|
||||
}
|
||||
|
||||
rootViewController.presentController = { [weak hostView] controller, level, animated, completion in
|
||||
if let strongSelf = hostView {
|
||||
strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom), level)
|
||||
|
||||
@@ -9,9 +9,9 @@ public final class NavigationBarTheme {
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
context.setFillColor(color.cgColor)
|
||||
|
||||
context.translateBy(x: 0.0, y: 2.0)
|
||||
context.translateBy(x: 0.0, y: -UIScreenPixel)
|
||||
|
||||
let _ = try? drawSvgPath(context, path: "M8.16012402,0.373030797 L0.635333572,7.39652821 L0.635333572,7.39652821 C-0.172148528,8.15021677 -0.215756811,9.41579564 0.537931744,10.2232777 C0.56927099,10.2568538 0.601757528,10.2893403 0.635333572,10.3206796 L8.16012402,17.344177 L8.16012402,17.344177 C8.69299787,17.8415514 9.51995274,17.8415514 10.0528266,17.344177 L10.0528266,17.344177 L10.0528266,17.344177 C10.5406633,16.8888394 10.567009,16.1242457 10.1116715,15.636409 C10.092738,15.6161242 10.0731114,15.5964976 10.0528266,15.5775641 L2.85430928,8.85860389 L10.0528266,2.13964366 L10.0528266,2.13964366 C10.5406633,1.68430612 10.567009,0.919712345 10.1116715,0.431875673 C10.092738,0.411590857 10.0731114,0.391964261 10.0528266,0.373030797 L10.0528266,0.373030797 L10.0528266,0.373030797 C9.51995274,-0.124343599 8.69299787,-0.124343599 8.16012402,0.373030797 Z ")
|
||||
let _ = try? drawSvgPath(context, path: "M3.60751322,11.5 L11.5468531,3.56066017 C12.1326395,2.97487373 12.1326395,2.02512627 11.5468531,1.43933983 C10.9610666,0.853553391 10.0113191,0.853553391 9.42553271,1.43933983 L0.449102936,10.4157696 C-0.149700979,11.0145735 -0.149700979,11.9854265 0.449102936,12.5842304 L9.42553271,21.5606602 C10.0113191,22.1464466 10.9610666,22.1464466 11.5468531,21.5606602 C12.1326395,20.9748737 12.1326395,20.0251263 11.5468531,19.4393398 L3.60751322,11.5 Z ")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -19,16 +19,22 @@ public final class NavigationBarTheme {
|
||||
public let primaryTextColor: UIColor
|
||||
public let backgroundColor: UIColor
|
||||
public let separatorColor: UIColor
|
||||
public let badgeBackgroundColor: UIColor
|
||||
public let badgeStrokeColor: UIColor
|
||||
public let badgeTextColor: UIColor
|
||||
|
||||
public init(buttonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor) {
|
||||
public init(buttonColor: UIColor, primaryTextColor: UIColor, backgroundColor: UIColor, separatorColor: UIColor, badgeBackgroundColor: UIColor, badgeStrokeColor: UIColor, badgeTextColor: UIColor) {
|
||||
self.buttonColor = buttonColor
|
||||
self.primaryTextColor = primaryTextColor
|
||||
self.backgroundColor = backgroundColor
|
||||
self.separatorColor = separatorColor
|
||||
self.badgeBackgroundColor = badgeBackgroundColor
|
||||
self.badgeStrokeColor = badgeStrokeColor
|
||||
self.badgeTextColor = badgeTextColor
|
||||
}
|
||||
|
||||
public func withUpdatedSeparatorColor(_ color: UIColor) -> NavigationBarTheme {
|
||||
return NavigationBarTheme(buttonColor: self.buttonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color)
|
||||
return NavigationBarTheme(buttonColor: self.buttonColor, primaryTextColor: self.primaryTextColor, backgroundColor: self.backgroundColor, separatorColor: color, badgeBackgroundColor: self.badgeBackgroundColor, badgeStrokeColor: self.badgeStrokeColor, badgeTextColor: self.badgeTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -509,7 +515,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
self.titleNode = ASTextNode()
|
||||
self.backButtonNode = NavigationButtonNode()
|
||||
self.badgeNode = NavigationBarBadgeNode(fillColor: .red, textColor: .white)
|
||||
self.badgeNode = NavigationBarBadgeNode(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor)
|
||||
self.badgeNode.isUserInteractionEnabled = false
|
||||
self.badgeNode.isHidden = true
|
||||
self.backButtonArrow = ASImageNode()
|
||||
@@ -581,6 +587,8 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.theme.primaryTextColor)
|
||||
}
|
||||
self.stripeNode.backgroundColor = self.theme.separatorColor
|
||||
|
||||
self.badgeNode.updateTheme(fillColor: theme.badgeBackgroundColor, strokeColor: theme.badgeStrokeColor, textColor: theme.badgeTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -810,7 +818,7 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
private func makeTransitionBadgeNode() -> ASDisplayNode? {
|
||||
if self.badgeNode.supernode != nil && !self.badgeNode.isHidden {
|
||||
let node = NavigationBarBadgeNode(fillColor: .red, textColor: .white)
|
||||
let node = NavigationBarBadgeNode(fillColor: self.theme.badgeBackgroundColor, strokeColor: self.theme.badgeStrokeColor, textColor: self.theme.badgeTextColor)
|
||||
node.text = self.badgeNode.text
|
||||
let nodeSize = node.measure(CGSize(width: 200.0, height: 100.0))
|
||||
node.frame = CGRect(origin: CGPoint(), size: nodeSize)
|
||||
|
||||
@@ -3,6 +3,7 @@ import AsyncDisplayKit
|
||||
|
||||
final class NavigationBarBadgeNode: ASDisplayNode {
|
||||
private var fillColor: UIColor
|
||||
private var strokeColor: UIColor
|
||||
private var textColor: UIColor
|
||||
|
||||
private let textNode: ASTextNode2
|
||||
@@ -17,8 +18,9 @@ final class NavigationBarBadgeNode: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
init(fillColor: UIColor, textColor: UIColor) {
|
||||
init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) {
|
||||
self.fillColor = fillColor
|
||||
self.strokeColor = strokeColor
|
||||
self.textColor = textColor
|
||||
|
||||
self.textNode = ASTextNode2()
|
||||
@@ -29,7 +31,7 @@ final class NavigationBarBadgeNode: ASDisplayNode {
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.displayWithoutProcessing = true
|
||||
self.backgroundNode.displaysAsynchronously = false
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor)
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0)
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -37,6 +39,14 @@ final class NavigationBarBadgeNode: ASDisplayNode {
|
||||
self.addSubnode(self.textNode)
|
||||
}
|
||||
|
||||
func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) {
|
||||
self.fillColor = fillColor
|
||||
self.strokeColor = strokeColor
|
||||
self.textColor = textColor
|
||||
self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0)
|
||||
self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor)
|
||||
}
|
||||
|
||||
override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
|
||||
let badgeSize = self.textNode.measure(constrainedSize)
|
||||
let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
|
||||
|
||||
@@ -75,7 +75,7 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
self.containerLayout = layout
|
||||
self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size)
|
||||
|
||||
let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight)
|
||||
let containedLayout = ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: layout.intrinsicInsets, safeInsets: layout.safeInsets, statusBarHeight: layout.statusBarHeight, inputHeight: layout.inputHeight, inputHeightIsInteractivellyChanging: layout.inputHeightIsInteractivellyChanging)
|
||||
|
||||
if let topViewController = self.topViewController {
|
||||
if let topViewController = topViewController as? ContainableController {
|
||||
@@ -220,13 +220,16 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
}
|
||||
|
||||
public func pushViewController(_ controller: ViewController) {
|
||||
if !controller.hasActiveInput {
|
||||
self.view.endEditing(true)
|
||||
let appliedLayout = self.containerLayout.withUpdatedInputHeight(nil)
|
||||
}
|
||||
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 {
|
||||
if strongSelf.containerLayout.withUpdatedInputHeight(nil) != appliedLayout {
|
||||
controller.containerLayoutUpdated(strongSelf.containerLayout.withUpdatedInputHeight(nil), transition: .immediate)
|
||||
let containerLayout = strongSelf.containerLayout.withUpdatedInputHeight(controller.hasActiveInput ? strongSelf.containerLayout.inputHeight : nil)
|
||||
if containerLayout != appliedLayout {
|
||||
controller.containerLayoutUpdated(containerLayout, transition: .immediate)
|
||||
}
|
||||
strongSelf.pushViewController(controller, animated: true)
|
||||
}
|
||||
@@ -320,7 +323,11 @@ open class NavigationController: UINavigationController, ContainableController,
|
||||
|
||||
if let controller = topViewController as? ContainableController {
|
||||
var layoutToApply = self.containerLayout
|
||||
if !self.viewControllers.contains(where: { $0 === controller }) {
|
||||
var hasActiveInput = false
|
||||
if let controller = controller as? ViewController {
|
||||
hasActiveInput = controller.hasActiveInput
|
||||
}
|
||||
if !hasActiveInput {
|
||||
layoutToApply = layoutToApply.withUpdatedInputHeight(nil)
|
||||
}
|
||||
controller.containerLayoutUpdated(layoutToApply, transition: .immediate)
|
||||
|
||||
@@ -29,6 +29,7 @@ static NSMutableArray *notificationHandlers() {
|
||||
}
|
||||
}
|
||||
|
||||
//printf("***** %s\n", [aName cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
[self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo];
|
||||
}
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ final class PresentationContext {
|
||||
return self.view != nil && self.layout != nil
|
||||
}
|
||||
|
||||
private var controllers: [ViewController] = []
|
||||
private(set) var controllers: [ViewController] = []
|
||||
|
||||
private var presentationDisposables = DisposableSet()
|
||||
|
||||
|
||||
@@ -79,13 +79,13 @@ class StatusBarManager {
|
||||
self.host = host
|
||||
}
|
||||
|
||||
func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, animated: Bool) {
|
||||
func updateState(surfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) {
|
||||
let previousSurfaces = self.surfaces
|
||||
self.surfaces = surfaces
|
||||
self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, animated: animated)
|
||||
self.updateSurfaces(previousSurfaces, forceInCallStatusBarText: forceInCallStatusBarText, forceHiddenBySystemWindows: forceHiddenBySystemWindows, animated: animated)
|
||||
}
|
||||
|
||||
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, animated: Bool) {
|
||||
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface], forceInCallStatusBarText: String?, forceHiddenBySystemWindows: Bool, animated: Bool) {
|
||||
let statusBarFrame = self.host.statusBarFrame
|
||||
guard let statusBarView = self.host.statusBarView else {
|
||||
return
|
||||
@@ -215,7 +215,7 @@ class StatusBarManager {
|
||||
statusBar.updateState(statusBar: statusBarView, inCallText: forceInCallStatusBarText, animated: animated)
|
||||
}
|
||||
|
||||
if let globalStatusBar = globalStatusBar {
|
||||
if let globalStatusBar = globalStatusBar, !forceHiddenBySystemWindows {
|
||||
let statusBarStyle: UIStatusBarStyle
|
||||
if forceInCallStatusBarText != nil {
|
||||
statusBarStyle = .lightContent
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
import AsyncDisplayKit
|
||||
|
||||
public enum StatusBarStyle {
|
||||
@@ -6,6 +7,28 @@ public enum StatusBarStyle {
|
||||
case White
|
||||
case Ignore
|
||||
case Hide
|
||||
|
||||
public init(systemStyle: UIStatusBarStyle) {
|
||||
switch systemStyle {
|
||||
case .default:
|
||||
self = .Black
|
||||
case .lightContent:
|
||||
self = .White
|
||||
case .blackOpaque:
|
||||
self = .Black
|
||||
}
|
||||
}
|
||||
|
||||
public var systemStyle: UIStatusBarStyle {
|
||||
switch self {
|
||||
case .Black:
|
||||
return .default
|
||||
case .White:
|
||||
return .lightContent
|
||||
default:
|
||||
return .default
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private enum StatusBarItemType {
|
||||
|
||||
@@ -36,14 +36,17 @@ final class TabBarControllerNode: ASDisplayNode {
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
let update = {
|
||||
self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - layout.insets(options: []).bottom - 49.0), size: CGSize(width: layout.size.width, height: 49.0))
|
||||
let tabBarHeight = 49.0 + layout.insets(options: []).bottom
|
||||
self.tabBarNode.frame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))
|
||||
if self.tabBarNode.isNodeLoaded {
|
||||
self.tabBarNode.layout()
|
||||
}
|
||||
}
|
||||
|
||||
switch transition {
|
||||
case .immediate:
|
||||
update()
|
||||
case let .animated(duration, curve):
|
||||
case .animated:
|
||||
update()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,15 +10,17 @@ public final class TabBarControllerTheme {
|
||||
public let tabBarTextColor: UIColor
|
||||
public let tabBarSelectedTextColor: UIColor
|
||||
public let tabBarBadgeBackgroundColor: UIColor
|
||||
public let tabBarBadgeStrokeColor: UIColor
|
||||
public let tabBarBadgeTextColor: UIColor
|
||||
|
||||
public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeTextColor: UIColor) {
|
||||
public init(backgroundColor: UIColor, tabBarBackgroundColor: UIColor, tabBarSeparatorColor: UIColor, tabBarTextColor: UIColor, tabBarSelectedTextColor: UIColor, tabBarBadgeBackgroundColor: UIColor, tabBarBadgeStrokeColor: UIColor, tabBarBadgeTextColor: UIColor) {
|
||||
self.backgroundColor = backgroundColor
|
||||
self.tabBarBackgroundColor = tabBarBackgroundColor
|
||||
self.tabBarSeparatorColor = tabBarSeparatorColor
|
||||
self.tabBarTextColor = tabBarTextColor
|
||||
self.tabBarSelectedTextColor = tabBarSelectedTextColor
|
||||
self.tabBarBadgeBackgroundColor = tabBarBadgeBackgroundColor
|
||||
self.tabBarBadgeStrokeColor = tabBarBadgeStrokeColor
|
||||
self.tabBarBadgeTextColor = tabBarBadgeTextColor
|
||||
}
|
||||
}
|
||||
@@ -32,20 +34,16 @@ open class TabBarController: ViewController {
|
||||
}
|
||||
}
|
||||
|
||||
public var controllers: [ViewController] = [] {
|
||||
didSet {
|
||||
self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem })
|
||||
private var controllers: [ViewController] = []
|
||||
|
||||
if oldValue.count == 0 && self.controllers.count != 0 {
|
||||
self.updateSelectedIndex()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var _selectedIndex: Int = 2
|
||||
private var _selectedIndex: Int?
|
||||
public var selectedIndex: Int {
|
||||
get {
|
||||
if let _selectedIndex = self._selectedIndex {
|
||||
return _selectedIndex
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} set(value) {
|
||||
let index = max(0, min(self.controllers.count - 1, value))
|
||||
if _selectedIndex != index {
|
||||
@@ -122,8 +120,8 @@ open class TabBarController: ViewController {
|
||||
self.currentController = nil
|
||||
}
|
||||
|
||||
if self._selectedIndex < self.controllers.count {
|
||||
self.currentController = self.controllers[self._selectedIndex]
|
||||
if let _selectedIndex = self._selectedIndex, _selectedIndex < self.controllers.count {
|
||||
self.currentController = self.controllers[_selectedIndex]
|
||||
}
|
||||
|
||||
var displayNavigationBar = false
|
||||
@@ -150,6 +148,8 @@ open class TabBarController: ViewController {
|
||||
if self.displayNavigationBar != displayNavigationBar {
|
||||
self.setDisplayNavigationBar(displayNavigationBar)
|
||||
}
|
||||
|
||||
self.tabBarControllerNode.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
@@ -177,4 +177,22 @@ open class TabBarController: ViewController {
|
||||
currentController.viewDidDisappear(animated)
|
||||
}
|
||||
}
|
||||
|
||||
public func setControllers(_ controllers: [ViewController], selectedIndex: Int?) {
|
||||
var updatedSelectedIndex: Int? = selectedIndex
|
||||
if updatedSelectedIndex == nil, let selectedIndex = self._selectedIndex, selectedIndex < self.controllers.count {
|
||||
if let index = controllers.index(where: { $0 === self.controllers[selectedIndex] }) {
|
||||
updatedSelectedIndex = index
|
||||
} else {
|
||||
updatedSelectedIndex = 0
|
||||
}
|
||||
}
|
||||
self.controllers = controllers
|
||||
self.tabBarControllerNode.tabBarNode.tabBarItems = self.controllers.map({ $0.tabBarItem })
|
||||
|
||||
if let updatedSelectedIndex = updatedSelectedIndex {
|
||||
self.selectedIndex = updatedSelectedIndex
|
||||
self.updateSelectedIndex()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ import AsyncDisplayKit
|
||||
|
||||
private let separatorHeight: CGFloat = 1.0 / UIScreen.main.scale
|
||||
private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor: UIColor, tintColor: UIColor) -> UIImage? {
|
||||
let font = Font.regular(10.0)
|
||||
let font = Font.medium(10.0)
|
||||
let titleSize = (title as NSString).boundingRect(with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), options: [.usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: font], context: nil).size
|
||||
|
||||
let imageSize: CGSize
|
||||
@@ -22,7 +22,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
||||
context.fill(CGRect(origin: CGPoint(), size: size))
|
||||
|
||||
if let image = image {
|
||||
let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 0.0), size: imageSize)
|
||||
let imageRect = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - imageSize.width) / 2.0), y: 1.0), size: imageSize)
|
||||
context.saveGState()
|
||||
context.translateBy(x: imageRect.midX, y: imageRect.midY)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
@@ -34,7 +34,7 @@ private func tabBarItemImage(_ image: UIImage?, title: String, backgroundColor:
|
||||
}
|
||||
}
|
||||
|
||||
(title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 3.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor])
|
||||
(title as NSString).draw(at: CGPoint(x: floorToScreenPixels((size.width - titleSize.width) / 2.0), y: size.height - titleSize.height - 2.0), withAttributes: [NSAttributedStringKey.font: font, NSAttributedStringKey.foregroundColor: tintColor])
|
||||
|
||||
let image = UIGraphicsGetImageFromCurrentImageContext()
|
||||
UIGraphicsEndImageContext()
|
||||
@@ -150,7 +150,7 @@ class TabBarNode: ASDisplayNode {
|
||||
self.separatorNode.isOpaque = true
|
||||
self.separatorNode.isLayerBacked = true
|
||||
|
||||
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, backgroundColor: nil)!
|
||||
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
|
||||
|
||||
super.init()
|
||||
|
||||
@@ -167,7 +167,12 @@ class TabBarNode: ASDisplayNode {
|
||||
self.separatorNode.backgroundColor = theme.tabBarSeparatorColor
|
||||
self.backgroundColor = theme.tabBarBackgroundColor
|
||||
|
||||
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, backgroundColor: nil)!
|
||||
self.badgeImage = generateStretchableFilledCircleImage(diameter: 18.0, color: theme.tabBarBadgeBackgroundColor, strokeColor: theme.tabBarBadgeStrokeColor, strokeWidth: 1.0, backgroundColor: nil)!
|
||||
for container in self.tabBarNodeContainers {
|
||||
if let attributedText = container.badgeTextNode.attributedText, !attributedText.string.isEmpty {
|
||||
container.badgeTextNode.attributedText = NSAttributedString(string: attributedText.string, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor)
|
||||
}
|
||||
}
|
||||
|
||||
for i in 0 ..< self.tabBarItems.count {
|
||||
self.updateNodeImage(i)
|
||||
@@ -275,7 +280,7 @@ class TabBarNode: ASDisplayNode {
|
||||
if container.badgeValue != container.appliedBadgeValue {
|
||||
container.appliedBadgeValue = container.badgeValue
|
||||
if let badgeValue = container.badgeValue, !badgeValue.isEmpty {
|
||||
container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: .white)
|
||||
container.badgeTextNode.attributedText = NSAttributedString(string: badgeValue, font: badgeFont, textColor: self.theme.tabBarBadgeTextColor)
|
||||
container.badgeBackgroundNode.isHidden = false
|
||||
container.badgeTextNode.isHidden = false
|
||||
} else {
|
||||
@@ -287,7 +292,7 @@ class TabBarNode: ASDisplayNode {
|
||||
if !container.badgeBackgroundNode.isHidden {
|
||||
let badgeSize = container.badgeTextNode.measure(CGSize(width: 200.0, height: 100.0))
|
||||
let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.calculatedSize.width / 2.0) - 3.0 + node.calculatedSize.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize)
|
||||
let backgroundFrame = CGRect(origin: CGPoint(x: floor(originX + node.frame.width / 2.0) - 3.0 + node.frame.width - backgroundSize.width - 1.0, y: 2.0), size: backgroundSize)
|
||||
container.badgeBackgroundNode.frame = backgroundFrame
|
||||
container.badgeTextNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: 3.0), size: badgeSize)
|
||||
}
|
||||
|
||||
@@ -33,7 +33,8 @@ private final class TextAlertContentActionNode: HighlightableButtonNode {
|
||||
|
||||
super.init()
|
||||
|
||||
self.setTitle(action.title, with: action.type == .defaultAction ? Font.medium(17.0) : Font.regular(17.0), with: UIColor(rgb: 0x007ee5), for: [])
|
||||
self.titleNode.maximumNumberOfLines = 2
|
||||
self.setAttributedTitle(NSAttributedString(string: action.title, font: Font.regular(17.0), textColor: UIColor(rgb: 0x007ee5), paragraphAlignment: .center), for: [])
|
||||
|
||||
self.highligthedChanged = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
|
||||
@@ -7,12 +7,17 @@
|
||||
- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller;
|
||||
- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController;
|
||||
- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:( UIViewController * _Nullable )rootController;
|
||||
- (void)state_setNeedsStatusBarAppearanceUpdate:(void (^_Nullable)())block;
|
||||
|
||||
@end
|
||||
|
||||
@interface UIView (Navigation)
|
||||
|
||||
@property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer;
|
||||
@property (nonatomic) bool disablesAutomaticKeyboardHandling;
|
||||
|
||||
- (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block;
|
||||
- (CGFloat)input_getInputAccessoryHeight;
|
||||
|
||||
@end
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ static const void *UIViewControllerNavigationControllerKey = &UIViewControllerNa
|
||||
static const void *UIViewControllerPresentingControllerKey = &UIViewControllerPresentingControllerKey;
|
||||
static const void *UIViewControllerPresentingProxyControllerKey = &UIViewControllerPresentingProxyControllerKey;
|
||||
static const void *disablesInteractiveTransitionGestureRecognizerKey = &disablesInteractiveTransitionGestureRecognizerKey;
|
||||
static const void *disablesAutomaticKeyboardHandlingKey = &disablesAutomaticKeyboardHandlingKey;
|
||||
static const void *setNeedsStatusBarAppearanceUpdateKey = &setNeedsStatusBarAppearanceUpdateKey;
|
||||
static const void *inputAccessoryHeightProviderKey = &inputAccessoryHeightProviderKey;
|
||||
|
||||
static bool notyfyingShiftState = false;
|
||||
|
||||
@@ -96,6 +99,7 @@ static bool notyfyingShiftState = false;
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(navigationController) newSelector:@selector(_65087dc8_navigationController)];
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentingViewController) newSelector:@selector(_65087dc8_presentingViewController)];
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(presentViewController:animated:completion:) newSelector:@selector(_65087dc8_presentViewController:animated:completion:)];
|
||||
[RuntimeUtils swizzleInstanceMethodOfClass:[UIViewController class] currentSelector:@selector(setNeedsStatusBarAppearanceUpdate) newSelector:@selector(_65087dc8_setNeedsStatusBarAppearanceUpdate)];
|
||||
|
||||
//[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIKeyboardImpl") currentSelector:@selector(notifyShiftState) withAnotherClass:[UIKeyboardImpl_65087dc8 class] newSelector:@selector(notifyShiftState)];
|
||||
//[RuntimeUtils swizzleInstanceMethodOfClass:NSClassFromString(@"UIInputWindowController") currentSelector:@selector(updateViewConstraints) withAnotherClass:[UIInputWindowController_65087dc8 class] newSelector:@selector(updateViewConstraints)];
|
||||
@@ -189,6 +193,19 @@ static bool notyfyingShiftState = false;
|
||||
[self _65087dc8_presentViewController:viewControllerToPresent animated:flag completion:completion];
|
||||
}
|
||||
|
||||
- (void)_65087dc8_setNeedsStatusBarAppearanceUpdate {
|
||||
[self _65087dc8_setNeedsStatusBarAppearanceUpdate];
|
||||
|
||||
void (^block)() = [self associatedObjectForKey:setNeedsStatusBarAppearanceUpdateKey];
|
||||
if (block) {
|
||||
block();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)state_setNeedsStatusBarAppearanceUpdate:(void (^_Nullable)())block {
|
||||
[self setAssociatedObject:[block copy] forKey:setNeedsStatusBarAppearanceUpdateKey];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation UIView (Navigation)
|
||||
@@ -201,6 +218,26 @@ static bool notyfyingShiftState = false;
|
||||
[self setAssociatedObject:@(disablesInteractiveTransitionGestureRecognizer) forKey:disablesInteractiveTransitionGestureRecognizerKey];
|
||||
}
|
||||
|
||||
- (bool)disablesAutomaticKeyboardHandling {
|
||||
return [[self associatedObjectForKey:disablesAutomaticKeyboardHandlingKey] boolValue];
|
||||
}
|
||||
|
||||
- (void)setDisablesAutomaticKeyboardHandling:(bool)disablesAutomaticKeyboardHandling {
|
||||
[self setAssociatedObject:@(disablesAutomaticKeyboardHandling) forKey:disablesAutomaticKeyboardHandlingKey];
|
||||
}
|
||||
|
||||
- (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block {
|
||||
[self setAssociatedObject:[block copy] forKey:inputAccessoryHeightProviderKey];
|
||||
}
|
||||
|
||||
- (CGFloat)input_getInputAccessoryHeight {
|
||||
CGFloat (^block)() = [self associatedObjectForKey:inputAccessoryHeightProviderKey];
|
||||
if (block) {
|
||||
return block();
|
||||
}
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
static NSString *TGEncodeText(NSString *string, int key)
|
||||
|
||||
@@ -38,7 +38,14 @@ open class ViewControllerPresentationArguments {
|
||||
return self.supportedOrientations
|
||||
}
|
||||
|
||||
public final var deferScreenEdgeGestures: UIRectEdge = []
|
||||
public final var deferScreenEdgeGestures: UIRectEdge = [] {
|
||||
didSet {
|
||||
if self.deferScreenEdgeGestures != oldValue {
|
||||
self.window?.invalidateDeferScreenEdgeGestures()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override open func preferredScreenEdgesDeferringSystemGestures() -> UIRectEdge {
|
||||
return .bottom
|
||||
}
|
||||
@@ -76,6 +83,8 @@ open class ViewControllerPresentationArguments {
|
||||
private weak var activeInputViewCandidate: UIResponder?
|
||||
private weak var activeInputView: UIResponder?
|
||||
|
||||
open var hasActiveInput: Bool = false
|
||||
|
||||
private var navigationBarOrigin: CGFloat = 0.0
|
||||
|
||||
public var navigationOffset: CGFloat = 0.0 {
|
||||
@@ -160,15 +169,22 @@ open class ViewControllerPresentationArguments {
|
||||
if !self.isViewLoaded {
|
||||
self.loadView()
|
||||
}
|
||||
self.view.frame = CGRect(origin: self.view.frame.origin, size: layout.size)
|
||||
transition.updateFrame(node: self.displayNode, frame: CGRect(origin: self.view.frame.origin, size: layout.size))
|
||||
if let _ = layout.statusBarHeight {
|
||||
self.statusBar.frame = CGRect(origin: CGPoint(), size: CGSize(width: layout.size.width, height: 40.0))
|
||||
}
|
||||
|
||||
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0))
|
||||
let navigationBarHeight: CGFloat = max(20.0, statusBarHeight) + 44.0
|
||||
let navigationBarOffset: CGFloat
|
||||
if statusBarHeight.isZero {
|
||||
navigationBarOffset = -20.0
|
||||
} else {
|
||||
navigationBarOffset = 0.0
|
||||
}
|
||||
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: navigationBarOffset), size: CGSize(width: layout.size.width, height: navigationBarHeight))
|
||||
if layout.statusBarHeight == nil {
|
||||
navigationBarFrame.size.height = 44.0
|
||||
navigationBarFrame.size.height = 64.0
|
||||
}
|
||||
|
||||
if !self.displayNavigationBar {
|
||||
|
||||
@@ -14,12 +14,14 @@ private class WindowRootViewController: UIViewController {
|
||||
}
|
||||
|
||||
private struct WindowLayout: Equatable {
|
||||
public let size: CGSize
|
||||
public let metrics: LayoutMetrics
|
||||
public let statusBarHeight: CGFloat?
|
||||
public let forceInCallStatusBarText: String?
|
||||
public let inputHeight: CGFloat?
|
||||
public let inputMinimized: Bool
|
||||
let size: CGSize
|
||||
let metrics: LayoutMetrics
|
||||
let statusBarHeight: CGFloat?
|
||||
let forceInCallStatusBarText: String?
|
||||
let inputHeight: CGFloat?
|
||||
let safeInsets: UIEdgeInsets
|
||||
let onScreenNavigationHeight: CGFloat?
|
||||
let upperKeyboardInputPositionBound: CGFloat?
|
||||
|
||||
static func ==(lhs: WindowLayout, rhs: WindowLayout) -> Bool {
|
||||
if !lhs.size.equalTo(rhs.size) {
|
||||
@@ -54,7 +56,15 @@ private struct WindowLayout: Equatable {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.inputMinimized != rhs.inputMinimized {
|
||||
if lhs.safeInsets != rhs.safeInsets {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.onScreenNavigationHeight != rhs.onScreenNavigationHeight {
|
||||
return false
|
||||
}
|
||||
|
||||
if lhs.upperKeyboardInputPositionBound != rhs.upperKeyboardInputPositionBound {
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -81,44 +91,58 @@ private struct UpdatingLayout {
|
||||
mutating func update(size: CGSize, metrics: LayoutMetrics, forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized)
|
||||
self.layout = WindowLayout(size: size, metrics: metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound)
|
||||
}
|
||||
|
||||
|
||||
mutating func update(forceInCallStatusBarText: String?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized)
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound)
|
||||
}
|
||||
|
||||
mutating func update(statusBarHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: self.layout.inputMinimized)
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound)
|
||||
}
|
||||
|
||||
mutating func update(inputHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, inputMinimized: self.layout.inputMinimized)
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound)
|
||||
}
|
||||
|
||||
mutating func update(inputMinimized: Bool, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
mutating func update(safeInsets: UIEdgeInsets, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, inputMinimized: inputMinimized)
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound)
|
||||
}
|
||||
|
||||
mutating func update(onScreenNavigationHeight: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: self.layout.upperKeyboardInputPositionBound)
|
||||
}
|
||||
|
||||
mutating func update(upperKeyboardInputPositionBound: CGFloat?, transition: ContainedViewLayoutTransition, overrideTransition: Bool) {
|
||||
self.update(transition: transition, override: overrideTransition)
|
||||
|
||||
self.layout = WindowLayout(size: self.layout.size, metrics: self.layout.metrics, statusBarHeight: self.layout.statusBarHeight, forceInCallStatusBarText: self.layout.forceInCallStatusBarText, inputHeight: self.layout.inputHeight, safeInsets: self.layout.safeInsets, onScreenNavigationHeight: self.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: upperKeyboardInputPositionBound)
|
||||
}
|
||||
}
|
||||
|
||||
private let orientationChangeDuration: Double = UIDevice.current.userInterfaceIdiom == .pad ? 0.4 : 0.3
|
||||
private let statusBarHiddenInLandscape: Bool = UIDevice.current.userInterfaceIdiom == .phone
|
||||
|
||||
private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout {
|
||||
var inputHeight: CGFloat? = layout.inputHeight
|
||||
if let inputHeightValue = inputHeight, layout.inputMinimized {
|
||||
inputHeight = floor(0.85 * inputHeightValue)
|
||||
private func inputHeightOffsetForLayout(_ layout: WindowLayout) -> CGFloat {
|
||||
if let inputHeight = layout.inputHeight, let upperBound = layout.upperKeyboardInputPositionBound {
|
||||
return max(0.0, upperBound - (layout.size.height - inputHeight))
|
||||
}
|
||||
return 0.0
|
||||
}
|
||||
|
||||
private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> ContainerViewLayout {
|
||||
let resolvedStatusBarHeight: CGFloat?
|
||||
if let statusBarHeight = layout.statusBarHeight {
|
||||
if layout.forceInCallStatusBarText != nil {
|
||||
@@ -130,7 +154,94 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container
|
||||
resolvedStatusBarHeight = nil
|
||||
}
|
||||
|
||||
return ContainerViewLayout(size: layout.size, metrics: layout.metrics, intrinsicInsets: UIEdgeInsets(), statusBarHeight: resolvedStatusBarHeight, inputHeight: inputHeight)
|
||||
var updatedInputHeight = layout.inputHeight
|
||||
if let inputHeight = updatedInputHeight, let _ = layout.upperKeyboardInputPositionBound {
|
||||
updatedInputHeight = inputHeight - inputHeightOffsetForLayout(layout)
|
||||
}
|
||||
|
||||
var resolvedSafeInsets = layout.safeInsets
|
||||
if layout.size.height.isEqual(to: 375.0) && layout.size.width.isEqual(to: 812.0) {
|
||||
resolvedSafeInsets.left = 44.0
|
||||
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)
|
||||
}
|
||||
|
||||
private func encodeText(_ string: String, _ key: Int) -> String {
|
||||
var result = ""
|
||||
for c in string.unicodeScalars {
|
||||
result.append(Character(UnicodeScalar(UInt32(Int(c.value) + key))!))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func doesViewTreeDisableInteractiveTransitionGestureRecognizer(_ view: UIView) -> Bool {
|
||||
if view.disablesInteractiveTransitionGestureRecognizer {
|
||||
return true
|
||||
}
|
||||
if let superview = view.superview {
|
||||
return doesViewTreeDisableInteractiveTransitionGestureRecognizer(superview)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private let transitionClass: AnyClass? = NSClassFromString(encodeText("VJUsbotjujpoWjfx", -1))
|
||||
private let previewingClass: AnyClass? = NSClassFromString("UIVisualEffectView")
|
||||
private let previewingActionGroupClass: AnyClass? = NSClassFromString("UIInterfaceActionGroupView")
|
||||
private func checkIsPreviewingView(_ view: UIView) -> Bool {
|
||||
if let transitionClass = transitionClass, view.isKind(of: transitionClass) {
|
||||
for subview in view.subviews {
|
||||
if let previewingClass = previewingClass, subview.isKind(of: previewingClass) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private func applyThemeToPreviewingView(_ view: UIView, accentColor: UIColor, darkBlur: Bool) {
|
||||
if let previewingActionGroupClass = previewingActionGroupClass, view.isKind(of: previewingActionGroupClass) {
|
||||
view.tintColor = accentColor
|
||||
if darkBlur {
|
||||
applyThemeToPreviewingEffectView(view)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for subview in view.subviews {
|
||||
applyThemeToPreviewingView(subview, accentColor: accentColor, darkBlur: darkBlur)
|
||||
}
|
||||
}
|
||||
|
||||
private func applyThemeToPreviewingEffectView(_ view: UIView) {
|
||||
if let previewingClass = previewingClass, view.isKind(of: previewingClass) {
|
||||
if let view = view as? UIVisualEffectView {
|
||||
view.effect = UIBlurEffect(style: .dark)
|
||||
}
|
||||
}
|
||||
|
||||
for subview in view.subviews {
|
||||
applyThemeToPreviewingEffectView(subview)
|
||||
}
|
||||
}
|
||||
|
||||
private func getFirstResponderAndAccessoryHeight(_ view: UIView, _ accessoryHeight: CGFloat? = nil) -> (UIView?, CGFloat?) {
|
||||
if view.isFirstResponder {
|
||||
return (view, accessoryHeight)
|
||||
} else {
|
||||
var updatedAccessoryHeight = accessoryHeight
|
||||
if let view = view as? WindowInputAccessoryHeightProvider {
|
||||
updatedAccessoryHeight = view.getWindowInputAccessoryHeight()
|
||||
}
|
||||
for subview in view.subviews {
|
||||
let (result, resultHeight) = getFirstResponderAndAccessoryHeight(subview, updatedAccessoryHeight)
|
||||
if let result = result {
|
||||
return (result, resultHeight)
|
||||
}
|
||||
}
|
||||
return (nil, nil)
|
||||
}
|
||||
}
|
||||
|
||||
public final class WindowHostView {
|
||||
@@ -147,6 +258,7 @@ public final class WindowHostView {
|
||||
var updateToInterfaceOrientation: (() -> Void)?
|
||||
var isUpdatingOrientationLayout = false
|
||||
var hitTest: ((CGPoint, UIEvent?) -> UIView?)?
|
||||
var invalidateDeferScreenEdgeGesture: (() -> Void)?
|
||||
|
||||
init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void, updateDeferScreenEdgeGestures: @escaping (UIRectEdge) -> Void) {
|
||||
self.view = view
|
||||
@@ -163,12 +275,23 @@ public struct WindowTracingTags {
|
||||
|
||||
public protocol WindowHost {
|
||||
func present(_ controller: ViewController, on level: PresentationSurfaceLevel)
|
||||
func invalidateDeferScreenEdgeGestures()
|
||||
}
|
||||
|
||||
private func layoutMetricsForScreenSize(_ size: CGSize) -> LayoutMetrics {
|
||||
return LayoutMetrics(widthClass: .compact, heightClass: .compact)
|
||||
}
|
||||
|
||||
private final class KeyboardGestureRecognizerDelegate: NSObject, UIGestureRecognizerDelegate {
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
public class Window1 {
|
||||
public let hostView: WindowHostView
|
||||
|
||||
@@ -180,13 +303,21 @@ public class Window1 {
|
||||
|
||||
private var windowLayout: WindowLayout
|
||||
private var updatingLayout: UpdatingLayout?
|
||||
private var updatedContainerLayout: ContainerViewLayout?
|
||||
private var upperKeyboardInputPositionBound: CGFloat?
|
||||
private var cachedWindowSubviewCount: Int = 0
|
||||
private var cachedHasPreview: Bool = false
|
||||
|
||||
private let presentationContext: PresentationContext
|
||||
|
||||
private var tracingStatusBarsInvalidated = false
|
||||
private var shouldUpdateDeferScreenEdgeGestures = false
|
||||
|
||||
private var statusBarHidden = false
|
||||
|
||||
public var previewThemeAccentColor: UIColor = .blue
|
||||
public var previewThemeDarkBlur: Bool = false
|
||||
|
||||
public private(set) var forceInCallStatusBarText: String? = nil
|
||||
public var inCallNavigate: (() -> Void)? {
|
||||
didSet {
|
||||
@@ -194,6 +325,10 @@ public class Window1 {
|
||||
}
|
||||
}
|
||||
|
||||
private let keyboardGestureRecognizerDelegate = KeyboardGestureRecognizerDelegate()
|
||||
private var keyboardGestureBeginLocation: CGPoint?
|
||||
private var keyboardGestureAccessoryHeight: CGFloat?
|
||||
|
||||
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
|
||||
self.hostView = hostView
|
||||
|
||||
@@ -209,14 +344,16 @@ public class Window1 {
|
||||
statusBarHeight = 20.0
|
||||
}
|
||||
|
||||
let minimized: Bool
|
||||
if let keyboardManager = self.keyboardManager {
|
||||
minimized = keyboardManager.minimized
|
||||
} else {
|
||||
minimized = false
|
||||
let boundsSize = self.hostView.view.bounds.size
|
||||
|
||||
var onScreenNavigationHeight: CGFloat?
|
||||
if (boundsSize.width.isEqual(to: 375.0) && boundsSize.height.isEqual(to: 812.0)) || boundsSize.height.isEqual(to: 375.0) && boundsSize.width.isEqual(to: 812.0) {
|
||||
onScreenNavigationHeight = 20.0
|
||||
}
|
||||
|
||||
self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, inputMinimized: minimized)
|
||||
let safeInsets = UIEdgeInsets()
|
||||
|
||||
self.windowLayout = WindowLayout(size: boundsSize, metrics: layoutMetricsForScreenSize(self.hostView.view.bounds.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: self.forceInCallStatusBarText, inputHeight: 0.0, safeInsets: safeInsets, onScreenNavigationHeight: onScreenNavigationHeight, upperKeyboardInputPositionBound: nil)
|
||||
self.presentationContext = PresentationContext()
|
||||
|
||||
self.hostView.present = { [weak self] controller, level in
|
||||
@@ -247,12 +384,8 @@ public class Window1 {
|
||||
return self?.hitTest(point, with: event)
|
||||
}
|
||||
|
||||
self.keyboardManager?.minimizedUpdated = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateLayout { current in
|
||||
current.update(inputMinimized: strongSelf.keyboardManager!.minimized, transition: .immediate, overrideTransition: false)
|
||||
}
|
||||
}
|
||||
self.hostView.invalidateDeferScreenEdgeGesture = { [weak self] in
|
||||
self?.invalidateDeferScreenEdgeGestures()
|
||||
}
|
||||
|
||||
self.presentationContext.view = self.hostView.view
|
||||
@@ -287,6 +420,22 @@ public class Window1 {
|
||||
strongSelf.updateLayout { $0.update(inputHeight: keyboardHeight.isLessThanOrEqualTo(0.0) ? nil : keyboardHeight, transition: .animated(duration: duration, curve: transitionCurve), overrideTransition: false) }
|
||||
}
|
||||
})
|
||||
|
||||
let recognizer = WindowPanRecognizer(target: self, action: #selector(self.panGesture(_:)))
|
||||
recognizer.cancelsTouchesInView = false
|
||||
recognizer.delaysTouchesBegan = false
|
||||
recognizer.delaysTouchesEnded = false
|
||||
recognizer.delegate = self.keyboardGestureRecognizerDelegate
|
||||
recognizer.began = { [weak self] point in
|
||||
self?.panGestureBegan(location: point)
|
||||
}
|
||||
recognizer.moved = { [weak self] point in
|
||||
self?.panGestureMoved(location: point)
|
||||
}
|
||||
recognizer.ended = { [weak self] point, velocity in
|
||||
self?.panGestureEnded(location: point, velocity: velocity)
|
||||
}
|
||||
self.hostView.view.addGestureRecognizer(recognizer)
|
||||
}
|
||||
|
||||
public required init(coder aDecoder: NSCoder) {
|
||||
@@ -317,6 +466,11 @@ public class Window1 {
|
||||
self.hostView.view.setNeedsLayout()
|
||||
}
|
||||
|
||||
public func invalidateDeferScreenEdgeGestures() {
|
||||
self.shouldUpdateDeferScreenEdgeGestures = true
|
||||
self.hostView.view.setNeedsLayout()
|
||||
}
|
||||
|
||||
public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
|
||||
for view in self.hostView.view.subviews.reversed() {
|
||||
if NSStringFromClass(type(of: view)) == "UITransitionView" {
|
||||
@@ -391,11 +545,25 @@ public class Window1 {
|
||||
}
|
||||
|
||||
private func layoutSubviews() {
|
||||
if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
|
||||
var hasPreview = false
|
||||
var updatedHasPreview = false
|
||||
for subview in self.hostView.view.subviews {
|
||||
if checkIsPreviewingView(subview) {
|
||||
applyThemeToPreviewingView(subview, accentColor: self.previewThemeAccentColor, darkBlur: self.previewThemeDarkBlur)
|
||||
hasPreview = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if hasPreview != self.cachedHasPreview {
|
||||
self.cachedHasPreview = hasPreview
|
||||
updatedHasPreview = true
|
||||
}
|
||||
|
||||
if self.tracingStatusBarsInvalidated || updatedHasPreview, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
|
||||
self.tracingStatusBarsInvalidated = false
|
||||
|
||||
if self.statusBarHidden {
|
||||
statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, animated: false)
|
||||
statusBarManager.updateState(surfaces: [], forceInCallStatusBarText: nil, forceHiddenBySystemWindows: false, animated: false)
|
||||
} else {
|
||||
var statusBarSurfaces: [StatusBarSurface] = []
|
||||
for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) {
|
||||
@@ -415,7 +583,8 @@ public class Window1 {
|
||||
animatedUpdate = true
|
||||
}
|
||||
}
|
||||
statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, animated: animatedUpdate)
|
||||
self.cachedWindowSubviewCount = self.hostView.view.window?.subviews.count ?? 0
|
||||
statusBarManager.updateState(surfaces: statusBarSurfaces, forceInCallStatusBarText: self.forceInCallStatusBarText, forceHiddenBySystemWindows: hasPreview, animated: animatedUpdate)
|
||||
}
|
||||
|
||||
var keyboardSurfaces: [KeyboardSurface] = []
|
||||
@@ -429,7 +598,13 @@ public class Window1 {
|
||||
keyboardManager.surfaces = keyboardSurfaces
|
||||
self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations())
|
||||
|
||||
self.hostView.updateDeferScreenEdgeGestures(self.presentationContext.combinedDeferScreenEdgeGestures())
|
||||
self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures())
|
||||
|
||||
self.shouldUpdateDeferScreenEdgeGestures = false
|
||||
} else if self.shouldUpdateDeferScreenEdgeGestures {
|
||||
self.shouldUpdateDeferScreenEdgeGestures = false
|
||||
|
||||
self.hostView.updateDeferScreenEdgeGestures(self.collectScreenEdgeGestures())
|
||||
}
|
||||
|
||||
if !UIWindow.isDeviceRotating() {
|
||||
@@ -467,11 +642,17 @@ public class Window1 {
|
||||
|
||||
private func updateLayout(_ update: (inout UpdatingLayout) -> ()) {
|
||||
if self.updatingLayout == nil {
|
||||
self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
|
||||
var updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
|
||||
update(&updatingLayout)
|
||||
if updatingLayout.layout != self.windowLayout {
|
||||
self.updatingLayout = updatingLayout
|
||||
self.hostView.view.setNeedsLayout()
|
||||
}
|
||||
} else {
|
||||
update(&self.updatingLayout!)
|
||||
self.hostView.view.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
private func commitUpdatingLayout() {
|
||||
if let updatingLayout = self.updatingLayout {
|
||||
@@ -494,13 +675,33 @@ public class Window1 {
|
||||
self.tracingStatusBarsInvalidated = true
|
||||
self.hostView.view.setNeedsLayout()
|
||||
}
|
||||
self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized)
|
||||
let previousInputOffset = inputHeightOffsetForLayout(self.windowLayout)
|
||||
self.windowLayout = WindowLayout(size: updatingLayout.layout.size, metrics: layoutMetricsForScreenSize(updatingLayout.layout.size), statusBarHeight: statusBarHeight, forceInCallStatusBarText: updatingLayout.layout.forceInCallStatusBarText, inputHeight: updatingLayout.layout.inputHeight, safeInsets: updatingLayout.layout.safeInsets, onScreenNavigationHeight: updatingLayout.layout.onScreenNavigationHeight, upperKeyboardInputPositionBound: updatingLayout.layout.upperKeyboardInputPositionBound)
|
||||
|
||||
self._rootController?.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition)
|
||||
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition)
|
||||
let childLayout = containedLayoutForWindowLayout(self.windowLayout)
|
||||
let childLayoutUpdated = self.updatedContainerLayout != childLayout
|
||||
self.updatedContainerLayout = childLayout
|
||||
|
||||
if childLayoutUpdated {
|
||||
self._rootController?.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
self.presentationContext.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
|
||||
for controller in self.topLevelOverlayControllers {
|
||||
controller.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: updatingLayout.transition)
|
||||
controller.containerLayoutUpdated(childLayout, transition: updatingLayout.transition)
|
||||
}
|
||||
}
|
||||
|
||||
let updatedInputOffset = inputHeightOffsetForLayout(self.windowLayout)
|
||||
if !previousInputOffset.isEqual(to: updatedInputOffset) {
|
||||
let hide = updatingLayout.transition.isAnimated && updatingLayout.layout.upperKeyboardInputPositionBound == updatingLayout.layout.size.height
|
||||
self.keyboardManager?.updateInteractiveInputOffset(updatedInputOffset, transition: updatingLayout.transition, completion: { [weak self] in
|
||||
if let strongSelf = self, hide {
|
||||
strongSelf.updateLayout {
|
||||
$0.update(upperKeyboardInputPositionBound: nil, transition: .immediate, overrideTransition: false)
|
||||
}
|
||||
strongSelf.hostView.view.endEditing(true)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,4 +714,83 @@ public class Window1 {
|
||||
public func presentNative(_ controller: UIViewController) {
|
||||
|
||||
}
|
||||
|
||||
private func panGestureBegan(location: CGPoint) {
|
||||
let keyboardGestureBeginLocation = location
|
||||
let view = self.hostView.view
|
||||
let (firstResponder, accessoryHeight) = getFirstResponderAndAccessoryHeight(view)
|
||||
if let inputHeight = self.windowLayout.inputHeight, !inputHeight.isZero, keyboardGestureBeginLocation.y < self.windowLayout.size.height - inputHeight - (accessoryHeight ?? 0.0) {
|
||||
var enableGesture = true
|
||||
if let view = self.hostView.view.hitTest(location, with: nil) {
|
||||
if doesViewTreeDisableInteractiveTransitionGestureRecognizer(view) {
|
||||
enableGesture = false
|
||||
}
|
||||
}
|
||||
if enableGesture, let _ = firstResponder {
|
||||
self.keyboardGestureBeginLocation = keyboardGestureBeginLocation
|
||||
self.keyboardGestureAccessoryHeight = accessoryHeight
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func panGestureMoved(location: CGPoint) {
|
||||
if let keyboardGestureBeginLocation = self.keyboardGestureBeginLocation {
|
||||
let currentLocation = location
|
||||
let deltaY = keyboardGestureBeginLocation.y - location.y
|
||||
if deltaY * deltaY >= 3.0 * 3.0 || self.windowLayout.upperKeyboardInputPositionBound != nil {
|
||||
self.updateLayout {
|
||||
$0.update(upperKeyboardInputPositionBound: currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0), transition: .immediate, overrideTransition: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func panGestureEnded(location: CGPoint, velocity: CGPoint?) {
|
||||
self.keyboardGestureBeginLocation = nil
|
||||
let currentLocation = location
|
||||
if let velocity = velocity, let inputHeight = self.windowLayout.inputHeight, velocity.y > 100.0 && currentLocation.y + (self.keyboardGestureAccessoryHeight ?? 0.0) > self.windowLayout.size.height - inputHeight {
|
||||
self.updateLayout {
|
||||
$0.update(upperKeyboardInputPositionBound: self.windowLayout.size.height, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false)
|
||||
}
|
||||
} else {
|
||||
self.updateLayout {
|
||||
$0.update(upperKeyboardInputPositionBound: nil, transition: .animated(duration: 0.25, curve: .spring), overrideTransition: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc func panGesture(_ recognizer: UIPanGestureRecognizer) {
|
||||
switch recognizer.state {
|
||||
case .began:
|
||||
self.panGestureBegan(location: recognizer.location(in: recognizer.view))
|
||||
case .changed:
|
||||
self.panGestureMoved(location: recognizer.location(in: recognizer.view))
|
||||
case .ended:
|
||||
self.panGestureEnded(location: recognizer.location(in: recognizer.view), velocity: recognizer.velocity(in: recognizer.view))
|
||||
case .cancelled:
|
||||
self.panGestureEnded(location: recognizer.location(in: recognizer.view), velocity: nil)
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func collectScreenEdgeGestures() -> UIRectEdge {
|
||||
var edges = self.presentationContext.combinedDeferScreenEdgeGestures()
|
||||
|
||||
for controller in self.topLevelOverlayControllers {
|
||||
if let controller = controller as? ViewController {
|
||||
edges = edges.union(controller.deferScreenEdgeGestures)
|
||||
}
|
||||
}
|
||||
|
||||
return edges
|
||||
}
|
||||
|
||||
public func forEachViewController(_ f: (ViewController) -> Bool) {
|
||||
for controller in self.presentationContext.controllers {
|
||||
if !f(controller) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
6
Display/WindowInputAccessoryHeightProvider.swift
Normal file
6
Display/WindowInputAccessoryHeightProvider.swift
Normal file
@@ -0,0 +1,6 @@
|
||||
import Foundation
|
||||
import UIKit
|
||||
|
||||
public protocol WindowInputAccessoryHeightProvider: class {
|
||||
func getWindowInputAccessoryHeight() -> CGFloat
|
||||
}
|
||||
80
Display/WindowPanRecognizer.swift
Normal file
80
Display/WindowPanRecognizer.swift
Normal file
@@ -0,0 +1,80 @@
|
||||
import Foundation
|
||||
|
||||
final class WindowPanRecognizer: UIGestureRecognizer {
|
||||
var began: ((CGPoint) -> Void)?
|
||||
var moved: ((CGPoint) -> Void)?
|
||||
var ended: ((CGPoint, CGPoint?) -> Void)?
|
||||
|
||||
private var previousPoints: [(CGPoint, Double)] = []
|
||||
|
||||
override func reset() {
|
||||
super.reset()
|
||||
|
||||
self.previousPoints.removeAll()
|
||||
}
|
||||
|
||||
private func addPoint(_ point: CGPoint) {
|
||||
self.previousPoints.append((point, CACurrentMediaTime()))
|
||||
if self.previousPoints.count > 6 {
|
||||
self.previousPoints.removeFirst()
|
||||
}
|
||||
}
|
||||
|
||||
private func estimateVerticalVelocity() -> CGFloat {
|
||||
let timestamp = CACurrentMediaTime()
|
||||
var sum: CGFloat = 0.0
|
||||
var count = 0
|
||||
if self.previousPoints.count > 1 {
|
||||
for i in 1 ..< self.previousPoints.count {
|
||||
if self.previousPoints[i].1 >= timestamp - 0.1 {
|
||||
sum += (self.previousPoints[i].0.y - self.previousPoints[i - 1].0.y) / CGFloat(self.previousPoints[i].1 - self.previousPoints[i - 1].1)
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if count != 0 {
|
||||
return sum / CGFloat(count * 5)
|
||||
} else {
|
||||
return 0.0
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesBegan(touches, with: event)
|
||||
|
||||
if let touch = touches.first {
|
||||
let location = touch.location(in: self.view)
|
||||
self.addPoint(location)
|
||||
self.began?(location)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesMoved(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesMoved(touches, with: event)
|
||||
|
||||
if let touch = touches.first {
|
||||
let location = touch.location(in: self.view)
|
||||
self.addPoint(location)
|
||||
self.moved?(location)
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesEnded(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesEnded(touches, with: event)
|
||||
|
||||
if let touch = touches.first {
|
||||
let location = touch.location(in: self.view)
|
||||
self.addPoint(location)
|
||||
self.ended?(location, CGPoint(x: 0.0, y: self.estimateVerticalVelocity()))
|
||||
}
|
||||
}
|
||||
|
||||
override func touchesCancelled(_ touches: Set<UITouch>, with event: UIEvent) {
|
||||
super.touchesCancelled(touches, with: event)
|
||||
|
||||
if let touch = touches.first {
|
||||
self.ended?(touch.location(in: self.view), nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user