no message

This commit is contained in:
Peter 2017-04-18 19:52:18 +03:00
parent a4de6e5fb6
commit 124e621ee2
33 changed files with 1156 additions and 301 deletions

View File

@ -44,7 +44,7 @@
D05CC26E1B69316F00E235A3 /* Display.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D05CC2631B69316F00E235A3 /* Display.framework */; };
D05CC2731B69316F00E235A3 /* DisplayTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2721B69316F00E235A3 /* DisplayTests.swift */; };
D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC29F1B69326400E235A3 /* NavigationController.swift */; };
D05CC2A21B69326C00E235A3 /* Window.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* Window.swift */; };
D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2A11B69326C00E235A3 /* WindowContent.swift */; };
D05CC2E31B69552C00E235A3 /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E21B69552C00E235A3 /* ViewController.swift */; };
D05CC2E71B69555800E235A3 /* CAAnimationUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */; };
D05CC2EC1B69558A00E235A3 /* RuntimeUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */; };
@ -82,10 +82,12 @@
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 */; };
D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.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 */; };
D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */; };
D0C0D28F1C997110001D2851 /* FBAnimationPerformanceTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */; settings = {ATTRIBUTES = (Public, ); }; };
D0C0D2901C997110001D2851 /* FBAnimationPerformanceTracker.mm in Sources */ = {isa = PBXBuildFile; fileRef = D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */; };
D0C2DFC61CC4431D0044FF83 /* ASTransformLayerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */; };
@ -102,7 +104,6 @@
D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */; };
D0C85DD21D1C08AE00124894 /* ActionSheetItemNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */; };
D0C85DD41D1C1E6A00124894 /* ActionSheetItemGroupNode.swift in Sources */ = {isa = PBXBuildFile; fileRef = D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */; };
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 */; };
@ -171,7 +172,7 @@
D05CC2721B69316F00E235A3 /* DisplayTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DisplayTests.swift; sourceTree = "<group>"; };
D05CC2741B69316F00E235A3 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
D05CC29F1B69326400E235A3 /* NavigationController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NavigationController.swift; sourceTree = "<group>"; };
D05CC2A11B69326C00E235A3 /* Window.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Window.swift; sourceTree = "<group>"; };
D05CC2A11B69326C00E235A3 /* WindowContent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WindowContent.swift; sourceTree = "<group>"; };
D05CC2E21B69552C00E235A3 /* ViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
D05CC2E41B69555800E235A3 /* CAAnimationUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CAAnimationUtils.swift; sourceTree = "<group>"; };
D05CC2EA1B69558A00E235A3 /* RuntimeUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RuntimeUtils.m; sourceTree = "<group>"; };
@ -209,10 +210,12 @@
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>"; };
D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetCheckboxItem.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>"; };
D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NativeWindowHostView.swift; sourceTree = "<group>"; };
D0C0D28D1C997110001D2851 /* FBAnimationPerformanceTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FBAnimationPerformanceTracker.h; sourceTree = "<group>"; };
D0C0D28E1C997110001D2851 /* FBAnimationPerformanceTracker.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = FBAnimationPerformanceTracker.mm; sourceTree = "<group>"; };
D0C2DFBB1CC4431D0044FF83 /* ASTransformLayerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ASTransformLayerNode.swift; sourceTree = "<group>"; };
@ -229,7 +232,6 @@
D0C85DCF1D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupsContainerNode.swift; sourceTree = "<group>"; };
D0C85DD11D1C08AE00124894 /* ActionSheetItemNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemNode.swift; sourceTree = "<group>"; };
D0C85DD31D1C1E6A00124894 /* ActionSheetItemGroupNode.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActionSheetItemGroupNode.swift; sourceTree = "<group>"; };
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>"; };
@ -272,7 +274,8 @@
D015F7501D1ADC6800E269B5 /* Window */ = {
isa = PBXGroup;
children = (
D05CC2A11B69326C00E235A3 /* Window.swift */,
D05CC2A11B69326C00E235A3 /* WindowContent.swift */,
D0BE93181E8ED71100DCC1E6 /* NativeWindowHostView.swift */,
);
name = Window;
sourceTree = "<group>";
@ -298,7 +301,7 @@
D08E90391D24159200533158 /* ActionSheetItem.swift */,
D08E903D1D24187900533158 /* ActionSheetItemGroup.swift */,
D08E903B1D2417E000533158 /* ActionSheetButtonItem.swift */,
D0C85DD51D1C600D00124894 /* ActionSheetButtonNode.swift */,
D096A44F1EA64F580000A7AE /* ActionSheetCheckboxItem.swift */,
);
name = "Action Sheet";
sourceTree = "<group>";
@ -737,6 +740,7 @@
D05CC2A01B69326400E235A3 /* NavigationController.swift in Sources */,
D06EE8451B7140FF00837186 /* Font.swift in Sources */,
D0C2DFCB1CC4431D0044FF83 /* ListViewAnimation.swift in Sources */,
D0BE93191E8ED71100DCC1E6 /* NativeWindowHostView.swift in Sources */,
D05CC3251B695B0700E235A3 /* NavigationBarProxy.m in Sources */,
D03E7DE61C96B96E00C07816 /* NavigationBarTransitionContainer.swift in Sources */,
D0C85DD01D1C082E00124894 /* ActionSheetItemGroupsContainerNode.swift in Sources */,
@ -746,7 +750,6 @@
D05CC31D1B695A9600E235A3 /* UIBarButtonItem+Proxy.m in Sources */,
D01E2BDE1D9049620066BF65 /* GridNode.swift in Sources */,
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 */,
@ -772,6 +775,7 @@
D00C7CD21E3657570080C3D5 /* TextFieldNode.swift in Sources */,
D0DC485F1BF949FB00F672FD /* TabBarContollerNode.swift in Sources */,
D05CC2FA1B6955D000E235A3 /* UINavigationItem+Proxy.m in Sources */,
D096A4501EA64F580000A7AE /* ActionSheetCheckboxItem.swift in Sources */,
D0C2DFCE1CC4431D0044FF83 /* ListViewAccessoryItem.swift in Sources */,
D0A749951E3A9E7B00AD786E /* SwitchNode.swift in Sources */,
D03725C51D6DF8B9007FC290 /* ContextMenuController.swift in Sources */,
@ -806,7 +810,7 @@
D02383861DE0E3B4004018B6 /* ListViewIntermediateState.swift in Sources */,
D0DA444E1E4DCA6E005FDCA7 /* AlertControllerNode.swift in Sources */,
D0B367201C94A53A00346D2E /* StatusBarProxyNode.swift in Sources */,
D05CC2A21B69326C00E235A3 /* Window.swift in Sources */,
D05CC2A21B69326C00E235A3 /* WindowContent.swift in Sources */,
D015F7541D1B0F6C00E269B5 /* SystemContainedControllerTransitionCoordinator.swift in Sources */,
D05CC3151B695A9600E235A3 /* NavigationTransitionCoordinator.swift in Sources */,
D03B0E701D6331FB00955575 /* StatusBarHost.swift in Sources */,

View File

@ -1,24 +1,116 @@
import Foundation
import AsyncDisplayKit
public enum ActionSheetButtonColor {
case accent
case destructive
case disabled
}
public class ActionSheetButtonItem: ActionSheetItem {
public let title: String
public let color: ActionSheetButtonColor
public let enabled: Bool
public let action: () -> Void
public init(title: String, color: ActionSheetButtonColor = .accent, action: @escaping () -> Void) {
public init(title: String, color: ActionSheetButtonColor = .accent, enabled: Bool = true, action: @escaping () -> Void) {
self.title = title
self.color = color
self.enabled = enabled
self.action = action
}
public func node() -> ActionSheetItemNode {
let textColorIsAccent = self.color == ActionSheetButtonColor.accent
let textColor = textColorIsAccent ? UIColor(0x007ee5) : UIColor.red
return ActionSheetButtonNode(title: NSAttributedString(string: title, font: ActionSheetButtonNode.defaultFont, textColor: textColor), action: self.action)
let node = ActionSheetButtonNode()
node.setItem(self)
return node
}
public func updateNode(_ node: ActionSheetItemNode) {
guard let node = node as? ActionSheetButtonNode else {
assertionFailure()
return
}
node.setItem(self)
}
}
public class ActionSheetButtonNode: ActionSheetItemNode {
public static let defaultFont: UIFont = Font.regular(20.0)
private var item: ActionSheetButtonItem?
private let button: HighlightTrackingButton
private let label: ASTextNode
override public init() {
self.button = HighlightTrackingButton()
self.label = ASTextNode()
self.label.isLayerBacked = true
self.label.maximumNumberOfLines = 1
self.label.displaysAsynchronously = false
super.init()
self.view.addSubview(self.button)
self.label.isUserInteractionEnabled = false
self.addSubnode(self.label)
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor
})
}
}
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
func setItem(_ item: ActionSheetButtonItem) {
self.item = item
let textColor: UIColor
switch item.color {
case .accent:
textColor = UIColor(0x007ee5)
case .destructive:
textColor = .red
case .disabled:
textColor = .gray
}
self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: textColor)
self.button.isEnabled = item.enabled
self.setNeedsLayout()
}
public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 57.0)
}
public override func layout() {
super.layout()
let size = self.bounds.size
self.button.frame = CGRect(origin: CGPoint(), size: size)
let labelSize = self.label.measure(size)
self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
}
@objc func buttonPressed() {
if let item = self.item {
item.action()
}
}
}

View File

