no message

This commit is contained in:
Peter 2017-02-11 17:02:35 +03:00
parent a4ea9ca8cf
commit 457a6ef8ee
29 changed files with 830 additions and 71 deletions

View File

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

View File

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

View File

@ -0,0 +1,10 @@
import Foundation
import AsyncDisplayKit
open class AlertContentNode: ASDisplayNode {
open func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
assertionFailure()
return CGSize()
}
}

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

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,4 +11,5 @@ public protocol GridSection {
public protocol GridItem {
var section: GridSection? { get }
func node(layout: GridNodeLayout) -> GridItemNode
func update(node: GridItemNode)
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@ -0,0 +1,10 @@
import Foundation
import AsyncDisplayKit
open class SwitchNode: ASDisplayNode {
override public init() {
super.init(viewBlock: {
return UISwitch()
}, didLoad: nil)
}
}

View File

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

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

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

View File

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

View File

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

View File

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

View File

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

View File

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