mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-08 08:31:13 +00:00
no message
This commit is contained in:
parent
a4ea9ca8cf
commit
457a6ef8ee
@ -9,6 +9,7 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
D0078A681C92B21400DF6D92 /* StatusBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0078A671C92B21400DF6D92 /* StatusBar.swift */; };
|
||||
D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */; };
|
||||
D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */; };
|
||||
D015F7521D1AE08D00E269B5 /* ContainableController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7511D1AE08D00E269B5 /* ContainableController.swift */; };
|
||||
D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */; };
|
||||
D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D015F7571D1B467200E269B5 /* ActionSheetController.swift */; };
|
||||
@ -80,6 +81,7 @@
|
||||
D08E903C1D2417E000533158 /* ActionSheetButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */; };
|
||||
D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */; };
|
||||
D08E90471D243C2F00533158 /* HighlightTrackingButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */; };
|
||||
D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0A749941E3A9E7B00AD786E /* SwitchNode.swift */; };
|
||||
D0AE2CA61C94548900F2FD3C /* GenerateImage.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE2CA51C94548900F2FD3C /* GenerateImage.swift */; };
|
||||
D0AE3D4D1D25C816001CCE13 /* NavigationBarTransitionState.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0AE3D4C1D25C816001CCE13 /* NavigationBarTransitionState.swift */; };
|
||||
D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0B3671F1C94A53A00346D2E /* StatusBarProxyNode.swift */; };
|
||||
@ -102,6 +104,10 @@
|
||||
D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */; };
|
||||
D0CD12161CCFEB4E000DE7BC /* ScrollToTopProxyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */; };
|
||||
D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0D94A161D3814F900740E02 /* UniversalTapRecognizer.swift */; };
|
||||
D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */; };
|
||||
D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */; };
|
||||
D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA444F1E4DCBDE005FDCA7 /* AlertContentNode.swift */; };
|
||||
D0DA44521E4DCC11005FDCA7 /* TextAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DA44511E4DCC11005FDCA7 /* TextAlertController.swift */; };
|
||||
D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48531BF93D8A00F672FD /* TabBarController.swift */; };
|
||||
D0DC48561BF945DD00F672FD /* TabBarNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC48551BF945DD00F672FD /* TabBarNode.swift */; };
|
||||
D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */; };
|
||||
@ -125,6 +131,7 @@
|
||||
/* Begin PBXFileReference section */
|
||||
D0078A671C92B21400DF6D92 /* StatusBar.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusBar.swift; sourceTree = "<group>"; };
|
||||
D007B9A71D1D3B5400DA746D /* PresentableViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PresentableViewController.swift; sourceTree = "<group>"; };
|
||||
D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextFieldNode.swift; sourceTree = "<group>"; };
|
||||
D015F7511D1AE08D00E269B5 /* ContainableController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ContainableController.swift; sourceTree = "<group>"; };
|
||||
D015F7531D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SystemContainedControllerTransitionCoordinator.swift; sourceTree = "<group>"; };
|
||||
D015F7571D1B467200E269B5 /* ActionSheetController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetController.swift; sourceTree = "<group>"; };
|
||||
@ -199,6 +206,7 @@
|
||||
D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonItem.swift; sourceTree = "<group>"; };
|
||||
D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroup.swift; sourceTree = "<group>"; };
|
||||
D08E90461D243C2F00533158 /* HighlightTrackingButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HighlightTrackingButton.swift; sourceTree = "<group>"; };
|
||||
D0A749941E3A9E7B00AD786E /* SwitchNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SwitchNode.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@ -221,6 +229,10 @@
|
||||
D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetButtonNode.swift; sourceTree = "<group>"; };
|
||||
D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScrollToTopProxyView.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>"; };
|
||||
D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertControllerNode.swift; sourceTree = "<group>"; };
|
||||
D0DA444F1E4DCBDE005FDCA7 /* AlertContentNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AlertContentNode.swift; sourceTree = "<group>"; };
|
||||
D0DA44511E4DCC11005FDCA7 /* TextAlertController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextAlertController.swift; sourceTree = "<group>"; };
|
||||
D0DC48531BF93D8A00F672FD /* TabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarController.swift; sourceTree = "<group>"; };
|
||||
D0DC48551BF945DD00F672FD /* TabBarNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarNode.swift; sourceTree = "<group>"; };
|
||||
D0DC485E1BF949FB00F672FD /* TabBarContollerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TabBarContollerNode.swift; sourceTree = "<group>"; };
|
||||
@ -312,6 +324,8 @@
|
||||
children = (
|
||||
D0CD12151CCFEB4E000DE7BC /* ScrollToTopProxyView.swift */,
|
||||
D0E35A021DE473B900BC6096 /* HighlightableButton.swift */,
|
||||
D00C7CD11E3657570080C3D5 /* TextFieldNode.swift */,
|
||||
D0A749941E3A9E7B00AD786E /* SwitchNode.swift */,
|
||||
);
|
||||
name = Nodes;
|
||||
sourceTree = "<group>";
|
||||
@ -520,6 +534,7 @@
|
||||
D015F7551D1B142300E269B5 /* Tab Bar */,
|
||||
D015F7561D1B465600E269B5 /* Action Sheet */,
|
||||
D03725BF1D6DF57B007FC290 /* Context Menu */,
|
||||
D0DA444A1E4DCA36005FDCA7 /* Alert */,
|
||||
);
|
||||
name = Controllers;
|
||||
sourceTree = "<group>";
|
||||
@ -543,6 +558,17 @@
|
||||
name = "List Node";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0DA444A1E4DCA36005FDCA7 /* Alert */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
D0DA444B1E4DCA4A005FDCA7 /* AlertController.swift */,
|
||||
D0DA444D1E4DCA6E005FDCA7 /* AlertControllerNode.swift */,
|
||||
D0DA444F1E4DCBDE005FDCA7 /* AlertContentNode.swift */,
|
||||
D0DA44511E4DCC11005FDCA7 /* TextAlertController.swift */,
|
||||
);
|
||||
name = Alert;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
D0DC48521BF93D7C00F672FD /* Tabs */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -708,8 +734,10 @@
|
||||
D01E2BE01D90498E0066BF65 /* GridNodeScroller.swift in Sources */,
|
||||
D0C85DD61D1C600D00124894 /* ActionSheetButtonNode.swift in Sources */,
|
||||
D0C2DFD01CC4431D0044FF83 /* ListViewAccessoryItemNode.swift in Sources */,
|
||||
D0DA44501E4DCBDE005FDCA7 /* AlertContentNode.swift in Sources */,
|
||||
D0D94A171D3814F900740E02 /* UniversalTapRecognizer.swift in Sources */,
|
||||
D015F7581D1B467200E269B5 /* ActionSheetController.swift in Sources */,
|
||||
D0DA444C1E4DCA4A005FDCA7 /* AlertController.swift in Sources */,
|
||||
D0C2DFC91CC4431D0044FF83 /* ListView.swift in Sources */,
|
||||
D03E7DF91C96C5F200C07816 /* NSWeakReference.m in Sources */,
|
||||
D0DC48541BF93D8B00F672FD /* TabBarController.swift in Sources */,
|
||||
@ -727,9 +755,11 @@
|
||||
D05CC31B1B695A9600E235A3 /* NavigationTitleNode.swift in Sources */,
|
||||
D05CC31C1B695A9600E235A3 /* BarButtonItemWrapper.swift in Sources */,
|
||||
D0C2DFCF1CC4431D0044FF83 /* ListViewScroller.swift in Sources */,
|
||||
D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */,
|
||||
D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */,
|
||||
D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */,
|
||||
D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */,
|
||||
D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */,
|
||||
D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */,
|
||||
D03725C31D6DF7A6007FC290 /* ContextMenuAction.swift in Sources */,
|
||||
D007B9A81D1D3B5400DA746D /* PresentableViewController.swift in Sources */,
|
||||
@ -737,6 +767,7 @@
|
||||
D03725C11D6DF594007FC290 /* ContextMenuNode.swift in Sources */,
|
||||
D053CB611D22B4F200DD41DF /* CATracingLayer.m in Sources */,
|
||||
D01E2BE41D904A000066BF65 /* GridItem.swift in Sources */,
|
||||
D0DA44521E4DCC11005FDCA7 /* TextAlertController.swift in Sources */,
|
||||
D081229D1D19AA1C005F7395 /* ContainerViewLayout.swift in Sources */,
|
||||
D0C2DFC71CC4431D0044FF83 /* ListViewItemNode.swift in Sources */,
|
||||
D01E2BE21D9049F60066BF65 /* GridItemNode.swift in Sources */,
|
||||
@ -757,6 +788,7 @@
|
||||
D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */,
|
||||
D08E903E1D24187900533158 /* ActionSheetItemGroup.swift in Sources */,
|
||||
D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */,
|
||||
D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */,
|
||||
D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */,
|
||||
D05CC2A21B69326C00E235A3 /* Window.swift in Sources */,
|
||||
D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */,
|
||||
|
@ -7,12 +7,12 @@
|
||||
<key>Display.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>20</integer>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
<key>DisplayTests.xcscheme</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>14</integer>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>SuppressBuildableAutocreation</key>
|
||||
|
10
Display/AlertContentNode.swift
Normal file
10
Display/AlertContentNode.swift
Normal file
@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
open class AlertContentNode: ASDisplayNode {
|
||||
open func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
assertionFailure()
|
||||
|
||||
return CGSize()
|
||||
}
|
||||
}
|
57
Display/AlertController.swift
Normal file
57
Display/AlertController.swift
Normal file
@ -0,0 +1,57 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
open class AlertController: ViewController {
|
||||
private var controllerNode: AlertControllerNode {
|
||||
return self.displayNode as! AlertControllerNode
|
||||
}
|
||||
|
||||
private let contentNode: AlertContentNode
|
||||
|
||||
public init(contentNode: AlertContentNode) {
|
||||
self.contentNode = contentNode
|
||||
|
||||
super.init(navigationBar: NavigationBar())
|
||||
|
||||
self.navigationBar.isHidden = true
|
||||
}
|
||||
|
||||
required public init(coder aDecoder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
override open func loadDisplayNode() {
|
||||
self.displayNode = AlertControllerNode(contentNode: self.contentNode)
|
||||
self.displayNodeDidLoad()
|
||||
|
||||
self.controllerNode.dismiss = { [weak self] in
|
||||
if let strongSelf = self {
|
||||
strongSelf.controllerNode.animateOut {
|
||||
self?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override open func viewDidAppear(_ animated: Bool) {
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
self.controllerNode.animateIn()
|
||||
}
|
||||
|
||||
override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
super.containerLayoutUpdated(layout, transition: transition)
|
||||
|
||||
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
|
||||
}
|
||||
|
||||
public func dismiss() {
|
||||
self.presentingViewController?.dismiss(animated: false, completion: nil)
|
||||
}
|
||||
|
||||
public func dismissAnimated() {
|
||||
self.controllerNode.animateOut { [weak self] in
|
||||
self?.dismiss()
|
||||
}
|
||||
}
|
||||
}
|
80
Display/AlertControllerNode.swift
Normal file
80
Display/AlertControllerNode.swift
Normal file
@ -0,0 +1,80 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
final class AlertControllerNode: ASDisplayNode {
|
||||
private let dimmingNode: ASDisplayNode
|
||||
private let containerNode: ASDisplayNode
|
||||
private let effectNode: ASDisplayNode
|
||||
private let contentNode: AlertContentNode
|
||||
|
||||
var dismiss: (() -> Void)?
|
||||
|
||||
init(contentNode: AlertContentNode) {
|
||||
self.dimmingNode = ASDisplayNode()
|
||||
self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
|
||||
|
||||
self.containerNode = ASDisplayNode()
|
||||
self.containerNode.backgroundColor = .white
|
||||
self.containerNode.layer.cornerRadius = 14.0
|
||||
self.containerNode.layer.masksToBounds = true
|
||||
|
||||
self.effectNode = ASDisplayNode(viewBlock: {
|
||||
let view = UIView()//UIVisualEffectView(effect: UIBlurEffect(style: .light))
|
||||
return view
|
||||
}, didLoad: nil)
|
||||
|
||||
self.contentNode = contentNode
|
||||
|
||||
super.init()
|
||||
|
||||
self.addSubnode(self.dimmingNode)
|
||||
|
||||
self.addSubnode(self.effectNode)
|
||||
|
||||
self.containerNode.addSubnode(self.contentNode)
|
||||
self.addSubnode(self.containerNode)
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.dimmingNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:))))
|
||||
}
|
||||
|
||||
func animateIn() {
|
||||
self.dimmingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
|
||||
self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
|
||||
self.containerNode.layer.animateSpring(from: 0.8 as NSNumber, to: 1.0 as NSNumber, keyPath: "transform.scale", duration: 0.5, initialVelocity: 0.0, removeOnCompletion: true, additive: false, completion: nil)
|
||||
}
|
||||
|
||||
func animateOut(completion: @escaping () -> Void) {
|
||||
self.dimmingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
|
||||
self.containerNode.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false, completion: { _ in
|
||||
completion()
|
||||
})
|
||||
}
|
||||
|
||||
func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
|
||||
transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
|
||||
|
||||
var insets = layout.insets(options: [.statusBar, .input])
|
||||
let maxWidth = min(340.0, layout.size.width - 70.0)
|
||||
insets.left = floor((layout.size.width - maxWidth) / 2.0)
|
||||
insets.right = floor((layout.size.width - maxWidth) / 2.0)
|
||||
let contentAvailableFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: CGSize(width: layout.size.width - insets.right, height: layout.size.height - insets.top - insets.bottom))
|
||||
let contentSize = self.contentNode.updateLayout(size: contentAvailableFrame.size, transition: transition)
|
||||
let containerSize = CGSize(width: contentSize.width, height: contentSize.height)
|
||||
let containerFrame = CGRect(origin: CGPoint(x: floor((layout.size.width - containerSize.width) / 2.0), y: contentAvailableFrame.minY + floor((contentAvailableFrame.size.height - containerSize.height) / 2.0)), size: containerSize)
|
||||
|
||||
transition.updateFrame(node: self.containerNode, frame: containerFrame)
|
||||
transition.updateFrame(node: self.effectNode, frame: containerFrame)
|
||||
transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size))
|
||||
}
|
||||
|
||||
@objc func dimmingNodeTapGesture(_ recognizer: UITapGestureRecognizer) {
|
||||
if case .ended = recognizer.state {
|
||||
self.dismiss?()
|
||||
}
|
||||
}
|
||||
}
|
@ -39,7 +39,7 @@ public extension CAAnimation {
|
||||
}
|
||||
|
||||
public extension CALayer {
|
||||
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) {
|
||||
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 {
|
||||
if timingFunction == kCAMediaTimingFunctionSpring {
|
||||
let animation = makeSpringAnimation(keyPath)
|
||||
animation.fromValue = from
|
||||
@ -59,7 +59,7 @@ public extension CALayer {
|
||||
animation.speed = speed * Float(animation.duration / duration)
|
||||
animation.isAdditive = additive
|
||||
|
||||
self.add(animation, forKey: keyPath)
|
||||
return animation
|
||||
} else {
|
||||
let k = Float(UIView.animationDurationFactor())
|
||||
var speed: Float = 1.0
|
||||
@ -84,9 +84,55 @@ public extension CALayer {
|
||||
animation.delegate = CALayerAnimationDelegate(completion: completion)
|
||||
}
|
||||
|
||||
self.add(animation, forKey: keyPath)
|
||||
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)
|
||||
self.add(animation, forKey: keyPath)
|
||||
}
|
||||
|
||||
public func animateGroup(_ animations: [CAAnimation], key: String) {
|
||||
let animationGroup = CAAnimationGroup()
|
||||
var timeOffset = 0.0
|
||||
for animation in animations {
|
||||
animation.beginTime = animation.beginTime + timeOffset
|
||||
timeOffset += animation.duration / Double(animation.speed)
|
||||
}
|
||||
animationGroup.animations = animations
|
||||
animationGroup.duration = timeOffset
|
||||
self.add(animationGroup, forKey: key)
|
||||
}
|
||||
|
||||
public func animateKeyframes(values: [AnyObject], duration: Double, keyPath: String, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
let k = Float(UIView.animationDurationFactor())
|
||||
var speed: Float = 1.0
|
||||
if k != 0 && k != 1 {
|
||||
speed = Float(1.0) / k
|
||||
}
|
||||
|
||||
let animation = CAKeyframeAnimation(keyPath: keyPath)
|
||||
animation.values = values
|
||||
var keyTimes: [NSNumber] = []
|
||||
for i in 0 ..< values.count {
|
||||
if i == 0 {
|
||||
keyTimes.append(0.0)
|
||||
} else if i == values.count - 1 {
|
||||
keyTimes.append(1.0)
|
||||
} else {
|
||||
keyTimes.append((Double(i) / Double(values.count - 1)) as NSNumber)
|
||||
}
|
||||
}
|
||||
animation.keyTimes = keyTimes
|
||||
animation.speed = speed
|
||||
animation.duration = duration
|
||||
if let completion = completion {
|
||||
animation.delegate = CALayerAnimationDelegate(completion: completion)
|
||||
}
|
||||
|
||||
self.add(animation, forKey: keyPath)
|
||||
}
|
||||
|
||||
public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
let animation = makeSpringBounceAnimation(keyPath, initialVelocity)
|
||||
@ -169,6 +215,10 @@ public extension CALayer {
|
||||
self.animate(from: from as NSNumber, to: to as NSNumber, keyPath: "bounds.origin.y", timingFunction: kCAMediaTimingFunctionEaseInEaseOut, duration: duration, mediaTimingFunction: mediaTimingFunction, additive: true)
|
||||
}
|
||||
|
||||
public func animatePositionKeyframes(values: [CGPoint], duration: Double, removeOnCompletion: Bool = true, completion: ((Bool) -> Void)? = nil) {
|
||||
self.animateKeyframes(values: values.map { NSValue(cgPoint: $0) }, duration: duration, keyPath: "position")
|
||||
}
|
||||
|
||||
public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
|
||||
if from == to {
|
||||
if let completion = completion {
|
||||
@ -179,7 +229,7 @@ public extension CALayer {
|
||||
var interrupted = false
|
||||
var completedPosition = false
|
||||
var completedBounds = false
|
||||
var partialCompletion: () -> Void = {
|
||||
let partialCompletion: () -> Void = {
|
||||
if interrupted || (completedPosition && completedBounds) {
|
||||
if let completion = completion {
|
||||
completion(!interrupted)
|
||||
|
@ -142,6 +142,12 @@ static void traceLayerSurfaces(int depth, CALayer * _Nonnull layer, NSMutableDic
|
||||
|
||||
@implementation CATracingLayer
|
||||
|
||||
- (void)setNeedsDisplay {
|
||||
}
|
||||
|
||||
- (void)displayIfNeeded {
|
||||
}
|
||||
|
||||
- (bool)isInvalidated {
|
||||
return [[self associatedObjectForKey:CATracingLayerInvalidatedKey] intValue] != 0;
|
||||
}
|
||||
|
@ -219,6 +219,38 @@ public extension ContainedViewLayoutTransition {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func updateBackgroundColor(node: ASDisplayNode, color: UIColor, completion: ((Bool) -> Void)? = nil) {
|
||||
if let nodeColor = node.backgroundColor, nodeColor.isEqual(color) {
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
switch self {
|
||||
case .immediate:
|
||||
node.backgroundColor = color
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
case let .animated(duration, curve):
|
||||
if let nodeColor = node.backgroundColor {
|
||||
node.backgroundColor = color
|
||||
node.layer.animate(from: nodeColor.cgColor, to: color.cgColor, keyPath: "backgroundColor", timingFunction: curve.timingFunction, duration: duration, completion: { result in
|
||||
if let completion = completion {
|
||||
completion(result)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
node.backgroundColor = color
|
||||
if let completion = completion {
|
||||
completion(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public protocol ContainableController: class {
|
||||
|
@ -22,13 +22,29 @@ public struct Font {
|
||||
}
|
||||
}
|
||||
|
||||
public static func light(_ size: CGFloat) -> UIFont {
|
||||
if #available(iOS 8.2, *) {
|
||||
return UIFont.systemFont(ofSize: size, weight: UIFontWeightLight)
|
||||
} else {
|
||||
return CTFontCreateWithName("HelveticaNeue-Light" as CFString, size, nil)
|
||||
}
|
||||
}
|
||||
|
||||
public static func italic(_ size: CGFloat) -> UIFont {
|
||||
return UIFont.italicSystemFont(ofSize: size)
|
||||
}
|
||||
}
|
||||
|
||||
public extension NSAttributedString {
|
||||
convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black) {
|
||||
self.init(string: string, attributes: [NSFontAttributeName: font, NSForegroundColorAttributeName as String: textColor])
|
||||
convenience init(string: String, font: UIFont, textColor: UIColor = UIColor.black, paragraphAlignment: NSTextAlignment? = nil) {
|
||||
var attributes: [String: AnyObject] = [:]
|
||||
attributes[NSFontAttributeName] = font
|
||||
attributes[NSForegroundColorAttributeName] = textColor
|
||||
if let paragraphAlignment = paragraphAlignment {
|
||||
let paragraphStyle = NSMutableParagraphStyle()
|
||||
paragraphStyle.alignment = paragraphAlignment
|
||||
attributes[NSParagraphStyleAttributeName] = paragraphStyle
|
||||
}
|
||||
self.init(string: string, attributes: attributes)
|
||||
}
|
||||
}
|
||||
|
@ -61,6 +61,41 @@ public func generateImage(_ size: CGSize, contextGenerator: (CGSize, CGContext)
|
||||
return UIImage(cgImage: image, scale: selectedScale, orientation: .up)
|
||||
}
|
||||
|
||||
public func generateImage(_ size: CGSize, opaque: Bool = false, scale: CGFloat? = nil, rotatedContext: (CGSize, CGContext) -> Void) -> UIImage? {
|
||||
let selectedScale = scale ?? deviceScale
|
||||
let scaledSize = CGSize(width: size.width * selectedScale, height: size.height * selectedScale)
|
||||
let bytesPerRow = (4 * Int(scaledSize.width) + 15) & (~15)
|
||||
let length = bytesPerRow * Int(scaledSize.height)
|
||||
let bytes = malloc(length)!.assumingMemoryBound(to: Int8.self)
|
||||
|
||||
guard let provider = CGDataProvider(dataInfo: bytes, data: bytes, size: length, releaseData: { bytes, _, _ in
|
||||
free(bytes)
|
||||
})
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let bitmapInfo = CGBitmapInfo(rawValue: CGBitmapInfo.byteOrder32Little.rawValue | (opaque ? CGImageAlphaInfo.noneSkipFirst.rawValue : CGImageAlphaInfo.premultipliedFirst.rawValue))
|
||||
|
||||
guard let context = CGContext(data: bytes, width: Int(scaledSize.width), height: Int(scaledSize.height), bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: deviceColorSpace, bitmapInfo: bitmapInfo.rawValue) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
context.scaleBy(x: selectedScale, y: selectedScale)
|
||||
context.translateBy(x: size.width / 2.0, y: size.height / 2.0)
|
||||
context.scaleBy(x: 1.0, y: -1.0)
|
||||
context.translateBy(x: -size.width / 2.0, y: -size.height / 2.0)
|
||||
|
||||
rotatedContext(size, context)
|
||||
|
||||
guard 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)
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return UIImage(cgImage: image, scale: selectedScale, orientation: .up)
|
||||
}
|
||||
|
||||
public func generateFilledCircleImage(diameter: CGFloat, color: UIColor?, backgroundColor: UIColor? = nil) -> UIImage? {
|
||||
return generateImage(CGSize(width: diameter, height: diameter), contextGenerator: { size, context in
|
||||
context.clear(CGRect(origin: CGPoint(), size: size))
|
||||
|
@ -11,4 +11,5 @@ public protocol GridSection {
|
||||
public protocol GridItem {
|
||||
var section: GridSection? { get }
|
||||
func node(layout: GridNodeLayout) -> GridItemNode
|
||||
func update(node: GridItemNode)
|
||||
}
|
||||
|
@ -256,7 +256,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
|
||||
for updatedItem in transaction.updateItems {
|
||||
self.items[updatedItem.index] = updatedItem.item
|
||||
if let itemNode = self.itemNodes[updatedItem.index] {
|
||||
//update node
|
||||
updatedItem.item.update(node: itemNode)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -17,6 +17,16 @@ private final class ListViewBackingLayer: CALayer {
|
||||
|
||||
override func setNeedsDisplay() {
|
||||
}
|
||||
|
||||
override func displayIfNeeded() {
|
||||
}
|
||||
|
||||
override func needsDisplay() -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
override func display() {
|
||||
}
|
||||
}
|
||||
|
||||
final class ListViewBackingView: UIView {
|
||||
@ -32,6 +42,9 @@ final class ListViewBackingView: UIView {
|
||||
override func layoutSubviews() {
|
||||
}
|
||||
|
||||
override func setNeedsDisplay() {
|
||||
}
|
||||
|
||||
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
|
||||
self.target?.touchesBegan(touches, with: event)
|
||||
}
|
||||
@ -1199,6 +1212,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
if updateAdjacentItemsIndices.isEmpty {
|
||||
completion(state, operations)
|
||||
} else {
|
||||
let updateAnimation: ListViewItemUpdateAnimation = animated ? .System(duration: insertionAnimationDuration) : .None
|
||||
|
||||
var updatedUpdateAdjacentItemsIndices = updateAdjacentItemsIndices
|
||||
|
||||
let nodeIndex = updateAdjacentItemsIndices.first!
|
||||
@ -1217,7 +1232,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
} else {
|
||||
self.async(f)
|
||||
}
|
||||
}, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: .None, completion: { layout, apply in
|
||||
}, node: referenceNode, width: state.visibleSize.width, previousItem: index == 0 ? nil : self.items[index - 1], nextItem: index == self.items.count - 1 ? nil : self.items[index + 1], animation: updateAnimation, completion: { layout, apply in
|
||||
var updatedState = state
|
||||
var updatedOperations = operations
|
||||
|
||||
@ -1321,20 +1336,24 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
var operations = inputOperations
|
||||
var updateIndicesAndItems = updateIndicesAndItems
|
||||
|
||||
if updateIndicesAndItems.isEmpty {
|
||||
completion(state, operations)
|
||||
} else {
|
||||
var updateItem = updateIndicesAndItems[0]
|
||||
if let previousNode = previousNodes[updateItem.index] {
|
||||
self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in
|
||||
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations)
|
||||
|
||||
updateIndicesAndItems.remove(at: 0)
|
||||
self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
})
|
||||
while true {
|
||||
if updateIndicesAndItems.isEmpty {
|
||||
completion(state, operations)
|
||||
break
|
||||
} else {
|
||||
updateIndicesAndItems.remove(at: 0)
|
||||
self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
let updateItem = updateIndicesAndItems[0]
|
||||
if let previousNode = previousNodes[updateItem.index] {
|
||||
self.nodeForItem(synchronous: synchronous, item: updateItem.item, previousNode: previousNode, index: updateItem.index, previousItem: updateItem.index == 0 ? nil : self.items[updateItem.index - 1], nextItem: updateItem.index == (self.items.count - 1) ? nil : self.items[updateItem.index + 1], width: state.visibleSize.width, updateAnimation: animated ? .System(duration: insertionAnimationDuration) : .None, completion: { _, layout, apply in
|
||||
state.updateNodeAtItemIndex(updateItem.index, layout: layout, direction: updateItem.directionHint, animation: animated ? .System(duration: insertionAnimationDuration) : .None, apply: apply, operations: &operations)
|
||||
|
||||
updateIndicesAndItems.remove(at: 0)
|
||||
self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
})
|
||||
break
|
||||
} else {
|
||||
updateIndicesAndItems.remove(at: 0)
|
||||
//self.updateNodes(synchronous: synchronous, animated: animated, updateIndicesAndItems: updateIndicesAndItems, inputState: state, previousNodes: previousNodes, inputOperations: operations, completion: completion)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1406,13 +1425,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
takenAnimation = true
|
||||
|
||||
if abs(layout.size.height - previousApparentHeight) > CGFloat(FLT_EPSILON) {
|
||||
node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in
|
||||
node.addApparentHeightAnimation(layout.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
|
||||
if let node = node {
|
||||
node.animateFrameTransition(progress)
|
||||
node.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
})
|
||||
node.transitionOffset += previousApparentHeight - layout.size.height
|
||||
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
if node.rotated {
|
||||
node.transitionOffset += previousApparentHeight - layout.size.height
|
||||
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1424,20 +1445,22 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
node.animateRemoved(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor())
|
||||
} else if animated {
|
||||
if !takenAnimation {
|
||||
node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in
|
||||
if let node = node {
|
||||
node.animateFrameTransition(progress)
|
||||
}
|
||||
})
|
||||
if !nodeFrame.size.height.isEqual(to: node.apparentHeight) {
|
||||
node.addApparentHeightAnimation(nodeFrame.size.height, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
|
||||
if let node = node {
|
||||
node.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if let previousFrame = previousFrame {
|
||||
if self.debugInfo {
|
||||
assert(true)
|
||||
}
|
||||
|
||||
let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y - previousApparentHeight + layout.size.height
|
||||
let transitionOffsetDelta = nodeFrame.origin.y - previousFrame.origin.y
|
||||
if node.rotated {
|
||||
node.transitionOffset -= transitionOffsetDelta
|
||||
node.transitionOffset -= transitionOffsetDelta - previousApparentHeight + layout.size.height
|
||||
} else {
|
||||
node.transitionOffset += transitionOffsetDelta
|
||||
}
|
||||
@ -1450,6 +1473,12 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
if self.debugInfo {
|
||||
assert(true)
|
||||
}
|
||||
if !node.rotated {
|
||||
if !node.insets.top.isZero {
|
||||
node.transitionOffset += node.insets.top
|
||||
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
}
|
||||
}
|
||||
node.animateInsertion(timestamp, duration: insertionAnimationDuration * UIView.animationDurationFactor(), short: false)
|
||||
}
|
||||
}
|
||||
@ -1679,20 +1708,23 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
|
||||
if abs(updatedApparentHeight - previousApparentHeight) > CGFloat(FLT_EPSILON) {
|
||||
node.apparentHeight = previousApparentHeight
|
||||
node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in
|
||||
node.animateFrameTransition(0.0, previousApparentHeight)
|
||||
node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
|
||||
if let node = node {
|
||||
node.animateFrameTransition(progress)
|
||||
node.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
})
|
||||
|
||||
let insetPart: CGFloat = previousInsets.top - layout.insets.top
|
||||
node.transitionOffset += previousApparentHeight - layout.size.height - insetPart
|
||||
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
if node.rotated {
|
||||
node.transitionOffset += previousApparentHeight - layout.size.height - insetPart
|
||||
node.addTransitionOffsetAnimation(0.0, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp)
|
||||
}
|
||||
} else {
|
||||
if node.shouldAnimateHorizontalFrameTransition() {
|
||||
node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress in
|
||||
node.addApparentHeightAnimation(updatedApparentHeight, duration: insertionAnimationDuration * UIView.animationDurationFactor(), beginAt: timestamp, update: { [weak node] progress, currentValue in
|
||||
if let node = node {
|
||||
node.animateFrameTransition(progress)
|
||||
node.animateFrameTransition(progress, currentValue)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -1957,13 +1989,15 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
previousLowerBound = previousFrame.maxY
|
||||
}
|
||||
} else {
|
||||
offset = previousNode.apparentFrame.minY - previousFrame.minY
|
||||
if previousNode.canBeUsedAsScrollToItemAnchor {
|
||||
offset = previousNode.apparentFrame.minY - previousFrame.minY
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if offset == nil {
|
||||
let updatedUpperBound = self.itemNodes[0].apparentFrame.minY
|
||||
let updatedLowerBound = self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY
|
||||
let updatedLowerBound = max(self.itemNodes[self.itemNodes.count - 1].apparentFrame.maxY, self.visibleSize.height)
|
||||
|
||||
switch scrollToItem.directionHint {
|
||||
case .Up:
|
||||
@ -2161,7 +2195,10 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
case let .animated(duration, curve):
|
||||
let previousFrame = headerNode.frame
|
||||
headerNode.frame = headerFrame
|
||||
let offset = -(headerFrame.minY - previousFrame.minY + transition.2)
|
||||
var offset = headerFrame.minY - previousFrame.minY + transition.2
|
||||
if headerNode.isRotated {
|
||||
offset = -offset
|
||||
}
|
||||
switch curve {
|
||||
case .spring:
|
||||
transition.0.animateOffsetAdditive(node: headerNode, offset: offset)
|
||||
@ -2617,7 +2654,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
if strongSelf.items[index].selectable {
|
||||
strongSelf.highlightedItemIndex = index
|
||||
for itemNode in strongSelf.itemNodes {
|
||||
if itemNode.index == index {
|
||||
if itemNode.index == index && itemNode.canBeSelected {
|
||||
if true { //!(itemNode.hitTest(CGPoint(x: strongSelf.touchesPosition.x - itemNode.frame.minX, y: strongSelf.touchesPosition.y - itemNode.frame.minY), with: event) is UIControl) {
|
||||
if !itemNode.isLayerBacked {
|
||||
strongSelf.view.bringSubview(toFront: itemNode.view)
|
||||
@ -2663,7 +2700,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
return nil
|
||||
}
|
||||
|
||||
public func forEachItemNode(_ f: @noescape(ASDisplayNode) -> Void) {
|
||||
public func forEachItemNode(_ f: (ASDisplayNode) -> Void) {
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.index != nil {
|
||||
f(itemNode)
|
||||
@ -2709,13 +2746,17 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
|
||||
self.highlightedItemIndex = index
|
||||
for itemNode in self.itemNodes {
|
||||
if itemNode.index == index {
|
||||
if !itemNode.isLayerBacked {
|
||||
self.view.bringSubview(toFront: itemNode.view)
|
||||
for (_, headerNode) in self.itemHeaderNodes {
|
||||
self.view.bringSubview(toFront: headerNode.view)
|
||||
if itemNode.canBeSelected {
|
||||
if !itemNode.isLayerBacked {
|
||||
self.view.bringSubview(toFront: itemNode.view)
|
||||
for (_, headerNode) in self.itemHeaderNodes {
|
||||
self.view.bringSubview(toFront: headerNode.view)
|
||||
}
|
||||
}
|
||||
itemNode.setHighlighted(true, animated: false)
|
||||
} else {
|
||||
self.highlightedItemIndex = nil
|
||||
}
|
||||
itemNode.setHighlighted(true, animated: false)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,14 @@ import SwiftSignalKit
|
||||
public enum ListViewItemUpdateAnimation {
|
||||
case None
|
||||
case System(duration: Double)
|
||||
|
||||
public var isAnimated: Bool {
|
||||
if case .None = self {
|
||||
return false
|
||||
} else {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public struct ListViewItemConfigureNodeFlags: OptionSet {
|
||||
|
@ -17,6 +17,7 @@ public protocol ListViewItemHeader: class {
|
||||
open class ListViewItemHeaderNode: ASDisplayNode {
|
||||
private final var spring: ListViewItemSpring?
|
||||
let wantsScrollDynamics: Bool
|
||||
let isRotated: Bool
|
||||
final private(set) var internalStickLocationDistanceFactor: CGFloat = 0.0
|
||||
final var internalStickLocationDistance: CGFloat = 0.0
|
||||
private var isFlashingOnScrolling = false
|
||||
@ -50,8 +51,9 @@ open class ListViewItemHeaderNode: ASDisplayNode {
|
||||
open func updateFlashingOnScrolling(_ isFlashingOnScrolling: Bool, animated: Bool) {
|
||||
}
|
||||
|
||||
public init(dynamicBounce: Bool = false) {
|
||||
public init(dynamicBounce: Bool = false, isRotated: Bool = false) {
|
||||
self.wantsScrollDynamics = dynamicBounce
|
||||
self.isRotated = isRotated
|
||||
if dynamicBounce {
|
||||
self.spring = ListViewItemSpring(stiffness: -280.0, damping: -24.0, mass: 0.85)
|
||||
}
|
||||
|
@ -83,11 +83,18 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
|
||||
public final var scrollPositioningInsets: UIEdgeInsets = UIEdgeInsets()
|
||||
|
||||
public final var canBeUsedAsScrollToItemAnchor: Bool = true
|
||||
|
||||
open var canBeSelected: Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
public final var insets: UIEdgeInsets = UIEdgeInsets() {
|
||||
didSet {
|
||||
let effectiveInsets = self.insets
|
||||
self.frame = CGRect(origin: self.frame.origin, size: CGSize(width: self.contentSize.width, height: self.contentSize.height + effectiveInsets.top + effectiveInsets.bottom))
|
||||
self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size)
|
||||
let bounds = self.bounds
|
||||
self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size)
|
||||
|
||||
if oldValue != self.insets {
|
||||
if let accessoryItemNode = self.accessoryItemNode {
|
||||
@ -110,14 +117,16 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
private var contentOffset: CGFloat = 0.0 {
|
||||
didSet {
|
||||
let effectiveInsets = self.insets
|
||||
self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size)
|
||||
let bounds = self.bounds
|
||||
self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size)
|
||||
}
|
||||
}
|
||||
|
||||
public var transitionOffset: CGFloat = 0.0 {
|
||||
didSet {
|
||||
let effectiveInsets = self.insets
|
||||
self.bounds = CGRect(origin: CGPoint(x: 0.0, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: self.bounds.size)
|
||||
let bounds = self.bounds
|
||||
self.bounds = CGRect(origin: CGPoint(x: bounds.origin.x, y: -effectiveInsets.top + self.contentOffset + self.transitionOffset), size: bounds.size)
|
||||
}
|
||||
}
|
||||
|
||||
@ -378,12 +387,12 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
self.setAnimationForKey("insets", animation: animation)
|
||||
}
|
||||
|
||||
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat) -> Void)? = nil) {
|
||||
public func addApparentHeightAnimation(_ value: CGFloat, duration: Double, beginAt: Double, update: ((CGFloat, CGFloat) -> Void)? = nil) {
|
||||
let animation = ListViewAnimation(from: self.apparentHeight, to: value, duration: duration, curve: listViewAnimationCurveSystem, beginAt: beginAt, update: { [weak self] progress, currentValue in
|
||||
if let strongSelf = self {
|
||||
strongSelf.apparentHeight = currentValue
|
||||
if let update = update {
|
||||
update(progress)
|
||||
update(progress, currentValue)
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -432,7 +441,7 @@ open class ListViewItemNode: ASDisplayNode {
|
||||
open func setHighlighted(_ highlighted: Bool, animated: Bool) {
|
||||
}
|
||||
|
||||
open func animateFrameTransition(_ progress: CGFloat) {
|
||||
open func animateFrameTransition(_ progress: CGFloat, _ currentValue: CGFloat) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -68,8 +68,13 @@ open class NavigationBar: ASDisplayNode {
|
||||
|
||||
private var itemTitleListenerKey: Int?
|
||||
private var itemTitleViewListenerKey: Int?
|
||||
|
||||
private var itemLeftButtonListenerKey: Int?
|
||||
private var itemLeftButtonSetEnabledListenerKey: Int?
|
||||
|
||||
private var itemRightButtonListenerKey: Int?
|
||||
private var itemRightButtonSetEnabledListenerKey: Int?
|
||||
|
||||
private var _item: UINavigationItem?
|
||||
public var item: UINavigationItem? {
|
||||
get {
|
||||
@ -80,10 +85,26 @@ open class NavigationBar: ASDisplayNode {
|
||||
previousValue.removeSetTitleListener(itemTitleListenerKey)
|
||||
self.itemTitleListenerKey = nil
|
||||
}
|
||||
|
||||
if let itemLeftButtonListenerKey = self.itemLeftButtonListenerKey {
|
||||
previousValue.removeSetLeftBarButtonItemListener(itemLeftButtonListenerKey)
|
||||
self.itemLeftButtonListenerKey = nil
|
||||
}
|
||||
|
||||
if let itemLeftButtonSetEnabledListenerKey = self.itemLeftButtonSetEnabledListenerKey {
|
||||
previousValue.leftBarButtonItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey)
|
||||
self.itemLeftButtonSetEnabledListenerKey = nil
|
||||
}
|
||||
|
||||
if let itemRightButtonListenerKey = self.itemRightButtonListenerKey {
|
||||
previousValue.removeSetRightBarButtonItemListener(itemRightButtonListenerKey)
|
||||
self.itemRightButtonListenerKey = nil
|
||||
}
|
||||
|
||||
if let itemRightButtonSetEnabledListenerKey = self.itemRightButtonSetEnabledListenerKey {
|
||||
previousValue.rightBarButtonItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey)
|
||||
self.itemRightButtonSetEnabledListenerKey = nil
|
||||
}
|
||||
}
|
||||
self._item = value
|
||||
|
||||
@ -105,16 +126,34 @@ open class NavigationBar: ASDisplayNode {
|
||||
}
|
||||
}
|
||||
|
||||
self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] _, _ in
|
||||
self.itemLeftButtonListenerKey = item.addSetLeftBarButtonItemListener { [weak self] previousItem, _, _ in
|
||||
if let strongSelf = self {
|
||||
if let itemLeftButtonSetEnabledListenerKey = strongSelf.itemLeftButtonSetEnabledListenerKey {
|
||||
previousItem?.removeSetEnabledListener(itemLeftButtonSetEnabledListenerKey)
|
||||
strongSelf.itemLeftButtonSetEnabledListenerKey = nil
|
||||
}
|
||||
|
||||
strongSelf.updateLeftButton()
|
||||
strongSelf.invalidateCalculatedLayout()
|
||||
strongSelf.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] _, _ in
|
||||
self.itemRightButtonListenerKey = item.addSetRightBarButtonItemListener { [weak self] previousItem, currentItem, _ in
|
||||
if let strongSelf = self {
|
||||
if let itemRightButtonSetEnabledListenerKey = strongSelf.itemRightButtonSetEnabledListenerKey {
|
||||
previousItem?.removeSetEnabledListener(itemRightButtonSetEnabledListenerKey)
|
||||
strongSelf.itemRightButtonSetEnabledListenerKey = nil
|
||||
}
|
||||
|
||||
if let currentItem = currentItem {
|
||||
strongSelf.itemRightButtonSetEnabledListenerKey = currentItem.addSetEnabledListener { _ in
|
||||
if let strongSelf = self {
|
||||
strongSelf.updateRightButton()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.updateRightButton()
|
||||
strongSelf.invalidateCalculatedLayout()
|
||||
strongSelf.setNeedsLayout()
|
||||
@ -197,6 +236,8 @@ open class NavigationBar: ASDisplayNode {
|
||||
self.backButtonArrow.removeFromSupernode()
|
||||
|
||||
self.leftButtonNode.text = leftBarButtonItem.title ?? ""
|
||||
self.leftButtonNode.bold = leftBarButtonItem.style == .done
|
||||
self.leftButtonNode.isEnabled = leftBarButtonItem.isEnabled
|
||||
if self.leftButtonNode.supernode == nil {
|
||||
self.clippingNode.addSubnode(self.leftButtonNode)
|
||||
}
|
||||
@ -230,6 +271,9 @@ open class NavigationBar: ASDisplayNode {
|
||||
if let item = self.item {
|
||||
if let rightBarButtonItem = item.rightBarButtonItem {
|
||||
self.rightButtonNode.text = rightBarButtonItem.title ?? ""
|
||||
self.rightButtonNode.image = rightBarButtonItem.image
|
||||
self.rightButtonNode.bold = rightBarButtonItem.style == .done
|
||||
self.rightButtonNode.isEnabled = rightBarButtonItem.isEnabled
|
||||
self.rightButtonNode.node = rightBarButtonItem.customDisplayNode
|
||||
if self.rightButtonNode.supernode == nil {
|
||||
self.clippingNode.addSubnode(self.rightButtonNode)
|
||||
|
@ -21,7 +21,35 @@ public class NavigationButtonNode: ASTextNode {
|
||||
set(value) {
|
||||
_text = value
|
||||
|
||||
self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||
}
|
||||
}
|
||||
|
||||
private var imageNode: ASImageNode?
|
||||
|
||||
private var _image: UIImage?
|
||||
public var image: UIImage? {
|
||||
get {
|
||||
return _image
|
||||
} set(value) {
|
||||
_image = value
|
||||
|
||||
if let _ = value {
|
||||
if self.imageNode == nil {
|
||||
let imageNode = ASImageNode()
|
||||
imageNode.displayWithoutProcessing = true
|
||||
imageNode.displaysAsynchronously = false
|
||||
self.imageNode = imageNode
|
||||
self.addSubnode(imageNode)
|
||||
}
|
||||
self.imageNode?.image = image
|
||||
} else if let imageNode = self.imageNode {
|
||||
imageNode.removeFromSupernode()
|
||||
self.imageNode = nil
|
||||
}
|
||||
|
||||
self.invalidateCalculatedLayout()
|
||||
self.setNeedsLayout()
|
||||
}
|
||||
}
|
||||
|
||||
@ -41,7 +69,7 @@ public class NavigationButtonNode: ASTextNode {
|
||||
public var color: UIColor = UIColor(0x007ee5) {
|
||||
didSet {
|
||||
if let text = self._text {
|
||||
self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -55,7 +83,7 @@ public class NavigationButtonNode: ASTextNode {
|
||||
if _bold != value {
|
||||
_bold = value
|
||||
|
||||
self.attributedString = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||
self.attributedText = NSAttributedString(string: text, attributes: self.attributesForCurrentState())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -78,6 +106,11 @@ public class NavigationButtonNode: ASTextNode {
|
||||
if let node = self.node {
|
||||
let nodeSize = node.measure(constrainedSize)
|
||||
return CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height))
|
||||
} else if let imageNode = self.imageNode {
|
||||
let nodeSize = imageNode.measure(constrainedSize)
|
||||
let size = CGSize(width: max(nodeSize.width, superSize.width), height: max(nodeSize.height, superSize.height))
|
||||
imageNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - nodeSize.width) / 2.0), y: floorToScreenPixels((size.height - nodeSize.height) / 2.0)), size: nodeSize)
|
||||
return size
|
||||
}
|
||||
return superSize
|
||||
}
|
||||
|
@ -226,9 +226,9 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle
|
||||
self.setViewControllers(controllers, animated: animated)
|
||||
}
|
||||
|
||||
public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise<Bool>?) {
|
||||
public func replaceTopController(_ controller: ViewController, animated: Bool, ready: ValuePromise<Bool>? = nil) {
|
||||
controller.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: {[weak self] _ in
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
ready?.set(true)
|
||||
var controllers = strongSelf.viewControllers
|
||||
@ -239,6 +239,21 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle
|
||||
}))
|
||||
}
|
||||
|
||||
public func replaceAllButRootController(_ controller: ViewController, animated: Bool, ready: ValuePromise<Bool>? = nil) {
|
||||
controller.containerLayoutUpdated(self.containerLayout, transition: .immediate)
|
||||
self.currentPushDisposable.set((controller.ready.get() |> take(1)).start(next: { [weak self] _ in
|
||||
if let strongSelf = self {
|
||||
ready?.set(true)
|
||||
var controllers = strongSelf.viewControllers
|
||||
while controllers.count > 1 {
|
||||
controllers.removeLast()
|
||||
}
|
||||
controllers.append(controller)
|
||||
strongSelf.setViewControllers(controllers, animated: animated)
|
||||
}
|
||||
}))
|
||||
}
|
||||
|
||||
open override func popViewController(animated: Bool) -> UIViewController? {
|
||||
var controller: UIViewController?
|
||||
var controllers = self.viewControllers
|
||||
|
@ -66,7 +66,9 @@ class StatusBarManager {
|
||||
|
||||
private func updateSurfaces(_ previousSurfaces: [StatusBarSurface]) {
|
||||
let statusBarFrame = self.host.statusBarFrame
|
||||
let statusBarView = self.host.statusBarView!
|
||||
guard let statusBarView = self.host.statusBarView else {
|
||||
return
|
||||
}
|
||||
|
||||
var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) })
|
||||
|
||||
|
10
Display/SwitchNode.swift
Normal file
10
Display/SwitchNode.swift
Normal file
@ -0,0 +1,10 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
open class SwitchNode: ASDisplayNode {
|
||||
override public init() {
|
||||
super.init(viewBlock: {
|
||||
return UISwitch()
|
||||
}, didLoad: nil)
|
||||
}
|
||||
}
|
@ -31,6 +31,10 @@ open class TabBarController: ViewController {
|
||||
_selectedIndex = index
|
||||
|
||||
self.updateSelectedIndex()
|
||||
} else {
|
||||
if let controller = self.currentController {
|
||||
controller.scrollToTop?()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -87,12 +91,13 @@ open class TabBarController: ViewController {
|
||||
|
||||
currentController.navigationItem.setTarget(self.navigationItem)
|
||||
displayNavigationBar = currentController.displayNavigationBar
|
||||
//currentController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
currentController.displayNode.recursivelyEnsureDisplaySynchronously(true)
|
||||
} else {
|
||||
self.navigationItem.title = nil
|
||||
self.navigationItem.leftBarButtonItem = nil
|
||||
self.navigationItem.rightBarButtonItem = nil
|
||||
self.navigationItem.titleView = nil
|
||||
self.navigationItem.backBarButtonItem = nil
|
||||
displayNavigationBar = false
|
||||
}
|
||||
if self.displayNavigationBar != displayNavigationBar {
|
||||
|
221
Display/TextAlertController.swift
Normal file
221
Display/TextAlertController.swift
Normal file
@ -0,0 +1,221 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public enum TextAlertActionType {
|
||||
case genericAction
|
||||
case defaultAction
|
||||
}
|
||||
|
||||
public struct TextAlertAction {
|
||||
public let type: TextAlertActionType
|
||||
public let title: String
|
||||
public let action: () -> Void
|
||||
|
||||
public init(type: TextAlertActionType, title: String, action: @escaping () -> Void) {
|
||||
self.type = type
|
||||
self.title = title
|
||||
self.action = action
|
||||
}
|
||||
}
|
||||
|
||||
private final class TextAlertContentActionNode: HighlightableButtonNode {
|
||||
private let backgroundNode: ASDisplayNode
|
||||
|
||||
let action: TextAlertAction
|
||||
|
||||
init(action: TextAlertAction) {
|
||||
self.backgroundNode = ASDisplayNode()
|
||||
self.backgroundNode.isLayerBacked = true
|
||||
self.backgroundNode.backgroundColor = UIColor(0xe0e5e6)
|
||||
self.backgroundNode.alpha = 0.0
|
||||
|
||||
self.action = action
|
||||
|
||||
super.init()
|
||||
|
||||
self.setTitle(action.title, with: action.type == .defaultAction ? Font.medium(17.0) : Font.regular(17.0), with: UIColor(0x007ee5), for: [])
|
||||
|
||||
self.highligthedChanged = { [weak self] value in
|
||||
if let strongSelf = self {
|
||||
if value {
|
||||
if strongSelf.backgroundNode.supernode == nil {
|
||||
strongSelf.insertSubnode(strongSelf.backgroundNode, at: 0)
|
||||
}
|
||||
strongSelf.backgroundNode.layer.removeAnimation(forKey: "opacity")
|
||||
strongSelf.backgroundNode.alpha = 1.0
|
||||
} else if !strongSelf.backgroundNode.alpha.isZero {
|
||||
strongSelf.backgroundNode.alpha = 0.0
|
||||
strongSelf.backgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.25)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override func didLoad() {
|
||||
super.didLoad()
|
||||
|
||||
self.addTarget(self, action: #selector(self.pressed), forControlEvents: .touchUpInside)
|
||||
}
|
||||
|
||||
@objc func pressed() {
|
||||
self.action.action()
|
||||
}
|
||||
|
||||
override func layout() {
|
||||
super.layout()
|
||||
|
||||
self.backgroundNode.frame = self.bounds
|
||||
}
|
||||
}
|
||||
|
||||
final class TextAlertContentNode: AlertContentNode {
|
||||
private let titleNode: ASTextNode?
|
||||
private let textNode: ASTextNode
|
||||
|
||||
private let actionNodesSeparator: ASDisplayNode
|
||||
private let actionNodes: [TextAlertContentActionNode]
|
||||
private let actionVerticalSeparators: [ASDisplayNode]
|
||||
|
||||
init(title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) {
|
||||
if let title = title {
|
||||
let titleNode = ASTextNode()
|
||||
titleNode.attributedText = title
|
||||
titleNode.displaysAsynchronously = false
|
||||
titleNode.isLayerBacked = true
|
||||
titleNode.maximumNumberOfLines = 1
|
||||
titleNode.truncationMode = .byTruncatingTail
|
||||
self.titleNode = titleNode
|
||||
} else {
|
||||
self.titleNode = nil
|
||||
}
|
||||
|
||||
self.textNode = ASTextNode()
|
||||
self.textNode.attributedText = text
|
||||
self.textNode.displaysAsynchronously = false
|
||||
self.textNode.isLayerBacked = true
|
||||
|
||||
self.actionNodesSeparator = ASDisplayNode()
|
||||
self.actionNodesSeparator.isLayerBacked = true
|
||||
self.actionNodesSeparator.backgroundColor = UIColor(0xc9cdd7)
|
||||
|
||||
self.actionNodes = actions.map { action -> TextAlertContentActionNode in
|
||||
return TextAlertContentActionNode(action: action)
|
||||
}
|
||||
|
||||
var actionVerticalSeparators: [ASDisplayNode] = []
|
||||
if actions.count > 1 {
|
||||
for _ in 0 ..< actions.count - 1 {
|
||||
let separatorNode = ASDisplayNode()
|
||||
separatorNode.isLayerBacked = true
|
||||
separatorNode.backgroundColor = UIColor(0xc9cdd7)
|
||||
actionVerticalSeparators.append(separatorNode)
|
||||
}
|
||||
}
|
||||
self.actionVerticalSeparators = actionVerticalSeparators
|
||||
|
||||
super.init()
|
||||
|
||||
if let titleNode = self.titleNode {
|
||||
self.addSubnode(titleNode)
|
||||
}
|
||||
self.addSubnode(self.textNode)
|
||||
|
||||
self.addSubnode(self.actionNodesSeparator)
|
||||
|
||||
for actionNode in self.actionNodes {
|
||||
self.addSubnode(actionNode)
|
||||
}
|
||||
|
||||
for separatorNode in self.actionVerticalSeparators {
|
||||
self.addSubnode(separatorNode)
|
||||
}
|
||||
}
|
||||
|
||||
override func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
|
||||
let insets = UIEdgeInsets(top: 18.0, left: 18.0, bottom: 18.0, right: 18.0)
|
||||
|
||||
var titleSize: CGSize?
|
||||
if let titleNode = self.titleNode {
|
||||
titleSize = titleNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
}
|
||||
let textSize = self.textNode.measure(CGSize(width: size.width - insets.left - insets.right, height: CGFloat.greatestFiniteMagnitude))
|
||||
|
||||
let actionsHeight: CGFloat = 44.0
|
||||
|
||||
var minActionsWidth: CGFloat = 0.0
|
||||
let maxActionWidth: CGFloat = floor(size.width / CGFloat(self.actionNodes.count))
|
||||
let actionTitleInsets: CGFloat = 8.0
|
||||
for actionNode in self.actionNodes {
|
||||
let actionTitleSize = actionNode.titleNode.measure(CGSize(width: maxActionWidth, height: actionsHeight))
|
||||
minActionsWidth += actionTitleSize.width + actionTitleInsets
|
||||
}
|
||||
|
||||
let resultSize: CGSize
|
||||
|
||||
if let titleNode = titleNode, let titleSize = titleSize {
|
||||
let contentWidth = max(max(titleSize.width, textSize.width), minActionsWidth)
|
||||
|
||||
let spacing: CGFloat = 6.0
|
||||
let titleFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - titleSize.width) / 2.0), y: insets.top), size: titleSize)
|
||||
transition.updateFrame(node: titleNode, frame: titleFrame)
|
||||
|
||||
let textFrame = CGRect(origin: CGPoint(x: insets.left + floor((contentWidth - textSize.width) / 2.0), y: titleFrame.maxY + spacing), size: textSize)
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
resultSize = CGSize(width: contentWidth + insets.left + insets.right, height: titleSize.height + spacing + textSize.height + actionsHeight + insets.top + insets.bottom)
|
||||
} else {
|
||||
let textFrame = CGRect(origin: CGPoint(x: insets.left, y: insets.top), size: textSize)
|
||||
transition.updateFrame(node: self.textNode, frame: textFrame)
|
||||
|
||||
resultSize = CGSize(width: textSize.width + insets.left + insets.right, height: textSize.height + actionsHeight + insets.top + insets.bottom)
|
||||
}
|
||||
|
||||
self.actionNodesSeparator.frame = CGRect(origin: CGPoint(x: 0.0, y: resultSize.height - actionsHeight - UIScreenPixel), size: CGSize(width: resultSize.width, height: UIScreenPixel))
|
||||
|
||||
var actionOffset: CGFloat = 0.0
|
||||
let actionWidth: CGFloat = floor(resultSize.width / CGFloat(self.actionNodes.count))
|
||||
var separatorIndex = -1
|
||||
var nodeIndex = 0
|
||||
for actionNode in self.actionNodes {
|
||||
if separatorIndex >= 0 {
|
||||
let separatorNode = self.actionVerticalSeparators[separatorIndex]
|
||||
transition.updateFrame(node: separatorNode, frame: CGRect(origin: CGPoint(x: actionOffset - UIScreenPixel, y: resultSize.height - actionsHeight), size: CGSize(width: UIScreenPixel, height: actionsHeight - UIScreenPixel)))
|
||||
}
|
||||
separatorIndex += 1
|
||||
|
||||
let currentActionWidth: CGFloat
|
||||
if nodeIndex == self.actionNodes.count - 1 {
|
||||
currentActionWidth = resultSize.width - actionOffset
|
||||
} else {
|
||||
currentActionWidth = actionWidth
|
||||
}
|
||||
|
||||
let actionNodeFrame = CGRect(origin: CGPoint(x: actionOffset, y: resultSize.height - actionsHeight), size: CGSize(width: currentActionWidth, height: actionsHeight))
|
||||
|
||||
actionOffset += currentActionWidth
|
||||
transition.updateFrame(node: actionNode, frame: actionNodeFrame)
|
||||
|
||||
nodeIndex += 1
|
||||
}
|
||||
|
||||
return resultSize
|
||||
}
|
||||
}
|
||||
|
||||
public func textAlertController(title: NSAttributedString?, text: NSAttributedString, actions: [TextAlertAction]) -> AlertController {
|
||||
return AlertController(contentNode: TextAlertContentNode(title: title, text: text, actions: actions))
|
||||
}
|
||||
|
||||
public func standardTextAlertController(title: String?, text: String, actions: [TextAlertAction]) -> AlertController {
|
||||
var dismissImpl: (() -> Void)?
|
||||
let controller = AlertController(contentNode: TextAlertContentNode(title: title != nil ? NSAttributedString(string: title!, font: Font.medium(17.0), textColor: .black, paragraphAlignment: .center) : nil, text: NSAttributedString(string: text, font: title == nil ? Font.medium(17.0) : Font.regular(13.0), textColor: .black, paragraphAlignment: .center), actions: actions.map { action in
|
||||
return TextAlertAction(type: action.type, title: action.title, action: {
|
||||
dismissImpl?()
|
||||
action.action()
|
||||
})
|
||||
}))
|
||||
dismissImpl = { [weak controller] in
|
||||
controller?.dismissAnimated()
|
||||
}
|
||||
return controller
|
||||
}
|
33
Display/TextFieldNode.swift
Normal file
33
Display/TextFieldNode.swift
Normal file
@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
import AsyncDisplayKit
|
||||
|
||||
public final class TextFieldNodeView: UITextField {
|
||||
public var didDeleteBackwardWhileEmpty: (() -> Void)?
|
||||
|
||||
override public func editingRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return bounds.offsetBy(dx: 0.0, dy: -UIScreenPixel)
|
||||
}
|
||||
|
||||
override public func placeholderRect(forBounds bounds: CGRect) -> CGRect {
|
||||
return self.editingRect(forBounds: bounds)
|
||||
}
|
||||
|
||||
override public func deleteBackward() {
|
||||
if self.text == nil || self.text!.isEmpty {
|
||||
self.didDeleteBackwardWhileEmpty?()
|
||||
}
|
||||
super.deleteBackward()
|
||||
}
|
||||
}
|
||||
|
||||
public class TextFieldNode: ASDisplayNode {
|
||||
public var textField: TextFieldNodeView {
|
||||
return self.view as! TextFieldNodeView
|
||||
}
|
||||
|
||||
override public init() {
|
||||
super.init(viewBlock: {
|
||||
return TextFieldNodeView()
|
||||
}, didLoad: nil)
|
||||
}
|
||||
}
|
@ -20,7 +20,7 @@ static const void *customDisplayNodeKey = &customDisplayNodeKey;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode {
|
||||
self = [super init];
|
||||
self = [self init];
|
||||
if (self != nil) {
|
||||
[self setAssociatedObject:customDisplayNode forKey:customDisplayNodeKey];
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
|
||||
typedef void (^UINavigationItemSetTitleListener)(NSString *);
|
||||
typedef void (^UINavigationItemSetTitleViewListener)(UIView *);
|
||||
typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, BOOL);
|
||||
typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem *, UIBarButtonItem *, BOOL);
|
||||
typedef void (^UITabBarItemSetBadgeListener)(NSString *);
|
||||
|
||||
@interface UINavigationItem (Proxy)
|
||||
|
@ -62,6 +62,8 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
||||
|
||||
- (void)_ac91f40f_setLeftBarButtonItem:(UIBarButtonItem *)leftBarButtonItem animated:(BOOL)animated
|
||||
{
|
||||
UIBarButtonItem *previousItem = self.leftBarButtonItem;
|
||||
|
||||
[self _ac91f40f_setLeftBarButtonItem:leftBarButtonItem animated:animated];
|
||||
|
||||
UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey];
|
||||
@ -69,7 +71,7 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
||||
[targetItem setLeftBarButtonItem:leftBarButtonItem animated:animated];
|
||||
} else {
|
||||
[(NSBag *)[self associatedObjectForKey:setLeftBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) {
|
||||
listener(leftBarButtonItem, animated);
|
||||
listener(previousItem, leftBarButtonItem, animated);
|
||||
}];
|
||||
}
|
||||
}
|
||||
@ -80,6 +82,8 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
||||
|
||||
- (void)_ac91f40f_setRightBarButtonItem:(UIBarButtonItem *)rightBarButtonItem animated:(BOOL)animated
|
||||
{
|
||||
UIBarButtonItem *previousItem = self.rightBarButtonItem;
|
||||
|
||||
[self _ac91f40f_setRightBarButtonItem:rightBarButtonItem animated:animated];
|
||||
|
||||
UINavigationItem *targetItem = [self associatedObjectForKey:targetItemKey];
|
||||
@ -87,7 +91,7 @@ static const void *setBadgeListenerBagKey = &setBadgeListenerBagKey;
|
||||
[targetItem setRightBarButtonItem:rightBarButtonItem animated:animated];
|
||||
} else {
|
||||
[(NSBag *)[self associatedObjectForKey:setRightBarButtonItemListenerBagKey] enumerateItems:^(UINavigationItemSetBarButtonItemListener listener) {
|
||||
listener(rightBarButtonItem, animated);
|
||||
listener(previousItem, rightBarButtonItem, animated);
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@
|
||||
- (BOOL)ignoreAppearanceMethodInvocations;
|
||||
- (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller;
|
||||
- (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController;
|
||||
- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:(UIViewController *)rootController;
|
||||
- (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:( UIViewController * _Nullable )rootController;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -16,6 +16,19 @@ private func findCurrentResponder(_ view: UIView) -> UIResponder? {
|
||||
}
|
||||
}
|
||||
|
||||
public enum ViewControllerPresentationAnimation {
|
||||
case none
|
||||
case modalSheet
|
||||
}
|
||||
|
||||
open class ViewControllerPresentationArguments {
|
||||
public let presentationAnimation: ViewControllerPresentationAnimation
|
||||
|
||||
public init(presentationAnimation: ViewControllerPresentationAnimation) {
|
||||
self.presentationAnimation = presentationAnimation
|
||||
}
|
||||
}
|
||||
|
||||
@objc open class ViewController: UIViewController, ContainableController {
|
||||
private var containerLayout = ContainerViewLayout()
|
||||
private let presentationContext: PresentationContext
|
||||
|
Loading…
x
Reference in New Issue
Block a user