@ -1,63 +0,0 @@
import UIKit
import AsyncDisplayKit
public class ActionSheetButtonNode: ActionSheetItemNode {
public static let defaultFont: UIFont = Font.regular(20.0)
private let action: () -> Void
private let button: HighlightTrackingButton
private let label: UILabel
private var calculatedLabelSize: CGSize?
public init(title: NSAttributedString, action: @escaping () -> Void) {
self.action = action
self.button = HighlightTrackingButton()
self.label = UILabel()
super.init()
self.view.addSubview(self.button)
self.label.attributedText = title
self.label.numberOfLines = 1
self.label.isUserInteractionEnabled = false
self.view.addSubview(self.label)
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor
})
}
}
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
self.label.sizeToFit()
self.calculatedLabelSize = self.label.frame.size
return CGSize(width: constrainedSize.width, height: 57.0)
}
public override func layout() {
super.layout()
self.button.frame = CGRect(origin: CGPoint(), size: self.calculatedSize)
if let calculatedLabelSize = self.calculatedLabelSize {
self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((self.calculatedSize.width - calculatedLabelSize.width) / 2.0), y: floorToScreenPixels((self.calculatedSize.height - calculatedLabelSize.height) / 2.0)), size: calculatedLabelSize)
}
}
@objc func buttonPressed() {
self.action()
}
}

View File

@ -0,0 +1,130 @@
import Foundation
import AsyncDisplayKit
private let checkIcon = generateImage(CGSize(width: 14.0, height: 11.0), rotatedContext: { size, context in
context.clear(CGRect(origin: CGPoint(), size: size))
context.setStrokeColor(UIColor(0x007ee5).cgColor)
context.setLineWidth(2.0)
context.move(to: CGPoint(x: 12.0, y: 1.0))
context.addLine(to: CGPoint(x: 4.16482734, y: 9.0))
context.addLine(to: CGPoint(x: 1.0, y: 5.81145833))
context.strokePath()
})
public class ActionSheetCheckboxItem: ActionSheetItem {
public let title: String
public let label: String
public let value: Bool
public let action: (Bool) -> Void
public init(title: String, label: String, value: Bool, action: @escaping (Bool) -> Void) {
self.title = title
self.label = label
self.value = value
self.action = action
}
public func node() -> ActionSheetItemNode {
let node = ActionSheetCheckboxItemNode()
node.setItem(self)
return node
}
public func updateNode(_ node: ActionSheetItemNode) {
guard let node = node as? ActionSheetCheckboxItemNode else {
assertionFailure()
return
}
node.setItem(self)
}
}
public class ActionSheetCheckboxItemNode: ActionSheetItemNode {
public static let defaultFont: UIFont = Font.regular(20.0)
private var item: ActionSheetCheckboxItem?
private let button: HighlightTrackingButton
private let titleNode: ASTextNode
private let labelNode: ASTextNode
private let checkNode: ASImageNode
public override init() {
self.button = HighlightTrackingButton()
self.titleNode = ASTextNode()
self.titleNode.maximumNumberOfLines = 1
self.titleNode.isUserInteractionEnabled = false
self.titleNode.displaysAsynchronously = false
self.labelNode = ASTextNode()
self.labelNode.maximumNumberOfLines = 1
self.labelNode.isUserInteractionEnabled = false
self.labelNode.displaysAsynchronously = false
self.checkNode = ASImageNode()
self.checkNode.isUserInteractionEnabled = false
self.checkNode.displayWithoutProcessing = true
self.checkNode.displaysAsynchronously = false
self.checkNode.image = checkIcon
super.init()
self.view.addSubview(self.button)
self.addSubnode(self.titleNode)
self.addSubnode(self.labelNode)
self.addSubnode(self.checkNode)
self.button.highligthedChanged = { [weak self] highlighted in
if let strongSelf = self {
if highlighted {
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.highlightedBackgroundColor
} else {
UIView.animate(withDuration: 0.3, animations: {
strongSelf.backgroundNode.backgroundColor = ActionSheetItemNode.defaultBackgroundColor
})
}
}
}
self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
}
func setItem(_ item: ActionSheetCheckboxItem) {
self.item = item
self.titleNode.attributedText = NSAttributedString(string: item.title, font: ActionSheetCheckboxItemNode.defaultFont, textColor: .black)
self.labelNode.attributedText = NSAttributedString(string: item.label, font: ActionSheetCheckboxItemNode.defaultFont, textColor: UIColor(0x8e8e93))
self.checkNode.isHidden = !item.value
self.setNeedsLayout()
}
public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
return CGSize(width: constrainedSize.width, height: 57.0)
}
public override func layout() {
super.layout()
let size = self.bounds.size
self.button.frame = CGRect(origin: CGPoint(), size: size)
let labelSize = self.labelNode.measure(CGSize(width: size.width - 44.0 - 15.0 - 8.0, height: size.height))
let titleSize = self.titleNode.measure(CGSize(width: size.width - 44.0 - labelSize.width - 15.0 - 8.0, height: size.height))
self.titleNode.frame = CGRect(origin: CGPoint(x: 44.0, y: floorToScreenPixels((size.height - titleSize.height) / 2.0)), size: titleSize)
self.labelNode.frame = CGRect(origin: CGPoint(x: size.width - 15.0 - labelSize.width, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
if let image = self.checkNode.image {
self.checkNode.frame = CGRect(origin: CGPoint(x: floor((44.0 - image.size.width) / 2.0), y: floor((size.height - image.size.height) / 2.0)), size: image.size)
}
}
@objc func buttonPressed() {
if let item = self.item {
item.action(!item.value)
}
}
}

View File

@ -49,4 +49,10 @@ open class ActionSheetController: ViewController {
self.actionSheetNode.setGroups(groups)
}
}
public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
if self.isViewLoaded {
self.actionSheetNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
}
}
}

View File

@ -155,4 +155,8 @@ final class ActionSheetControllerNode: ASDisplayNode, UIScrollViewDelegate {
func setGroups(_ groups: [ActionSheetItemGroup]) {
self.itemGroupsContainerNode.setGroups(groups)
}
public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
self.itemGroupsContainerNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
}
}

View File

@ -2,4 +2,5 @@ import Foundation
public protocol ActionSheetItem {
func node() -> ActionSheetItemNode
func updateNode(_ node: ActionSheetItemNode) -> Void
}

View File

@ -209,4 +209,8 @@ final class ActionSheetItemGroupNode: ASDisplayNode, UIScrollViewDelegate {
node.layer.animateAlpha(from: from, to: to, duration: duration)
}
}
func itemNode(at index: Int) -> ActionSheetItemNode {
return self.itemNodes[index]
}
}

View File

@ -4,6 +4,7 @@ import AsyncDisplayKit
private let groupSpacing: CGFloat = 16.0
final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
private var groups: [ActionSheetItemGroup] = []
private var groupNodes: [ActionSheetItemGroupNode] = []
override init() {
@ -11,6 +12,8 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
}
func setGroups(_ groups: [ActionSheetItemGroup]) {
self.groups = groups
for groupNode in self.groupNodes {
groupNode.removeFromSupernode()
}
@ -72,4 +75,16 @@ final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
node.animateDimViewsAlpha(from: from, to: to, duration: duration)
}
}
public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
var item = self.groups[groupIndex].items[itemIndex]
let itemNode = self.groupNodes[groupIndex].itemNode(at: itemIndex)
item = f(item)
item.updateNode(itemNode)
var groupItems = self.groups[groupIndex].items
groupItems[itemIndex] = item
self.groups[groupIndex] = ActionSheetItemGroup(items: groupItems)
}
}

View File

