no message

This commit is contained in:
Peter
2017-11-14 20:39:42 +03:00
parent f330a1dc07
commit dc9cec5a0e
38 changed files with 1159 additions and 313 deletions

View File

@@ -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 */,

View File

@@ -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)

View File

@@ -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()

View File

@@ -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

View File

@@ -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))

View File

@@ -1,6 +1,6 @@
import Foundation
public protocol ActionSheetItem {
func node() -> ActionSheetItemNode
func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode
func updateNode(_ node: ActionSheetItemNode) -> Void
}

View File

@@ -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() {

View File

@@ -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)
}

View File

@@ -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()

View File

@@ -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()
}

View 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
}
}

View File

@@ -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) {

View File

@@ -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 {

View File

@@ -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
}
}

View File

@@ -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() {

View File

@@ -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 {

View File

@@ -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()

View File

@@ -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?()
}
}
}

View File

@@ -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:

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -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)

View File

@@ -29,6 +29,7 @@ static NSMutableArray *notificationHandlers() {
}
}
//printf("***** %s\n", [aName cStringUsingEncoding:NSUTF8StringEncoding]);
[self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo];
}

View File

@@ -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()

View File

@@ -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

View File

@@ -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 {

View File

@@ -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()
}
}

View File

@@ -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()
}
}
}

View File

@@ -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)
}

View File

@@ -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 {

View File

@@ -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

View File

@@ -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)

View File

@@ -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 {

View File

@@ -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
}
}
}
}

View File

@@ -0,0 +1,6 @@
import Foundation
import UIKit
public protocol WindowInputAccessoryHeightProvider: class {
func getWindowInputAccessoryHeight() -> CGFloat
}

View 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)
}
}
}