@ -45,8 +45,8 @@ open class AlertController: ViewController {
self.controllerNode.containerLayoutUpdated(layout, transition: transition)
}
override open func dismiss() {
self.presentingViewController?.dismiss(animated: false, completion: nil)
override open func dismiss(completion: (() -> Void)? = nil) {
self.presentingViewController?.dismiss(animated: false, completion: completion)
}
public func dismissAnimated() {

View File

@ -134,8 +134,8 @@ public extension CALayer {
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)
public func animateSpring(from: AnyObject, to: AnyObject, keyPath: String, duration: Double, initialVelocity: CGFloat = 0.0, damping: CGFloat = 88.0, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
let animation = makeSpringBounceAnimation(keyPath, initialVelocity, damping)
animation.fromValue = from
animation.toValue = to
animation.isRemovedOnCompletion = removeOnCompletion
@ -187,8 +187,8 @@ public extension CALayer {
self.animate(from: NSNumber(value: Float(from)), to: NSNumber(value: Float(to)), keyPath: "transform.scale", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, completion: completion)
}
func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to {
func animatePosition(from: CGPoint, to: CGPoint, duration: Double, timingFunction: String = kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to && !force {
if let completion = completion {
completion(true)
}
@ -197,8 +197,8 @@ public extension CALayer {
self.animate(from: NSValue(cgPoint: from), to: NSValue(cgPoint: to), keyPath: "position", timingFunction: timingFunction, duration: duration, removeOnCompletion: removeOnCompletion, additive: additive, completion: completion)
}
func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to {
func animateBounds(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to && !force {
if let completion = completion {
completion(true)
}
@ -219,8 +219,8 @@ public extension CALayer {
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 {
public func animateFrame(from: CGRect, to: CGRect, duration: Double, timingFunction: String, removeOnCompletion: Bool = true, additive: Bool = false, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if from == to && !force {
if let completion = completion {
completion(true)
}
@ -236,14 +236,14 @@ public extension CALayer {
}
}
}
self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in
self.animatePosition(from: CGPoint(x: from.midX, y: from.midY), to: CGPoint(x: to.midX, y: to.midY), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in
if !value {
interrupted = true
}
completedPosition = true
partialCompletion()
})
self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, completion: { value in
self.animateBounds(from: CGRect(origin: self.bounds.origin, size: from.size), to: CGRect(origin: self.bounds.origin, size: to.size), duration: duration, timingFunction: timingFunction, removeOnCompletion: removeOnCompletion, additive: additive, force: force, completion: { value in
if !value {
interrupted = true
}

View File

@ -231,7 +231,41 @@ static void traceLayerSurfaces(int32_t tracingTag, int depth, CALayer * _Nonnull
- (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
if ([anim isKindOfClass:[CABasicAnimation class]]) {
if ([key isEqualToString:@"position"]) {
if (false && [key isEqualToString:@"bounds.origin.y"]) {
CABasicAnimation *animCopy = [anim copy];
CGFloat from = [animCopy.fromValue floatValue];
CGFloat to = [animCopy.toValue floatValue];
animCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, to - from, 0.0f)];
animCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
animCopy.keyPath = @"sublayerTransform";
__weak CATracingLayer *weakSelf = self;
anim.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{
__strong CATracingLayer *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf invalidateUpTheTree];
}
}];
[super addAnimation:anim forKey:key];
CABasicAnimation *positionAnimCopy = [animCopy copy];
positionAnimCopy.fromValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0.0, 0.0, 0.0f)];
positionAnimCopy.toValue = [NSValue valueWithCATransform3D:CATransform3DIdentity];
positionAnimCopy.additive = true;
positionAnimCopy.delegate = [[CATracingLayerAnimationDelegate alloc] initWithDelegate:anim.delegate animationStopped:^{
__strong CATracingLayer *strongSelf = weakSelf;
if (strongSelf != nil) {
[strongSelf invalidateUpTheTree];
}
}];
[self invalidateUpTheTree];
[self mirrorAnimationDownTheTree:animCopy key:@"sublayerTransform"];
[self mirrorPositionAnimationDownTheTree:positionAnimCopy key:@"sublayerTransform"];
} else if ([key isEqualToString:@"position"]) {
CABasicAnimation *animCopy = [anim copy];
CGPoint from = [animCopy.fromValue CGPointValue];
CGPoint to = [animCopy.toValue CGPointValue];

View File

@ -31,8 +31,8 @@ public enum ContainedViewLayoutTransition {
}
public extension ContainedViewLayoutTransition {
func updateFrame(node: ASDisplayNode, frame: CGRect, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) {
func updateFrame(node: ASDisplayNode, frame: CGRect, force: Bool = false, completion: ((Bool) -> Void)? = nil) {
if node.frame.equalTo(frame) && !force {
completion?(true)
} else {
switch self {
@ -44,7 +44,7 @@ public extension ContainedViewLayoutTransition {
case let .animated(duration, curve):
let previousFrame = node.frame
node.frame = frame
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, completion: { result in
node.layer.animateFrame(from: previousFrame, to: frame, duration: duration, timingFunction: curve.timingFunction, force: force, completion: { result in
if let completion = completion {
completion(result)
}

View File

@ -14,6 +14,14 @@ public struct Font {
}
}
public static func semibold(_ size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) {
return UIFont.systemFont(ofSize: size, weight: UIFontWeightSemibold)
} else {
return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
}
}
public static func bold(_ size: CGFloat) -> UIFont {
if #available(iOS 8.2, *) {
return UIFont.boldSystemFont(ofSize: size)

View File

@ -12,4 +12,11 @@ public protocol GridItem {
var section: GridSection? { get }
func node(layout: GridNodeLayout) -> GridItemNode
func update(node: GridItemNode)
var aspectRatio: CGFloat { get }
}
public extension GridItem {
var aspectRatio: CGFloat {
return 1.0
}
}

View File

@ -2,5 +2,16 @@ import Foundation
import AsyncDisplayKit
open class GridItemNode: ASDisplayNode {
open var isVisibleInGrid = false
open var isGridScrolling = false
final var cachedFrame: CGRect = CGRect()
override open var frame: CGRect {
get {
return self.cachedFrame
} set(value) {
self.cachedFrame = value
super.frame = value
}
}
}

View File

@ -47,21 +47,43 @@ public struct GridNodeScrollToItem {
}
}
public enum GridNodeLayoutType: Equatable {
case fixed(itemSize: CGSize)
case balanced(idealHeight: CGFloat)
public static func ==(lhs: GridNodeLayoutType, rhs: GridNodeLayoutType) -> Bool {
switch lhs {
case let .fixed(itemSize):
if case .fixed(itemSize) = rhs {
return true
} else {
return false
}
case let .balanced(idealHeight):
if case .balanced(idealHeight) = rhs {
return true
} else {
return false
}
}
}
}
public struct GridNodeLayout: Equatable {
public let size: CGSize
public let insets: UIEdgeInsets
public let preloadSize: CGFloat
public let itemSize: CGSize
public let type: GridNodeLayoutType
public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, itemSize: CGSize) {
public init(size: CGSize, insets: UIEdgeInsets, preloadSize: CGFloat, type: GridNodeLayoutType) {
self.size = size
self.insets = insets
self.preloadSize = preloadSize
self.itemSize = itemSize
self.type = type
}
public static func ==(lhs: GridNodeLayout, rhs: GridNodeLayout) -> Bool {
return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.itemSize.equalTo(rhs.itemSize)
return lhs.size.equalTo(rhs.size) && lhs.insets == rhs.insets && lhs.preloadSize.isEqual(to: rhs.preloadSize) && lhs.type == rhs.type
}
}
@ -223,7 +245,7 @@ private struct WrappedGridItemNode: Hashable {
}
open class GridNode: GridNodeScroller, UIScrollViewDelegate {
private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, itemSize: CGSize())
private var gridLayout = GridNodeLayout(size: CGSize(), insets: UIEdgeInsets(), preloadSize: 0.0, type: .fixed(itemSize: CGSize()))
private var firstIndexInSectionOffset: Int = 0
private var items: [GridItem] = []
private var itemNodes: [Int: GridItemNode] = [:]
@ -235,6 +257,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
public var visibleItemsUpdated: ((GridNodeVisibleItems) -> Void)?
public var presentationLayoutUpdated: ((GridNodeCurrentPresentationLayout, ContainedViewLayoutTransition) -> Void)?
public final var floatingSections = false
public override init() {
super.init()
@ -315,10 +339,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
self.items.insert(insertedItem.item, at: insertedItem.index)
}
let sortedInsertItems = transaction.insertItems.sorted(by: { $0.index < $1.index })
var remappedInsertionItemNodes: [Int: GridItemNode] = [:]
for (index, itemNode) in remappedDeletionItemNodes {
var indexOffset = 0
for insertedItem in transaction.insertItems {
for insertedItem in sortedInsertItems {
if insertedItem.index <= index + indexOffset {
indexOffset += 1
}
@ -343,14 +369,26 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
generatedScrollToItem = nil
}
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition)
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(stationaryItems: transaction.stationaryItems, layoutTransactionOffset: layoutTransactionOffset, scrollToItem: generatedScrollToItem), removedNodes: removedNodes, updateLayoutTransition: transaction.updateLayout?.transition, completion: completion)
}
completion(self.displayedItemRange())
public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
self.updateItemNodeVisibilititesAndScrolling()
}
public func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
if !decelerate {
self.updateItemNodeVisibilititesAndScrolling()
}
}
public func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
self.updateItemNodeVisibilititesAndScrolling()
}
public func scrollViewDidScroll(_ scrollView: UIScrollView) {
if !self.applyingContentOffset {
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil)
self.applyPresentaionLayoutTransition(self.generatePresentationLayoutTransition(layoutTransactionOffset: 0.0), removedNodes: [], updateLayoutTransition: nil, completion: { _ in })
}
}
@ -379,8 +417,10 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
var items: [GridNodePresentationItem] = []
var sections: [GridNodePresentationSection] = []
let itemsInRow = Int(gridLayout.size.width / gridLayout.itemSize.width)
let itemsInRowWidth = CGFloat(itemsInRow) * gridLayout.itemSize.width
switch gridLayout.type {
case let .fixed(itemSize):
let itemsInRow = Int(gridLayout.size.width / itemSize.width)
let itemsInRowWidth = CGFloat(itemsInRow) * itemSize.width
let remainingWidth = gridLayout.size.width - itemsInRowWidth
let itemSpacing = floorToScreenPixels(remainingWidth / CGFloat(itemsInRow + 1))
@ -401,7 +441,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
if !keepSection {
if incrementedCurrentRow {
nextItemOrigin.x = itemSpacing
nextItemOrigin.y += gridLayout.itemSize.height
nextItemOrigin.y += itemSize.height
incrementedCurrentRow = false
}
@ -415,25 +455,107 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
if !incrementedCurrentRow {
incrementedCurrentRow = true
contentSize.height += gridLayout.itemSize.height
contentSize.height += itemSize.height
}
if index == 0 {
let itemsInRow = Int(gridLayout.size.width) / Int(gridLayout.itemSize.width)
let itemsInRow = Int(gridLayout.size.width) / Int(itemSize.width)
let normalizedIndexOffset = self.firstIndexInSectionOffset % itemsInRow
nextItemOrigin.x += (gridLayout.itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset)
nextItemOrigin.x += (itemSize.width + itemSpacing) * CGFloat(normalizedIndexOffset)
}
items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: gridLayout.itemSize)))
items.append(GridNodePresentationItem(index: index, frame: CGRect(origin: nextItemOrigin, size: itemSize)))
index += 1
nextItemOrigin.x += gridLayout.itemSize.width + itemSpacing
if nextItemOrigin.x + gridLayout.itemSize.width > gridLayout.size.width {
nextItemOrigin.x += itemSize.width + itemSpacing
if nextItemOrigin.x + itemSize.width > gridLayout.size.width {
nextItemOrigin.x = itemSpacing
nextItemOrigin.y += gridLayout.itemSize.height
nextItemOrigin.y += itemSize.height
incrementedCurrentRow = false
}
}
case let .balanced(idealHeight):
var weights: [Int] = []
for item in self.items {
weights.append(Int(item.aspectRatio * 100))
}
var totalItemSize: CGFloat = 0.0
for i in 0 ..< self.items.count {
totalItemSize += self.items[i].aspectRatio * idealHeight
}
let numberOfRows = max(Int(round(totalItemSize / gridLayout.size.width)), 1)
let partition = linearPartitionForWeights(weights, numberOfPartitions:numberOfRows)
var i = 0
var offset = CGPoint(x: 0.0, y: 0.0)
var previousItemSize: CGFloat = 0.0
var contentMaxValueInScrollDirection: CGFloat = 0.0
let maxWidth = gridLayout.size.width
let minimumInteritemSpacing: CGFloat = 1.0
let minimumLineSpacing: CGFloat = 1.0
let viewportWidth: CGFloat = gridLayout.size.width
let preferredRowSize = idealHeight
var rowIndex = -1
for row in partition {
rowIndex += 1
var summedRatios: CGFloat = 0.0
var j = i
var n = i + row.count
while j < n {
summedRatios += self.items[j].aspectRatio
j += 1
}
var rowSize = gridLayout.size.width - (CGFloat(row.count - 1) * minimumInteritemSpacing)
if rowIndex == partition.count - 1 {
if row.count < 2 {
rowSize = floor(viewportWidth / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing)
} else if row.count < 3 {
rowSize = floor(viewportWidth * 2.0 / 3.0) - (CGFloat(row.count - 1) * minimumInteritemSpacing)
}
}
j = i
n = i + row.count
while j < n {
let preferredAspectRatio = self.items[j].aspectRatio
let actualSize = CGSize(width: round(rowSize / summedRatios * (preferredAspectRatio)), height: preferredRowSize)
var frame = CGRect(x: offset.x, y: offset.y, width: actualSize.width, height: actualSize.height)
if frame.origin.x + frame.size.width >= maxWidth - 2.0 {
frame.size.width = max(1.0, maxWidth - frame.origin.x)
}
items.append(GridNodePresentationItem(index: j, frame: frame))
offset.x += actualSize.width + minimumInteritemSpacing
previousItemSize = actualSize.height
contentMaxValueInScrollDirection = frame.maxY
j += 1
}
if row.count > 0 {
offset = CGPoint(x: 0.0, y: offset.y + previousItemSize + minimumLineSpacing)
}
i += row.count
}
contentSize = CGSize(width: gridLayout.size.width, height: contentMaxValueInScrollDirection)
}
return GridNodeItemLayout(contentSize: contentSize, items: items, sections: sections)
} else {
@ -446,15 +568,17 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
var transitionDirectionHint: GridNodePreviousItemsTransitionDirectionHint = .up
var transition: ContainedViewLayoutTransition = .immediate
let contentOffset: CGPoint
switch stationaryItems {
var updatedStationaryItems = stationaryItems
if scrollToItem != nil {
updatedStationaryItems = .none
}
switch updatedStationaryItems {
case .none:
if let scrollToItem = scrollToItem {
let itemFrame = self.itemLayout.items[scrollToItem.index]
var additionalOffset: CGFloat = 0.0
if scrollToItem.adjustForTopInset {
additionalOffset = -gridLayout.insets.top
} else if scrollToItem.adjustForSection {
if scrollToItem.adjustForSection {
var adjustForSection: GridSection?
if scrollToItem.index == 0 {
if let itemSection = self.items[scrollToItem.index].section {
@ -475,6 +599,12 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
if let adjustForSection = adjustForSection {
additionalOffset = -adjustForSection.height
}
if scrollToItem.adjustForTopInset {
additionalOffset += -gridLayout.insets.top
}
} else if scrollToItem.adjustForTopInset {
additionalOffset = -gridLayout.insets.top
}
let displayHeight = max(0.0, self.gridLayout.size.height - self.gridLayout.insets.top - self.gridLayout.insets.bottom)
@ -518,7 +648,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
var selectedContentOffset: CGPoint?
for (index, itemNode) in self.itemNodes {
if stationaryItemIndices.contains(index) {
let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y
//let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y
selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y)
break
}
@ -532,7 +662,7 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
case .all:
var selectedContentOffset: CGPoint?
for (index, itemNode) in self.itemNodes {
let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y
//let currentScreenOffset = itemNode.frame.origin.y - self.scrollView.contentOffset.y
selectedContentOffset = CGPoint(x: 0.0, y: self.itemLayout.items[index].frame.origin.y - itemNode.frame.origin.y + self.scrollView.contentOffset.y)
break
}
@ -548,6 +678,8 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
let upperDisplayBound = contentOffset.y + self.gridLayout.size.height + self.gridLayout.preloadSize
var presentationItems: [GridNodePresentationItem] = []
var validSections = Set<WrappedGridSection>()
for item in self.itemLayout.items {
if item.frame.origin.y < lowerDisplayBound {
continue
@ -556,13 +688,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
break
}
presentationItems.append(item)
if self.floatingSections {
if let section = self.items[item.index].section {
validSections.insert(WrappedGridSection(section))
}
}
}
var presentationSections: [GridNodePresentationSection] = []
for section in self.itemLayout.sections {
if section.frame.origin.y < lowerDisplayBound {
if !validSections.contains(WrappedGridSection(section.section)) {
continue
}
}
if section.frame.origin.y + section.frame.size.height > upperDisplayBound {
break
}
@ -575,7 +714,21 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
}
}
private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?) {
private func lowestSectionNode() -> ASDisplayNode? {
var lowestHeaderNode: ASDisplayNode?
var lowestHeaderNodeIndex: Int?
for (_, headerNode) in self.sectionNodes {
if let index = self.subnodes.index(of: headerNode) {
if lowestHeaderNodeIndex == nil || index < lowestHeaderNodeIndex! {
lowestHeaderNodeIndex = index
lowestHeaderNode = headerNode
}
}
}
return lowestHeaderNode
}
private func applyPresentaionLayoutTransition(_ presentationLayoutTransition: GridNodePresentationLayoutTransition, removedNodes: [GridItemNode], updateLayoutTransition: ContainedViewLayoutTransition?, completion: (GridNodeDisplayedItemRange) -> Void) {
var previousItemFrames: ([WrappedGridItemNode: CGRect])?
switch presentationLayoutTransition.transition {
case .animated:
@ -603,29 +756,44 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
}
applyingContentOffset = false
let lowestSectionNode: ASDisplayNode? = self.lowestSectionNode()
var existingItemIndices = Set<Int>()
for item in presentationLayoutTransition.layout.items {
existingItemIndices.insert(item.index)
if let itemNode = self.itemNodes[item.index] {
if itemNode.frame != item.frame {
itemNode.frame = item.frame
}
} else {
let itemNode = self.items[item.index].node(layout: presentationLayoutTransition.layout.layout)
itemNode.frame = item.frame
self.addItemNode(index: item.index, itemNode: itemNode)
self.addItemNode(index: item.index, itemNode: itemNode, lowestSectionNode: lowestSectionNode)
}
}
var existingSections = Set<WrappedGridSection>()
for section in presentationLayoutTransition.layout.sections {
for i in 0 ..< presentationLayoutTransition.layout.sections.count {
let section = presentationLayoutTransition.layout.sections[i]
let wrappedSection = WrappedGridSection(section.section)
existingSections.insert(wrappedSection)
var sectionFrame = section.frame
if self.floatingSections {
var maxY = CGFloat.greatestFiniteMagnitude
if i != presentationLayoutTransition.layout.sections.count - 1 {
maxY = presentationLayoutTransition.layout.sections[i + 1].frame.minY - sectionFrame.height
}
sectionFrame.origin.y = max(sectionFrame.minY, min(maxY, presentationLayoutTransition.layout.contentOffset.y + presentationLayoutTransition.layout.layout.insets.top))
}
if let sectionNode = self.sectionNodes[wrappedSection] {
sectionNode.frame = section.frame
sectionNode.frame = sectionFrame
} else {
let sectionNode = section.section.node()
sectionNode.frame = section.frame
sectionNode.frame = sectionFrame
self.addSectionNode(section: wrappedSection, sectionNode: sectionNode)
}
}
@ -698,7 +866,6 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
itemNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true)
}
for (wrappedSection, sectionNode) in self.sectionNodes where existingSections.contains(wrappedSection) {
let position = sectionNode.layer.position
sectionNode.layer.animatePosition(from: CGPoint(x: 0.0, y: offset), to: CGPoint(), duration: duration, timingFunction: timingFunction, additive: true)
}
@ -777,16 +944,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
}
}
completion(self.displayedItemRange())
self.updateItemNodeVisibilititesAndScrolling()
if let visibleItemsUpdated = self.visibleItemsUpdated {
if presentationLayoutTransition.layout.items.count != 0 {
let topIndex = presentationLayoutTransition.layout.items.first!.index
let bottomIndex = presentationLayoutTransition.layout.items.last!.index
var topVisible: (Int, GridItem) = (topIndex, self.items[topIndex])
var bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex])
let bottomVisible: (Int, GridItem) = (bottomIndex, self.items[bottomIndex])
let lowerDisplayBound = presentationLayoutTransition.layout.contentOffset.y
let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height
//let upperDisplayBound = presentationLayoutTransition.layout.contentOffset.y + self.gridLayout.size.height
for item in presentationLayoutTransition.layout.items {
if lowerDisplayBound.isLess(than: item.frame.maxY) {
@ -816,13 +987,17 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
}
}
private func addItemNode(index: Int, itemNode: GridItemNode) {
private func addItemNode(index: Int, itemNode: GridItemNode, lowestSectionNode: ASDisplayNode?) {
assert(self.itemNodes[index] == nil)
self.itemNodes[index] = itemNode
if itemNode.supernode == nil {
if let lowestSectionNode = lowestSectionNode {
self.insertSubnode(itemNode, belowSubnode: lowestSectionNode)
} else {
self.addSubnode(itemNode)
}
}
}
private func addSectionNode(section: WrappedGridSection, sectionNode: ASDisplayNode) {
assert(self.sectionNodes[section] == nil)
@ -848,6 +1023,20 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
}
}
private func updateItemNodeVisibilititesAndScrolling() {
let visibleRect = self.scrollView.bounds
let isScrolling = self.scrollView.isDragging || self.scrollView.isDecelerating
for (_, itemNode) in self.itemNodes {
let visible = itemNode.frame.intersects(visibleRect)
if itemNode.isVisibleInGrid != visible {
itemNode.isVisibleInGrid = visible
}
if itemNode.isGridScrolling != isScrolling {
itemNode.isGridScrolling = isScrolling
}
}
}
public func forEachItemNode(_ f: (ASDisplayNode) -> Void) {
for (_, node) in self.itemNodes {
f(node)
@ -872,4 +1061,127 @@ open class GridNode: GridNodeScroller, UIScrollViewDelegate {
f(row)
}
}
public func itemNodeAtPoint(_ point: CGPoint) -> ASDisplayNode? {
for (_, node) in self.itemNodes {
if node.frame.contains(point) {
return node
}
}
return nil
}
}
private func NH_LP_TABLE_LOOKUP(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int) -> Int {
return table[i * rowsize + j]
}
private func NH_LP_TABLE_LOOKUP_SET(_ table: inout [Int], _ i: Int, _ j: Int, _ rowsize: Int, _ value: Int) {
table[i * rowsize + j] = value
}
private func linearPartitionTable(_ weights: [Int], numberOfPartitions: Int) -> [Int] {
let n = weights.count
let k = numberOfPartitions
let tableSize = n * k;
var tmpTable = Array<Int>(repeatElement(0, count: tableSize))
let solutionSize = (n - 1) * (k - 1)
var solution = Array<Int>(repeatElement(0, count: solutionSize))
for i in 0 ..< n {
let offset = i != 0 ? NH_LP_TABLE_LOOKUP(&tmpTable, i - 1, 0, k) : 0
NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, 0, k, Int(weights[i]) + offset)
}
for j in 0 ..< k {
NH_LP_TABLE_LOOKUP_SET(&tmpTable, 0, j, k, Int(weights[0]))
}
for i in 1 ..< n {
for j in 1 ..< k {
var currentMin = 0
var minX = Int.max
for x in 0 ..< i {
let c1 = NH_LP_TABLE_LOOKUP(&tmpTable, x, j - 1, k)
let c2 = NH_LP_TABLE_LOOKUP(&tmpTable, i, 0, k) - NH_LP_TABLE_LOOKUP(&tmpTable, x, 0, k)
let cost = max(c1, c2)
if x == 0 || cost < currentMin {
currentMin = cost;
minX = x
}
}
NH_LP_TABLE_LOOKUP_SET(&tmpTable, i, j, k, currentMin)
NH_LP_TABLE_LOOKUP_SET(&solution, i - 1, j - 1, k - 1, minX)
}
}
return solution
}
private func linearPartitionForWeights(_ weights: [Int], numberOfPartitions: Int) -> [[Int]] {
var n = weights.count
var k = numberOfPartitions
if k <= 0 {
return []
}
if k >= n {
var partition: [[Int]] = []
for weight in weights {
partition.append([weight])
}
return partition
}
if n == 1 {
return [weights]
}
var solution = linearPartitionTable(weights, numberOfPartitions: numberOfPartitions)
let solutionRowSize = numberOfPartitions - 1
k = k - 2;
n = n - 1;
var answer: [[Int]] = []
while k >= 0 {
if n < 1 {
answer.insert([], at: 0)
} else {
var currentAnswer: [Int] = []
var i = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize) + 1
let range = n + 1
while i < range {
currentAnswer.append(weights[i])
i += 1
}
answer.insert(currentAnswer, at: 0)
n = NH_LP_TABLE_LOOKUP(&solution, n - 1, k, solutionRowSize)
}
k = k - 1
}
var currentAnswer: [Int] = []
var i = 0
let range = n + 1
while i < range {
currentAnswer.append(weights[i])
i += 1
}
answer.insert(currentAnswer, at: 0)
return answer
}

View File

@ -126,7 +126,7 @@ open class LegacyPresentedController: ViewController {
self.controllerNode.containerLayoutUpdated(layout, navigationBarHeight: self.navigationHeight, transition: transition)
}
override open func dismiss() {
override open func dismiss(completion: (() -> Void)? = nil) {
switch self.presentation {
case .modal:
self.controllerNode.animateModalOut { [weak self] in
@ -135,7 +135,7 @@ open class LegacyPresentedController: ViewController {
} else if let controller = self?.legacyController as? TGNavigationController {
controller.didDismiss()
}*/
self?.presentingViewController?.dismiss(animated: false, completion: nil)
self?.presentingViewController?.dismiss(animated: false, completion: completion)
}
case .custom:
/*if let controller = self.legacyController as? TGViewController {
@ -143,7 +143,7 @@ open class LegacyPresentedController: ViewController {
} else if let controller = self.legacyController as? TGNavigationController {
controller.didDismiss()
}*/
self.presentingViewController?.dismiss(animated: false, completion: nil)
self.presentingViewController?.dismiss(animated: false, completion: completion)
}
}
}

View File

@ -459,7 +459,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.updateItemHeaders()
for (headerId, headerNode) in self.itemHeaderNodes {
for (_, headerNode) in self.itemHeaderNodes {
//let position = headerNode.position
//headerNode.position = CGPoint(x: position.x, y: position.y - deltaY)
@ -490,6 +490,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
self.updateVisibleContentOffset()
self.updateVisibleItemRange()
self.updateItemNodesVisibilities()
//CATransaction.commit()
}
@ -2104,6 +2105,8 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
self.updateItemNodesVisibilities()
self.updateScroller()
self.setNeedsAnimations()
@ -2117,6 +2120,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
completion()
} else {
self.updateItemHeaders(headerNodesTransition, animateInsertion: animated || !requestItemInsertionAnimationsIndices.isEmpty)
self.updateItemNodesVisibilities()
if animated {
self.setNeedsAnimations()
@ -2280,7 +2284,7 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
addHeader(previousHeaderId, previousUpperBound, previousLowerBound, previousHeaderItem, hasValidNodes)
}
var currentIds = Set(self.itemHeaderNodes.keys)
let currentIds = Set(self.itemHeaderNodes.keys)
for id in currentIds.subtracting(visibleHeaderNodes) {
if let headerNode = self.itemHeaderNodes.removeValue(forKey: id) {
headerNode.removeFromSupernode()
@ -2288,6 +2292,20 @@ open class ListView: ASDisplayNode, UIScrollViewDelegate, UIGestureRecognizerDel
}
}
private func updateItemNodesVisibilities() {
let visibilityRect = CGRect(origin: CGPoint(x: 0.0, y: self.insets.top), size: CGSize(width: self.visibleSize.width, height: self.visibleSize.height - self.insets.top - self.insets.bottom))
for itemNode in self.itemNodes {
let itemFrame = itemNode.apparentFrame
var visibility: ListViewItemNodeVisibility = .none
if visibilityRect.intersects(itemFrame) {
visibility = .visible
}
if visibility != itemNode.visibility {
itemNode.visibility = visibility
}
}
}
private func updateAccessoryNodes(animated: Bool, currentTimestamp: Double) {
var index = -1
let count = self.itemNodes.count

View File

@ -52,6 +52,12 @@ public struct ListViewItemNodeLayout {
}
}
public enum ListViewItemNodeVisibility {
case none
case nearlyVisible
case visible
}
open class ListViewItemNode: ASDisplayNode {
let rotated: Bool
final var index: Int?
@ -85,6 +91,8 @@ open class ListViewItemNode: ASDisplayNode {
public final var canBeUsedAsScrollToItemAnchor: Bool = true
open var visibility: ListViewItemNodeVisibility = .none
open var canBeSelected: Bool {
return true
}

View File

@ -0,0 +1,149 @@
import Foundation
import SwiftSignalKit
private let defaultOrientations: UIInterfaceOrientationMask = {
if UIDevice.current.userInterfaceIdiom == .pad {
return .all
} else {
return .allButUpsideDown
}
}()
private class WindowRootViewController: UIViewController {
var presentController: ((UIViewController, Bool, (() -> Void)?) -> Void)?
var orientations: UIInterfaceOrientationMask = defaultOrientations {
didSet {
if oldValue != self.orientations {
if self.orientations == .portrait {
if UIDevice.current.orientation != .portrait {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
} else {
UIViewController.attemptRotationToDeviceOrientation()
}
}
}
}
override var preferredStatusBarStyle: UIStatusBarStyle {
return .default
}
override var prefersStatusBarHidden: Bool {
return false
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return orientations
}
}
private final class NativeWindow: UIWindow, WindowHost {
var updateSize: ((CGSize) -> Void)?
var layoutSubviewsEvent: (() -> Void)?
var updateIsUpdatingOrientationLayout: ((Bool) -> Void)?
var updateToInterfaceOrientation: (() -> Void)?
var presentController: ((ViewController) -> Void)?
var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
override var frame: CGRect {
get {
return super.frame
} set(value) {
let sizeUpdated = super.frame.size != value.size
super.frame = value
if sizeUpdated {
self.updateSize?(value.size)
}
}
}
override var bounds: CGRect {
get {
return super.bounds
}
set(value) {
let sizeUpdated = super.bounds.size != value.size
super.bounds = value
if sizeUpdated {
self.updateSize?(value.size)
}
}
}
override func layoutSubviews() {
super.layoutSubviews()
self.layoutSubviewsEvent?()
}
override func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) {
self.updateIsUpdatingOrientationLayout?(true)
super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3)
self.updateIsUpdatingOrientationLayout?(false)
self.updateToInterfaceOrientation?()
}
func present(_ controller: ViewController) {
self.presentController?(controller)
}
override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
return self.hitTestImpl?(point, event)
}
}
public func nativeWindowHostView() -> WindowHostView {
let window = NativeWindow(frame: UIScreen.main.bounds)
let rootViewController = WindowRootViewController()
window.rootViewController = rootViewController
rootViewController.viewWillAppear(false)
rootViewController.viewDidAppear(false)
rootViewController.view.isHidden = true
let hostView = WindowHostView(view: window, isRotating: {
return window.isRotating()
}, updateSupportedInterfaceOrientations: { orientations in
rootViewController.orientations = orientations
})
window.updateSize = { [weak hostView] size in
hostView?.updateSize?(size)
}
window.layoutSubviewsEvent = { [weak hostView] in
hostView?.layoutSubviews?()
}
window.updateIsUpdatingOrientationLayout = { [weak hostView] value in
hostView?.isUpdatingOrientationLayout = value
}
window.updateToInterfaceOrientation = { [weak hostView] in
hostView?.updateToInterfaceOrientation?()
}
window.presentController = { [weak hostView] controller in
hostView?.present?(controller)
}
window.hitTestImpl = { [weak hostView] point, event in
return hostView?.hitTest?(point, event)
}
rootViewController.presentController = { [weak hostView] controller, animated, completion in
if let strongSelf = hostView {
strongSelf.present?(LegacyPresentedController(legacyController: controller, presentation: .custom))
if let completion = completion {
completion()
}
}
}
return hostView
}

View File

@ -35,7 +35,7 @@ open class NavigationBar: ASDisplayNode {
open var foregroundColor: UIColor = UIColor.black {
didSet {
if let title = self.title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor)
}
}
}
@ -53,7 +53,7 @@ open class NavigationBar: ASDisplayNode {
private var collapsed: Bool {
get {
return self.frame.size.height < (20.0 + 44.0)
return self.frame.size.height.isLess(than: 44.0)
}
}
@ -173,7 +173,7 @@ open class NavigationBar: ASDisplayNode {
private var title: String? {
didSet {
if let title = self.title {
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: self.foregroundColor)
self.titleNode.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: self.foregroundColor)
if self.titleNode.supernode == nil {
self.clippingNode.addSubnode(self.titleNode)
}
@ -544,7 +544,7 @@ open class NavigationBar: ASDisplayNode {
public func makeTransitionTitleNode(foregroundColor: UIColor) -> ASDisplayNode? {
if let title = self.title {
let node = ASTextNode()
node.attributedText = NSAttributedString(string: title, font: Font.medium(17.0), textColor: foregroundColor)
node.attributedText = NSAttributedString(string: title, font: Font.semibold(17.0), textColor: foregroundColor)
return node
} else {
return nil

View File

@ -504,4 +504,17 @@ open class NavigationController: NavigationControllerProxy, ContainableControlle
}
return false
}
public final var window: WindowHost? {
if let window = self.view.window as? WindowHost {
return window
} else if let superwindow = self.view.window {
for subview in superwindow.subviews {
if let subview = subview as? WindowHost {
return subview
}
}
}
return nil
}
}

View File

@ -33,6 +33,8 @@ final class PresentationContext {
private var presentationDisposables = DisposableSet()
var topLevelSubview: UIView?
public func present(_ controller: ViewController) {
let controllerReady = controller.ready.get()
|> filter({ $0 })
@ -40,7 +42,7 @@ final class PresentationContext {
|> deliverOnMainQueue
|> timeout(2.0, queue: Queue.mainQueue(), alternate: .single(true))
if let view = self.view, let initialLayout = self.layout {
if let _ = self.view, let initialLayout = self.layout {
controller.view.frame = CGRect(origin: CGPoint(), size: initialLayout.size)
controller.containerLayoutUpdated(initialLayout, transition: .immediate)
@ -60,11 +62,19 @@ final class PresentationContext {
controller.setIgnoreAppearanceMethodInvocations(true)
if layout != initialLayout {
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
view.addSubview(controller.view)
controller.containerLayoutUpdated(layout, transition: .immediate)
if let topLevelSubview = strongSelf.topLevelSubview {
view.insertSubview(controller.view, belowSubview: topLevelSubview)
} else {
view.addSubview(controller.view)
}
controller.containerLayoutUpdated(layout, transition: .immediate)
} else {
if let topLevelSubview = strongSelf.topLevelSubview {
view.insertSubview(controller.view, belowSubview: topLevelSubview)
} else {
view.addSubview(controller.view)
}
}
controller.setIgnoreAppearanceMethodInvocations(false)
view.layer.invalidateUpTheTree()
controller.viewWillAppear(false)
@ -115,7 +125,11 @@ final class PresentationContext {
if let view = self.view, let layout = self.layout {
for controller in self.controllers {
controller.viewWillAppear(false)
if let topLevelSubview = self.topLevelSubview {
view.insertSubview(controller.view, belowSubview: topLevelSubview)
} else {
view.addSubview(controller.view)
}
controller.view.frame = CGRect(origin: CGPoint(), size: layout.size)
controller.containerLayoutUpdated(layout, transition: .immediate)
controller.viewDidAppear(false)
@ -141,4 +155,14 @@ final class PresentationContext {
}
return nil
}
func combinedSupportedOrientations() -> UIInterfaceOrientationMask {
var mask: UIInterfaceOrientationMask = .all
for controller in self.controllers {
mask = mask.intersection(controller.supportedInterfaceOrientations)
}
return mask
}
}

View File

@ -27,7 +27,7 @@ public class StatusBarSurface {
public class StatusBar: ASDisplayNode {
public var statusBarStyle: StatusBarStyle = .Black {
didSet {
if self.statusBarStyle != statusBarStyle {
if self.statusBarStyle != oldValue {
self.layer.invalidateUpTheTree()
}
}
@ -40,7 +40,7 @@ public class StatusBar: ASDisplayNode {
return UITracingLayerView()
}, didLoad: nil)
self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: Window.statusBarTracingTag))
self.layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: true, userData: self, tracingTag: WindowTracingTags.statusBar))
self.clipsToBounds = true
self.isUserInteractionEnabled = false

View File

@ -18,7 +18,13 @@ private func mapStatusBar(_ statusBar: StatusBar) -> MappedStatusBar {
}
private func mappedSurface(_ surface: StatusBarSurface) -> MappedStatusBarSurface {
return MappedStatusBarSurface(statusBars: surface.statusBars.map(mapStatusBar), surface: surface)
var statusBars: [MappedStatusBar] = []
for statusBar in surface.statusBars {
if statusBar.statusBarStyle != .Ignore {
statusBars.append(mapStatusBar(statusBar))
}
}
return MappedStatusBarSurface(statusBars: statusBars, surface: surface)
}
private func optimizeMappedSurface(statusBarSize: CGSize, surface: MappedStatusBarSurface) -> MappedStatusBarSurface {
@ -70,12 +76,36 @@ class StatusBarManager {
return
}
var mappedSurfaces = self.surfaces.map({ optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mappedSurface($0)) })
var mappedSurfaces: [MappedStatusBarSurface] = []
var mapIndex = 0
var doNotOptimize = false
for surface in self.surfaces {
inner: for statusBar in surface.statusBars {
if statusBar.statusBarStyle == .Hide {
doNotOptimize = true
break inner
}
}
let mapped = mappedSurface(surface)
if doNotOptimize {
mappedSurfaces.append(mapped)
} else {
mappedSurfaces.append(optimizeMappedSurface(statusBarSize: statusBarFrame.size, surface: mapped))
}
mapIndex += 1
}
var reduceSurfaces = true
var reduceSurfacesStatusBarStyleAndAlpha: (StatusBarStyle, CGFloat)?
var reduceIndex = 0
outer: for surface in mappedSurfaces {
for mappedStatusBar in surface.statusBars {
if reduceIndex == 0 && mappedStatusBar.style == .Hide {
reduceSurfaces = false
break outer
}
if mappedStatusBar.frame.origin.equalTo(CGPoint()) {
let statusBarAlpha = mappedStatusBar.statusBar?.alpha ?? 1.0
if let reduceSurfacesStatusBarStyleAndAlpha = reduceSurfacesStatusBarStyleAndAlpha {
@ -92,6 +122,7 @@ class StatusBarManager {
}
}
}
reduceIndex += 1
}
if reduceSurfaces {
@ -112,29 +143,33 @@ class StatusBarManager {
var globalStatusBar: (StatusBarStyle, CGFloat)?
var coveredIdentity = false
var statusBarIndex = 0
for i in 0 ..< mappedSurfaces.count {
for mappedStatusBar in mappedSurfaces[i].statusBars {
if let statusBar = mappedStatusBar.statusBar {
if mappedStatusBar.frame.origin.equalTo(CGPoint()) && !statusBar.layer.hasPositionOrOpacityAnimations() {
if !coveredIdentity {
if statusBar.statusBarStyle != .Hide {
coveredIdentity = CGFloat(1.0).isLessThanOrEqualTo(statusBar.alpha)
if i == 0 && globalStatusBar == nil {
if statusBarIndex == 0 && globalStatusBar == nil {
globalStatusBar = (mappedStatusBar.style, statusBar.alpha)
} else {
visibleStatusBars.append(statusBar)
}
}
}
} else {
visibleStatusBars.append(statusBar)
}
} else {
if !coveredIdentity {
coveredIdentity = true
if i == 0 && globalStatusBar == nil {
if statusBarIndex == 0 && globalStatusBar == nil {
globalStatusBar = (mappedStatusBar.style, 1.0)
}
}
}
statusBarIndex += 1
}
}

View File

@ -4,6 +4,8 @@ import AsyncDisplayKit
public enum StatusBarStyle {
case Black
case White
case Ignore
case Hide
}
private enum StatusBarItemType {
@ -142,7 +144,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp
let baseColor: UInt32
switch style {
case .Black:
case .Black, .Ignore, .Hide:
baseColor = 0x000000
case .White:
baseColor = 0xffffff
@ -193,7 +195,7 @@ private func tintStatusBarItem(_ context: DrawingContext, type: StatusBarItemTyp
let baseColor: UInt32
switch style {
case .Black:
case .Black, .Ignore, .Hide:
baseColor = 0x000000
case .White:
baseColor = 0xffffff

View File

@ -27,6 +27,8 @@ open class SwitchNode: ASDisplayNode {
override open func didLoad() {
super.didLoad()
(self.view as! UISwitch).backgroundColor = .white
(self.view as! UISwitch).setOn(self._isOn, animated: false)
(self.view as! UISwitch).addTarget(self, action: #selector(switchValueChanged(_:)), for: .valueChanged)

View File

@ -8,6 +8,6 @@
@end
CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath);
CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity);
CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping);
CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t);

View File

@ -41,11 +41,11 @@ CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) {
return springAnimation;
}
CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity) {
CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) {
CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath];
springAnimation.mass = 5.0f;
springAnimation.stiffness = 900.0f;
springAnimation.damping = 88.0f;
springAnimation.damping = damping;
static bool canSetInitialVelocity = true;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{

View File

@ -118,11 +118,15 @@ public extension UIImage {
private func makeSubtreeSnapshot(layer: CALayer) -> UIView? {
let view = UIView()
//view.layer.isHidden = layer.isHidden
view.layer.opacity = layer.opacity
view.layer.contents = layer.contents
view.layer.contentsRect = layer.contentsRect
view.layer.contentsScale = layer.contentsScale
view.layer.contentsCenter = layer.contentsCenter
view.layer.contentsGravity = layer.contentsGravity
view.layer.masksToBounds = layer.masksToBounds
view.layer.cornerRadius = layer.cornerRadius
if let sublayers = layer.sublayers {
for sublayer in sublayers {
let subtree = makeSubtreeSnapshot(layer: sublayer)

View File

@ -33,6 +33,11 @@ open class ViewControllerPresentationArguments {
private var containerLayout = ContainerViewLayout()
private let presentationContext: PresentationContext
public final var supportedOrientations: UIInterfaceOrientationMask = .all
override open var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return self.supportedOrientations
}
public private(set) var presentationArguments: Any?
private var _displayNode: ASDisplayNode?
@ -137,9 +142,9 @@ open class ViewControllerPresentationArguments {
let statusBarHeight: CGFloat = layout.statusBarHeight ?? 0.0
var navigationBarFrame = CGRect(origin: CGPoint(x: 0.0, y: max(0.0, statusBarHeight - 20.0)), size: CGSize(width: layout.size.width, height: 64.0))
if statusBarHeight.isLessThanOrEqualTo(0.0) {
navigationBarFrame.origin.y -= 20.0
navigationBarFrame.size.height = 20.0 + 32.0
if layout.statusBarHeight == nil {
//navigationBarFrame.origin.y -= 20.0
navigationBarFrame.size.height = 44.0
}
if !self.displayNavigationBar {
@ -169,7 +174,7 @@ open class ViewControllerPresentationArguments {
open func displayNodeDidLoad() {
if let layer = self.displayNode.layer as? CATracingLayer {
layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: Window.keyboardTracingTag))
layer.setTraceableInfo(CATracingLayerInfo(shouldBeAdjustedToInverseTransform: false, userData: self.displayNode.layer, tracingTag: WindowTracingTags.keyboard))
}
self.updateScrollToTopView()
}
@ -195,7 +200,14 @@ open class ViewControllerPresentationArguments {
}
override open func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
preconditionFailure("use present(_:in)")
super.present(viewControllerToPresent, animated: flag, completion: completion)
return
if let controller = viewControllerToPresent as? ViewController {
self.present(controller, in: .window)
} else {
preconditionFailure("use present(_:in) for \(viewControllerToPresent)")
}
}
override open func dismiss(animated flag: Bool, completion: (() -> Void)? = nil) {
@ -206,12 +218,12 @@ open class ViewControllerPresentationArguments {
}
}
private var window: Window? {
if let window = self.view.window as? Window {
public final var window: WindowHost? {
if let window = self.view.window as? WindowHost {
return window
} else if let superwindow = self.view.window {
for subview in superwindow.subviews {
if let subview = subview as? Window {
if let subview = subview as? WindowHost {
return subview
}
}
@ -247,6 +259,6 @@ open class ViewControllerPresentationArguments {
super.viewDidAppear(animated)
}
open func dismiss() {
open func dismiss(completion: (() -> Void)? = nil) {
}
}

View File

@ -11,12 +11,6 @@ private class WindowRootViewController: UIViewController {
override var prefersStatusBarHidden: Bool {
return false
}
/*override func present(_ viewControllerToPresent: UIViewController, animated flag: Bool, completion: (() -> Void)? = nil) {
if let presentController = self.presentController {
presentController(viewControllerToPresent, flag, completion)
}
}*/
}
private struct WindowLayout: Equatable {
@ -115,9 +109,37 @@ private func containedLayoutForWindowLayout(_ layout: WindowLayout) -> Container
return ContainerViewLayout(size: layout.size, intrinsicInsets: UIEdgeInsets(), statusBarHeight: layout.statusBarHeight, inputHeight: inputHeight)
}
public class Window: UIWindow {
public static let statusBarTracingTag: Int32 = 0
public static let keyboardTracingTag: Int32 = 1
public final class WindowHostView {
public let view: UIView
public let isRotating: () -> Bool
let updateSupportedInterfaceOrientations: (UIInterfaceOrientationMask) -> Void
var present: ((ViewController) -> Void)?
var updateSize: ((CGSize) -> Void)?
var layoutSubviews: (() -> Void)?
var updateToInterfaceOrientation: (() -> Void)?
var isUpdatingOrientationLayout = false
var hitTest: ((CGPoint, UIEvent?) -> UIView?)?
init(view: UIView, isRotating: @escaping () -> Bool, updateSupportedInterfaceOrientations: @escaping (UIInterfaceOrientationMask) -> Void) {
self.view = view
self.isRotating = isRotating
self.updateSupportedInterfaceOrientations = updateSupportedInterfaceOrientations
}
}
public struct WindowTracingTags {
public static let statusBar: Int32 = 0
public static let keyboard: Int32 = 1
}
public protocol WindowHost {
func present(_ controller: ViewController)
}
public class Window1 {
public let hostView: WindowHostView
private let statusBarHost: StatusBarHost?
private let statusBarManager: StatusBarManager?
@ -128,15 +150,15 @@ public class Window: UIWindow {
private var windowLayout: WindowLayout
private var updatingLayout: UpdatingLayout?
public var isUpdatingOrientationLayout = false
private let presentationContext: PresentationContext
private var tracingStatusBarsInvalidated = false
private var statusBarHidden = false
public init(frame: CGRect, statusBarHost: StatusBarHost?) {
public init(hostView: WindowHostView, statusBarHost: StatusBarHost?) {
self.hostView = hostView
self.statusBarHost = statusBarHost
let statusBarHeight: CGFloat
if let statusBarHost = statusBarHost {
@ -156,15 +178,33 @@ public class Window: UIWindow {
minimized = false
}
self.windowLayout = WindowLayout(size: frame.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized)
self.windowLayout = WindowLayout(size: self.hostView.view.bounds.size, statusBarHeight: statusBarHeight, inputHeight: 0.0, inputMinimized: minimized)
self.presentationContext = PresentationContext()
super.init(frame: frame)
self.hostView.present = { [weak self] controller in
self?.present(controller)
}
self.layer.setInvalidateTracingSublayers { [weak self] in
self.hostView.updateSize = { [weak self] size in
self?.updateSize(size)
}
self.hostView.view.layer.setInvalidateTracingSublayers { [weak self] in
self?.invalidateTracingStatusBars()
}
self.hostView.layoutSubviews = { [weak self] in
self?.layoutSubviews()
}
self.hostView.updateToInterfaceOrientation = { [weak self] in
self?.updateToInterfaceOrientation()
}
self.hostView.hitTest = { [weak self] point, event in
return self?.hitTest(point, with: event)
}
self.keyboardManager?.minimizedUpdated = { [weak self] in
if let strongSelf = self {
strongSelf.updateLayout { current in
@ -173,24 +213,9 @@ public class Window: UIWindow {
}
}
self.presentationContext.view = self
self.presentationContext.view = self.hostView.view
self.presentationContext.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate)
let rootViewController = WindowRootViewController()
super.rootViewController = rootViewController
rootViewController.viewWillAppear(false)
rootViewController.viewDidAppear(false)
rootViewController.view.isHidden = true
rootViewController.presentController = { [weak self] controller, animated, completion in
if let strongSelf = self {
strongSelf.present(LegacyPresentedController(legacyController: controller, presentation: .custom))
if let completion = completion {
completion()
}
}
}
self.statusBarChangeObserver = NotificationCenter.default.addObserver(forName: NSNotification.Name.UIApplicationWillChangeStatusBarFrame, object: nil, queue: OperationQueue.main, using: { [weak self] notification in
if let strongSelf = self {
let statusBarHeight: CGFloat = max(20.0, (notification.userInfo?[UIApplicationStatusBarFrameUserInfoKey] as? NSValue)?.cgRectValue.height ?? 20.0)
@ -205,7 +230,7 @@ public class Window: UIWindow {
let keyboardFrame: CGRect = (notification.userInfo?[UIKeyboardFrameEndUserInfoKey] as? NSValue)?.cgRectValue ?? CGRect()
let keyboardHeight = max(0.0, UIScreen.main.bounds.size.height - keyboardFrame.minY)
var duration: Double = (notification.userInfo?[UIKeyboardAnimationDurationUserInfoKey] as? NSNumber)?.doubleValue ?? 0.0
if duration > DBL_EPSILON {
if duration > Double.ulpOfOne {
duration = 0.5
}
let curve: UInt = (notification.userInfo?[UIKeyboardAnimationCurveUserInfoKey] as? NSNumber)?.uintValue ?? 7
@ -237,54 +262,36 @@ public class Window: UIWindow {
private func invalidateTracingStatusBars() {
self.tracingStatusBarsInvalidated = true
self.setNeedsLayout()
self.hostView.view.setNeedsLayout()
}
public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
for view in self.hostView.view.subviews.reversed() {
if NSStringFromClass(type(of: view)) == "UITransitionView" {
if let result = view.hitTest(point, with: event) {
return result
}
}
}
if let result = self._topLevelOverlayController?.view.hitTest(point, with: event) {
return result
}
public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
if let result = self.presentationContext.hitTest(point, with: event) {
return result
}
return self.viewController?.view.hitTest(point, with: event)
}
public override var frame: CGRect {
get {
return super.frame
}
set(value) {
let sizeUpdated = super.frame.size != value.size
super.frame = value
if sizeUpdated {
func updateSize(_ value: CGSize) {
let transition: ContainedViewLayoutTransition
if self.isRotating() {
if self.hostView.isRotating() {
transition = .animated(duration: orientationChangeDuration, curve: .easeInOut)
} else {
transition = .immediate
}
self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) }
}
}
}
public override var bounds: CGRect {
get {
return super.frame
}
set(value) {
let sizeUpdated = super.bounds.size != value.size
super.bounds = value
if sizeUpdated {
let transition: ContainedViewLayoutTransition
if self.isRotating() {
transition = .animated(duration: orientationChangeDuration, curve: .easeInOut)
} else {
transition = .immediate
}
self.updateLayout { $0.update(size: value.size, transition: transition, overrideTransition: true) }
}
}
self.updateLayout { $0.update(size: value, transition: transition, overrideTransition: true) }
}
private var _rootController: ContainableController?
@ -301,14 +308,33 @@ public class Window: UIWindow {
if let rootController = self._rootController {
rootController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate)
self.addSubview(rootController.view)
self.hostView.view.addSubview(rootController.view)
}
}
}
override public func layoutSubviews() {
super.layoutSubviews()
private var _topLevelOverlayController: ContainableController?
public var topLevelOverlayController: ContainableController? {
get {
return _topLevelOverlayController
}
set(value) {
if let topLevelOverlayController = self._topLevelOverlayController {
topLevelOverlayController.view.removeFromSuperview()
}
self._topLevelOverlayController = value
if let topLevelOverlayController = self._topLevelOverlayController {
topLevelOverlayController.containerLayoutUpdated(containedLayoutForWindowLayout(self.windowLayout), transition: .immediate)
self.hostView.view.addSubview(topLevelOverlayController.view)
}
self.presentationContext.topLevelSubview = self._topLevelOverlayController?.view
}
}
private func layoutSubviews() {
if self.tracingStatusBarsInvalidated, let statusBarManager = statusBarManager, let keyboardManager = keyboardManager {
self.tracingStatusBarsInvalidated = false
@ -316,7 +342,7 @@ public class Window: UIWindow {
statusBarManager.surfaces = []
} else {
var statusBarSurfaces: [StatusBarSurface] = []
for layers in self.layer.traceableLayerSurfaces(withTag: Window.statusBarTracingTag) {
for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.statusBar) {
let surface = StatusBarSurface()
for layer in layers {
let traceableInfo = layer.traceableInfo()
@ -326,12 +352,12 @@ public class Window: UIWindow {
}
statusBarSurfaces.append(surface)
}
self.layer.adjustTraceableLayerTransforms(CGSize())
self.hostView.view.layer.adjustTraceableLayerTransforms(CGSize())
statusBarManager.surfaces = statusBarSurfaces
}
var keyboardSurfaces: [KeyboardSurface] = []
for layers in self.layer.traceableLayerSurfaces(withTag: Window.keyboardTracingTag) {
for layers in self.hostView.view.layer.traceableLayerSurfaces(withTag: WindowTracingTags.keyboard) {
for layer in layers {
if let view = layer.delegate as? UITracingLayerView {
keyboardSurfaces.append(KeyboardSurface(host: view))
@ -339,22 +365,23 @@ public class Window: UIWindow {
}
}
keyboardManager.surfaces = keyboardSurfaces
self.hostView.updateSupportedInterfaceOrientations(self.presentationContext.combinedSupportedOrientations())
}
if !Window.isDeviceRotating() {
if !self.isUpdatingOrientationLayout {
if !UIWindow.isDeviceRotating() {
if !self.hostView.isUpdatingOrientationLayout {
self.commitUpdatingLayout()
} else {
self.addPostUpdateToInterfaceOrientationBlock(f: { [weak self] in
if let strongSelf = self {
strongSelf.setNeedsLayout()
strongSelf.hostView.view.setNeedsLayout()
}
})
}
} else {
Window.addPostDeviceOrientationDidChange({ [weak self] in
UIWindow.addPostDeviceOrientationDidChange({ [weak self] in
if let strongSelf = self {
strongSelf.setNeedsLayout()
strongSelf.hostView.view.setNeedsLayout()
}
})
}
@ -362,11 +389,7 @@ public class Window: UIWindow {
var postUpdateToInterfaceOrientationBlocks: [(Void) -> Void] = []
override public func _update(toInterfaceOrientation arg1: Int32, duration arg2: Double, force arg3: Bool) {
self.isUpdatingOrientationLayout = true
super._update(toInterfaceOrientation: arg1, duration: arg2, force: arg3)
self.isUpdatingOrientationLayout = false
private func updateToInterfaceOrientation() {
let blocks = self.postUpdateToInterfaceOrientationBlocks
self.postUpdateToInterfaceOrientationBlocks = []
for f in blocks {
@ -383,14 +406,14 @@ public class Window: UIWindow {
self.updatingLayout = UpdatingLayout(layout: self.windowLayout, transition: .immediate)
}
update(&self.updatingLayout!)
self.setNeedsLayout()
self.hostView.view.setNeedsLayout()
}
private func commitUpdatingLayout() {
if let updatingLayout = self.updatingLayout {
self.updatingLayout = nil
if updatingLayout.layout != self.windowLayout {
var statusBarHeight: CGFloat
var statusBarHeight: CGFloat?
if let statusBarHost = self.statusBarHost {
statusBarHeight = statusBarHost.statusBarFrame.size.height
} else {
@ -398,14 +421,14 @@ public class Window: UIWindow {
}
let statusBarWasHidden = self.statusBarHidden
if statusBarHiddenInLandscape && updatingLayout.layout.size.width > updatingLayout.layout.size.height {
statusBarHeight = 0.0
statusBarHeight = nil
self.statusBarHidden = true
} else {
self.statusBarHidden = false
}
if self.statusBarHidden != statusBarWasHidden {
self.tracingStatusBarsInvalidated = true
self.setNeedsLayout()
self.hostView.view.setNeedsLayout()
}
self.windowLayout = WindowLayout(size: updatingLayout.layout.size, statusBarHeight: statusBarHeight, inputHeight: updatingLayout.layout.inputHeight, inputMinimized: updatingLayout.layout.inputMinimized)