├── .gitignore
├── .gitmodules
├── Display.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ └── contents.xcworkspacedata
└── xcuserdata
│ └── peter.xcuserdatad
│ └── xcschemes
│ └── Display.xcscheme
├── Display
├── ASTransformLayerNode.swift
├── Accessibility.swift
├── AccessibilityAreaNode.swift
├── ActionSheetButtonItem.swift
├── ActionSheetCheckboxItem.swift
├── ActionSheetController.swift
├── ActionSheetControllerNode.swift
├── ActionSheetItem.swift
├── ActionSheetItemGroup.swift
├── ActionSheetItemGroupNode.swift
├── ActionSheetItemGroupsContainerNode.swift
├── ActionSheetItemNode.swift
├── ActionSheetSwitchItem.swift
├── ActionSheetTextItem.swift
├── ActionSheetTheme.swift
├── AlertContentNode.swift
├── AlertController.swift
├── AlertControllerNode.swift
├── CAAnimationUtils.swift
├── CASeeThroughTracingLayer.h
├── CASeeThroughTracingLayer.m
├── CATracingLayer.h
├── CATracingLayer.m
├── ChildWindowHostView.swift
├── CollectionIndexNode.swift
├── ContainableController.swift
├── ContainedViewLayoutTransition.swift
├── ContainerViewLayout.swift
├── ContextMenuAction.swift
├── ContextMenuActionNode.swift
├── ContextMenuContainerNode.swift
├── ContextMenuController.swift
├── ContextMenuNode.swift
├── DeviceMetrics.swift
├── Display.h
├── DisplayLinkDispatcher.swift
├── EditableTextNode.swift
├── FBAnimationPerformanceTracker.h
├── FBAnimationPerformanceTracker.mm
├── Font.swift
├── GenerateImage.swift
├── GlobalOverlayPresentationContext.swift
├── GridItem.swift
├── GridItemNode.swift
├── GridNode.swift
├── GridNodeScroller.swift
├── HapticFeedback.swift
├── HighlightTrackingButton.swift
├── HighlightableButton.swift
├── ImmediateTextNode.swift
├── Info.plist
├── InteractiveTransitionGestureRecognizer.swift
├── KeyShortcut.swift
├── KeyShortcutsController.swift
├── KeyboardManager.swift
├── LayoutSizes.swift
├── LegacyPresentedController.swift
├── LegacyPresentedControllerNode.swift
├── LinkHighlightingNode.swift
├── ListView.swift
├── ListViewAccessoryItem.swift
├── ListViewAccessoryItemNode.swift
├── ListViewAnimation.swift
├── ListViewFloatingHeaderNode.swift
├── ListViewIntermediateState.swift
├── ListViewItem.swift
├── ListViewItemHeader.swift
├── ListViewItemNode.swift
├── ListViewOverscrollBackgroundNode.swift
├── ListViewReorderingGestureRecognizer.swift
├── ListViewReorderingItemNode.swift
├── ListViewScroller.swift
├── ListViewTapGestureRecognizer.swift
├── ListViewTempItemNode.swift
├── ListViewTransactionQueue.swift
├── MinimizeKeyboardGestureRecognizer.swift
├── NSBag.h
├── NSBag.m
├── NSWeakReference.h
├── NSWeakReference.m
├── NativeWindowHostView.swift
├── NavigationBackArrowLight@2x.png
├── NavigationBackButtonNode.swift
├── NavigationBar.swift
├── NavigationBarBadge.swift
├── NavigationBarContentNode.swift
├── NavigationBarProxy.h
├── NavigationBarProxy.m
├── NavigationBarTitleTransitionNode.swift
├── NavigationBarTitleView.swift
├── NavigationBarTransitionContainer.swift
├── NavigationBarTransitionState.swift
├── NavigationButtonNode.swift
├── NavigationController.swift
├── NavigationControllerProxy.h
├── NavigationControllerProxy.m
├── NavigationShadow@2x.png
├── NavigationTitleNode.swift
├── NavigationTransitionCoordinator.swift
├── NotificationCenterUtils.h
├── NotificationCenterUtils.m
├── PageControlNode.swift
├── PeekController.swift
├── PeekControllerContent.swift
├── PeekControllerGestureRecognizer.swift
├── PeekControllerMenuItemNode.swift
├── PeekControllerMenuNode.swift
├── PeekControllerNode.swift
├── PresentationContext.swift
├── RuntimeUtils.h
├── RuntimeUtils.m
├── RuntimeUtils.swift
├── ScrollToTopProxyView.swift
├── Spring.swift
├── StatusBar.swift
├── StatusBarHost.swift
├── StatusBarManager.swift
├── StatusBarProxyNode.swift
├── SwitchNode.swift
├── TabBarContollerNode.swift
├── TabBarController.swift
├── TabBarNode.swift
├── TabBarTapRecognizer.swift
├── TapLongTapOrDoubleTapGestureRecognizer.swift
├── TextAlertController.swift
├── TextFieldNode.swift
├── TextNode.swift
├── Theme.swift
├── Toolbar.swift
├── ToolbarNode.swift
├── TooltipController.swift
├── TooltipControllerNode.swift
├── UIBarButtonItem+Proxy.h
├── UIBarButtonItem+Proxy.m
├── UIKitUtils.h
├── UIKitUtils.m
├── UIKitUtils.swift
├── UIMenuItem+Icons.h
├── UIMenuItem+Icons.m
├── UINavigationItem+Proxy.h
├── UINavigationItem+Proxy.m
├── UIViewController+Navigation.h
├── UIViewController+Navigation.m
├── UIWindow+OrientationChange.h
├── UIWindow+OrientationChange.m
├── UniversalMasterController.swift
├── UniversalTapRecognizer.swift
├── ViewController.swift
├── ViewControllerPreviewing.swift
├── ViewControllerTracingNode.swift
├── VolumeControlStatusBar.swift
├── WindowContent.swift
├── WindowCoveringView.swift
├── WindowInputAccessoryHeightProvider.swift
└── WindowPanRecognizer.swift
└── DisplayTests
├── DisplayTests.swift
└── Info.plist
/.gitignore:
--------------------------------------------------------------------------------
1 | fastlane/README.md
2 | fastlane/report.xml
3 | fastlane/test_output/*
4 | *.pbxuser
5 | !default.pbxuser
6 | *.mode1v3
7 | !default.mode1v3
8 | *.mode2v3
9 | !default.mode2v3
10 | *.perspectivev3
11 | !default.perspectivev3
12 | xcuserdata
13 | *.xccheckout
14 | *.xcscmblueprint
15 | *.moved-aside
16 | DerivedData
17 | *.hmap
18 | *.ipa
19 | *.xcuserstate
20 | .DS_Store
21 | *.dSYM
22 | *.dSYM.zip
23 | *.ipa
24 | */xcuserdata/*
25 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peter-iakovlev/Display/ffb131ac55bed21c37052a09c27150d5d380321e/.gitmodules
--------------------------------------------------------------------------------
/Display.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/Display.xcodeproj/xcuserdata/peter.xcuserdatad/xcschemes/Display.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
32 |
33 |
34 |
35 |
45 |
46 |
52 |
53 |
54 |
55 |
56 |
57 |
63 |
64 |
70 |
71 |
72 |
73 |
75 |
76 |
79 |
80 |
81 |
--------------------------------------------------------------------------------
/Display/ASTransformLayerNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | class ASTransformLayer: CATransformLayer {
5 | override var contents: Any? {
6 | get {
7 | return nil
8 | } set(value) {
9 |
10 | }
11 | }
12 |
13 | override var backgroundColor: CGColor? {
14 | get {
15 | return nil
16 | } set(value) {
17 |
18 | }
19 | }
20 |
21 | override func setNeedsLayout() {
22 | }
23 |
24 | override func layoutSublayers() {
25 | }
26 | }
27 |
28 | class ASTransformView: UIView {
29 | override class var layerClass: AnyClass {
30 | return ASTransformLayer.self
31 | }
32 | }
33 |
34 | open class ASTransformLayerNode: ASDisplayNode {
35 | public override init() {
36 | super.init()
37 | self.setLayerBlock({
38 | return ASTransformLayer()
39 | })
40 | }
41 | }
42 |
43 | open class ASTransformViewNode: ASDisplayNode {
44 | public override init() {
45 | super.init()
46 |
47 | self.setViewBlock({
48 | return ASTransformView()
49 | })
50 | }
51 | }
52 |
53 | open class ASTransformNode: ASDisplayNode {
54 | public init(layerBacked: Bool = true) {
55 | if layerBacked {
56 | super.init()
57 | self.setLayerBlock({
58 | return ASTransformLayer()
59 | })
60 | } else {
61 | super.init()
62 |
63 | self.setViewBlock({
64 | return ASTransformView()
65 | })
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Display/Accessibility.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import AsyncDisplayKit
4 |
5 | public func addAccessibilityChildren(of node: ASDisplayNode, container: Any, to list: inout [Any]) {
6 | if node.isAccessibilityElement {
7 | let element = UIAccessibilityElement(accessibilityContainer: container)
8 | element.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(node.bounds, node.view)
9 | element.accessibilityLabel = node.accessibilityLabel
10 | element.accessibilityValue = node.accessibilityValue
11 | element.accessibilityTraits = node.accessibilityTraits
12 | element.accessibilityHint = node.accessibilityHint
13 | element.accessibilityIdentifier = node.accessibilityIdentifier
14 |
15 | //node.accessibilityFrame = UIAccessibilityConvertFrameToScreenCoordinates(node.bounds, node.view)
16 | list.append(element)
17 | } else if let accessibilityElements = node.accessibilityElements {
18 | list.append(contentsOf: accessibilityElements)
19 | }
20 | }
21 |
22 |
--------------------------------------------------------------------------------
/Display/AccessibilityAreaNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public final class AccessibilityAreaNode: ASDisplayNode {
5 | public var activate: (() -> Bool)?
6 |
7 | override public init() {
8 | super.init()
9 |
10 | self.isAccessibilityElement = true
11 | }
12 |
13 | override public func accessibilityActivate() -> Bool {
14 | return self.activate?() ?? false
15 | }
16 |
17 | override public func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
18 | return nil
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Display/ActionSheetButtonItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public enum ActionSheetButtonColor {
5 | case accent
6 | case destructive
7 | case disabled
8 | }
9 |
10 |
11 | public enum ActionSheetButtonFont {
12 | case `default`
13 | case bold
14 | }
15 |
16 | public class ActionSheetButtonItem: ActionSheetItem {
17 | public let title: String
18 | public let color: ActionSheetButtonColor
19 | public let font: ActionSheetButtonFont
20 | public let enabled: Bool
21 | public let action: () -> Void
22 |
23 | public init(title: String, color: ActionSheetButtonColor = .accent, font: ActionSheetButtonFont = .default, enabled: Bool = true, action: @escaping () -> Void) {
24 | self.title = title
25 | self.color = color
26 | self.font = font
27 | self.enabled = enabled
28 | self.action = action
29 | }
30 |
31 | public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
32 | let node = ActionSheetButtonNode(theme: theme)
33 | node.setItem(self)
34 | return node
35 | }
36 |
37 | public func updateNode(_ node: ActionSheetItemNode) {
38 | guard let node = node as? ActionSheetButtonNode else {
39 | assertionFailure()
40 | return
41 | }
42 |
43 | node.setItem(self)
44 | }
45 | }
46 |
47 | public class ActionSheetButtonNode: ActionSheetItemNode {
48 | private let theme: ActionSheetControllerTheme
49 |
50 | public static let defaultFont: UIFont = Font.regular(20.0)
51 | public static let boldFont: UIFont = Font.medium(20.0)
52 |
53 | private var item: ActionSheetButtonItem?
54 |
55 | private let button: HighlightTrackingButton
56 | private let label: ASTextNode
57 |
58 | override public init(theme: ActionSheetControllerTheme) {
59 | self.theme = theme
60 |
61 | self.button = HighlightTrackingButton()
62 |
63 | self.label = ASTextNode()
64 | self.label.isUserInteractionEnabled = false
65 | self.label.maximumNumberOfLines = 1
66 | self.label.displaysAsynchronously = false
67 | self.label.truncationMode = .byTruncatingTail
68 |
69 | super.init(theme: theme)
70 |
71 | self.view.addSubview(self.button)
72 |
73 | self.label.isUserInteractionEnabled = false
74 | self.addSubnode(self.label)
75 |
76 | self.button.highligthedChanged = { [weak self] highlighted in
77 | if let strongSelf = self {
78 | if highlighted {
79 | strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemHighlightedBackgroundColor
80 | } else {
81 | UIView.animate(withDuration: 0.3, animations: {
82 | strongSelf.backgroundNode.backgroundColor = strongSelf.theme.itemBackgroundColor
83 | })
84 | }
85 | }
86 | }
87 |
88 | self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
89 | }
90 |
91 | func setItem(_ item: ActionSheetButtonItem) {
92 | self.item = item
93 |
94 | let textColor: UIColor
95 | let textFont: UIFont
96 | switch item.color {
97 | case .accent:
98 | textColor = self.theme.standardActionTextColor
99 | case .destructive:
100 | textColor = self.theme.destructiveActionTextColor
101 | case .disabled:
102 | textColor = self.theme.disabledActionTextColor
103 | }
104 | switch item.font {
105 | case .default:
106 | textFont = ActionSheetButtonNode.defaultFont
107 | case .bold:
108 | textFont = ActionSheetButtonNode.boldFont
109 | }
110 | self.label.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
111 |
112 | self.button.isEnabled = item.enabled
113 |
114 | self.setNeedsLayout()
115 | }
116 |
117 | public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
118 | return CGSize(width: constrainedSize.width, height: 57.0)
119 | }
120 |
121 | public override func layout() {
122 | super.layout()
123 |
124 | let size = self.bounds.size
125 |
126 | self.button.frame = CGRect(origin: CGPoint(), size: size)
127 |
128 | let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 10.0), height: size.height))
129 | self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
130 | }
131 |
132 | @objc func buttonPressed() {
133 | if let item = self.item {
134 | item.action()
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/Display/ActionSheetController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | open class ActionSheetController: ViewController, PresentableController {
4 | private var actionSheetNode: ActionSheetControllerNode {
5 | return self.displayNode as! ActionSheetControllerNode
6 | }
7 |
8 | public var theme: ActionSheetControllerTheme {
9 | didSet {
10 | if oldValue != self.theme {
11 | self.actionSheetNode.theme = self.theme
12 | }
13 | }
14 | }
15 |
16 | private var groups: [ActionSheetItemGroup] = []
17 |
18 | private var isDismissed: Bool = false
19 |
20 | public var dismissed: ((Bool) -> Void)?
21 |
22 | public init(theme: ActionSheetControllerTheme) {
23 | self.theme = theme
24 |
25 | super.init(navigationBarPresentationData: nil)
26 |
27 | self.blocksBackgroundWhenInOverlay = true
28 | }
29 |
30 | required public init(coder aDecoder: NSCoder) {
31 | fatalError("init(coder:) has not been implemented")
32 | }
33 |
34 | public func dismissAnimated() {
35 | if !self.isDismissed {
36 | self.isDismissed = true
37 | self.actionSheetNode.animateOut(cancelled: false)
38 | }
39 | }
40 |
41 | open override func loadDisplayNode() {
42 | self.displayNode = ActionSheetControllerNode(theme: self.theme)
43 | self.displayNodeDidLoad()
44 |
45 | self.actionSheetNode.dismiss = { [weak self] cancelled in
46 | self?.dismissed?(cancelled)
47 | self?.presentingViewController?.dismiss(animated: false)
48 | }
49 |
50 | self.actionSheetNode.setGroups(self.groups)
51 | }
52 |
53 | override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
54 | super.containerLayoutUpdated(layout, transition: transition)
55 |
56 | self.actionSheetNode.containerLayoutUpdated(layout, transition: transition)
57 | }
58 |
59 | open override func viewDidAppear(_ animated: Bool) {
60 | super.viewDidAppear(animated)
61 |
62 | self.viewDidAppear(completion: {})
63 | }
64 |
65 | public func viewDidAppear(completion: @escaping () -> Void) {
66 | self.actionSheetNode.animateIn(completion: completion)
67 | }
68 |
69 | public func setItemGroups(_ groups: [ActionSheetItemGroup]) {
70 | self.groups = groups
71 | if self.isViewLoaded {
72 | self.actionSheetNode.setGroups(groups)
73 | }
74 | }
75 |
76 | public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
77 | if self.isViewLoaded {
78 | self.actionSheetNode.updateItem(groupIndex: groupIndex, itemIndex: itemIndex, f)
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Display/ActionSheetItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol ActionSheetItem {
4 | func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode
5 | func updateNode(_ node: ActionSheetItemNode) -> Void
6 | }
7 |
--------------------------------------------------------------------------------
/Display/ActionSheetItemGroup.swift:
--------------------------------------------------------------------------------
1 |
2 | public final class ActionSheetItemGroup {
3 | let items: [ActionSheetItem]
4 | let leadingVisibleNodeCount: CGFloat?
5 |
6 | public init(items: [ActionSheetItem], leadingVisibleNodeCount: CGFloat? = nil) {
7 | self.items = items
8 | self.leadingVisibleNodeCount = leadingVisibleNodeCount
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Display/ActionSheetItemGroupsContainerNode.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import AsyncDisplayKit
3 |
4 | private let groupSpacing: CGFloat = 8.0
5 |
6 | final class ActionSheetItemGroupsContainerNode: ASDisplayNode {
7 | var theme: ActionSheetControllerTheme {
8 | didSet {
9 | self.setGroups(self.groups)
10 | self.setNeedsLayout()
11 | }
12 | }
13 |
14 | private var groups: [ActionSheetItemGroup] = []
15 | private var groupNodes: [ActionSheetItemGroupNode] = []
16 |
17 | init(theme: ActionSheetControllerTheme) {
18 | self.theme = theme
19 |
20 | super.init()
21 | }
22 |
23 | func setGroups(_ groups: [ActionSheetItemGroup]) {
24 | self.groups = groups
25 |
26 | for groupNode in self.groupNodes {
27 | groupNode.removeFromSupernode()
28 | }
29 | self.groupNodes.removeAll()
30 |
31 | for group in groups {
32 | let groupNode = ActionSheetItemGroupNode(theme: self.theme)
33 | groupNode.updateItemNodes(group.items.map({ $0.node(theme: self.theme) }), leadingVisibleNodeCount: group.leadingVisibleNodeCount ?? 1000.0)
34 | self.groupNodes.append(groupNode)
35 | self.addSubnode(groupNode)
36 | }
37 | }
38 |
39 | override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
40 | var groupsHeight: CGFloat = 0.0
41 |
42 | for groupNode in self.groupNodes.reversed() {
43 | if CGFloat(0.0).isLess(than: groupsHeight) {
44 | groupsHeight += groupSpacing
45 | }
46 |
47 | let size = groupNode.measure(CGSize(width: constrainedSize.width, height: max(0.0, constrainedSize.height - groupsHeight)))
48 | groupsHeight += size.height
49 | }
50 |
51 | return CGSize(width: constrainedSize.width, height: min(groupsHeight, constrainedSize.height))
52 | }
53 |
54 | override func layout() {
55 | var groupsHeight: CGFloat = 0.0
56 | for i in 0 ..< self.groupNodes.count {
57 | let groupNode = self.groupNodes[i]
58 |
59 | let size = groupNode.calculatedSize
60 |
61 | if i != 0 {
62 | groupsHeight += groupSpacing
63 | self.groupNodes[i - 1].trailingDimView.frame = CGRect(x: 0.0, y: groupNodes[i - 1].bounds.size.height, width: size.width, height: groupSpacing)
64 | }
65 |
66 | groupNode.frame = CGRect(origin: CGPoint(x: 0.0, y: groupsHeight), size: size)
67 | groupNode.trailingDimView.frame = CGRect()
68 |
69 | groupsHeight += size.height
70 | }
71 | }
72 |
73 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
74 | for groupNode in self.groupNodes {
75 | if groupNode.frame.contains(point) {
76 | return groupNode.hitTest(self.convert(point, to: groupNode), with: event)
77 | }
78 | }
79 | return nil
80 | }
81 |
82 | func animateDimViewsAlpha(from: CGFloat, to: CGFloat, duration: Double) {
83 | for node in self.groupNodes {
84 | node.animateDimViewsAlpha(from: from, to: to, duration: duration)
85 | }
86 | }
87 |
88 | public func updateItem(groupIndex: Int, itemIndex: Int, _ f: (ActionSheetItem) -> ActionSheetItem) {
89 | var item = self.groups[groupIndex].items[itemIndex]
90 | let itemNode = self.groupNodes[groupIndex].itemNode(at: itemIndex)
91 | item = f(item)
92 | item.updateNode(itemNode)
93 |
94 | var groupItems = self.groups[groupIndex].items
95 | groupItems[itemIndex] = item
96 |
97 | self.groups[groupIndex] = ActionSheetItemGroup(items: groupItems)
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/Display/ActionSheetItemNode.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import AsyncDisplayKit
3 |
4 | open class ActionSheetItemNode: ASDisplayNode {
5 | private let theme: ActionSheetControllerTheme
6 |
7 | public let backgroundNode: ASDisplayNode
8 | private let overflowSeparatorNode: ASDisplayNode
9 |
10 | public init(theme: ActionSheetControllerTheme) {
11 | self.theme = theme
12 |
13 | self.backgroundNode = ASDisplayNode()
14 | self.backgroundNode.backgroundColor = self.theme.itemBackgroundColor
15 |
16 | self.overflowSeparatorNode = ASDisplayNode()
17 | self.overflowSeparatorNode.backgroundColor = self.theme.itemHighlightedBackgroundColor
18 |
19 | super.init()
20 |
21 | self.addSubnode(self.backgroundNode)
22 | self.addSubnode(self.overflowSeparatorNode)
23 | }
24 |
25 | open override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
26 | return CGSize(width: constrainedSize.width, height: 57.0)
27 | }
28 |
29 | open override func layout() {
30 | self.backgroundNode.frame = CGRect(origin: CGPoint(), size: self.calculatedSize)
31 | self.overflowSeparatorNode.frame = CGRect(origin: CGPoint(x: 0.0, y: self.calculatedSize.height), size: CGSize(width: self.calculatedSize.width, height: UIScreenPixel))
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/Display/ActionSheetSwitchItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public class ActionSheetSwitchItem: ActionSheetItem {
5 | public let title: String
6 | public let isOn: Bool
7 | public let action: (Bool) -> Void
8 |
9 | public init(title: String, isOn: Bool, action: @escaping (Bool) -> Void) {
10 | self.title = title
11 | self.isOn = isOn
12 | self.action = action
13 | }
14 |
15 | public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
16 | let node = ActionSheetSwitchNode(theme: theme)
17 | node.setItem(self)
18 | return node
19 | }
20 |
21 | public func updateNode(_ node: ActionSheetItemNode) {
22 | guard let node = node as? ActionSheetSwitchNode else {
23 | assertionFailure()
24 | return
25 | }
26 |
27 | node.setItem(self)
28 | }
29 | }
30 |
31 | public class ActionSheetSwitchNode: ActionSheetItemNode {
32 | private let theme: ActionSheetControllerTheme
33 |
34 | private var item: ActionSheetSwitchItem?
35 |
36 | private let button: HighlightTrackingButton
37 | private let label: ASTextNode
38 | private let switchNode: SwitchNode
39 |
40 | override public init(theme: ActionSheetControllerTheme) {
41 | self.theme = theme
42 |
43 | self.button = HighlightTrackingButton()
44 |
45 | self.label = ASTextNode()
46 | self.label.isUserInteractionEnabled = false
47 | self.label.maximumNumberOfLines = 1
48 | self.label.displaysAsynchronously = false
49 | self.label.truncationMode = .byTruncatingTail
50 |
51 | self.switchNode = SwitchNode()
52 | self.switchNode.frameColor = theme.switchFrameColor
53 | self.switchNode.contentColor = theme.switchContentColor
54 | self.switchNode.handleColor = theme.switchHandleColor
55 |
56 | super.init(theme: theme)
57 |
58 | self.view.addSubview(self.button)
59 |
60 | self.label.isUserInteractionEnabled = false
61 | self.addSubnode(self.label)
62 | self.addSubnode(self.switchNode)
63 |
64 | self.button.addTarget(self, action: #selector(self.buttonPressed), for: .touchUpInside)
65 | self.switchNode.valueUpdated = { [weak self] value in
66 | self?.item?.action(value)
67 | }
68 | }
69 |
70 | func setItem(_ item: ActionSheetSwitchItem) {
71 | self.item = item
72 |
73 | self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetButtonNode.defaultFont, textColor: self.theme.primaryTextColor)
74 |
75 | self.switchNode.isOn = item.isOn
76 |
77 | self.setNeedsLayout()
78 | }
79 |
80 | public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
81 | return CGSize(width: constrainedSize.width, height: 57.0)
82 | }
83 |
84 | public override func layout() {
85 | super.layout()
86 |
87 | let size = self.bounds.size
88 |
89 | self.button.frame = CGRect(origin: CGPoint(), size: size)
90 |
91 | let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 51.0 - 16.0 * 2.0), height: size.height))
92 | self.label.frame = CGRect(origin: CGPoint(x: 16.0, y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
93 |
94 | let switchSize = CGSize(width: 51.0, height: 31.0)
95 | self.switchNode.frame = CGRect(origin: CGPoint(x: size.width - 16.0 - switchSize.width, y: floor((size.height - switchSize.height) / 2.0)), size: switchSize)
96 | }
97 |
98 | @objc func buttonPressed() {
99 | let value = !self.switchNode.isOn
100 | self.switchNode.setOn(value, animated: true)
101 | self.item?.action(value)
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/Display/ActionSheetTextItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public class ActionSheetTextItem: ActionSheetItem {
5 | public let title: String
6 |
7 | public init(title: String) {
8 | self.title = title
9 | }
10 |
11 | public func node(theme: ActionSheetControllerTheme) -> ActionSheetItemNode {
12 | let node = ActionSheetTextNode(theme: theme)
13 | node.setItem(self)
14 | return node
15 | }
16 |
17 | public func updateNode(_ node: ActionSheetItemNode) {
18 | guard let node = node as? ActionSheetTextNode else {
19 | assertionFailure()
20 | return
21 | }
22 |
23 | node.setItem(self)
24 | }
25 | }
26 |
27 | public class ActionSheetTextNode: ActionSheetItemNode {
28 | public static let defaultFont: UIFont = Font.regular(13.0)
29 |
30 | private let theme: ActionSheetControllerTheme
31 |
32 | private var item: ActionSheetTextItem?
33 |
34 | private let label: ASTextNode
35 |
36 | override public init(theme: ActionSheetControllerTheme) {
37 | self.theme = theme
38 |
39 | self.label = ASTextNode()
40 | self.label.isUserInteractionEnabled = false
41 | self.label.maximumNumberOfLines = 0
42 | self.label.displaysAsynchronously = false
43 | self.label.truncationMode = .byTruncatingTail
44 |
45 | super.init(theme: theme)
46 |
47 | self.label.isUserInteractionEnabled = false
48 | self.addSubnode(self.label)
49 | }
50 |
51 | func setItem(_ item: ActionSheetTextItem) {
52 | self.item = item
53 |
54 | self.label.attributedText = NSAttributedString(string: item.title, font: ActionSheetTextNode.defaultFont, textColor: self.theme.secondaryTextColor, paragraphAlignment: .center)
55 |
56 | self.setNeedsLayout()
57 | }
58 |
59 | public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
60 | let labelSize = self.label.measure(CGSize(width: max(1.0, constrainedSize.width - 20.0), height: constrainedSize.height))
61 | return CGSize(width: constrainedSize.width, height: max(57.0, labelSize.height + 32.0))
62 | }
63 |
64 | public override func layout() {
65 | super.layout()
66 |
67 | let size = self.bounds.size
68 |
69 | let labelSize = self.label.measure(CGSize(width: max(1.0, size.width - 20.0), height: size.height))
70 | self.label.frame = CGRect(origin: CGPoint(x: floorToScreenPixels((size.width - labelSize.width) / 2.0), y: floorToScreenPixels((size.height - labelSize.height) / 2.0)), size: labelSize)
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/Display/ActionSheetTheme.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public enum ActionSheetControllerThemeBackgroundType {
5 | case light
6 | case dark
7 | }
8 |
9 | public final class ActionSheetControllerTheme: Equatable {
10 | public let dimColor: UIColor
11 | public let backgroundType: ActionSheetControllerThemeBackgroundType
12 | public let itemBackgroundColor: UIColor
13 | public let itemHighlightedBackgroundColor: UIColor
14 | public let standardActionTextColor: UIColor
15 | public let destructiveActionTextColor: UIColor
16 | public let disabledActionTextColor: UIColor
17 | public let primaryTextColor: UIColor
18 | public let secondaryTextColor: UIColor
19 | public let controlAccentColor: UIColor
20 | public let controlColor: UIColor
21 | public let switchFrameColor: UIColor
22 | public let switchContentColor: UIColor
23 | public let switchHandleColor: UIColor
24 |
25 | public init(dimColor: UIColor, backgroundType: ActionSheetControllerThemeBackgroundType, itemBackgroundColor: UIColor, itemHighlightedBackgroundColor: UIColor, standardActionTextColor: UIColor, destructiveActionTextColor: UIColor, disabledActionTextColor: UIColor, primaryTextColor: UIColor, secondaryTextColor: UIColor, controlAccentColor: UIColor, controlColor: UIColor, switchFrameColor: UIColor, switchContentColor: UIColor, switchHandleColor: UIColor) {
26 | self.dimColor = dimColor
27 | self.backgroundType = backgroundType
28 | self.itemBackgroundColor = itemBackgroundColor
29 | self.itemHighlightedBackgroundColor = itemHighlightedBackgroundColor
30 | self.standardActionTextColor = standardActionTextColor
31 | self.destructiveActionTextColor = destructiveActionTextColor
32 | self.disabledActionTextColor = disabledActionTextColor
33 | self.primaryTextColor = primaryTextColor
34 | self.secondaryTextColor = secondaryTextColor
35 | self.controlAccentColor = controlAccentColor
36 | self.controlColor = controlColor
37 | self.switchFrameColor = switchFrameColor
38 | self.switchContentColor = switchContentColor
39 | self.switchHandleColor = switchHandleColor
40 | }
41 |
42 | public static func ==(lhs: ActionSheetControllerTheme, rhs: ActionSheetControllerTheme) -> Bool {
43 | if lhs.dimColor != rhs.dimColor {
44 | return false
45 | }
46 | if lhs.backgroundType != rhs.backgroundType {
47 | return false
48 | }
49 | if lhs.itemBackgroundColor != rhs.itemBackgroundColor {
50 | return false
51 | }
52 | if lhs.itemHighlightedBackgroundColor != rhs.itemHighlightedBackgroundColor {
53 | return false
54 | }
55 | if lhs.standardActionTextColor != rhs.standardActionTextColor {
56 | return false
57 | }
58 | if lhs.destructiveActionTextColor != rhs.destructiveActionTextColor {
59 | return false
60 | }
61 | if lhs.disabledActionTextColor != rhs.disabledActionTextColor {
62 | return false
63 | }
64 | if lhs.primaryTextColor != rhs.primaryTextColor {
65 | return false
66 | }
67 | if lhs.secondaryTextColor != rhs.secondaryTextColor {
68 | return false
69 | }
70 | if lhs.controlAccentColor != rhs.controlAccentColor {
71 | return false
72 | }
73 | if lhs.controlColor != rhs.controlColor {
74 | return false
75 | }
76 | if lhs.switchFrameColor != rhs.switchFrameColor {
77 | return false
78 | }
79 | if lhs.switchContentColor != rhs.switchContentColor {
80 | return false
81 | }
82 | if lhs.switchHandleColor != rhs.switchHandleColor {
83 | return false
84 | }
85 | return true
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Display/AlertContentNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | open class AlertContentNode: ASDisplayNode {
5 | open var requestLayout: ((ContainedViewLayoutTransition) -> Void)?
6 |
7 | open var dismissOnOutsideTap: Bool {
8 | return true
9 | }
10 |
11 | open func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
12 | assertionFailure()
13 |
14 | return CGSize()
15 | }
16 |
17 | open func updateTheme(_ theme: AlertControllerTheme) {
18 |
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Display/AlertController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public final class AlertControllerTheme: Equatable {
5 | public let backgroundColor: UIColor
6 | public let separatorColor: UIColor
7 | public let highlightedItemColor: UIColor
8 | public let primaryColor: UIColor
9 | public let secondaryColor: UIColor
10 | public let accentColor: UIColor
11 | public let destructiveColor: UIColor
12 | public let disabledColor: UIColor
13 |
14 | public init(backgroundColor: UIColor, separatorColor: UIColor, highlightedItemColor: UIColor, primaryColor: UIColor, secondaryColor: UIColor, accentColor: UIColor, destructiveColor: UIColor, disabledColor: UIColor) {
15 | self.backgroundColor = backgroundColor
16 | self.separatorColor = separatorColor
17 | self.highlightedItemColor = highlightedItemColor
18 | self.primaryColor = primaryColor
19 | self.secondaryColor = secondaryColor
20 | self.accentColor = accentColor
21 | self.destructiveColor = destructiveColor
22 | self.disabledColor = disabledColor
23 | }
24 |
25 | public static func ==(lhs: AlertControllerTheme, rhs: AlertControllerTheme) -> Bool {
26 | if lhs.backgroundColor != rhs.backgroundColor {
27 | return false
28 | }
29 | if lhs.separatorColor != rhs.separatorColor {
30 | return false
31 | }
32 | if lhs.highlightedItemColor != rhs.highlightedItemColor {
33 | return false
34 | }
35 | if lhs.primaryColor != rhs.primaryColor {
36 | return false
37 | }
38 | if lhs.secondaryColor != rhs.secondaryColor {
39 | return false
40 | }
41 | if lhs.accentColor != rhs.accentColor {
42 | return false
43 | }
44 | if lhs.destructiveColor != rhs.destructiveColor {
45 | return false
46 | }
47 | if lhs.disabledColor != rhs.disabledColor {
48 | return false
49 | }
50 | return true
51 | }
52 | }
53 |
54 | open class AlertController: ViewController {
55 | private var controllerNode: AlertControllerNode {
56 | return self.displayNode as! AlertControllerNode
57 | }
58 |
59 | public var theme: AlertControllerTheme {
60 | didSet {
61 | if oldValue != self.theme {
62 | self.controllerNode.updateTheme(self.theme)
63 | }
64 | }
65 | }
66 | private let contentNode: AlertContentNode
67 | private let allowInputInset: Bool
68 |
69 | public var dismissed: (() -> Void)?
70 |
71 | public init(theme: AlertControllerTheme, contentNode: AlertContentNode, allowInputInset: Bool = true) {
72 | self.theme = theme
73 | self.contentNode = contentNode
74 | self.allowInputInset = allowInputInset
75 |
76 | super.init(navigationBarPresentationData: nil)
77 |
78 | self.blocksBackgroundWhenInOverlay = true
79 |
80 | self.statusBar.statusBarStyle = .Ignore
81 | }
82 |
83 | required public init(coder aDecoder: NSCoder) {
84 | fatalError("init(coder:) has not been implemented")
85 | }
86 |
87 | override open func loadDisplayNode() {
88 | self.displayNode = AlertControllerNode(contentNode: self.contentNode, theme: self.theme, allowInputInset: self.allowInputInset)
89 | self.displayNodeDidLoad()
90 |
91 | self.controllerNode.dismiss = { [weak self] in
92 | if let strongSelf = self, strongSelf.contentNode.dismissOnOutsideTap {
93 | strongSelf.controllerNode.animateOut {
94 | self?.dismiss()
95 | }
96 | }
97 | }
98 | }
99 |
100 | override open func viewDidAppear(_ animated: Bool) {
101 | super.viewDidAppear(animated)
102 |
103 | self.controllerNode.animateIn()
104 | }
105 |
106 | override open func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
107 | super.containerLayoutUpdated(layout, transition: transition)
108 |
109 | self.controllerNode.containerLayoutUpdated(layout, transition: transition)
110 | }
111 |
112 | override open func dismiss(completion: (() -> Void)? = nil) {
113 | self.dismissed?()
114 | self.presentingViewController?.dismiss(animated: false, completion: completion)
115 | }
116 |
117 | public func dismissAnimated() {
118 | self.controllerNode.animateOut { [weak self] in
119 | self?.dismiss()
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Display/AlertControllerNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | final class AlertControllerNode: ASDisplayNode {
5 | private let dimmingNode: ASDisplayNode
6 | private let containerNode: ASDisplayNode
7 | private let effectNode: ASDisplayNode
8 | private let contentNode: AlertContentNode
9 | private let allowInputInset: Bool
10 |
11 | private var containerLayout: ContainerViewLayout?
12 |
13 | var dismiss: (() -> Void)?
14 |
15 | init(contentNode: AlertContentNode, theme: AlertControllerTheme, allowInputInset: Bool) {
16 | self.allowInputInset = allowInputInset
17 | self.dimmingNode = ASDisplayNode()
18 | self.dimmingNode.backgroundColor = UIColor(white: 0.0, alpha: 0.5)
19 |
20 | self.containerNode = ASDisplayNode()
21 | self.containerNode.backgroundColor = theme.backgroundColor
22 | self.containerNode.layer.cornerRadius = 14.0
23 | self.containerNode.layer.masksToBounds = true
24 |
25 | self.effectNode = ASDisplayNode(viewBlock: {
26 | let view = UIView()
27 | return view
28 | })
29 |
30 | self.contentNode = contentNode
31 |
32 | super.init()
33 |
34 | self.addSubnode(self.dimmingNode)
35 |
36 | self.addSubnode(self.effectNode)
37 |
38 | self.containerNode.addSubnode(self.contentNode)
39 | self.addSubnode(self.containerNode)
40 |
41 | self.contentNode.requestLayout = { [weak self] transition in
42 | if let strongSelf = self, let containerLayout = self?.containerLayout {
43 | strongSelf.containerLayoutUpdated(containerLayout, transition: transition)
44 | }
45 | }
46 | }
47 |
48 | override func didLoad() {
49 | super.didLoad()
50 |
51 | self.dimmingNode.view.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.dimmingNodeTapGesture(_:))))
52 | }
53 |
54 | func updateTheme(_ theme: AlertControllerTheme) {
55 | self.containerNode.backgroundColor = theme.backgroundColor
56 | self.contentNode.updateTheme(theme)
57 | }
58 |
59 | func animateIn() {
60 | self.dimmingNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.3)
61 | self.containerNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.25)
62 | 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)
63 | }
64 |
65 | func animateOut(completion: @escaping () -> Void) {
66 | self.dimmingNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
67 | self.containerNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3, removeOnCompletion: false)
68 | self.containerNode.layer.animateScale(from: 1.0, to: 0.8, duration: 0.4, removeOnCompletion: false, completion: { _ in
69 | completion()
70 | })
71 | }
72 |
73 | func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
74 | self.containerLayout = layout
75 |
76 | transition.updateFrame(node: self.dimmingNode, frame: CGRect(origin: CGPoint(), size: layout.size))
77 |
78 | var insetOptions: ContainerViewLayoutInsetOptions = [.statusBar]
79 | if self.allowInputInset {
80 | insetOptions.insert(.input)
81 | }
82 | var insets = layout.insets(options: insetOptions)
83 | let maxWidth = min(240.0, layout.size.width - 70.0)
84 | insets.left = floor((layout.size.width - maxWidth) / 2.0)
85 | insets.right = floor((layout.size.width - maxWidth) / 2.0)
86 | 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))
87 | let contentSize = self.contentNode.updateLayout(size: contentAvailableFrame.size, transition: transition)
88 | let containerSize = CGSize(width: contentSize.width, height: contentSize.height)
89 | 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)
90 |
91 | transition.updateFrame(node: self.containerNode, frame: containerFrame)
92 | transition.updateFrame(node: self.effectNode, frame: containerFrame)
93 | transition.updateFrame(node: self.contentNode, frame: CGRect(origin: CGPoint(), size: containerFrame.size))
94 | }
95 |
96 | @objc func dimmingNodeTapGesture(_ recognizer: UITapGestureRecognizer) {
97 | if case .ended = recognizer.state {
98 | self.dismiss?()
99 | }
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/Display/CASeeThroughTracingLayer.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface CASeeThroughTracingLayer : CALayer
4 |
5 | @end
6 |
7 | @interface CASeeThroughTracingView : UIView
8 |
9 | @end
10 |
--------------------------------------------------------------------------------
/Display/CASeeThroughTracingLayer.m:
--------------------------------------------------------------------------------
1 | #import "CASeeThroughTracingLayer.h"
2 |
3 | @interface CASeeThroughTracingLayer () {
4 | CGPoint _parentOffset;
5 | }
6 |
7 | @end
8 |
9 | @implementation CASeeThroughTracingLayer
10 |
11 | - (void)addAnimation:(CAAnimation *)anim forKey:(NSString *)key {
12 | [super addAnimation:anim forKey:key];
13 | }
14 |
15 | - (void)setFrame:(CGRect)frame {
16 | [super setFrame:frame];
17 |
18 | [self _mirrorTransformToSublayers];
19 | }
20 |
21 | - (void)setBounds:(CGRect)bounds {
22 | [super setBounds:bounds];
23 |
24 | [self _mirrorTransformToSublayers];
25 | }
26 |
27 | - (void)setPosition:(CGPoint)position {
28 | [super setPosition:position];
29 |
30 | [self _mirrorTransformToSublayers];
31 | }
32 |
33 | - (void)_mirrorTransformToSublayers {
34 | CGRect bounds = self.bounds;
35 | CGPoint position = self.position;
36 |
37 | CGPoint sublayerParentOffset = _parentOffset;
38 | sublayerParentOffset.x += position.x - (bounds.size.width) / 2.0f;
39 | sublayerParentOffset.y += position.y - (bounds.size.width) / 2.0f;
40 |
41 | for (CALayer *sublayer in self.sublayers) {
42 | if ([sublayer isKindOfClass:[CASeeThroughTracingLayer class]]) {
43 | ((CASeeThroughTracingLayer *)sublayer)->_parentOffset = sublayerParentOffset;
44 | [(CASeeThroughTracingLayer *)sublayer _mirrorTransformToSublayers];
45 | }
46 | }
47 | }
48 |
49 | @end
50 |
51 | @implementation CASeeThroughTracingView
52 |
53 | + (Class)layerClass {
54 | return [CASeeThroughTracingLayer class];
55 | }
56 |
57 | @end
58 |
--------------------------------------------------------------------------------
/Display/CATracingLayer.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface CATracingLayer : CALayer
4 |
5 | @end
6 |
7 | @interface CATracingLayerInfo : NSObject
8 |
9 | @property (nonatomic, readonly) bool shouldBeAdjustedToInverseTransform;
10 | @property (nonatomic, weak, readonly) id _Nullable userData;
11 | @property (nonatomic, readonly) int32_t tracingTag;
12 | @property (nonatomic, readonly) int32_t disableChildrenTracingTags;
13 |
14 | - (instancetype _Nonnull)initWithShouldBeAdjustedToInverseTransform:(bool)shouldBeAdjustedToInverseTransform userData:(id _Nullable)userData tracingTag:(int32_t)tracingTag disableChildrenTracingTags:(int32_t)disableChildrenTracingTags;
15 |
16 | @end
17 |
18 | @interface UITracingLayerView : UIView
19 |
20 | - (void)scheduleWithLayout:(void (^_Nonnull)())block;
21 |
22 | @end
23 |
24 | @interface CALayer (Tracing)
25 |
26 | - (CATracingLayerInfo * _Nullable)traceableInfo;
27 | - (void)setTraceableInfo:(CATracingLayerInfo * _Nullable)info;
28 |
29 | - (bool)hasPositionOrOpacityAnimations;
30 | - (bool)hasPositionAnimations;
31 |
32 | - (void)setInvalidateTracingSublayers:(void (^_Nullable)())block;
33 | - (NSArray *> * _Nonnull)traceableLayerSurfacesWithTag:(int32_t)tracingTag;
34 | - (void)adjustTraceableLayerTransforms:(CGSize)offset;
35 |
36 | - (void)setPositionAnimationMirrorTarget:(CALayer * _Nullable)animationMirrorTarget;
37 |
38 | - (void)invalidateUpTheTree;
39 |
40 | @end
41 |
--------------------------------------------------------------------------------
/Display/ChildWindowHostView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | private final class ChildWindowHostView: UIView, WindowHost {
5 | var updateSize: ((CGSize) -> Void)?
6 | var layoutSubviewsEvent: (() -> Void)?
7 | var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
8 | var presentController: ((ContainableController, PresentationSurfaceLevel, Bool, @escaping () -> Void) -> Void)?
9 | var invalidateDeferScreenEdgeGestureImpl: (() -> Void)?
10 | var invalidatePreferNavigationUIHiddenImpl: (() -> Void)?
11 | var cancelInteractiveKeyboardGesturesImpl: (() -> Void)?
12 | var forEachControllerImpl: (((ContainableController) -> Void) -> Void)?
13 | var getAccessibilityElementsImpl: (() -> [Any]?)?
14 |
15 | override var frame: CGRect {
16 | didSet {
17 | if self.frame.size != oldValue.size {
18 | self.updateSize?(self.frame.size)
19 | }
20 | }
21 | }
22 |
23 | override func layoutSubviews() {
24 | super.layoutSubviews()
25 |
26 | self.layoutSubviewsEvent?()
27 | }
28 |
29 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
30 | return self.hitTestImpl?(point, event)
31 | }
32 |
33 | func invalidateDeferScreenEdgeGestures() {
34 | self.invalidateDeferScreenEdgeGestureImpl?()
35 | }
36 |
37 | func invalidatePreferNavigationUIHidden() {
38 | self.invalidatePreferNavigationUIHiddenImpl?()
39 | }
40 |
41 | func cancelInteractiveKeyboardGestures() {
42 | self.cancelInteractiveKeyboardGesturesImpl?()
43 | }
44 |
45 | func forEachController(_ f: (ContainableController) -> Void) {
46 | self.forEachControllerImpl?(f)
47 | }
48 |
49 | func present(_ controller: ContainableController, on level: PresentationSurfaceLevel, blockInteraction: Bool, completion: @escaping () -> Void) {
50 | self.presentController?(controller, level, blockInteraction, completion)
51 | }
52 |
53 | func presentInGlobalOverlay(_ controller: ContainableController) {
54 | }
55 | }
56 |
57 | public func childWindowHostView(parent: UIView) -> WindowHostView {
58 | let view = ChildWindowHostView()
59 | view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
60 |
61 | let hostView = WindowHostView(containerView: view, eventView: view, isRotating: {
62 | return false
63 | }, updateSupportedInterfaceOrientations: { orientations in
64 | }, updateDeferScreenEdgeGestures: { edges in
65 | }, updatePreferNavigationUIHidden: { value in
66 | })
67 |
68 | view.updateSize = { [weak hostView] size in
69 | hostView?.updateSize?(size, 0.0)
70 | }
71 |
72 | view.layoutSubviewsEvent = { [weak hostView] in
73 | hostView?.layoutSubviews?()
74 | }
75 |
76 | /*window.updateIsUpdatingOrientationLayout = { [weak hostView] value in
77 | hostView?.isUpdatingOrientationLayout = value
78 | }
79 |
80 | window.updateToInterfaceOrientation = { [weak hostView] in
81 | hostView?.updateToInterfaceOrientation?()
82 | }*/
83 |
84 | view.presentController = { [weak hostView] controller, level, block, f in
85 | hostView?.present?(controller, level, block, f)
86 | }
87 |
88 | /*view.presentNativeImpl = { [weak hostView] controller in
89 | hostView?.presentNative?(controller)
90 | }*/
91 |
92 | view.hitTestImpl = { [weak hostView] point, event in
93 | return hostView?.hitTest?(point, event)
94 | }
95 |
96 | view.invalidateDeferScreenEdgeGestureImpl = { [weak hostView] in
97 | return hostView?.invalidateDeferScreenEdgeGesture?()
98 | }
99 |
100 | view.invalidatePreferNavigationUIHiddenImpl = { [weak hostView] in
101 | return hostView?.invalidatePreferNavigationUIHidden?()
102 | }
103 |
104 | view.cancelInteractiveKeyboardGesturesImpl = { [weak hostView] in
105 | hostView?.cancelInteractiveKeyboardGestures?()
106 | }
107 |
108 | view.forEachControllerImpl = { [weak hostView] f in
109 | hostView?.forEachController?(f)
110 | }
111 |
112 | view.getAccessibilityElementsImpl = { [weak hostView] in
113 | return hostView?.getAccessibilityElements?()
114 | }
115 |
116 | return hostView
117 | }
118 |
--------------------------------------------------------------------------------
/Display/ContainableController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import AsyncDisplayKit
3 | import SwiftSignalKit
4 |
5 | public protocol PresentableController: class {
6 | func viewDidAppear(completion: @escaping () -> Void)
7 | }
8 |
9 | public protocol ContainableController: class {
10 | var view: UIView! { get }
11 | var displayNode: ASDisplayNode { get }
12 | var isViewLoaded: Bool { get }
13 | var isOpaqueWhenInOverlay: Bool { get }
14 | var blocksBackgroundWhenInOverlay: Bool { get }
15 | var ready: Promise { get }
16 |
17 | func combinedSupportedOrientations(currentOrientationToLock: UIInterfaceOrientationMask) -> ViewControllerSupportedOrientations
18 | var deferScreenEdgeGestures: UIRectEdge { get }
19 |
20 | func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition)
21 | func updateToInterfaceOrientation(_ orientation: UIInterfaceOrientation)
22 |
23 | func viewWillAppear(_ animated: Bool)
24 | func viewWillDisappear(_ animated: Bool)
25 | func viewDidAppear(_ animated: Bool)
26 | func viewDidDisappear(_ animated: Bool)
27 | }
28 |
--------------------------------------------------------------------------------
/Display/ContainerViewLayout.swift:
--------------------------------------------------------------------------------
1 |
2 | public struct ContainerViewLayoutInsetOptions: OptionSet {
3 | public let rawValue: Int
4 |
5 | public init(rawValue: Int) {
6 | self.rawValue = rawValue
7 | }
8 |
9 | public init() {
10 | self.rawValue = 0
11 | }
12 |
13 | public static let statusBar = ContainerViewLayoutInsetOptions(rawValue: 1 << 0)
14 | public static let input = ContainerViewLayoutInsetOptions(rawValue: 1 << 1)
15 | }
16 |
17 | public enum ContainerViewLayoutSizeClass {
18 | case compact
19 | case regular
20 | }
21 |
22 | public struct LayoutMetrics: Equatable {
23 | public let widthClass: ContainerViewLayoutSizeClass
24 | public let heightClass: ContainerViewLayoutSizeClass
25 |
26 | public init(widthClass: ContainerViewLayoutSizeClass, heightClass: ContainerViewLayoutSizeClass) {
27 | self.widthClass = widthClass
28 | self.heightClass = heightClass
29 | }
30 |
31 | public init() {
32 | self.widthClass = .compact
33 | self.heightClass = .compact
34 | }
35 | }
36 |
37 | public struct ContainerViewLayout: Equatable {
38 | public let size: CGSize
39 | public let metrics: LayoutMetrics
40 | public let intrinsicInsets: UIEdgeInsets
41 | public let safeInsets: UIEdgeInsets
42 | public let statusBarHeight: CGFloat?
43 | public var inputHeight: CGFloat?
44 | public let standardInputHeight: CGFloat
45 | public let inputHeightIsInteractivellyChanging: Bool
46 | public let inVoiceOver: Bool
47 |
48 | public init(size: CGSize, metrics: LayoutMetrics, intrinsicInsets: UIEdgeInsets, safeInsets: UIEdgeInsets, statusBarHeight: CGFloat?, inputHeight: CGFloat?, standardInputHeight: CGFloat, inputHeightIsInteractivellyChanging: Bool, inVoiceOver: Bool) {
49 | self.size = size
50 | self.metrics = metrics
51 | self.intrinsicInsets = intrinsicInsets
52 | self.safeInsets = safeInsets
53 | self.statusBarHeight = statusBarHeight
54 | self.inputHeight = inputHeight
55 | self.standardInputHeight = standardInputHeight
56 | self.inputHeightIsInteractivellyChanging = inputHeightIsInteractivellyChanging
57 | self.inVoiceOver = inVoiceOver
58 | }
59 |
60 | public func insets(options: ContainerViewLayoutInsetOptions) -> UIEdgeInsets {
61 | var insets = self.intrinsicInsets
62 | if let statusBarHeight = self.statusBarHeight , options.contains(.statusBar) {
63 | insets.top += statusBarHeight
64 | }
65 | if let inputHeight = self.inputHeight , options.contains(.input) {
66 | insets.bottom = max(inputHeight, insets.bottom)
67 | }
68 | return insets
69 | }
70 |
71 | public func addedInsets(insets: UIEdgeInsets) -> ContainerViewLayout {
72 | return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: UIEdgeInsets(top: self.intrinsicInsets.top + insets.top, left: self.intrinsicInsets.left + insets.left, bottom: self.intrinsicInsets.bottom + insets.bottom, right: self.intrinsicInsets.right + insets.right), safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
73 | }
74 |
75 | public func withUpdatedSize(_ size: CGSize) -> ContainerViewLayout {
76 | return ContainerViewLayout(size: size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
77 | }
78 |
79 | public func withUpdatedInputHeight(_ inputHeight: CGFloat?) -> ContainerViewLayout {
80 | return ContainerViewLayout(size: self.size, metrics: self.metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
81 | }
82 |
83 | public func withUpdatedMetrics(_ metrics: LayoutMetrics) -> ContainerViewLayout {
84 | return ContainerViewLayout(size: self.size, metrics: metrics, intrinsicInsets: self.intrinsicInsets, safeInsets: self.safeInsets, statusBarHeight: self.statusBarHeight, inputHeight: self.inputHeight, standardInputHeight: self.standardInputHeight, inputHeightIsInteractivellyChanging: self.inputHeightIsInteractivellyChanging, inVoiceOver: self.inVoiceOver)
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/Display/ContextMenuAction.swift:
--------------------------------------------------------------------------------
1 |
2 | public enum ContextMenuActionContent {
3 | case text(title: String, accessibilityLabel: String)
4 | case icon(UIImage)
5 | }
6 |
7 | public struct ContextMenuAction {
8 | public let content: ContextMenuActionContent
9 | public let action: () -> Void
10 |
11 | public init(content: ContextMenuActionContent, action: @escaping () -> Void) {
12 | self.content = content
13 | self.action = action
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/Display/ContextMenuActionNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | final private class ContextMenuActionButton: HighlightTrackingButton {
5 | override func convert(_ point: CGPoint, from view: UIView?) -> CGPoint {
6 | if view is UIWindow {
7 | return super.convert(point, from: nil)
8 | } else {
9 | return super.convert(point, from: view)
10 | }
11 | }
12 | }
13 |
14 | final class ContextMenuActionNode: ASDisplayNode {
15 | private let textNode: ImmediateTextNode?
16 | private var textSize: CGSize?
17 | private let iconNode: ASImageNode?
18 | private let action: () -> Void
19 | private let button: ContextMenuActionButton
20 | private let actionArea: AccessibilityAreaNode
21 |
22 | var dismiss: (() -> Void)?
23 |
24 | init(action: ContextMenuAction) {
25 | self.actionArea = AccessibilityAreaNode()
26 | self.actionArea.accessibilityTraits = UIAccessibilityTraitButton
27 |
28 | switch action.content {
29 | case let .text(title, accessibilityLabel):
30 | self.actionArea.accessibilityLabel = accessibilityLabel
31 |
32 | let textNode = ImmediateTextNode()
33 | textNode.isUserInteractionEnabled = false
34 | textNode.displaysAsynchronously = false
35 | textNode.attributedText = NSAttributedString(string: title, font: Font.regular(14.0), textColor: UIColor.white)
36 | textNode.isAccessibilityElement = false
37 |
38 | self.textNode = textNode
39 | self.iconNode = nil
40 | case let .icon(image):
41 | let iconNode = ASImageNode()
42 | iconNode.displaysAsynchronously = false
43 | iconNode.displayWithoutProcessing = true
44 | iconNode.image = image
45 |
46 | self.iconNode = iconNode
47 | self.textNode = nil
48 | }
49 | self.action = action.action
50 |
51 | self.button = ContextMenuActionButton()
52 | self.button.isAccessibilityElement = false
53 |
54 | super.init()
55 |
56 | self.backgroundColor = UIColor(white: 0.0, alpha: 0.8)
57 | if let textNode = self.textNode {
58 | self.addSubnode(textNode)
59 | }
60 | if let iconNode = self.iconNode {
61 | self.addSubnode(iconNode)
62 | }
63 |
64 | self.button.highligthedChanged = { [weak self] highlighted in
65 | self?.backgroundColor = highlighted ? UIColor(white: 0.0, alpha: 0.4) : UIColor(white: 0.0, alpha: 0.8)
66 | }
67 | self.view.addSubview(self.button)
68 | self.addSubnode(self.actionArea)
69 |
70 | self.actionArea.activate = { [weak self] in
71 | self?.buttonPressed()
72 | return true
73 | }
74 | }
75 |
76 | override func didLoad() {
77 | super.didLoad()
78 |
79 | self.button.addTarget(self, action: #selector(self.buttonPressed), for: [.touchUpInside])
80 | }
81 |
82 | @objc private func buttonPressed() {
83 | self.backgroundColor = UIColor(white: 0.0, alpha: 0.4)
84 |
85 | self.action()
86 | if let dismiss = self.dismiss {
87 | dismiss()
88 | }
89 | }
90 |
91 | override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
92 | if let textNode = self.textNode {
93 | let textSize = textNode.updateLayout(constrainedSize)
94 | self.textSize = textSize
95 | return CGSize(width: textSize.width + 36.0, height: 54.0)
96 | } else if let iconNode = self.iconNode, let image = iconNode.image {
97 | return CGSize(width: image.size.width + 36.0, height: 54.0)
98 | } else {
99 | return CGSize(width: 36.0, height: 54.0)
100 | }
101 | }
102 |
103 | override func layout() {
104 | super.layout()
105 |
106 | self.button.frame = self.bounds
107 | self.actionArea.frame = self.bounds
108 |
109 | if let textNode = self.textNode, let textSize = self.textSize {
110 | textNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - textSize.width) / 2.0), y: floor((self.bounds.size.height - textSize.height) / 2.0)), size: textSize)
111 | }
112 | if let iconNode = self.iconNode, let image = iconNode.image {
113 | let iconSize = image.size
114 | iconNode.frame = CGRect(origin: CGPoint(x: floor((self.bounds.size.width - iconSize.width) / 2.0), y: floor((self.bounds.size.height - iconSize.height) / 2.0)), size: iconSize)
115 | }
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/Display/ContextMenuContainerNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | private struct CachedMaskParams: Equatable {
5 | let size: CGSize
6 | let relativeArrowPosition: CGFloat
7 | let arrowOnBottom: Bool
8 | }
9 |
10 | private final class ContextMenuContainerMaskView: UIView {
11 | override class var layerClass: AnyClass {
12 | return CAShapeLayer.self
13 | }
14 | }
15 |
16 | public final class ContextMenuContainerNode: ASDisplayNode {
17 | private var cachedMaskParams: CachedMaskParams?
18 | private let maskView = ContextMenuContainerMaskView()
19 |
20 | public var relativeArrowPosition: (CGFloat, Bool)?
21 |
22 | //private let effectView: UIVisualEffectView
23 |
24 | override public init() {
25 | //self.effectView = UIVisualEffectView(effect: UIBlurEffect(style: .light))
26 |
27 | super.init()
28 |
29 | self.backgroundColor = UIColor(rgb: 0xeaecec)
30 | //self.view.addSubview(self.effectView)
31 | //self.effectView.mask = self.maskView
32 | self.view.mask = self.maskView
33 | }
34 |
35 | override public func didLoad() {
36 | super.didLoad()
37 |
38 | self.layer.allowsGroupOpacity = true
39 | }
40 |
41 | override public func layout() {
42 | super.layout()
43 |
44 | self.updateLayout(transition: .immediate)
45 | }
46 |
47 | public func updateLayout(transition: ContainedViewLayoutTransition) {
48 | //self.effectView.frame = self.bounds
49 |
50 | let maskParams = CachedMaskParams(size: self.bounds.size, relativeArrowPosition: self.relativeArrowPosition?.0 ?? self.bounds.size.width / 2.0, arrowOnBottom: self.relativeArrowPosition?.1 ?? true)
51 | if self.cachedMaskParams != maskParams {
52 | let path = UIBezierPath()
53 | let cornerRadius: CGFloat = 6.0
54 | let verticalInset: CGFloat = 9.0
55 | let arrowWidth: CGFloat = 18.0
56 | let requestedArrowPosition = maskParams.relativeArrowPosition
57 | let arrowPosition = max(cornerRadius + arrowWidth / 2.0, min(maskParams.size.width - cornerRadius - arrowWidth / 2.0, requestedArrowPosition))
58 | let arrowOnBottom = maskParams.arrowOnBottom
59 |
60 | path.move(to: CGPoint(x: 0.0, y: verticalInset + cornerRadius))
61 | path.addArc(withCenter: CGPoint(x: cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat.pi, endAngle: CGFloat(3.0 * CGFloat.pi / 2.0), clockwise: true)
62 | if !arrowOnBottom {
63 | path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: verticalInset))
64 | path.addLine(to: CGPoint(x: arrowPosition, y: 0.0))
65 | path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: verticalInset))
66 | }
67 | path.addLine(to: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset))
68 | path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: verticalInset + cornerRadius), radius: cornerRadius, startAngle: CGFloat(3.0 * CGFloat.pi / 2.0), endAngle: 0.0, clockwise: true)
69 | path.addLine(to: CGPoint(x: maskParams.size.width, y: maskParams.size.height - cornerRadius - verticalInset))
70 | path.addArc(withCenter: CGPoint(x: maskParams.size.width - cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: 0.0, endAngle: CGFloat(CGFloat.pi / 2.0), clockwise: true)
71 | if arrowOnBottom {
72 | path.addLine(to: CGPoint(x: arrowPosition + arrowWidth / 2.0, y: maskParams.size.height - verticalInset))
73 | path.addLine(to: CGPoint(x: arrowPosition, y: maskParams.size.height))
74 | path.addLine(to: CGPoint(x: arrowPosition - arrowWidth / 2.0, y: maskParams.size.height - verticalInset))
75 | }
76 | path.addLine(to: CGPoint(x: cornerRadius, y: maskParams.size.height - verticalInset))
77 | path.addArc(withCenter: CGPoint(x: cornerRadius, y: maskParams.size.height - cornerRadius - verticalInset), radius: cornerRadius, startAngle: CGFloat(CGFloat.pi / 2.0), endAngle: CGFloat(M_PI), clockwise: true)
78 | path.close()
79 |
80 | self.cachedMaskParams = maskParams
81 | if let layer = self.maskView.layer as? CAShapeLayer {
82 | if case let .animated(duration, curve) = transition, let previousPath = layer.path {
83 | layer.animate(from: previousPath, to: path.cgPath, keyPath: "path", timingFunction: curve.timingFunction, duration: duration)
84 | }
85 | layer.path = path.cgPath
86 | }
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/Display/ContextMenuController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public final class ContextMenuControllerPresentationArguments {
5 | fileprivate let sourceNodeAndRect: () -> (ASDisplayNode, CGRect, ASDisplayNode, CGRect)?
6 |
7 | public init(sourceNodeAndRect: @escaping () -> (ASDisplayNode, CGRect, ASDisplayNode, CGRect)?) {
8 | self.sourceNodeAndRect = sourceNodeAndRect
9 | }
10 | }
11 |
12 | public final class ContextMenuController: ViewController {
13 | private var contextMenuNode: ContextMenuNode {
14 | return self.displayNode as! ContextMenuNode
15 | }
16 |
17 | private let actions: [ContextMenuAction]
18 | private let catchTapsOutside: Bool
19 | private let hasHapticFeedback: Bool
20 |
21 | private var layout: ContainerViewLayout?
22 |
23 | public var dismissed: (() -> Void)?
24 |
25 | public init(actions: [ContextMenuAction], catchTapsOutside: Bool = false, hasHapticFeedback: Bool = false) {
26 | self.actions = actions
27 | self.catchTapsOutside = catchTapsOutside
28 | self.hasHapticFeedback = hasHapticFeedback
29 |
30 | super.init(navigationBarPresentationData: nil)
31 | }
32 |
33 | required public init(coder aDecoder: NSCoder) {
34 | fatalError("init(coder:) has not been implemented")
35 | }
36 |
37 | override public func loadDisplayNode() {
38 | self.displayNode = ContextMenuNode(actions: self.actions, dismiss: { [weak self] in
39 | self?.dismissed?()
40 | self?.contextMenuNode.animateOut {
41 | self?.presentingViewController?.dismiss(animated: false)
42 | }
43 | }, catchTapsOutside: self.catchTapsOutside, hasHapticFeedback: self.hasHapticFeedback)
44 | self.displayNodeDidLoad()
45 | }
46 |
47 | override public func viewDidAppear(_ animated: Bool) {
48 | super.viewDidAppear(animated)
49 |
50 | self.contextMenuNode.animateIn()
51 | }
52 |
53 | override public func dismiss(completion: (() -> Void)? = nil) {
54 | self.dismissed?()
55 | self.contextMenuNode.animateOut { [weak self] in
56 | self?.presentingViewController?.dismiss(animated: false)
57 | }
58 | }
59 |
60 | override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
61 | super.containerLayoutUpdated(layout, transition: transition)
62 |
63 | if self.layout != nil && self.layout! != layout {
64 | self.dismissed?()
65 | self.contextMenuNode.animateOut { [weak self] in
66 | self?.presentingViewController?.dismiss(animated: false)
67 | }
68 | } else {
69 | self.layout = layout
70 |
71 | if let presentationArguments = self.presentationArguments as? ContextMenuControllerPresentationArguments, let (sourceNode, sourceRect, containerNode, containerRect) = presentationArguments.sourceNodeAndRect() {
72 | self.contextMenuNode.sourceRect = sourceNode.view.convert(sourceRect, to: nil)
73 | self.contextMenuNode.containerRect = containerNode.view.convert(containerRect, to: nil)
74 | } else {
75 | self.contextMenuNode.sourceRect = nil
76 | self.contextMenuNode.containerRect = nil
77 | }
78 |
79 | self.contextMenuNode.containerLayoutUpdated(layout, transition: transition)
80 | }
81 | }
82 |
83 | override public func viewWillAppear(_ animated: Bool) {
84 | super.viewWillAppear(animated)
85 |
86 | self.contextMenuNode.animateIn()
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/Display/Display.h:
--------------------------------------------------------------------------------
1 | //
2 | // Display.h
3 | // Display
4 | //
5 | // Created by Peter on 29/07/15.
6 | // Copyright © 2015 Telegram. All rights reserved.
7 | //
8 |
9 | #import
10 |
11 | //! Project version number for Display.
12 | FOUNDATION_EXPORT double DisplayVersionNumber;
13 |
14 | //! Project version string for Display.
15 | FOUNDATION_EXPORT const unsigned char DisplayVersionString[];
16 |
17 | // In this header, you should import all the public headers of your framework using statements like #import
18 |
19 | #import
20 | #import
21 | #import
22 | #import
23 | #import
24 | #import
25 | #import
26 | #import
27 | #import
28 | #import
29 | #import
30 | #import
31 | #import
32 | #import
33 | #import
34 | #import
35 |
--------------------------------------------------------------------------------
/Display/DisplayLinkDispatcher.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public class DisplayLinkDispatcher: NSObject {
4 | private var displayLink: CADisplayLink!
5 | private var blocksToDispatch: [() -> Void] = []
6 | private let limit: Int
7 |
8 | public init(limit: Int = 0) {
9 | self.limit = limit
10 |
11 | super.init()
12 |
13 | if #available(iOS 10.0, *) {
14 | //self.displayLink.preferredFramesPerSecond = 60
15 | } else {
16 | self.displayLink = CADisplayLink(target: self, selector: #selector(self.run))
17 | self.displayLink.isPaused = true
18 | self.displayLink.add(to: RunLoop.main, forMode: RunLoopMode.commonModes)
19 | }
20 | }
21 |
22 | public func dispatch(f: @escaping () -> Void) {
23 | if self.displayLink == nil {
24 | if Thread.isMainThread {
25 | f()
26 | } else {
27 | DispatchQueue.main.async(execute: f)
28 | }
29 | } else {
30 | self.blocksToDispatch.append(f)
31 | self.displayLink.isPaused = false
32 | }
33 | }
34 |
35 | @objc func run() {
36 | for _ in 0 ..< (self.limit == 0 ? 1000 : self.limit) {
37 | if self.blocksToDispatch.count == 0 {
38 | self.displayLink.isPaused = true
39 | break
40 | } else {
41 | let f = self.blocksToDispatch.removeFirst()
42 | f()
43 | }
44 | }
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/Display/EditableTextNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public class EditableTextNode: ASEditableTextNode {
5 | override public var keyboardAppearance: UIKeyboardAppearance {
6 | get {
7 | return super.keyboardAppearance
8 | }
9 | set {
10 | guard newValue != self.keyboardAppearance else {
11 | return
12 | }
13 | let resigning = self.isFirstResponder()
14 | if resigning {
15 | self.resignFirstResponder()
16 | }
17 | super.keyboardAppearance = newValue
18 | if resigning {
19 | self.becomeFirstResponder()
20 | }
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Display/FBAnimationPerformanceTracker.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | /*
4 | * This is an example provided by Facebook are for non-commercial testing and
5 | * evaluation purposes only.
6 | *
7 | * Facebook reserves all rights not expressly granted.
8 | *
9 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
10 | * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
11 | * FITNESS FOR A PARTICULAR PURPOSE AND NON INFRINGEMENT. IN NO EVENT SHALL
12 | * FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
13 | * AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
14 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
15 | *
16 | *
17 | * FBAnimationPerformanceTracker
18 | * -----------------------------------------------------------------------
19 | *
20 | * This class provides animation performance tracking functionality. It basically
21 | * measures the app's frame rate during an operation, and reports this information.
22 | *
23 | * 1) In Foo's designated initializer, construct a tracker object
24 | *
25 | * 2) Add calls to -start and -stop in appropriate places, e.g. for a ScrollView
26 | *
27 | * - (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView {
28 | * [_apTracker start];
29 | * }
30 | *
31 | * - (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView
32 | * {
33 | * if (!scrollView.dragging) {
34 | * [_apTracker stop];
35 | * }
36 | * }
37 | *
38 | * - (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate {
39 | * if (!decelerate) {
40 | * [_apTracker stop];
41 | * }
42 | * }
43 | *
44 | * Notes
45 | * -----
46 | * [] The tracker operates by creating a CADisplayLink object to measure the frame rate of the display
47 | * during start/stop interval.
48 | *
49 | * [] Calls to -stop that were not preceded by a matching call to -start have no effect.
50 | *
51 | * [] 2 calls to -start in a row will trash the data accumulated so far and not log anything.
52 | *
53 | *
54 | * Configuration object for the core tracker
55 | *
56 | * ===============================================================================
57 | * I highly recommend for you to use the standard configuration provided
58 | * These are essentially here so that the computation of the metric is transparent
59 | * and you can feel confident in what the numbers mean.
60 | * ===============================================================================
61 | */
62 | struct FBAnimationPerformanceTrackerConfig
63 | {
64 | // Number of frame drop that defines a "small" drop event. By default, 1.
65 | NSInteger smallDropEventFrameNumber;
66 | // Number of frame drop that defines a "large" drop event. By default, 4.
67 | NSInteger largeDropEventFrameNumber;
68 | // Number of maximum frame drops to which the drop will be trimmed down to. Currently 15.
69 | NSInteger maxFrameDropAccount;
70 |
71 | // If YES, will report stack traces
72 | BOOL reportStackTraces;
73 | };
74 | typedef struct FBAnimationPerformanceTrackerConfig FBAnimationPerformanceTrackerConfig;
75 |
76 |
77 | @protocol FBAnimationPerformanceTrackerDelegate
78 |
79 | /**
80 | * Core Metric
81 | *
82 | * You are responsible for the aggregation of these metrics (it being on the client or the server). I recommend to implement both
83 | * to limit the payload you are sending to the server.
84 | *
85 | * The final recommended metric being: - SUM(duration) / SUM(smallDropEvent) aka the number of seconds between one frame drop or more
86 | * - SUM(duration) / SUM(largeDropEvent) aka the number of seconds between four frame drops or more
87 | *
88 | * The first metric will tell you how smooth is your scroll view.
89 | * The second metric will tell you how clowny your scroll view can get.
90 | *
91 | * Every time stop is called, this event will fire reporting the performance.
92 | *
93 | * NOTE on this metric:
94 | * - It has been tested at scale on many Facebook apps.
95 | * - It follows the curves of devices.
96 | * - You will need about 100K calls for the number to converge.
97 | * - It is perfectly correlated to X = Percentage of time spent at 60fps. Number of seconds between one frame drop = 1 / ( 1 - Time spent at 60 fps)
98 | * - We report fraction of drops. 7 frame drop = 1.75 of a large frame drop if a large drop is 4 frame drop.
99 | * This is to preserve the correlation mentionned above.
100 | */
101 | - (void)reportDurationInMS:(NSInteger)duration smallDropEvent:(double)smallDropEvent largeDropEvent:(double)largeDropEvent;
102 |
103 | /**
104 | * Stack traces
105 | *
106 | * Dark magic of the animation tracker. In case of a frame drop, this will return a stack trace.
107 | * This will NOT be reported on the main-thread, but off-main thread to save a few CPU cycles.
108 | *
109 | * The slide is constant value that needs to be reported with the stack for processing.
110 | * This currently only allows for symbolication of your own image.
111 | *
112 | * Future work includes symbolicating all modules. I personnaly find it usually
113 | * good enough to know the name of the module.
114 | *
115 | * The stack will have the following format:
116 | * Foundation:0x123|MyApp:0x234|MyApp:0x345|
117 | *
118 | * The slide will have the following format:
119 | * 0x456
120 | */
121 | - (void)reportStackTrace:(NSString *)stack withSlide:(NSString *)slide;
122 |
123 | @end
124 |
125 | @interface FBAnimationPerformanceTracker : NSObject
126 |
127 | - (instancetype)initWithConfig:(FBAnimationPerformanceTrackerConfig)config;
128 |
129 | + (FBAnimationPerformanceTrackerConfig)standardConfig;
130 |
131 | @property (weak, nonatomic, readwrite) id delegate;
132 |
133 | - (void)start;
134 | - (void)stop;
135 |
136 | @end
137 |
--------------------------------------------------------------------------------
/Display/Font.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public struct Font {
5 | public static func regular(_ size: CGFloat) -> UIFont {
6 | return UIFont.systemFont(ofSize: size)
7 | }
8 |
9 | public static func medium(_ size: CGFloat) -> UIFont {
10 | if #available(iOS 8.2, *) {
11 | return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.medium)
12 | } else {
13 | return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
14 | }
15 | }
16 |
17 | public static func semibold(_ size: CGFloat) -> UIFont {
18 | if #available(iOS 8.2, *) {
19 | return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.semibold)
20 | } else {
21 | return CTFontCreateWithName("HelveticaNeue-Medium" as CFString, size, nil)
22 | }
23 | }
24 |
25 | public static func bold(_ size: CGFloat) -> UIFont {
26 | if #available(iOS 8.2, *) {
27 | return UIFont.boldSystemFont(ofSize: size)
28 | } else {
29 | return CTFontCreateWithName("HelveticaNeue-Bold" as CFString, size, nil)
30 | }
31 | }
32 |
33 | public static func light(_ size: CGFloat) -> UIFont {
34 | if #available(iOS 8.2, *) {
35 | return UIFont.systemFont(ofSize: size, weight: UIFont.Weight.light)
36 | } else {
37 | return CTFontCreateWithName("HelveticaNeue-Light" as CFString, size, nil)
38 | }
39 | }
40 |
41 | public static func semiboldItalic(_ size: CGFloat) -> UIFont {
42 | if let descriptor = UIFont.systemFont(ofSize: size).fontDescriptor.withSymbolicTraits([.traitBold, .traitItalic]) {
43 | return UIFont(descriptor: descriptor, size: size)
44 | } else {
45 | return UIFont.italicSystemFont(ofSize: size)
46 | }
47 | }
48 |
49 | public static func monospace(_ size: CGFloat) -> UIFont {
50 | return UIFont(name: "Menlo-Regular", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
51 | }
52 |
53 | public static func semiboldMonospace(_ size: CGFloat) -> UIFont {
54 | return UIFont(name: "Menlo-Bold", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
55 | }
56 |
57 | public static func italicMonospace(_ size: CGFloat) -> UIFont {
58 | return UIFont(name: "Menlo-Italic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
59 | }
60 |
61 | public static func semiboldItalicMonospace(_ size: CGFloat) -> UIFont {
62 | return UIFont(name: "Menlo-BoldItalic", size: size - 1.0) ?? UIFont.systemFont(ofSize: size)
63 | }
64 |
65 | public static func italic(_ size: CGFloat) -> UIFont {
66 | return UIFont.italicSystemFont(ofSize: size)
67 | }
68 | }
69 |
70 | public extension NSAttributedString {
71 | convenience init(string: String, font: UIFont? = nil, textColor: UIColor = UIColor.black, paragraphAlignment: NSTextAlignment? = nil) {
72 | var attributes: [NSAttributedStringKey: AnyObject] = [:]
73 | if let font = font {
74 | attributes[NSAttributedStringKey.font] = font
75 | }
76 | attributes[NSAttributedStringKey.foregroundColor] = textColor
77 | if let paragraphAlignment = paragraphAlignment {
78 | let paragraphStyle = NSMutableParagraphStyle()
79 | paragraphStyle.alignment = paragraphAlignment
80 | attributes[NSAttributedStringKey.paragraphStyle] = paragraphStyle
81 | }
82 | self.init(string: string, attributes: attributes)
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/Display/GridItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol GridSection {
4 | var height: CGFloat { get }
5 | var hashValue: Int { get }
6 |
7 | func isEqual(to: GridSection) -> Bool
8 | func node() -> ASDisplayNode
9 | }
10 |
11 | public protocol GridItem {
12 | var section: GridSection? { get }
13 | func node(layout: GridNodeLayout, synchronousLoad: Bool) -> GridItemNode
14 | func update(node: GridItemNode)
15 | var aspectRatio: CGFloat { get }
16 | var fillsRowWithHeight: CGFloat? { get }
17 | var fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? { get }
18 | }
19 |
20 | public extension GridItem {
21 | var aspectRatio: CGFloat {
22 | return 1.0
23 | }
24 |
25 | var fillsRowWithHeight: CGFloat? {
26 | return nil
27 | }
28 |
29 | var fillsRowWithDynamicHeight: ((CGFloat) -> CGFloat)? {
30 | return nil
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/Display/GridItemNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | open class GridItemNode: ASDisplayNode {
5 | open var isVisibleInGrid = false
6 | open var isGridScrolling = false
7 |
8 | final var cachedFrame: CGRect = CGRect()
9 | override open var frame: CGRect {
10 | get {
11 | return self.cachedFrame
12 | } set(value) {
13 | self.cachedFrame = value
14 | super.frame = value
15 | }
16 | }
17 |
18 | open func updateLayout(item: GridItem, size: CGSize, isVisible: Bool, synchronousLoads: Bool) {
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Display/GridNodeScroller.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | private class GridNodeScrollerLayer: CALayer {
4 | override func setNeedsDisplay() {
5 | }
6 | }
7 |
8 | private class GridNodeScrollerView: UIScrollView {
9 | override class var layerClass: AnyClass {
10 | return GridNodeScrollerLayer.self
11 | }
12 |
13 | override init(frame: CGRect) {
14 | super.init(frame: frame)
15 |
16 | if #available(iOSApplicationExtension 11.0, *) {
17 | self.contentInsetAdjustmentBehavior = .never
18 | }
19 | }
20 |
21 | required init?(coder aDecoder: NSCoder) {
22 | fatalError("init(coder:) has not been implemented")
23 | }
24 |
25 | override func touchesShouldCancel(in view: UIView) -> Bool {
26 | return true
27 | }
28 |
29 | @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
30 | return false
31 | }
32 | }
33 |
34 | open class GridNodeScroller: ASDisplayNode, UIGestureRecognizerDelegate {
35 | public var scrollView: UIScrollView {
36 | return self.view as! UIScrollView
37 | }
38 |
39 | override init() {
40 | super.init()
41 |
42 | self.setViewBlock({
43 | return GridNodeScrollerView(frame: CGRect())
44 | })
45 |
46 | self.scrollView.scrollsToTop = false
47 | }
48 |
49 | required public init?(coder aDecoder: NSCoder) {
50 | fatalError("init(coder:) has not been implemented")
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Display/HapticFeedback.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public enum ImpactHapticFeedbackStyle: Hashable {
5 | case light
6 | case medium
7 | case heavy
8 | }
9 |
10 | @available(iOSApplicationExtension 10.0, *)
11 | private final class HapticFeedbackImpl {
12 | private lazy var impactGenerator: [ImpactHapticFeedbackStyle : UIImpactFeedbackGenerator] = {
13 | [.light: UIImpactFeedbackGenerator(style: .light),
14 | .medium: UIImpactFeedbackGenerator(style: .medium),
15 | .heavy: UIImpactFeedbackGenerator(style: .heavy)] }()
16 | private lazy var selectionGenerator = { UISelectionFeedbackGenerator() }()
17 | private lazy var notificationGenerator = { UINotificationFeedbackGenerator() }()
18 |
19 | func prepareTap() {
20 | self.selectionGenerator.prepare()
21 | }
22 |
23 | func tap() {
24 | self.selectionGenerator.selectionChanged()
25 | }
26 |
27 | func prepareImpact(_ style: ImpactHapticFeedbackStyle) {
28 | self.impactGenerator[style]?.prepare()
29 | }
30 |
31 | func impact(_ style: ImpactHapticFeedbackStyle) {
32 | self.impactGenerator[style]?.impactOccurred()
33 | }
34 |
35 | func success() {
36 | self.notificationGenerator.notificationOccurred(.success)
37 | }
38 |
39 | func prepareError() {
40 | self.notificationGenerator.prepare()
41 | }
42 |
43 | func error() {
44 | self.notificationGenerator.notificationOccurred(.error)
45 | }
46 |
47 | @objc dynamic func f() {
48 | }
49 | }
50 |
51 | public final class HapticFeedback {
52 | private var impl: AnyObject?
53 |
54 | public init() {
55 | }
56 |
57 | deinit {
58 | let impl = self.impl
59 | DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 1.0, execute: {
60 | if #available(iOSApplicationExtension 10.0, *) {
61 | if let impl = impl as? HapticFeedbackImpl {
62 | impl.f()
63 | }
64 | }
65 | })
66 | }
67 |
68 | @available(iOSApplicationExtension 10.0, *)
69 | private func withImpl(_ f: (HapticFeedbackImpl) -> Void) {
70 | if self.impl == nil {
71 | self.impl = HapticFeedbackImpl()
72 | }
73 | f(self.impl as! HapticFeedbackImpl)
74 | }
75 |
76 | public func prepareTap() {
77 | if #available(iOSApplicationExtension 10.0, *) {
78 | self.withImpl { impl in
79 | impl.prepareTap()
80 | }
81 | }
82 | }
83 |
84 | public func tap() {
85 | if #available(iOSApplicationExtension 10.0, *) {
86 | self.withImpl { impl in
87 | impl.tap()
88 | }
89 | }
90 | }
91 |
92 | public func prepareImpact(_ style: ImpactHapticFeedbackStyle = .medium) {
93 | if #available(iOSApplicationExtension 10.0, *) {
94 | self.withImpl { impl in
95 | impl.prepareImpact(style)
96 | }
97 | }
98 | }
99 |
100 | public func impact(_ style: ImpactHapticFeedbackStyle = .medium) {
101 | if #available(iOSApplicationExtension 10.0, *) {
102 | self.withImpl { impl in
103 | impl.impact(style)
104 | }
105 | }
106 | }
107 |
108 | public func success() {
109 | if #available(iOSApplicationExtension 10.0, *) {
110 | self.withImpl { impl in
111 | impl.success()
112 | }
113 | }
114 | }
115 |
116 | public func prepareError() {
117 | if #available(iOSApplicationExtension 10.0, *) {
118 | self.withImpl { impl in
119 | impl.prepareError()
120 | }
121 | }
122 | }
123 |
124 | public func error() {
125 | if #available(iOSApplicationExtension 10.0, *) {
126 | self.withImpl { impl in
127 | impl.error()
128 | }
129 | }
130 | }
131 | }
132 |
133 |
--------------------------------------------------------------------------------
/Display/HighlightTrackingButton.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | open class HighlightTrackingButton: UIButton {
4 | private var internalHighlighted = false
5 |
6 | public var internalHighligthedChanged: (Bool) -> Void = { _ in }
7 | public var highligthedChanged: (Bool) -> Void = { _ in }
8 |
9 | open override func beginTracking(_ touch: UITouch, with event: UIEvent?) -> Bool {
10 | if !self.internalHighlighted {
11 | self.internalHighlighted = true
12 | self.highligthedChanged(true)
13 | self.internalHighligthedChanged(true)
14 | }
15 |
16 | return super.beginTracking(touch, with: event)
17 | }
18 |
19 | open override func endTracking(_ touch: UITouch?, with event: UIEvent?) {
20 | if self.internalHighlighted {
21 | self.internalHighlighted = false
22 | self.highligthedChanged(false)
23 | self.internalHighligthedChanged(false)
24 | }
25 |
26 | super.endTracking(touch, with: event)
27 | }
28 |
29 | open override func cancelTracking(with event: UIEvent?) {
30 | if self.internalHighlighted {
31 | self.internalHighlighted = false
32 | self.highligthedChanged(false)
33 | self.internalHighligthedChanged(false)
34 | }
35 |
36 | super.cancelTracking(with: event)
37 | }
38 |
39 | open override func touchesCancelled(_ touches: Set, with event: UIEvent?) {
40 | if self.internalHighlighted {
41 | self.internalHighlighted = false
42 | self.highligthedChanged(false)
43 | self.internalHighligthedChanged(false)
44 | }
45 |
46 | super.touchesCancelled(touches, with: event)
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/Display/HighlightableButton.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import AsyncDisplayKit
4 |
5 | open class HighlightableButton: HighlightTrackingButton {
6 | override public init(frame: CGRect) {
7 | super.init(frame: frame)
8 |
9 | self.adjustsImageWhenHighlighted = false
10 | self.adjustsImageWhenDisabled = false
11 | self.internalHighligthedChanged = { [weak self] highlighted in
12 | if let strongSelf = self {
13 | if highlighted {
14 | strongSelf.layer.removeAnimation(forKey: "opacity")
15 | strongSelf.alpha = 0.4
16 | } else {
17 | strongSelf.alpha = 1.0
18 | strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
19 | }
20 | }
21 | }
22 | }
23 |
24 | required public init?(coder aDecoder: NSCoder) {
25 | fatalError("init(coder:) has not been implemented")
26 | }
27 | }
28 |
29 | open class HighlightTrackingButtonNode: ASButtonNode {
30 | private var internalHighlighted = false
31 |
32 | public var highligthedChanged: (Bool) -> Void = { _ in }
33 |
34 | open override func beginTracking(with touch: UITouch, with event: UIEvent?) -> Bool {
35 | if !self.internalHighlighted {
36 | self.internalHighlighted = true
37 | self.highligthedChanged(true)
38 | }
39 |
40 | return super.beginTracking(with: touch, with: event)
41 | }
42 |
43 | open override func endTracking(with touch: UITouch?, with event: UIEvent?) {
44 | if self.internalHighlighted {
45 | self.internalHighlighted = false
46 | self.highligthedChanged(false)
47 | }
48 |
49 | super.endTracking(with: touch, with: event)
50 | }
51 |
52 | open override func cancelTracking(with event: UIEvent?) {
53 | if self.internalHighlighted {
54 | self.internalHighlighted = false
55 | self.highligthedChanged(false)
56 | }
57 |
58 | super.cancelTracking(with: event)
59 | }
60 |
61 | open override func touchesCancelled(_ touches: Set?, with event: UIEvent?) {
62 | super.touchesCancelled(touches, with: event)
63 |
64 | if self.internalHighlighted {
65 | self.internalHighlighted = false
66 | self.highligthedChanged(false)
67 | }
68 | }
69 | }
70 |
71 | open class HighlightableButtonNode: HighlightTrackingButtonNode {
72 | override public init() {
73 | super.init()
74 |
75 | self.highligthedChanged = { [weak self] highlighted in
76 | if let strongSelf = self {
77 | if highlighted {
78 | strongSelf.layer.removeAnimation(forKey: "opacity")
79 | strongSelf.alpha = 0.4
80 | } else {
81 | strongSelf.alpha = 1.0
82 | strongSelf.layer.animateAlpha(from: 0.4, to: 1.0, duration: 0.2)
83 | }
84 | }
85 | }
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/Display/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | FMWK
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | $(CURRENT_PROJECT_VERSION)
23 | NSPrincipalClass
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/Display/InteractiveTransitionGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | private func hasHorizontalGestures(_ view: UIView, point: CGPoint?) -> Bool {
5 | if view.disablesInteractiveTransitionGestureRecognizer {
6 | return true
7 | }
8 |
9 | if let point = point, let test = view.interactiveTransitionGestureRecognizerTest, test(point) {
10 | return true
11 | }
12 |
13 | if let view = view as? ListViewBackingView {
14 | let transform = view.transform
15 | let angle: Double = Double(atan2f(Float(transform.b), Float(transform.a)))
16 | let term1: Double = abs(angle - Double.pi / 2.0)
17 | let term2: Double = abs(angle + Double.pi / 2.0)
18 | let term3: Double = abs(angle - Double.pi * 3.0 / 2.0)
19 | if term1 < 0.001 || term2 < 0.001 || term3 < 0.001 {
20 | return true
21 | }
22 | }
23 |
24 | if let superview = view.superview {
25 | return hasHorizontalGestures(superview, point: point != nil ? view.convert(point!, to: superview) : nil)
26 | } else {
27 | return false
28 | }
29 | }
30 |
31 | class InteractiveTransitionGestureRecognizer: UIPanGestureRecognizer {
32 | var validatedGesture = false
33 | var firstLocation: CGPoint = CGPoint()
34 |
35 | override init(target: Any?, action: Selector?) {
36 | super.init(target: target, action: action)
37 |
38 | self.maximumNumberOfTouches = 1
39 | }
40 |
41 | override func reset() {
42 | super.reset()
43 |
44 | validatedGesture = false
45 | }
46 |
47 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
48 | super.touchesBegan(touches, with: event)
49 |
50 | let touch = touches.first!
51 | self.firstLocation = touch.location(in: self.view)
52 |
53 | if let target = self.view?.hitTest(self.firstLocation, with: event) {
54 | if hasHorizontalGestures(target, point: self.view?.convert(self.firstLocation, to: target)) {
55 | self.state = .cancelled
56 | }
57 | }
58 | }
59 |
60 | override func touchesMoved(_ touches: Set, with event: UIEvent) {
61 | let location = touches.first!.location(in: self.view)
62 | let translation = CGPoint(x: location.x - firstLocation.x, y: location.y - firstLocation.y)
63 |
64 | let absTranslationX: CGFloat = abs(translation.x)
65 | let absTranslationY: CGFloat = abs(translation.y)
66 |
67 | if !validatedGesture {
68 | if self.firstLocation.x < 16.0 {
69 | validatedGesture = true
70 | } else if translation.x < 0.0 {
71 | self.state = .failed
72 | } else if absTranslationY > 2.0 && absTranslationY > absTranslationX * 2.0 {
73 | self.state = .failed
74 | } else if absTranslationX > 2.0 && absTranslationY * 2.0 < absTranslationX {
75 | validatedGesture = true
76 | }
77 | }
78 |
79 | if validatedGesture {
80 | super.touchesMoved(touches, with: event)
81 | }
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Display/KeyShortcut.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public struct KeyShortcut: Hashable {
4 | let title: String
5 | let input: String
6 | let modifiers: UIKeyModifierFlags
7 | let action: () -> Void
8 |
9 | public init(title: String = "", input: String = "", modifiers: UIKeyModifierFlags = [], action: @escaping () -> Void = {}) {
10 | self.title = title
11 | self.input = input
12 | self.modifiers = modifiers
13 | self.action = action
14 | }
15 |
16 | public var hashValue: Int {
17 | return input.hashValue ^ modifiers.hashValue
18 | }
19 |
20 | public static func ==(lhs: KeyShortcut, rhs: KeyShortcut) -> Bool {
21 | return lhs.hashValue == rhs.hashValue
22 | }
23 | }
24 |
25 | extension UIKeyModifierFlags: Hashable {
26 | public var hashValue: Int {
27 | return self.rawValue
28 | }
29 | }
30 |
31 | extension KeyShortcut {
32 | var uiKeyCommand: UIKeyCommand {
33 | if #available(iOSApplicationExtension 9.0, *), !self.title.isEmpty {
34 | return UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:)), discoverabilityTitle: self.title)
35 | } else {
36 | return UIKeyCommand(input: self.input, modifierFlags: self.modifiers, action: #selector(KeyShortcutsController.handleKeyCommand(_:)))
37 | }
38 | }
39 |
40 | func isEqual(to command: UIKeyCommand) -> Bool {
41 | return self.input == command.input && self.modifiers == command.modifierFlags
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/Display/KeyShortcutsController.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | public protocol KeyShortcutResponder {
4 | var keyShortcuts: [KeyShortcut] { get };
5 | }
6 |
7 | public class KeyShortcutsController: UIResponder {
8 | private var effectiveShortcuts: [KeyShortcut]?
9 | private var viewControllerEnumerator: ((ContainableController) -> Bool) -> Void
10 |
11 | public static var isAvailable: Bool {
12 | if #available(iOSApplicationExtension 8.0, *), UIDevice.current.userInterfaceIdiom == .pad {
13 | return true
14 | } else {
15 | return false
16 | }
17 | }
18 |
19 | public init(enumerator: @escaping ((ContainableController) -> Bool) -> Void) {
20 | self.viewControllerEnumerator = enumerator
21 | super.init()
22 | }
23 |
24 | public override var keyCommands: [UIKeyCommand]? {
25 | var convertedCommands: [UIKeyCommand] = []
26 | var shortcuts: [KeyShortcut] = []
27 |
28 | self.viewControllerEnumerator({ viewController -> Bool in
29 | guard let viewController = viewController as? KeyShortcutResponder else {
30 | return true
31 | }
32 | shortcuts.append(contentsOf: viewController.keyShortcuts)
33 | return true
34 | })
35 |
36 | // iOS 8 fix
37 | convertedCommands.append(KeyShortcut(modifiers:[.command]).uiKeyCommand)
38 | convertedCommands.append(KeyShortcut(modifiers:[.alternate]).uiKeyCommand)
39 |
40 | convertedCommands.append(contentsOf: shortcuts.map { $0.uiKeyCommand })
41 |
42 | self.effectiveShortcuts = shortcuts
43 |
44 | return convertedCommands
45 | }
46 |
47 | @objc func handleKeyCommand(_ command: UIKeyCommand) {
48 | if let shortcut = findShortcut(for: command) {
49 | shortcut.action()
50 | }
51 | }
52 |
53 | private func findShortcut(for command: UIKeyCommand) -> KeyShortcut? {
54 | if let shortcuts = self.effectiveShortcuts {
55 | for shortcut in shortcuts {
56 | if shortcut.isEqual(to: command) {
57 | return shortcut
58 | }
59 | }
60 | }
61 | return nil
62 | }
63 |
64 | public override func canPerformAction(_ action: Selector, withSender sender: Any?) -> Bool {
65 | if let keyCommand = sender as? UIKeyCommand, let _ = findShortcut(for: keyCommand) {
66 | return true
67 | } else {
68 | return super.canPerformAction(action, withSender: sender)
69 | }
70 | }
71 |
72 | public override func target(forAction action: Selector, withSender sender: Any?) -> Any? {
73 | if let keyCommand = sender as? UIKeyCommand, let _ = findShortcut(for: keyCommand) {
74 | return self
75 | } else {
76 | return super.target(forAction: action, withSender: sender)
77 | }
78 | }
79 |
80 | public override var canBecomeFirstResponder: Bool {
81 | return true
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Display/KeyboardManager.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | struct KeyboardSurface {
5 | let host: UIView
6 | }
7 |
8 | private func getFirstResponder(_ view: UIView) -> UIView? {
9 | if view.isFirstResponder {
10 | return view
11 | } else {
12 | for subview in view.subviews {
13 | if let result = getFirstResponder(subview) {
14 | return result
15 | }
16 | }
17 | return nil
18 | }
19 | }
20 |
21 | class KeyboardManager {
22 | private let host: StatusBarHost
23 |
24 | private weak var previousPositionAnimationMirrorSource: CATracingLayer?
25 | private weak var previousFirstResponderView: UIView?
26 | private var interactiveInputOffset: CGFloat = 0.0
27 |
28 | var surfaces: [KeyboardSurface] = [] {
29 | didSet {
30 | self.updateSurfaces(oldValue)
31 | }
32 | }
33 |
34 | init(host: StatusBarHost) {
35 | self.host = host
36 | }
37 |
38 | func getCurrentKeyboardHeight() -> CGFloat {
39 | guard let keyboardView = self.host.keyboardView else {
40 | return 0.0
41 | }
42 | return keyboardView.bounds.height
43 | }
44 |
45 | func updateInteractiveInputOffset(_ offset: CGFloat, transition: ContainedViewLayoutTransition, completion: @escaping () -> Void) {
46 | guard let keyboardView = self.host.keyboardView else {
47 | return
48 | }
49 |
50 | self.interactiveInputOffset = offset
51 |
52 | let previousBounds = keyboardView.bounds
53 | let updatedBounds = CGRect(origin: CGPoint(x: 0.0, y: -offset), size: previousBounds.size)
54 | keyboardView.layer.bounds = updatedBounds
55 | if transition.isAnimated {
56 | transition.animateOffsetAdditive(layer: keyboardView.layer, offset: previousBounds.minY - updatedBounds.minY, completion: completion)
57 | } else {
58 | completion()
59 | }
60 |
61 | //transition.updateSublayerTransformOffset(layer: keyboardView.layer, offset: CGPoint(x: 0.0, y: offset))
62 | }
63 |
64 | private func updateSurfaces(_ previousSurfaces: [KeyboardSurface]) {
65 | guard let keyboardWindow = self.host.keyboardWindow else {
66 | return
67 | }
68 |
69 | var firstResponderView: UIView?
70 | var firstResponderDisableAutomaticKeyboardHandling: UIResponderDisableAutomaticKeyboardHandling = []
71 | for surface in self.surfaces {
72 | if let view = getFirstResponder(surface.host) {
73 | firstResponderView = surface.host
74 | firstResponderDisableAutomaticKeyboardHandling = view.disableAutomaticKeyboardHandling
75 | break
76 | }
77 | }
78 |
79 | if let firstResponderView = firstResponderView {
80 | let containerOrigin = firstResponderView.convert(CGPoint(), to: nil)
81 | var filteredTranslation = containerOrigin.x
82 | if firstResponderDisableAutomaticKeyboardHandling.contains(.forward) {
83 | filteredTranslation = max(0.0, filteredTranslation)
84 | }
85 | if firstResponderDisableAutomaticKeyboardHandling.contains(.backward) {
86 | filteredTranslation = min(0.0, filteredTranslation)
87 | }
88 | let horizontalTranslation = CATransform3DMakeTranslation(filteredTranslation, 0.0, 0.0)
89 | let currentTransform = keyboardWindow.layer.sublayerTransform
90 | if !CATransform3DEqualToTransform(horizontalTranslation, currentTransform) {
91 | //print("set to \(CGPoint(x: containerOrigin.x, y: self.interactiveInputOffset))")
92 | keyboardWindow.layer.sublayerTransform = horizontalTranslation
93 | }
94 | if let tracingLayer = firstResponderView.layer as? CATracingLayer, firstResponderDisableAutomaticKeyboardHandling.isEmpty {
95 | if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource, previousPositionAnimationMirrorSource !== tracingLayer {
96 | previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil)
97 | }
98 | tracingLayer.setPositionAnimationMirrorTarget(keyboardWindow.layer)
99 | self.previousPositionAnimationMirrorSource = tracingLayer
100 | } else if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource {
101 | previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil)
102 | self.previousPositionAnimationMirrorSource = nil
103 | }
104 | } else {
105 | keyboardWindow.layer.sublayerTransform = CATransform3DIdentity
106 | if let previousPositionAnimationMirrorSource = self.previousPositionAnimationMirrorSource {
107 | previousPositionAnimationMirrorSource.setPositionAnimationMirrorTarget(nil)
108 | self.previousPositionAnimationMirrorSource = nil
109 | }
110 | if let previousFirstResponderView = previousFirstResponderView {
111 | if previousFirstResponderView.window == nil {
112 | keyboardWindow.isHidden = true
113 | keyboardWindow.layer.cancelAnimationsRecursive(key: "position")
114 | keyboardWindow.layer.cancelAnimationsRecursive(key: "bounds")
115 | keyboardWindow.isHidden = false
116 | }
117 | }
118 | }
119 |
120 | self.previousFirstResponderView = firstResponderView
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/Display/LayoutSizes.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public func horizontalContainerFillingSizeForLayout(layout: ContainerViewLayout, sideInset: CGFloat) -> CGFloat {
4 | if case .regular = layout.metrics.widthClass {
5 | return min(layout.size.width, 414.0) - sideInset * 2.0
6 | } else {
7 | return layout.size.width - sideInset * 2.0
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Display/LegacyPresentedControllerNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | final class LegacyPresentedControllerNode: ASDisplayNode {
5 | private var containerLayout: ContainerViewLayout?
6 |
7 | var controllerView: UIView? {
8 | didSet {
9 | if let controllerView = self.controllerView, let containerLayout = self.containerLayout {
10 | controllerView.frame = CGRect(origin: CGPoint(), size: containerLayout.size)
11 | }
12 | }
13 | }
14 |
15 | override init() {
16 | super.init()
17 |
18 | self.setViewBlock({
19 | return UITracingLayerView()
20 | })
21 | }
22 |
23 | func containerLayoutUpdated(_ layout: ContainerViewLayout, navigationBarHeight: CGFloat, transition: ContainedViewLayoutTransition) {
24 | self.containerLayout = layout
25 | if let controllerView = self.controllerView {
26 | controllerView.frame = CGRect(origin: CGPoint(), size: layout.size)
27 | }
28 | }
29 |
30 | func animateModalIn() {
31 | self.layer.animatePosition(from: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), to: self.layer.position, duration: 0.5, timingFunction: kCAMediaTimingFunctionSpring)
32 | }
33 |
34 | func animateModalOut(completion: @escaping () -> Void) {
35 | self.layer.animatePosition(from: self.layer.position, to: CGPoint(x: self.layer.position.x, y: self.layer.position.y + self.layer.bounds.size.height), duration: 0.2, timingFunction: kCAMediaTimingFunctionEaseInEaseOut, removeOnCompletion: false, completion: { _ in
36 | completion()
37 | })
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Display/ListViewAccessoryItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public protocol ListViewAccessoryItem {
4 | func isEqualToItem(_ other: ListViewAccessoryItem) -> Bool
5 | func node() -> ListViewAccessoryItemNode
6 | }
7 |
--------------------------------------------------------------------------------
/Display/ListViewAccessoryItemNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if os(macOS)
3 | #else
4 | import AsyncDisplayKit
5 | #endif
6 |
7 | open class ListViewAccessoryItemNode: ASDisplayNode {
8 | var transitionOffset: CGPoint = CGPoint() {
9 | didSet {
10 | self.bounds = CGRect(origin: self.transitionOffset, size: self.bounds.size)
11 | }
12 | }
13 |
14 | private var transitionOffsetAnimation: ListViewAnimation?
15 |
16 | final func animateTransitionOffset(_ from: CGPoint, beginAt: Double, duration: Double, curve: @escaping (CGFloat) -> CGFloat) {
17 | self.transitionOffset = from
18 | self.transitionOffsetAnimation = ListViewAnimation(from: from, to: CGPoint(), duration: duration, curve: curve, beginAt: beginAt, update: { [weak self] _, currentValue in
19 | if let strongSelf = self {
20 | strongSelf.transitionOffset = currentValue
21 | }
22 | })
23 | }
24 |
25 | final func removeAllAnimations() {
26 | self.transitionOffsetAnimation = nil
27 | self.transitionOffset = CGPoint()
28 | }
29 |
30 | final func animate(_ timestamp: Double) -> Bool {
31 | if let animation = self.transitionOffsetAnimation {
32 | animation.applyAt(timestamp)
33 |
34 | if animation.completeAt(timestamp) {
35 | self.transitionOffsetAnimation = nil
36 | } else {
37 | return true
38 | }
39 | }
40 |
41 | return false
42 | }
43 |
44 | override open func layout() {
45 | super.layout()
46 |
47 | self.updateLayout(size: self.bounds.size, leftInset: 0.0, rightInset: 0.0)
48 | }
49 |
50 | open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat) {
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/Display/ListViewFloatingHeaderNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | open class ListViewFloatingHeaderNode: ASDisplayNode {
5 | open func updateLayout(width: CGFloat, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
6 | return 0.0
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/Display/ListViewItem.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if os(macOS)
3 | import SwiftSignalKitMac
4 | #else
5 | import SwiftSignalKit
6 | #endif
7 |
8 | public enum ListViewItemUpdateAnimation {
9 | case None
10 | case System(duration: Double)
11 |
12 | public var isAnimated: Bool {
13 | if case .None = self {
14 | return false
15 | } else {
16 | return true
17 | }
18 | }
19 | }
20 |
21 | public struct ListViewItemConfigureNodeFlags: OptionSet {
22 | public var rawValue: Int32
23 |
24 | public init() {
25 | self.rawValue = 0
26 | }
27 |
28 | public init(rawValue: Int32) {
29 | self.rawValue = rawValue
30 | }
31 |
32 | public static let preferSynchronousResourceLoading = ListViewItemConfigureNodeFlags(rawValue: 1 << 0)
33 | }
34 |
35 | public struct ListViewItemApply {
36 | public let isOnScreen: Bool
37 |
38 | public init(isOnScreen: Bool) {
39 | self.isOnScreen = isOnScreen
40 | }
41 | }
42 |
43 | public protocol ListViewItem {
44 | func nodeConfiguredForParams(async: @escaping (@escaping () -> Void) -> Void, params: ListViewItemLayoutParams, synchronousLoads: Bool, previousItem: ListViewItem?, nextItem: ListViewItem?, completion: @escaping (ListViewItemNode, @escaping () -> (Signal?, (ListViewItemApply) -> Void)) -> Void)
45 | func updateNode(async: @escaping (@escaping () -> Void) -> Void, node: @escaping () -> ListViewItemNode, params: ListViewItemLayoutParams, previousItem: ListViewItem?, nextItem: ListViewItem?, animation: ListViewItemUpdateAnimation, completion: @escaping (ListViewItemNodeLayout, @escaping (ListViewItemApply) -> Void) -> Void)
46 |
47 | var accessoryItem: ListViewAccessoryItem? { get }
48 | var headerAccessoryItem: ListViewAccessoryItem? { get }
49 | var selectable: Bool { get }
50 | var approximateHeight: CGFloat { get }
51 |
52 | func selected(listView: ListView)
53 | }
54 |
55 | public extension ListViewItem {
56 | var accessoryItem: ListViewAccessoryItem? {
57 | return nil
58 | }
59 |
60 | var headerAccessoryItem: ListViewAccessoryItem? {
61 | return nil
62 | }
63 |
64 | var selectable: Bool {
65 | return false
66 | }
67 |
68 | var approximateHeight: CGFloat {
69 | return 44.0
70 | }
71 |
72 | func selected(listView: ListView) {
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Display/ListViewOverscrollBackgroundNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if os(macOS)
3 | #else
4 | import AsyncDisplayKit
5 | #endif
6 |
7 | final class ListViewOverscrollBackgroundNode: ASDisplayNode {
8 | private let backgroundNode: ASDisplayNode
9 |
10 | var color: UIColor {
11 | didSet {
12 | self.backgroundNode.backgroundColor = color
13 | }
14 | }
15 |
16 | init(color: UIColor) {
17 | self.color = color
18 |
19 | self.backgroundNode = ASDisplayNode()
20 | self.backgroundNode.backgroundColor = color
21 | self.backgroundNode.isLayerBacked = true
22 |
23 | super.init()
24 |
25 | self.addSubnode(self.backgroundNode)
26 | }
27 |
28 | func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) {
29 | transition.updateFrame(node: self.backgroundNode, frame: CGRect(origin: CGPoint(), size: size))
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/Display/ListViewReorderingGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | final class ListViewReorderingGestureRecognizer: UIGestureRecognizer {
5 | private let shouldBegin: (CGPoint) -> Bool
6 | private let ended: () -> Void
7 | private let moved: (CGFloat) -> Void
8 |
9 | private var initialLocation: CGPoint?
10 |
11 | init(shouldBegin: @escaping (CGPoint) -> Bool, ended: @escaping () -> Void, moved: @escaping (CGFloat) -> Void) {
12 | self.shouldBegin = shouldBegin
13 | self.ended = ended
14 | self.moved = moved
15 |
16 | super.init(target: nil, action: nil)
17 | }
18 |
19 | override func reset() {
20 | super.reset()
21 |
22 | self.initialLocation = nil
23 | }
24 |
25 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
26 | super.touchesBegan(touches, with: event)
27 |
28 | if self.state == .possible {
29 | if let location = touches.first?.location(in: self.view), self.shouldBegin(location) {
30 | self.initialLocation = location
31 | self.state = .began
32 | } else {
33 | self.state = .failed
34 | }
35 | }
36 | }
37 |
38 | override func touchesEnded(_ touches: Set, with event: UIEvent) {
39 | super.touchesEnded(touches, with: event)
40 |
41 | if self.state == .began || self.state == .changed {
42 | self.ended()
43 | self.state = .failed
44 | }
45 | }
46 |
47 | override func touchesCancelled(_ touches: Set, with event: UIEvent) {
48 | super.touchesCancelled(touches, with: event)
49 |
50 | if self.state == .began || self.state == .changed {
51 | self.ended()
52 | self.state = .failed
53 | }
54 | }
55 |
56 | override func touchesMoved(_ touches: Set, with event: UIEvent) {
57 | super.touchesMoved(touches, with: event)
58 |
59 | if (self.state == .began || self.state == .changed), let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
60 | self.state = .changed
61 | let offset = location.y - initialLocation.y
62 | self.moved(offset)
63 | }
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/Display/ListViewReorderingItemNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | final class ListViewReorderingItemNode: ASDisplayNode {
5 | weak var itemNode: ListViewItemNode?
6 |
7 | var currentState: (Int, Int)?
8 |
9 | private let copyView: UIView?
10 | private let initialLocation: CGPoint
11 |
12 | init(itemNode: ListViewItemNode, initialLocation: CGPoint) {
13 | self.itemNode = itemNode
14 | self.copyView = itemNode.view.snapshotView(afterScreenUpdates: false)
15 | self.initialLocation = initialLocation
16 |
17 | super.init()
18 |
19 | if let copyView = self.copyView {
20 | self.view.addSubview(copyView)
21 | copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y), size: copyView.bounds.size)
22 | copyView.bounds = itemNode.bounds
23 | }
24 | }
25 |
26 | func updateOffset(offset: CGFloat) {
27 | if let copyView = self.copyView {
28 | copyView.frame = CGRect(origin: CGPoint(x: initialLocation.x, y: initialLocation.y + offset), size: copyView.bounds.size)
29 | }
30 | }
31 |
32 | func currentOffset() -> CGFloat? {
33 | if let copyView = self.copyView {
34 | return copyView.center.y
35 | }
36 | return nil
37 | }
38 |
39 | func animateCompletion(completion: @escaping () -> Void) {
40 | if let copyView = self.copyView, let itemNode = self.itemNode {
41 | itemNode.isHidden = false
42 | itemNode.transitionOffset = itemNode.apparentFrame.midY - copyView.frame.midY
43 | itemNode.addTransitionOffsetAnimation(0.0, duration: 0.2, beginAt: CACurrentMediaTime())
44 | completion()
45 | } else {
46 | completion()
47 | }
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Display/ListViewScroller.swift:
--------------------------------------------------------------------------------
1 | #if os(macOS)
2 | #else
3 | import UIKit
4 | #endif
5 |
6 | class ListViewScroller: UIScrollView, UIGestureRecognizerDelegate {
7 | override init(frame: CGRect) {
8 | super.init(frame: frame)
9 |
10 | #if os(iOS)
11 | self.scrollsToTop = false
12 | if #available(iOSApplicationExtension 11.0, *) {
13 | self.contentInsetAdjustmentBehavior = .never
14 | }
15 | #endif
16 | }
17 |
18 | required init?(coder aDecoder: NSCoder) {
19 | fatalError("init(coder:) has not been implemented")
20 | }
21 |
22 | @objc func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
23 | if otherGestureRecognizer is ListViewTapGestureRecognizer {
24 | return true
25 | }
26 | return false
27 | }
28 |
29 | #if os(iOS)
30 | override func touchesShouldCancel(in view: UIView) -> Bool {
31 | return true
32 | }
33 | #endif
34 | }
35 |
--------------------------------------------------------------------------------
/Display/ListViewTapGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public final class ListViewTapGestureRecognizer: UITapGestureRecognizer {
5 |
6 | }
7 |
--------------------------------------------------------------------------------
/Display/ListViewTempItemNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | final class ListViewTempItemNode: ListViewItemNode {
4 |
5 | }
6 |
--------------------------------------------------------------------------------
/Display/ListViewTransactionQueue.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | #if os(iOS)
3 | import SwiftSignalKit
4 | #else
5 | import SwiftSignalKitMac
6 | #endif
7 |
8 | public typealias ListViewTransaction = (@escaping () -> Void) -> Void
9 |
10 | public final class ListViewTransactionQueue {
11 | private var transactions: [ListViewTransaction] = []
12 | public final var transactionCompleted: () -> Void = { }
13 |
14 | public init() {
15 | }
16 |
17 | public func addTransaction(_ transaction: @escaping ListViewTransaction) {
18 | precondition(Thread.isMainThread)
19 | let beginTransaction = self.transactions.count == 0
20 | self.transactions.append(transaction)
21 |
22 | if beginTransaction {
23 | transaction({ [weak self] in
24 | precondition(Thread.isMainThread)
25 |
26 | if Thread.isMainThread {
27 | if let strongSelf = self {
28 | strongSelf.endTransaction()
29 | }
30 | } else {
31 | Queue.mainQueue().async {
32 | if let strongSelf = self {
33 | strongSelf.endTransaction()
34 | }
35 | }
36 | }
37 | })
38 | }
39 | }
40 |
41 | private func endTransaction() {
42 | precondition(Thread.isMainThread)
43 | Queue.mainQueue().async {
44 | self.transactionCompleted()
45 | if !self.transactions.isEmpty {
46 | let _ = self.transactions.removeFirst()
47 | }
48 |
49 | if let nextTransaction = self.transactions.first {
50 | nextTransaction({ [weak self] in
51 | precondition(Thread.isMainThread)
52 |
53 | if Thread.isMainThread {
54 | if let strongSelf = self {
55 | strongSelf.endTransaction()
56 | }
57 | } else {
58 | Queue.mainQueue().async {
59 | if let strongSelf = self {
60 | strongSelf.endTransaction()
61 | }
62 | }
63 | }
64 | })
65 | }
66 | }
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Display/MinimizeKeyboardGestureRecognizer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | final class MinimizeKeyboardGestureRecognizer: UISwipeGestureRecognizer, UIGestureRecognizerDelegate {
5 | override init(target: Any?, action: Selector?) {
6 | super.init(target: target, action: action)
7 |
8 | self.cancelsTouchesInView = false
9 | self.delaysTouchesBegan = false
10 | self.delaysTouchesEnded = false
11 | self.delegate = self
12 |
13 | self.direction = [.left, .right]
14 | self.numberOfTouchesRequired = 2
15 | }
16 |
17 | func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
18 | return true
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Display/NSBag.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NSBag : NSObject
4 |
5 | - (NSInteger)addItem:(id)item;
6 | - (void)enumerateItems:(void (^)(id))block;
7 | - (void)removeItem:(NSInteger)key;
8 |
9 | @end
10 |
--------------------------------------------------------------------------------
/Display/NSBag.m:
--------------------------------------------------------------------------------
1 | #import "NSBag.h"
2 |
3 | @interface NSBag ()
4 | {
5 | NSInteger _nextKey;
6 | NSMutableArray *_items;
7 | NSMutableArray *_itemKeys;
8 | }
9 |
10 | @end
11 |
12 | @implementation NSBag
13 |
14 | - (instancetype)init
15 | {
16 | self = [super init];
17 | if (self != nil)
18 | {
19 | _items = [[NSMutableArray alloc] init];
20 | _itemKeys = [[NSMutableArray alloc] init];
21 | }
22 | return self;
23 | }
24 |
25 | - (NSInteger)addItem:(id)item
26 | {
27 | if (item == nil)
28 | return -1;
29 |
30 | NSInteger key = _nextKey;
31 | [_items addObject:item];
32 | [_itemKeys addObject:@(key)];
33 | _nextKey++;
34 |
35 | return key;
36 | }
37 |
38 | - (void)enumerateItems:(void (^)(id))block
39 | {
40 | if (block)
41 | {
42 | for (id item in _items)
43 | {
44 | block(item);
45 | }
46 | }
47 | }
48 |
49 | - (void)removeItem:(NSInteger)key
50 | {
51 | NSUInteger index = 0;
52 | for (NSNumber *itemKey in _itemKeys)
53 | {
54 | if ([itemKey integerValue] == key)
55 | {
56 | [_items removeObjectAtIndex:index];
57 | [_itemKeys removeObjectAtIndex:index];
58 | break;
59 | }
60 | index++;
61 | }
62 | }
63 |
64 | @end
65 |
--------------------------------------------------------------------------------
/Display/NSWeakReference.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NSWeakReference : NSObject
4 |
5 | @property (nonatomic, weak) id value;
6 |
7 | - (instancetype)initWithValue:(id)value;
8 |
9 | @end
10 |
--------------------------------------------------------------------------------
/Display/NSWeakReference.m:
--------------------------------------------------------------------------------
1 | #import "NSWeakReference.h"
2 |
3 | @implementation NSWeakReference
4 |
5 | - (instancetype)initWithValue:(id)value {
6 | self = [super init];
7 | if (self != nil) {
8 | self.value = value;
9 | }
10 | return self;
11 | }
12 |
13 | @end
14 |
--------------------------------------------------------------------------------
/Display/NavigationBackArrowLight@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peter-iakovlev/Display/ffb131ac55bed21c37052a09c27150d5d380321e/Display/NavigationBackArrowLight@2x.png
--------------------------------------------------------------------------------
/Display/NavigationBarBadge.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | final class NavigationBarBadgeNode: ASDisplayNode {
5 | private var fillColor: UIColor
6 | private var strokeColor: UIColor
7 | private var textColor: UIColor
8 |
9 | private let textNode: ASTextNode
10 | private let backgroundNode: ASImageNode
11 |
12 | private let font: UIFont = Font.regular(13.0)
13 |
14 | var text: String = "" {
15 | didSet {
16 | self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor)
17 | self.invalidateCalculatedLayout()
18 | }
19 | }
20 |
21 | init(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) {
22 | self.fillColor = fillColor
23 | self.strokeColor = strokeColor
24 | self.textColor = textColor
25 |
26 | self.textNode = ASTextNode()
27 | self.textNode.isUserInteractionEnabled = false
28 | self.textNode.displaysAsynchronously = false
29 |
30 | self.backgroundNode = ASImageNode()
31 | self.backgroundNode.isLayerBacked = true
32 | self.backgroundNode.displayWithoutProcessing = true
33 | self.backgroundNode.displaysAsynchronously = false
34 | self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0)
35 |
36 | super.init()
37 |
38 | self.addSubnode(self.backgroundNode)
39 | self.addSubnode(self.textNode)
40 | }
41 |
42 | func updateTheme(fillColor: UIColor, strokeColor: UIColor, textColor: UIColor) {
43 | self.fillColor = fillColor
44 | self.strokeColor = strokeColor
45 | self.textColor = textColor
46 | self.backgroundNode.image = generateStretchableFilledCircleImage(diameter: 18.0, color: fillColor, strokeColor: strokeColor, strokeWidth: 1.0)
47 | self.textNode.attributedText = NSAttributedString(string: self.text, font: self.font, textColor: self.textColor)
48 | }
49 |
50 | override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
51 | let badgeSize = self.textNode.measure(constrainedSize)
52 | let backgroundSize = CGSize(width: max(18.0, badgeSize.width + 10.0 + 1.0), height: 18.0)
53 | let backgroundFrame = CGRect(origin: CGPoint(), size: backgroundSize)
54 | self.backgroundNode.frame = backgroundFrame
55 | self.textNode.frame = CGRect(origin: CGPoint(x: floorToScreenPixels(backgroundFrame.midX - badgeSize.width / 2.0), y: floorToScreenPixels((backgroundFrame.size.height - badgeSize.height) / 2.0)), size: badgeSize)
56 |
57 | return backgroundSize
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/Display/NavigationBarContentNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public enum NavigationBarContentMode {
5 | case replacement
6 | case expansion
7 | }
8 |
9 | open class NavigationBarContentNode: ASDisplayNode {
10 | open var requestContainerLayout: (ContainedViewLayoutTransition) -> Void = { _ in }
11 |
12 | open var height: CGFloat {
13 | return self.nominalHeight
14 | }
15 |
16 | open var clippedHeight: CGFloat {
17 | return self.nominalHeight
18 | }
19 |
20 | open var nominalHeight: CGFloat {
21 | return 44.0
22 | }
23 |
24 | open var mode: NavigationBarContentMode {
25 | return .replacement
26 | }
27 |
28 | open func updateLayout(size: CGSize, leftInset: CGFloat, rightInset: CGFloat, transition: ContainedViewLayoutTransition) {
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Display/NavigationBarProxy.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NavigationBarProxy : UINavigationBar
4 |
5 | @property (nonatomic, copy) void (^setItemsProxy)(NSArray *, NSArray *, bool);
6 |
7 | @end
8 |
--------------------------------------------------------------------------------
/Display/NavigationBarProxy.m:
--------------------------------------------------------------------------------
1 | #import "NavigationBarProxy.h"
2 |
3 | @interface NavigationBarProxy ()
4 | {
5 | NSArray *_items;
6 | }
7 |
8 | @end
9 |
10 | @implementation NavigationBarProxy
11 |
12 | - (instancetype)initWithFrame:(CGRect)frame
13 | {
14 | self = [super initWithFrame:frame];
15 | if (self != nil)
16 | {
17 | }
18 | return self;
19 | }
20 |
21 | - (void)pushNavigationItem:(UINavigationItem *)item animated:(BOOL)animated
22 | {
23 | [self setItems:[[self items] arrayByAddingObject:item] animated:animated];
24 | }
25 |
26 | - (UINavigationItem *)popNavigationItemAnimated:(BOOL)animated
27 | {
28 | NSMutableArray *items = [[NSMutableArray alloc] initWithArray:[self items]];
29 | UINavigationItem *lastItem = [items lastObject];
30 | [items removeLastObject];
31 | [self setItems:items animated:animated];
32 | return lastItem;
33 | }
34 |
35 | - (UINavigationItem *)topItem
36 | {
37 | return [[self items] lastObject];
38 | }
39 |
40 | - (UINavigationItem *)backItem
41 | {
42 | NSLog(@"backItem");
43 | return nil;
44 | }
45 |
46 | - (NSArray *)items
47 | {
48 | if (_items == nil)
49 | return @[];
50 | return _items;
51 | }
52 |
53 | - (void)setItems:(NSArray *)items
54 | {
55 | [self setItems:items animated:false];
56 | }
57 |
58 | - (void)setItems:(NSArray *)items animated:(BOOL)animated
59 | {
60 | NSArray *previousItems = _items;
61 | _items = items;
62 |
63 | if (_setItemsProxy)
64 | _setItemsProxy(previousItems, items, animated);
65 | }
66 |
67 | @end
68 |
--------------------------------------------------------------------------------
/Display/NavigationBarTitleTransitionNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public protocol NavigationBarTitleTransitionNode {
5 | func makeTransitionMirrorNode() -> ASDisplayNode
6 | }
7 |
--------------------------------------------------------------------------------
/Display/NavigationBarTitleView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public protocol NavigationBarTitleView {
5 | func animateLayoutTransition()
6 |
7 | func updateLayout(size: CGSize, clearBounds: CGRect, transition: ContainedViewLayoutTransition)
8 | }
9 |
--------------------------------------------------------------------------------
/Display/NavigationBarTransitionContainer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | class NavigationBarTransitionContainer: ASDisplayNode {
5 | var progress: CGFloat = 0.0 {
6 | didSet {
7 | self.layout()
8 | }
9 | }
10 |
11 | let transition: NavigationTransition
12 | let topNavigationBar: NavigationBar
13 | let bottomNavigationBar: NavigationBar
14 |
15 | let topClippingNode: ASDisplayNode
16 | let bottomClippingNode: ASDisplayNode
17 |
18 | let topNavigationBarSupernode: ASDisplayNode?
19 | let bottomNavigationBarSupernode: ASDisplayNode?
20 |
21 | init(transition: NavigationTransition, topNavigationBar: NavigationBar, bottomNavigationBar: NavigationBar) {
22 | self.transition = transition
23 |
24 | self.topNavigationBar = topNavigationBar
25 | self.topNavigationBarSupernode = topNavigationBar.supernode
26 |
27 | self.bottomNavigationBar = bottomNavigationBar
28 | self.bottomNavigationBarSupernode = bottomNavigationBar.supernode
29 |
30 | self.topClippingNode = ASDisplayNode()
31 | self.topClippingNode.clipsToBounds = true
32 | self.bottomClippingNode = ASDisplayNode()
33 | self.bottomClippingNode.clipsToBounds = true
34 |
35 | super.init()
36 |
37 | self.topClippingNode.addSubnode(self.topNavigationBar)
38 | self.bottomClippingNode.addSubnode(self.bottomNavigationBar)
39 |
40 | self.addSubnode(self.bottomClippingNode)
41 | self.addSubnode(self.topClippingNode)
42 | }
43 |
44 | func complete() {
45 | self.topNavigationBarSupernode?.addSubnode(self.topNavigationBar)
46 | self.bottomNavigationBarSupernode?.addSubnode(self.bottomNavigationBar)
47 | }
48 |
49 | override func layout() {
50 | super.layout()
51 |
52 | let size = self.bounds.size
53 |
54 | let position: CGFloat
55 | switch self.transition {
56 | case .Push:
57 | position = 1.0 - progress
58 | case .Pop:
59 | position = progress
60 | }
61 |
62 | let offset = floorToScreenPixels(size.width * position)
63 |
64 | self.topClippingNode.frame = CGRect(origin: CGPoint(x: offset, y: 0.0), size: size)
65 |
66 | self.bottomClippingNode.frame = CGRect(origin: CGPoint(x: 0.0, y: 0.0), size: CGSize(width: offset, height: size.height))
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/Display/NavigationBarTransitionState.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | enum NavigationBarTransitionRole {
4 | case top
5 | case bottom
6 | }
7 |
8 | final class NavigationBarTransitionState {
9 | weak var navigationBar: NavigationBar?
10 | let transition: NavigationTransition
11 | let role: NavigationBarTransitionRole
12 | let progress: CGFloat
13 |
14 | init(navigationBar: NavigationBar, transition: NavigationTransition, role: NavigationBarTransitionRole, progress: CGFloat) {
15 | self.navigationBar = navigationBar
16 | self.transition = transition
17 | self.role = role
18 | self.progress = progress
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/Display/NavigationControllerProxy.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface NavigationControllerProxy : UINavigationController
4 |
5 | - (instancetype)init;
6 |
7 | @end
8 |
--------------------------------------------------------------------------------
/Display/NavigationControllerProxy.m:
--------------------------------------------------------------------------------
1 | #import "NavigationControllerProxy.h"
2 |
3 | #import "NavigationBarProxy.h"
4 |
5 | @implementation NavigationControllerProxy
6 |
7 | - (instancetype)init
8 | {
9 | self = [super initWithNavigationBarClass:[NavigationBarProxy class] toolbarClass:[UIToolbar class]];
10 | if (self != nil) {
11 | }
12 | return self;
13 | }
14 |
15 | @end
16 |
--------------------------------------------------------------------------------
/Display/NavigationShadow@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peter-iakovlev/Display/ffb131ac55bed21c37052a09c27150d5d380321e/Display/NavigationShadow@2x.png
--------------------------------------------------------------------------------
/Display/NavigationTitleNode.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import AsyncDisplayKit
3 |
4 | public class NavigationTitleNode: ASDisplayNode {
5 | private let label: ASTextNode
6 |
7 | private var _text: NSString = ""
8 | public var text: NSString {
9 | get {
10 | return self._text
11 | }
12 | set(value) {
13 | self._text = value
14 | self.setText(value)
15 | }
16 | }
17 |
18 | public var color: UIColor = UIColor.black {
19 | didSet {
20 | self.setText(self._text)
21 | }
22 | }
23 |
24 | public init(text: NSString) {
25 | self.label = ASTextNode()
26 | self.label.maximumNumberOfLines = 1
27 | self.label.truncationMode = .byTruncatingTail
28 | self.label.displaysAsynchronously = false
29 |
30 | super.init()
31 |
32 | self.addSubnode(self.label)
33 |
34 | self.setText(text)
35 | }
36 |
37 | public required init(coder aDecoder: NSCoder) {
38 | fatalError("init(coder:) has not been implemented")
39 | }
40 |
41 | private func setText(_ text: NSString) {
42 | var titleAttributes = [NSAttributedStringKey : AnyObject]()
43 | titleAttributes[NSAttributedStringKey.font] = UIFont.boldSystemFont(ofSize: 17.0)
44 | titleAttributes[NSAttributedStringKey.foregroundColor] = self.color
45 | let titleString = NSAttributedString(string: text as String, attributes: titleAttributes)
46 | self.label.attributedText = titleString
47 | self.invalidateCalculatedLayout()
48 | }
49 |
50 | public override func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
51 | self.label.measure(constrainedSize)
52 | return self.label.calculatedSize
53 | }
54 |
55 | public override func layout() {
56 | self.label.frame = self.bounds
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/Display/NotificationCenterUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | typedef bool (^NotificationHandlerBlock)(NSString *, id, NSDictionary *, void (^)());
4 |
5 | @interface NotificationCenterUtils : NSObject
6 |
7 | + (void)addNotificationHandler:(NotificationHandlerBlock)handler;
8 |
9 | @end
10 |
--------------------------------------------------------------------------------
/Display/NotificationCenterUtils.m:
--------------------------------------------------------------------------------
1 | #import "NotificationCenterUtils.h"
2 |
3 | #import "RuntimeUtils.h"
4 | #import
5 |
6 | static NSMutableArray *notificationHandlers() {
7 | static NSMutableArray *array = nil;
8 | static dispatch_once_t onceToken;
9 | dispatch_once(&onceToken, ^{
10 | array = [[NSMutableArray alloc] init];
11 | });
12 | return array;
13 | }
14 |
15 | @interface NSNotificationCenter (_a65afc19)
16 |
17 | @end
18 |
19 | @implementation NSNotificationCenter (_a65afc19)
20 |
21 | - (void)_a65afc19_postNotificationName:(NSString *)aName object:(id)anObject userInfo:(NSDictionary *)aUserInfo
22 | {
23 | if ([NSThread isMainThread]) {
24 | for (NotificationHandlerBlock handler in notificationHandlers())
25 | {
26 | if (handler(aName, anObject, aUserInfo, ^{
27 | [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo];
28 | })) {
29 | return;
30 | }
31 | }
32 | }
33 |
34 | [self _a65afc19_postNotificationName:aName object:anObject userInfo:aUserInfo];
35 | }
36 |
37 | @end
38 |
39 | @interface CATransaction (Swizzle)
40 |
41 | + (void)swizzle_flush;
42 |
43 | @end
44 |
45 | @implementation CATransaction (Swizzle)
46 |
47 | + (void)swizzle_flush {
48 | //printf("===flush\n");
49 |
50 | [self swizzle_flush];
51 | }
52 |
53 | @end
54 |
55 | @implementation NotificationCenterUtils
56 |
57 | + (void)load {
58 | static dispatch_once_t onceToken;
59 | dispatch_once(&onceToken, ^{
60 | [RuntimeUtils swizzleInstanceMethodOfClass:[NSNotificationCenter class] currentSelector:@selector(postNotificationName:object:userInfo:) newSelector:@selector(_a65afc19_postNotificationName:object:userInfo:)];
61 |
62 | //[RuntimeUtils swizzleClassMethodOfClass:[CATransaction class] currentSelector:@selector(flush) newSelector:@selector(swizzle_flush)];
63 | });
64 | }
65 |
66 | + (void)addNotificationHandler:(NotificationHandlerBlock)handler {
67 | [notificationHandlers() addObject:[handler copy]];
68 | }
69 |
70 | @end
71 |
--------------------------------------------------------------------------------
/Display/PageControlNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public final class PageControlNode: ASDisplayNode {
5 | private let dotSize: CGFloat
6 | private let dotSpacing: CGFloat
7 | public var dotColor: UIColor {
8 | didSet {
9 | if !oldValue.isEqual(self.dotColor) {
10 | self.normalDotImage = generateFilledCircleImage(diameter: dotSize, color: self.dotColor)!
11 | for dotNode in self.dotNodes {
12 | if dotNode === oldValue {
13 | dotNode.image = self.normalDotImage
14 | }
15 | }
16 | }
17 | }
18 | }
19 | public var inactiveDotColor: UIColor {
20 | didSet {
21 | if !oldValue.isEqual(self.inactiveDotColor) {
22 | self.inactiveDotImage = generateFilledCircleImage(diameter: dotSize, color: self.inactiveDotColor)!
23 | for dotNode in self.dotNodes {
24 | if dotNode === oldValue {
25 | dotNode.image = self.inactiveDotImage
26 | }
27 | }
28 | }
29 | }
30 | }
31 | private var dotNodes: [ASImageNode] = []
32 |
33 | private var normalDotImage: UIImage
34 | private var inactiveDotImage: UIImage
35 |
36 | public init(dotSize: CGFloat = 7.0, dotSpacing: CGFloat = 9.0, dotColor: UIColor, inactiveDotColor: UIColor) {
37 | self.dotSize = dotSize
38 | self.dotSpacing = dotSpacing
39 | self.dotColor = dotColor
40 | self.inactiveDotColor = inactiveDotColor
41 | self.normalDotImage = generateFilledCircleImage(diameter: dotSize, color: dotColor)!
42 | self.inactiveDotImage = generateFilledCircleImage(diameter: dotSize, color: inactiveDotColor)!
43 |
44 | super.init()
45 | }
46 |
47 | public var pagesCount: Int = 0 {
48 | didSet {
49 | if self.pagesCount != oldValue {
50 | while self.dotNodes.count > self.pagesCount {
51 | self.dotNodes[self.dotNodes.count - 1].removeFromSupernode()
52 | self.dotNodes.removeLast()
53 | }
54 | while self.dotNodes.count < self.pagesCount {
55 | let dotNode = ASImageNode()
56 | dotNode.image = self.normalDotImage
57 | dotNode.displaysAsynchronously = false
58 | dotNode.displayWithoutProcessing = true
59 | dotNode.isUserInteractionEnabled = false
60 | self.dotNodes.append(dotNode)
61 | self.addSubnode(dotNode)
62 | }
63 | }
64 | }
65 | }
66 |
67 | public func setPage(_ pageValue: CGFloat) {
68 | let page = Int(round(pageValue))
69 |
70 | for i in 0 ..< self.dotNodes.count {
71 | if i != page {
72 | self.dotNodes[i].image = self.inactiveDotImage
73 | } else {
74 | self.dotNodes[i].image = self.normalDotImage
75 | }
76 | }
77 | }
78 |
79 | override public func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
80 | return CGSize(width: self.dotSize * CGFloat(self.pagesCount) + self.dotSpacing * max(CGFloat(self.pagesCount - 1), 0.0), height: self.dotSize)
81 | }
82 |
83 | override public func layout() {
84 | super.layout()
85 |
86 | let dotSize = CGSize(width: self.dotSize, height: self.dotSize)
87 |
88 | let nominalWidth = self.dotSize * CGFloat(self.pagesCount) + self.dotSpacing * max(CGFloat(self.pagesCount - 1), 0.0)
89 |
90 | let startX = floor((self.bounds.size.width - nominalWidth) / 2)
91 |
92 | for i in 0 ..< self.dotNodes.count {
93 | self.dotNodes[i].frame = CGRect(origin: CGPoint(x: startX + CGFloat(i) * (dotSize.width + self.dotSpacing), y: 0.0), size: dotSize)
94 | }
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/Display/PeekController.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public final class PeekControllerTheme {
5 | public let isDark: Bool
6 | public let menuBackgroundColor: UIColor
7 | public let menuItemHighligtedColor: UIColor
8 | public let menuItemSeparatorColor: UIColor
9 | public let accentColor: UIColor
10 | public let destructiveColor: UIColor
11 |
12 | public init(isDark: Bool, menuBackgroundColor: UIColor, menuItemHighligtedColor: UIColor, menuItemSeparatorColor: UIColor, accentColor: UIColor, destructiveColor: UIColor) {
13 | self.isDark = isDark
14 | self.menuBackgroundColor = menuBackgroundColor
15 | self.menuItemHighligtedColor = menuItemHighligtedColor
16 | self.menuItemSeparatorColor = menuItemSeparatorColor
17 | self.accentColor = accentColor
18 | self.destructiveColor = destructiveColor
19 | }
20 | }
21 |
22 | public final class PeekController: ViewController {
23 | private var controllerNode: PeekControllerNode {
24 | return self.displayNode as! PeekControllerNode
25 | }
26 |
27 | private let theme: PeekControllerTheme
28 | private let content: PeekControllerContent
29 | var sourceNode: () -> ASDisplayNode?
30 |
31 | private var animatedIn = false
32 |
33 | public init(theme: PeekControllerTheme, content: PeekControllerContent, sourceNode: @escaping () -> ASDisplayNode?) {
34 | self.theme = theme
35 | self.content = content
36 | self.sourceNode = sourceNode
37 |
38 | super.init(navigationBarPresentationData: nil)
39 | }
40 |
41 | required public init(coder aDecoder: NSCoder) {
42 | fatalError("init(coder:) has not been implemented")
43 | }
44 |
45 | override public func loadDisplayNode() {
46 | self.displayNode = PeekControllerNode(theme: self.theme, content: self.content, requestDismiss: { [weak self] in
47 | self?.dismiss()
48 | })
49 | self.displayNodeDidLoad()
50 | }
51 |
52 | private func getSourceRect() -> CGRect {
53 | if let sourceNode = self.sourceNode() {
54 | return sourceNode.view.convert(sourceNode.bounds, to: self.view)
55 | } else {
56 | let size = self.displayNode.bounds.size
57 | return CGRect(origin: CGPoint(x: floor((size.width - 10.0) / 2.0), y: floor((size.height - 10.0) / 2.0)), size: CGSize(width: 10.0, height: 10.0))
58 | }
59 | }
60 |
61 | override public func viewDidAppear(_ animated: Bool) {
62 | super.viewDidAppear(animated)
63 |
64 | if !self.animatedIn {
65 | self.animatedIn = true
66 | self.controllerNode.animateIn(from: self.getSourceRect())
67 | }
68 | }
69 |
70 | override public func containerLayoutUpdated(_ layout: ContainerViewLayout, transition: ContainedViewLayoutTransition) {
71 | super.containerLayoutUpdated(layout, transition: transition)
72 |
73 | self.controllerNode.containerLayoutUpdated(layout, transition: transition)
74 | }
75 |
76 | override public func dismiss(completion: (() -> Void)? = nil) {
77 | self.controllerNode.animateOut(to: self.getSourceRect(), completion: { [weak self] in
78 | self?.presentingViewController?.dismiss(animated: false, completion: nil)
79 | })
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/Display/PeekControllerContent.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public enum PeekControllerContentPresentation {
5 | case contained
6 | case freeform
7 | }
8 |
9 | public enum PeerkControllerMenuActivation {
10 | case drag
11 | case press
12 | }
13 |
14 | public protocol PeekControllerContent {
15 | func presentation() -> PeekControllerContentPresentation
16 | func menuActivation() -> PeerkControllerMenuActivation
17 | func menuItems() -> [PeekControllerMenuItem]
18 | func node() -> PeekControllerContentNode & ASDisplayNode
19 |
20 | func topAccessoryNode() -> ASDisplayNode?
21 |
22 | func isEqual(to: PeekControllerContent) -> Bool
23 | }
24 |
25 | public protocol PeekControllerContentNode {
26 | func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize
27 | }
28 |
--------------------------------------------------------------------------------
/Display/PeekControllerMenuItemNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public enum PeekControllerMenuItemColor {
5 | case accent
6 | case destructive
7 | }
8 |
9 | public enum PeekControllerMenuItemFont {
10 | case `default`
11 | case bold
12 | }
13 |
14 | public struct PeekControllerMenuItem {
15 | public let title: String
16 | public let color: PeekControllerMenuItemColor
17 | public let font: PeekControllerMenuItemFont
18 | public let action: () -> Void
19 |
20 | public init(title: String, color: PeekControllerMenuItemColor, font: PeekControllerMenuItemFont = .default, action: @escaping () -> Void) {
21 | self.title = title
22 | self.color = color
23 | self.font = font
24 | self.action = action
25 | }
26 | }
27 |
28 | final class PeekControllerMenuItemNode: HighlightTrackingButtonNode {
29 | private let item: PeekControllerMenuItem
30 | private let activatedAction: () -> Void
31 |
32 | private let separatorNode: ASDisplayNode
33 | private let highlightedBackgroundNode: ASDisplayNode
34 | private let textNode: ASTextNode
35 |
36 | init(theme: PeekControllerTheme, item: PeekControllerMenuItem, activatedAction: @escaping () -> Void) {
37 | self.item = item
38 | self.activatedAction = activatedAction
39 |
40 | self.separatorNode = ASDisplayNode()
41 | self.separatorNode.isLayerBacked = true
42 | self.separatorNode.backgroundColor = theme.menuItemSeparatorColor
43 |
44 | self.highlightedBackgroundNode = ASDisplayNode()
45 | self.highlightedBackgroundNode.isLayerBacked = true
46 | self.highlightedBackgroundNode.backgroundColor = theme.menuItemHighligtedColor
47 | self.highlightedBackgroundNode.alpha = 0.0
48 |
49 | self.textNode = ASTextNode()
50 | self.textNode.isUserInteractionEnabled = false
51 | self.textNode.displaysAsynchronously = false
52 |
53 | let textColor: UIColor
54 | let textFont: UIFont
55 | switch item.color {
56 | case .accent:
57 | textColor = theme.accentColor
58 | case .destructive:
59 | textColor = theme.destructiveColor
60 | }
61 | switch item.font {
62 | case .default:
63 | textFont = Font.regular(20.0)
64 | case .bold:
65 | textFont = Font.medium(20.0)
66 | }
67 | self.textNode.attributedText = NSAttributedString(string: item.title, font: textFont, textColor: textColor)
68 |
69 | super.init()
70 |
71 | self.addSubnode(self.separatorNode)
72 | self.addSubnode(self.highlightedBackgroundNode)
73 | self.addSubnode(self.textNode)
74 |
75 | self.highligthedChanged = { [weak self] highlighted in
76 | if let strongSelf = self {
77 | if highlighted {
78 | strongSelf.view.superview?.bringSubview(toFront: strongSelf.view)
79 | strongSelf.highlightedBackgroundNode.alpha = 1.0
80 | } else {
81 | strongSelf.highlightedBackgroundNode.alpha = 0.0
82 | strongSelf.highlightedBackgroundNode.layer.animateAlpha(from: 1.0, to: 0.0, duration: 0.3)
83 | }
84 | }
85 | }
86 |
87 | self.addTarget(self, action: #selector(self.buttonPressed), forControlEvents: .touchUpInside)
88 | }
89 |
90 | func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
91 | let height: CGFloat = 57.0
92 | transition.updateFrame(node: self.highlightedBackgroundNode, frame: CGRect(origin: CGPoint(), size: CGSize(width: width, height: height)))
93 | transition.updateFrame(node: self.separatorNode, frame: CGRect(origin: CGPoint(x: 0.0, y: height), size: CGSize(width: width, height: UIScreenPixel)))
94 |
95 | let textSize = self.textNode.measure(CGSize(width: width - 10.0, height: height))
96 | transition.updateFrame(node: self.textNode, frame: CGRect(origin: CGPoint(x: floor((width - textSize.width) / 2.0), y: floor((height - textSize.height) / 2.0)), size: textSize))
97 |
98 | return height
99 | }
100 |
101 | @objc func buttonPressed() {
102 | self.activatedAction()
103 | self.item.action()
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/Display/PeekControllerMenuNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | final class PeekControllerMenuNode: ASDisplayNode {
5 | private let itemNodes: [PeekControllerMenuItemNode]
6 |
7 | init(theme: PeekControllerTheme, items: [PeekControllerMenuItem], activatedAction: @escaping () -> Void) {
8 | self.itemNodes = items.map { PeekControllerMenuItemNode(theme: theme, item: $0, activatedAction: activatedAction) }
9 |
10 | super.init()
11 |
12 | self.backgroundColor = theme.menuBackgroundColor
13 | self.cornerRadius = 16.0
14 | self.clipsToBounds = true
15 |
16 | for itemNode in self.itemNodes {
17 | self.addSubnode(itemNode)
18 | }
19 | }
20 |
21 | func updateLayout(width: CGFloat, transition: ContainedViewLayoutTransition) -> CGFloat {
22 | var verticalOffset: CGFloat = 0.0
23 | for itemNode in self.itemNodes {
24 | let itemHeight = itemNode.updateLayout(width: width, transition: transition)
25 | transition.updateFrame(node: itemNode, frame: CGRect(origin: CGPoint(x: 0.0, y: verticalOffset), size: CGSize(width: width, height: itemHeight)))
26 | verticalOffset += itemHeight
27 | }
28 | return verticalOffset - UIScreenPixel
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/Display/RuntimeUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | typedef enum {
4 | NSObjectAssociationPolicyRetain = 0,
5 | NSObjectAssociationPolicyCopy = 1
6 | } NSObjectAssociationPolicy;
7 |
8 | @interface RuntimeUtils : NSObject
9 |
10 | + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector;
11 | + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector;
12 | + (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector;
13 |
14 | @end
15 |
16 | @interface NSObject (AssociatedObject)
17 |
18 | - (void)setAssociatedObject:(id)object forKey:(void const *)key;
19 | - (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy;
20 | - (id)associatedObjectForKey:(void const *)key;
21 | - (bool)checkObjectIsKindOfClass:(Class)targetClass;
22 | - (void)setClass:(Class)newClass;
23 |
24 | @end
25 |
--------------------------------------------------------------------------------
/Display/RuntimeUtils.m:
--------------------------------------------------------------------------------
1 | #import "RuntimeUtils.h"
2 |
3 | #import
4 |
5 | @implementation RuntimeUtils
6 |
7 | + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector {
8 | Method origMethod = nil, newMethod = nil;
9 |
10 | origMethod = class_getInstanceMethod(targetClass, currentSelector);
11 | newMethod = class_getInstanceMethod(targetClass, newSelector);
12 | if ((origMethod != nil) && (newMethod != nil)) {
13 | if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
14 | class_replaceMethod(targetClass, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
15 | } else {
16 | method_exchangeImplementations(origMethod, newMethod);
17 | }
18 | }
19 | }
20 |
21 | + (void)swizzleInstanceMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector withAnotherClass:(Class)anotherClass newSelector:(SEL)newSelector {
22 | Method origMethod = nil, newMethod = nil;
23 |
24 | origMethod = class_getInstanceMethod(targetClass, currentSelector);
25 | newMethod = class_getInstanceMethod(anotherClass, newSelector);
26 | if ((origMethod != nil) && (newMethod != nil)) {
27 | method_exchangeImplementations(origMethod, newMethod);
28 | }
29 | }
30 |
31 | + (void)swizzleClassMethodOfClass:(Class)targetClass currentSelector:(SEL)currentSelector newSelector:(SEL)newSelector {
32 | Method origMethod = nil, newMethod = nil;
33 |
34 | origMethod = class_getClassMethod(targetClass, currentSelector);
35 | newMethod = class_getClassMethod(targetClass, newSelector);
36 |
37 | targetClass = object_getClass((id)targetClass);
38 |
39 | if ((origMethod != nil) && (newMethod != nil)) {
40 | if(class_addMethod(targetClass, currentSelector, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
41 | class_replaceMethod(targetClass, newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod));
42 | } else {
43 | method_exchangeImplementations(origMethod, newMethod);
44 | }
45 | }
46 | }
47 |
48 | @end
49 |
50 | @implementation NSObject (AssociatedObject)
51 |
52 | - (void)setAssociatedObject:(id)object forKey:(void const *)key
53 | {
54 | [self setAssociatedObject:object forKey:key associationPolicy:NSObjectAssociationPolicyRetain];
55 | }
56 |
57 | - (void)setAssociatedObject:(id)object forKey:(void const *)key associationPolicy:(NSObjectAssociationPolicy)associationPolicy
58 | {
59 | int policy = 0;
60 | switch (associationPolicy)
61 | {
62 | case NSObjectAssociationPolicyRetain:
63 | policy = OBJC_ASSOCIATION_RETAIN_NONATOMIC;
64 | break;
65 | case NSObjectAssociationPolicyCopy:
66 | policy = OBJC_ASSOCIATION_COPY_NONATOMIC;
67 | break;
68 | default:
69 | policy = OBJC_ASSOCIATION_RETAIN_NONATOMIC;
70 | break;
71 | }
72 | objc_setAssociatedObject(self, key, object, policy);
73 | }
74 |
75 | - (id)associatedObjectForKey:(void const *)key
76 | {
77 | return objc_getAssociatedObject(self, key);
78 | }
79 |
80 | - (bool)checkObjectIsKindOfClass:(Class)targetClass {
81 | return [self isKindOfClass:targetClass];
82 | }
83 |
84 | - (void)setClass:(Class)newClass {
85 | object_setClass(self, newClass);
86 | }
87 |
88 | static Class freedomMakeClass(Class superclass, Class subclass, SEL *copySelectors, int copySelectorsCount)
89 | {
90 | if (superclass == Nil || subclass == Nil)
91 | return nil;
92 |
93 | Class decoratedClass = objc_allocateClassPair(superclass, [[NSString alloc] initWithFormat:@"%@_%@", NSStringFromClass(superclass), NSStringFromClass(subclass)].UTF8String, 0);
94 |
95 | unsigned int count = 0;
96 | Method *methodList = class_copyMethodList(subclass, &count);
97 | if (methodList != NULL) {
98 | for (unsigned int i = 0; i < count; i++) {
99 | SEL methodName = method_getName(methodList[i]);
100 | class_addMethod(decoratedClass, methodName, method_getImplementation(methodList[i]), method_getTypeEncoding(methodList[i]));
101 | }
102 |
103 | free(methodList);
104 | }
105 |
106 | objc_registerClassPair(decoratedClass);
107 |
108 | return decoratedClass;
109 | }
110 |
111 | @end
112 |
--------------------------------------------------------------------------------
/Display/RuntimeUtils.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | private let systemVersion = { () -> (Int, Int) in
5 | let string = UIDevice.current.systemVersion as NSString
6 | var minor = 0
7 | let range = string.range(of: ".")
8 | if range.location != NSNotFound {
9 | minor = Int((string.substring(from: range.location + 1) as NSString).intValue)
10 | }
11 | return (Int(string.intValue), minor)
12 | }()
13 |
14 | public func matchMinimumSystemVersion(major: Int, minor: Int = 0) -> Bool {
15 | let version = systemVersion
16 | if version.0 == major {
17 | return version.1 >= minor
18 | } else if version.0 < major {
19 | return false
20 | } else {
21 | return true
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Display/ScrollToTopProxyView.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | class ScrollToTopView: UIScrollView, UIScrollViewDelegate {
4 | var action: (() -> Void)?
5 |
6 | override init(frame: CGRect) {
7 | super.init(frame: frame)
8 |
9 | self.delegate = self
10 | self.scrollsToTop = true
11 | if #available(iOSApplicationExtension 11.0, *) {
12 | self.contentInsetAdjustmentBehavior = .never
13 | }
14 | }
15 |
16 | required init?(coder aDecoder: NSCoder) {
17 | fatalError("init(coder:) has not been implemented")
18 | }
19 |
20 | override var frame: CGRect {
21 | didSet {
22 | let frame = self.frame
23 | self.contentSize = CGSize(width: frame.width, height: frame.height + 1.0)
24 | self.contentOffset = CGPoint(x: 0.0, y: 1.0)
25 | }
26 | }
27 |
28 | @objc func scrollViewShouldScrollToTop(_ scrollView: UIScrollView) -> Bool {
29 | if let action = self.action {
30 | action()
31 | }
32 |
33 | return false
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/Display/Spring.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | struct ViewportItemSpring {
4 | let stiffness: CGFloat
5 | let damping: CGFloat
6 | let mass: CGFloat
7 | var velocity: CGFloat = 0.0
8 |
9 | init(stiffness: CGFloat, damping: CGFloat, mass: CGFloat) {
10 | self.stiffness = stiffness
11 | self.damping = damping
12 | self.mass = mass
13 | }
14 | }
15 |
16 | private func a(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat
17 | {
18 | return 1.0 - 3.0 * a2 + 3.0 * a1
19 | }
20 |
21 | private func b(_ a1: CGFloat, _ a2: CGFloat) -> CGFloat
22 | {
23 | return 3.0 * a2 - 6.0 * a1
24 | }
25 |
26 | private func c(_ a1: CGFloat) -> CGFloat
27 | {
28 | return 3.0 * a1
29 | }
30 |
31 | private func calcBezier(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat
32 | {
33 | return ((a(a1, a2)*t + b(a1, a2))*t + c(a1)) * t
34 | }
35 |
36 | private func calcSlope(_ t: CGFloat, _ a1: CGFloat, _ a2: CGFloat) -> CGFloat
37 | {
38 | return 3.0 * a(a1, a2) * t * t + 2.0 * b(a1, a2) * t + c(a1)
39 | }
40 |
41 | private func getTForX(_ x: CGFloat, _ x1: CGFloat, _ x2: CGFloat) -> CGFloat {
42 | var t = x
43 | var i = 0
44 | while i < 4 {
45 | let currentSlope = calcSlope(t, x1, x2)
46 | if currentSlope == 0.0 {
47 | return t
48 | } else {
49 | let currentX = calcBezier(t, x1, x2) - x
50 | t -= currentX / currentSlope
51 | }
52 |
53 | i += 1
54 | }
55 |
56 | return t
57 | }
58 |
59 | func bezierPoint(_ x1: CGFloat, _ y1: CGFloat, _ x2: CGFloat, _ y2: CGFloat, _ x: CGFloat) -> CGFloat
60 | {
61 | var value = calcBezier(getTForX(x, x1, x2), y1, y2)
62 | if value >= 0.997 {
63 | value = 1.0
64 | }
65 | return value
66 | }
67 |
--------------------------------------------------------------------------------
/Display/StatusBarHost.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import SwiftSignalKit
3 |
4 | public protocol StatusBarHost {
5 | var statusBarFrame: CGRect { get }
6 | var statusBarStyle: UIStatusBarStyle { get set }
7 | var statusBarWindow: UIView? { get }
8 | var statusBarView: UIView? { get }
9 |
10 | var keyboardWindow: UIWindow? { get }
11 | var keyboardView: UIView? { get }
12 |
13 | var handleVolumeControl: Signal { get }
14 | }
15 |
--------------------------------------------------------------------------------
/Display/SwitchNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | private final class SwitchNodeViewLayer: CALayer {
5 | override func setNeedsDisplay() {
6 | }
7 | }
8 |
9 | private final class SwitchNodeView: UISwitch {
10 | override class var layerClass: AnyClass {
11 | return SwitchNodeViewLayer.self
12 | }
13 | }
14 |
15 | open class SwitchNode: ASDisplayNode {
16 | public var valueUpdated: ((Bool) -> Void)?
17 |
18 | public var frameColor = UIColor(rgb: 0xe0e0e0) {
19 | didSet {
20 | if self.isNodeLoaded {
21 | (self.view as! UISwitch).tintColor = self.frameColor
22 | }
23 | }
24 | }
25 | public var handleColor = UIColor(rgb: 0xffffff) {
26 | didSet {
27 | if self.isNodeLoaded {
28 | //(self.view as! UISwitch).thumbTintColor = self.handleColor
29 | }
30 | }
31 | }
32 | public var contentColor = UIColor(rgb: 0x42d451) {
33 | didSet {
34 | if self.isNodeLoaded {
35 | (self.view as! UISwitch).onTintColor = self.contentColor
36 | }
37 | }
38 | }
39 |
40 | private var _isOn: Bool = false
41 | public var isOn: Bool {
42 | get {
43 | return self._isOn
44 | } set(value) {
45 | if (value != self._isOn) {
46 | self._isOn = value
47 | if self.isNodeLoaded {
48 | (self.view as! UISwitch).setOn(value, animated: false)
49 | }
50 | }
51 | }
52 | }
53 |
54 | override public init() {
55 | super.init()
56 |
57 | self.setViewBlock({
58 | return SwitchNodeView()
59 | })
60 | }
61 |
62 | override open func didLoad() {
63 | super.didLoad()
64 |
65 | self.view.isAccessibilityElement = false
66 |
67 | (self.view as! UISwitch).backgroundColor = self.backgroundColor
68 | (self.view as! UISwitch).tintColor = self.frameColor
69 | //(self.view as! UISwitch).thumbTintColor = self.handleColor
70 | (self.view as! UISwitch).onTintColor = self.contentColor
71 |
72 | (self.view as! UISwitch).setOn(self._isOn, animated: false)
73 |
74 | (self.view as! UISwitch).addTarget(self, action: #selector(switchValueChanged(_:)), for: .valueChanged)
75 | }
76 |
77 | public func setOn(_ value: Bool, animated: Bool) {
78 | self._isOn = value
79 | if self.isNodeLoaded {
80 | (self.view as! UISwitch).setOn(value, animated: animated)
81 | }
82 | }
83 |
84 | override open func calculateSizeThatFits(_ constrainedSize: CGSize) -> CGSize {
85 | return CGSize(width: 51.0, height: 31.0)
86 | }
87 |
88 | @objc func switchValueChanged(_ view: UISwitch) {
89 | self._isOn = view.isOn
90 | self.valueUpdated?(view.isOn)
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/Display/TabBarContollerNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public enum ToolbarActionOption {
5 | case left
6 | case right
7 | case middle
8 | }
9 |
10 | final class TabBarControllerNode: ASDisplayNode {
11 | private var theme: TabBarControllerTheme
12 | let tabBarNode: TabBarNode
13 | private let navigationBar: NavigationBar?
14 | private var toolbarNode: ToolbarNode?
15 | private let toolbarActionSelected: (ToolbarActionOption) -> Void
16 |
17 | var currentControllerNode: ASDisplayNode? {
18 | didSet {
19 | oldValue?.removeFromSupernode()
20 |
21 | if let currentControllerNode = self.currentControllerNode {
22 | self.insertSubnode(currentControllerNode, at: 0)
23 | }
24 | }
25 | }
26 |
27 | init(theme: TabBarControllerTheme, navigationBar: NavigationBar?, itemSelected: @escaping (Int, Bool, [ASDisplayNode]) -> Void, toolbarActionSelected: @escaping (ToolbarActionOption) -> Void) {
28 | self.theme = theme
29 | self.navigationBar = navigationBar
30 | self.tabBarNode = TabBarNode(theme: theme, itemSelected: itemSelected)
31 | self.toolbarActionSelected = toolbarActionSelected
32 |
33 | super.init()
34 |
35 | self.setViewBlock({
36 | return UITracingLayerView()
37 | })
38 |
39 | self.backgroundColor = theme.backgroundColor
40 |
41 | self.addSubnode(self.tabBarNode)
42 | }
43 |
44 | func updateTheme(_ theme: TabBarControllerTheme) {
45 | self.theme = theme
46 | self.backgroundColor = theme.backgroundColor
47 |
48 | self.tabBarNode.updateTheme(theme)
49 | self.toolbarNode?.updateTheme(theme)
50 | }
51 |
52 | func containerLayoutUpdated(_ layout: ContainerViewLayout, toolbar: Toolbar?, transition: ContainedViewLayoutTransition) {
53 | var tabBarHeight: CGFloat
54 | var options: ContainerViewLayoutInsetOptions = []
55 | if layout.metrics.widthClass == .regular {
56 | options.insert(.input)
57 | }
58 | let bottomInset: CGFloat = layout.insets(options: options).bottom
59 | if !layout.safeInsets.left.isZero {
60 | tabBarHeight = 34.0 + bottomInset
61 | } else {
62 | tabBarHeight = 49.0 + bottomInset
63 | }
64 |
65 | let tabBarFrame = CGRect(origin: CGPoint(x: 0.0, y: layout.size.height - tabBarHeight), size: CGSize(width: layout.size.width, height: tabBarHeight))
66 |
67 | transition.updateFrame(node: self.tabBarNode, frame: tabBarFrame)
68 | self.tabBarNode.updateLayout(size: layout.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, transition: transition)
69 |
70 | if let toolbar = toolbar {
71 | if let toolbarNode = self.toolbarNode {
72 | transition.updateFrame(node: toolbarNode, frame: tabBarFrame)
73 | toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: transition)
74 | } else {
75 | let toolbarNode = ToolbarNode(theme: self.theme, left: { [weak self] in
76 | self?.toolbarActionSelected(.left)
77 | }, right: { [weak self] in
78 | self?.toolbarActionSelected(.right)
79 | }, middle: { [weak self] in
80 | self?.toolbarActionSelected(.middle)
81 | })
82 | toolbarNode.frame = tabBarFrame
83 | toolbarNode.updateLayout(size: tabBarFrame.size, leftInset: layout.safeInsets.left, rightInset: layout.safeInsets.right, bottomInset: bottomInset, toolbar: toolbar, transition: .immediate)
84 | self.addSubnode(toolbarNode)
85 | self.toolbarNode = toolbarNode
86 | if transition.isAnimated {
87 | toolbarNode.layer.animateAlpha(from: 0.0, to: 1.0, duration: 0.2)
88 | }
89 | }
90 | } else if let toolbarNode = self.toolbarNode {
91 | self.toolbarNode = nil
92 | transition.updateAlpha(node: toolbarNode, alpha: 0.0, completion: { [weak toolbarNode] _ in
93 | toolbarNode?.removeFromSupernode()
94 | })
95 | }
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/Display/TabBarTapRecognizer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import SwiftSignalKit
4 |
5 | final class TabBarTapRecognizer: UIGestureRecognizer {
6 | private let tap: (CGPoint) -> Void
7 | private let longTap: (CGPoint) -> Void
8 |
9 | private var initialLocation: CGPoint?
10 | private var longTapTimer: SwiftSignalKit.Timer?
11 |
12 | init(tap: @escaping (CGPoint) -> Void, longTap: @escaping (CGPoint) -> Void) {
13 | self.tap = tap
14 | self.longTap = longTap
15 |
16 | super.init(target: nil, action: nil)
17 | }
18 |
19 | override func reset() {
20 | super.reset()
21 |
22 | self.initialLocation = nil
23 | self.longTapTimer?.invalidate()
24 | self.longTapTimer = nil
25 | }
26 |
27 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
28 | super.touchesBegan(touches, with: event)
29 |
30 | if self.initialLocation == nil {
31 | self.initialLocation = touches.first?.location(in: self.view)
32 | let longTapTimer = SwiftSignalKit.Timer(timeout: 0.4, repeat: false, completion: { [weak self] in
33 | guard let strongSelf = self else {
34 | return
35 | }
36 | if let initialLocation = strongSelf.initialLocation {
37 | strongSelf.initialLocation = nil
38 | strongSelf.longTap(initialLocation)
39 | strongSelf.state = .ended
40 | }
41 | }, queue: Queue.mainQueue())
42 | self.longTapTimer?.invalidate()
43 | self.longTapTimer = longTapTimer
44 | longTapTimer.start()
45 | }
46 | }
47 |
48 | override func touchesEnded(_ touches: Set, with event: UIEvent) {
49 | super.touchesEnded(touches, with: event)
50 |
51 | if let initialLocation = self.initialLocation {
52 | self.initialLocation = nil
53 | self.longTapTimer?.invalidate()
54 | self.longTapTimer = nil
55 | self.tap(initialLocation)
56 | self.state = .ended
57 | }
58 | }
59 |
60 | override func touchesMoved(_ touches: Set, with event: UIEvent) {
61 | super.touchesMoved(touches, with: event)
62 |
63 | if let initialLocation = self.initialLocation, let location = touches.first?.location(in: self.view) {
64 | let deltaX = initialLocation.x - location.x
65 | let deltaY = initialLocation.y - location.y
66 | if deltaX * deltaX + deltaY * deltaY > 4.0 {
67 | self.longTapTimer?.invalidate()
68 | self.longTapTimer = nil
69 | self.initialLocation = nil
70 | self.state = .failed
71 | }
72 | }
73 | }
74 |
75 | override func touchesCancelled(_ touches: Set, with event: UIEvent) {
76 | super.touchesCancelled(touches, with: event)
77 |
78 | self.initialLocation = nil
79 | self.longTapTimer?.invalidate()
80 | self.longTapTimer = nil
81 | self.state = .failed
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/Display/TextFieldNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | public final class TextFieldNodeView: UITextField {
5 | public var didDeleteBackwardWhileEmpty: (() -> Void)?
6 |
7 | var fixOffset: Bool = true
8 |
9 | override public func editingRect(forBounds bounds: CGRect) -> CGRect {
10 | return bounds.offsetBy(dx: 0.0, dy: 0.0).integral
11 | }
12 |
13 | override public func textRect(forBounds bounds: CGRect) -> CGRect {
14 | return bounds.offsetBy(dx: 0.0, dy: 0.0).integral
15 | }
16 |
17 | override public func placeholderRect(forBounds bounds: CGRect) -> CGRect {
18 | return self.editingRect(forBounds: bounds.offsetBy(dx: 0.0, dy: -1.0))
19 | }
20 |
21 | override public func deleteBackward() {
22 | if self.text == nil || self.text!.isEmpty {
23 | self.didDeleteBackwardWhileEmpty?()
24 | }
25 | super.deleteBackward()
26 | }
27 |
28 | override public var keyboardAppearance: UIKeyboardAppearance {
29 | get {
30 | return super.keyboardAppearance
31 | }
32 | set {
33 | guard newValue != self.keyboardAppearance else {
34 | return
35 | }
36 | let resigning = self.isFirstResponder
37 | if resigning {
38 | self.resignFirstResponder()
39 | }
40 | super.keyboardAppearance = newValue
41 | if resigning {
42 | self.becomeFirstResponder()
43 | }
44 | }
45 | }
46 | }
47 |
48 | public class TextFieldNode: ASDisplayNode {
49 | public var textField: TextFieldNodeView {
50 | return self.view as! TextFieldNodeView
51 | }
52 |
53 | public var fixOffset: Bool = true {
54 | didSet {
55 | self.textField.fixOffset = self.fixOffset
56 | }
57 | }
58 |
59 | override public init() {
60 | super.init()
61 |
62 | self.setViewBlock({
63 | return TextFieldNodeView()
64 | })
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/Display/Theme.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public class Theme {
4 | public let accentColor: UIColor
5 |
6 | public init(accentColor: UIColor) {
7 | self.accentColor = accentColor
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/Display/Toolbar.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public struct ToolbarAction: Equatable {
4 | public let title: String
5 | public let isEnabled: Bool
6 |
7 | public init(title: String, isEnabled: Bool) {
8 | self.title = title
9 | self.isEnabled = isEnabled
10 | }
11 | }
12 |
13 | public struct Toolbar: Equatable {
14 | public let leftAction: ToolbarAction?
15 | public let rightAction: ToolbarAction?
16 | public let middleAction: ToolbarAction?
17 |
18 | public init(leftAction: ToolbarAction?, rightAction: ToolbarAction?, middleAction: ToolbarAction?) {
19 | self.leftAction = leftAction
20 | self.rightAction = rightAction
21 | self.middleAction = middleAction
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/Display/UIBarButtonItem+Proxy.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | typedef void (^UIBarButtonItemSetTitleListener)(NSString *);
5 | typedef void (^UIBarButtonItemSetEnabledListener)(BOOL);
6 |
7 | @interface UIBarButtonItem (Proxy)
8 |
9 | @property (nonatomic, strong, readonly) ASDisplayNode *customDisplayNode;
10 | @property (nonatomic, readonly) bool backButtonAppearance;
11 |
12 | - (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode;
13 | - (instancetype)initWithBackButtonAppearanceWithTitle:(NSString *)title target:(id)target action:(SEL)action;
14 |
15 | - (void)performActionOnTarget;
16 |
17 | - (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener;
18 | - (void)removeSetTitleListener:(NSInteger)key;
19 | - (NSInteger)addSetEnabledListener:(UIBarButtonItemSetEnabledListener)listener;
20 | - (void)removeSetEnabledListener:(NSInteger)key;
21 |
22 | @end
23 |
--------------------------------------------------------------------------------
/Display/UIBarButtonItem+Proxy.m:
--------------------------------------------------------------------------------
1 | #import "UIBarButtonItem+Proxy.h"
2 |
3 | #import "NSBag.h"
4 | #import "RuntimeUtils.h"
5 |
6 | static const void *setEnabledListenerBagKey = &setEnabledListenerBagKey;
7 | static const void *setTitleListenerBagKey = &setTitleListenerBagKey;
8 | static const void *customDisplayNodeKey = &customDisplayNodeKey;
9 | static const void *backButtonAppearanceKey = &backButtonAppearanceKey;
10 |
11 | @implementation UIBarButtonItem (Proxy)
12 |
13 | + (void)load
14 | {
15 | static dispatch_once_t onceToken;
16 | dispatch_once(&onceToken, ^
17 | {
18 | [RuntimeUtils swizzleInstanceMethodOfClass:[UIBarButtonItem class] currentSelector:@selector(setEnabled:) newSelector:@selector(_c1e56039_setEnabled:)];
19 | [RuntimeUtils swizzleInstanceMethodOfClass:[UIBarButtonItem class] currentSelector:@selector(setTitle:) newSelector:@selector(_c1e56039_setTitle:)];
20 | });
21 | }
22 |
23 | - (instancetype)initWithCustomDisplayNode:(ASDisplayNode *)customDisplayNode {
24 | self = [self init];
25 | if (self != nil) {
26 | [self setAssociatedObject:customDisplayNode forKey:customDisplayNodeKey];
27 | }
28 | return self;
29 | }
30 |
31 | - (instancetype)initWithBackButtonAppearanceWithTitle:(NSString *)title target:(id)target action:(SEL)action {
32 | self = [self initWithTitle:title style:UIBarButtonItemStylePlain target:target action:action];
33 | if (self != nil) {
34 | [self setAssociatedObject:@true forKey:backButtonAppearanceKey];
35 | }
36 | return self;
37 | }
38 |
39 | - (ASDisplayNode *)customDisplayNode {
40 | return [self associatedObjectForKey:customDisplayNodeKey];
41 | }
42 |
43 | - (bool)backButtonAppearance {
44 | return [[self associatedObjectForKey:backButtonAppearanceKey] boolValue];
45 | }
46 |
47 | - (void)_c1e56039_setEnabled:(BOOL)enabled
48 | {
49 | [self _c1e56039_setEnabled:enabled];
50 |
51 | [(NSBag *)[self associatedObjectForKey:setEnabledListenerBagKey] enumerateItems:^(UIBarButtonItemSetEnabledListener listener)
52 | {
53 | listener(enabled);
54 | }];
55 | }
56 |
57 | - (void)_c1e56039_setTitle:(NSString *)title
58 | {
59 | [self _c1e56039_setTitle:title];
60 |
61 | [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] enumerateItems:^(UIBarButtonItemSetTitleListener listener)
62 | {
63 | listener(title);
64 | }];
65 | }
66 |
67 | - (void)performActionOnTarget
68 | {
69 | if (self.target == nil) {
70 | return;
71 | }
72 |
73 | #pragma clang diagnostic push
74 | #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
75 | [self.target performSelector:self.action];
76 | #pragma clang diagnostic pop
77 | }
78 |
79 | - (NSInteger)addSetTitleListener:(UIBarButtonItemSetTitleListener)listener
80 | {
81 | NSBag *bag = [self associatedObjectForKey:setTitleListenerBagKey];
82 | if (bag == nil)
83 | {
84 | bag = [[NSBag alloc] init];
85 | [self setAssociatedObject:bag forKey:setTitleListenerBagKey];
86 | }
87 | return [bag addItem:[listener copy]];
88 | }
89 |
90 | - (void)removeSetTitleListener:(NSInteger)key
91 | {
92 | [(NSBag *)[self associatedObjectForKey:setTitleListenerBagKey] removeItem:key];
93 | }
94 |
95 | - (NSInteger)addSetEnabledListener:(UIBarButtonItemSetEnabledListener)listener
96 | {
97 | NSBag *bag = [self associatedObjectForKey:setEnabledListenerBagKey];
98 | if (bag == nil)
99 | {
100 | bag = [[NSBag alloc] init];
101 | [self setAssociatedObject:bag forKey:setEnabledListenerBagKey];
102 | }
103 | return [bag addItem:[listener copy]];
104 | }
105 |
106 | - (void)removeSetEnabledListener:(NSInteger)key
107 | {
108 | [(NSBag *)[self associatedObjectForKey:setEnabledListenerBagKey] removeItem:key];
109 | }
110 |
111 | @end
112 |
--------------------------------------------------------------------------------
/Display/UIKitUtils.h:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | @interface UIView (AnimationUtils)
5 |
6 | + (double)animationDurationFactor;
7 |
8 | @end
9 |
10 | CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath);
11 | CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping);
12 | CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t);
13 |
14 |
--------------------------------------------------------------------------------
/Display/UIKitUtils.m:
--------------------------------------------------------------------------------
1 | #import "UIKitUtils.h"
2 |
3 | #import
4 |
5 | #if TARGET_IPHONE_SIMULATOR
6 | UIKIT_EXTERN float UIAnimationDragCoefficient(); // UIKit private drag coeffient, use judiciously
7 | #endif
8 |
9 | @implementation UIView (AnimationUtils)
10 |
11 | + (double)animationDurationFactor
12 | {
13 | #if TARGET_IPHONE_SIMULATOR
14 | return (double)UIAnimationDragCoefficient();
15 | #endif
16 |
17 | return 1.0f;
18 | }
19 |
20 | @end
21 |
22 | @interface CASpringAnimation ()
23 |
24 | @end
25 |
26 | @implementation CASpringAnimation (AnimationUtils)
27 |
28 | - (CGFloat)valueAt:(CGFloat)t {
29 | static dispatch_once_t onceToken;
30 | static float (*impl)(id, float) = NULL;
31 | static double (*dimpl)(id, double) = NULL;
32 | dispatch_once(&onceToken, ^{
33 | Method method = class_getInstanceMethod([CASpringAnimation class], NSSelectorFromString([@"_" stringByAppendingString:@"solveForInput:"]));
34 | if (method) {
35 | const char *encoding = method_getTypeEncoding(method);
36 | NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:encoding];
37 | const char *argType = [signature getArgumentTypeAtIndex:2];
38 | if (strncmp(argType, "f", 1) == 0) {
39 | impl = (float (*)(id, float))method_getImplementation(method);
40 | } else if (strncmp(argType, "d", 1) == 0) {
41 | dimpl = (double (*)(id, double))method_getImplementation(method);
42 | }
43 | }
44 | });
45 | if (impl) {
46 | float result = impl(self, (float)t);
47 | return (CGFloat)result;
48 | } else if (dimpl) {
49 | double result = dimpl(self, (double)t);
50 | return (CGFloat)result;
51 | }
52 | return t;
53 | }
54 |
55 | @end
56 |
57 | CABasicAnimation * _Nonnull makeSpringAnimation(NSString * _Nonnull keyPath) {
58 | CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath];
59 | springAnimation.mass = 3.0f;
60 | springAnimation.stiffness = 1000.0f;
61 | springAnimation.damping = 500.0f;
62 | springAnimation.duration = 0.5;
63 | springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
64 | return springAnimation;
65 | }
66 |
67 | CABasicAnimation * _Nonnull makeSpringBounceAnimation(NSString * _Nonnull keyPath, CGFloat initialVelocity, CGFloat damping) {
68 | CASpringAnimation *springAnimation = [CASpringAnimation animationWithKeyPath:keyPath];
69 | springAnimation.mass = 5.0f;
70 | springAnimation.stiffness = 900.0f;
71 | springAnimation.damping = damping;
72 | static bool canSetInitialVelocity = true;
73 | static dispatch_once_t onceToken;
74 | dispatch_once(&onceToken, ^{
75 | canSetInitialVelocity = [springAnimation respondsToSelector:@selector(setInitialVelocity:)];
76 | });
77 | if (canSetInitialVelocity) {
78 | springAnimation.initialVelocity = initialVelocity;
79 | springAnimation.duration = springAnimation.settlingDuration;
80 | } else {
81 | springAnimation.duration = 0.1;
82 | }
83 | springAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
84 | return springAnimation;
85 | }
86 |
87 | CGFloat springAnimationValueAt(CABasicAnimation * _Nonnull animation, CGFloat t) {
88 | return [(CASpringAnimation *)animation valueAt:t];
89 | }
90 |
--------------------------------------------------------------------------------
/Display/UIMenuItem+Icons.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface UIMenuItem (Icons)
4 |
5 | - (instancetype)initWithTitle:(NSString *)title icon:(UIImage *)icon action:(SEL)action;
6 |
7 | @end
8 |
9 |
--------------------------------------------------------------------------------
/Display/UIMenuItem+Icons.m:
--------------------------------------------------------------------------------
1 | #import "UIMenuItem+Icons.h"
2 |
3 | #import "NSBag.h"
4 | #import "RuntimeUtils.h"
5 |
6 | static const void *imageKey = &imageKey;
7 | static const void *imageViewKey = &imageViewKey;
8 | static NSString *const imageItemIdetifier = @"\uFEFF\u200B";
9 |
10 | @interface UIMenuController (Icons)
11 |
12 | @end
13 |
14 | @implementation UIMenuController (Icons)
15 |
16 | - (UIMenuItem *)findImageItemByTitle:(NSString *)title {
17 | if ([title hasSuffix:imageItemIdetifier]) {
18 | for (UIMenuItem *item in self.menuItems) {
19 | if ([item.title isEqualToString:title]) {
20 | return item;
21 | }
22 | }
23 | }
24 | return nil;
25 | }
26 |
27 | @end
28 |
29 |
30 | @implementation UIMenuItem (Icons)
31 |
32 | - (instancetype)initWithTitle:(NSString *)title icon:(UIImage *)icon action:(SEL)action {
33 | NSString *combinedTitle = title;
34 | if (icon != nil) {
35 | combinedTitle = [NSString stringWithFormat:@"%@%@", title, imageItemIdetifier];
36 | }
37 | self = [self initWithTitle:combinedTitle action:action];
38 | if (self != nil) {
39 | if (icon != nil) {
40 | [self _tg_setImage:icon];
41 | }
42 | }
43 | return self;
44 | }
45 |
46 | - (UIImage *)_tg_image {
47 | return (UIImage *)[self associatedObjectForKey:imageKey];
48 | }
49 |
50 | - (void)_tg_setImage:(UIImage *)image {
51 | [self setAssociatedObject:image forKey:imageKey associationPolicy:NSObjectAssociationPolicyRetain];
52 | }
53 |
54 | @end
55 |
56 | @interface NSString (Items)
57 |
58 | @end
59 |
60 | @implementation NSString (Items)
61 |
62 | + (void)load
63 | {
64 | static dispatch_once_t onceToken;
65 | dispatch_once(&onceToken, ^
66 | {
67 | [RuntimeUtils swizzleInstanceMethodOfClass:[NSString class] currentSelector:@selector(sizeWithAttributes:) newSelector:@selector(_78724db9_sizeWithAttributes:)];
68 | });
69 | }
70 |
71 | - (CGSize)_78724db9_sizeWithAttributes:(NSDictionary *)attrs {
72 | UIMenuItem *item = [[UIMenuController sharedMenuController] findImageItemByTitle:self];
73 | UIImage *image = item._tg_image;
74 | if (image != nil) {
75 | return image.size;
76 | } else {
77 | return [self _78724db9_sizeWithAttributes:attrs];
78 | }
79 | }
80 |
81 | @end
82 |
83 |
84 | @interface UILabel (Icons)
85 |
86 | @end
87 |
88 | @implementation UILabel (Icons)
89 |
90 | + (void)load
91 | {
92 | static dispatch_once_t onceToken;
93 | dispatch_once(&onceToken, ^
94 | {
95 | [RuntimeUtils swizzleInstanceMethodOfClass:[UILabel class] currentSelector:@selector(drawTextInRect:) newSelector:@selector(_78724db9_drawTextInRect:)];
96 | [RuntimeUtils swizzleInstanceMethodOfClass:[UILabel class] currentSelector:@selector(layoutSubviews) newSelector:@selector(_78724db9_layoutSubviews)];
97 | [RuntimeUtils swizzleInstanceMethodOfClass:[UILabel class] currentSelector:@selector(setFrame:) newSelector:@selector(_78724db9_setFrame:)];
98 | });
99 | }
100 |
101 | - (void)_78724db9_drawTextInRect:(CGRect)rect {
102 | UIMenuItem *item = [[UIMenuController sharedMenuController] findImageItemByTitle:self.text];
103 | UIImage *image = item._tg_image;
104 | if (image == nil) {
105 | [self _78724db9_drawTextInRect:rect];
106 | }
107 | }
108 |
109 | - (void)_78724db9_layoutSubviews {
110 | UIMenuItem *item = [[UIMenuController sharedMenuController] findImageItemByTitle:self.text];
111 | UIImage *image = item._tg_image;
112 | if (image == nil) {
113 | [self _78724db9_layoutSubviews];
114 | return;
115 | }
116 |
117 | CGPoint point = CGPointMake(ceil((self.bounds.size.width - image.size.width) / 2.0), ceil((self.bounds.size.height - image.size.height) / 2.0));
118 | UIImageView *imageView = [self associatedObjectForKey:imageViewKey];
119 | if (imageView == nil) {
120 | imageView = [[UIImageView alloc] init];
121 | [self addSubview:imageView];
122 | [self setAssociatedObject:imageView forKey:imageViewKey associationPolicy:NSObjectAssociationPolicyRetain];
123 | }
124 |
125 | imageView.image = image;
126 | imageView.frame = CGRectMake(point.x, point.y, image.size.width, image.size.height);
127 | }
128 |
129 | - (void)_78724db9_setFrame:(CGRect)frame
130 | {
131 | bool hasImage = [[UIMenuController sharedMenuController] findImageItemByTitle:self.text]._tg_image != nil;
132 | CGRect rect = frame;
133 | if (hasImage && self.superview != nil) {
134 | rect = self.superview.bounds;
135 | }
136 | [self _78724db9_setFrame:rect];
137 | }
138 |
139 | @end
140 |
--------------------------------------------------------------------------------
/Display/UINavigationItem+Proxy.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | typedef void (^UINavigationItemSetTitleListener)(NSString * _Nullable, bool);
4 | typedef void (^UINavigationItemSetTitleViewListener)(UIView * _Nullable);
5 | typedef void (^UINavigationItemSetImageListener)(UIImage * _Nullable);
6 | typedef void (^UINavigationItemSetBarButtonItemListener)(UIBarButtonItem * _Nullable, UIBarButtonItem * _Nullable, BOOL);
7 | typedef void (^UINavigationItemSetMutipleBarButtonItemsListener)(NSArray * _Nullable, BOOL);
8 | typedef void (^UITabBarItemSetBadgeListener)(NSString * _Nullable);
9 |
10 | @interface UINavigationItem (Proxy)
11 |
12 | - (void)setTargetItem:(UINavigationItem * _Nullable)targetItem;
13 | - (BOOL)hasTargetItem;
14 |
15 | - (void)setTitle:(NSString * _Nullable)title animated:(bool)animated;
16 |
17 | - (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener _Nonnull)listener;
18 | - (void)removeSetTitleListener:(NSInteger)key;
19 | - (NSInteger)addSetTitleViewListener:(UINavigationItemSetTitleViewListener _Nonnull)listener;
20 | - (void)removeSetTitleViewListener:(NSInteger)key;
21 | - (NSInteger)addSetLeftBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener;
22 | - (void)removeSetLeftBarButtonItemListener:(NSInteger)key;
23 | - (NSInteger)addSetRightBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener;
24 | - (void)removeSetRightBarButtonItemListener:(NSInteger)key;
25 | - (NSInteger)addSetMultipleRightBarButtonItemsListener:(UINavigationItemSetMutipleBarButtonItemsListener _Nonnull)listener;
26 | - (void)removeSetMultipleRightBarButtonItemsListener:(NSInteger)key;
27 | - (NSInteger)addSetBackBarButtonItemListener:(UINavigationItemSetBarButtonItemListener _Nonnull)listener;
28 | - (void)removeSetBackBarButtonItemListener:(NSInteger)key;
29 | - (NSInteger)addSetBadgeListener:(UITabBarItemSetBadgeListener _Nonnull)listener;
30 | - (void)removeSetBadgeListener:(NSInteger)key;
31 |
32 | @property (nonatomic, strong) NSString * _Nullable badge;
33 |
34 | @end
35 |
36 | NSInteger UITabBarItem_addSetBadgeListener(UITabBarItem * _Nonnull item, UITabBarItemSetBadgeListener _Nonnull listener);
37 |
38 | @interface UITabBarItem (Proxy)
39 |
40 | - (void)removeSetBadgeListener:(NSInteger)key;
41 |
42 | - (NSInteger)addSetTitleListener:(UINavigationItemSetTitleListener _Nonnull)listener;
43 | - (void)removeSetTitleListener:(NSInteger)key;
44 |
45 | - (NSInteger)addSetImageListener:(UINavigationItemSetImageListener _Nonnull)listener;
46 | - (void)removeSetImageListener:(NSInteger)key;
47 |
48 | - (NSInteger)addSetSelectedImageListener:(UINavigationItemSetImageListener _Nonnull)listener;
49 | - (void)removeSetSelectedImageListener:(NSInteger)key;
50 |
51 | - (NSObject * _Nullable)userInfo;
52 | - (void)setUserInfo:(NSObject * _Nullable)userInfo;
53 |
54 | @end
55 |
--------------------------------------------------------------------------------
/Display/UIViewController+Navigation.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | typedef NS_OPTIONS(NSUInteger, UIResponderDisableAutomaticKeyboardHandling) {
4 | UIResponderDisableAutomaticKeyboardHandlingForward = 1 << 0,
5 | UIResponderDisableAutomaticKeyboardHandlingBackward = 1 << 1
6 | };
7 |
8 | @interface UIViewController (Navigation)
9 |
10 | - (void)setHintWillBePresentedInPreviewingContext:(BOOL)value;
11 | - (BOOL)isPresentedInPreviewingContext;
12 | - (void)setIgnoreAppearanceMethodInvocations:(BOOL)ignoreAppearanceMethodInvocations;
13 | - (BOOL)ignoreAppearanceMethodInvocations;
14 | - (void)navigation_setNavigationController:(UINavigationController * _Nullable)navigationControlller;
15 | - (void)navigation_setPresentingViewController:(UIViewController * _Nullable)presentingViewController;
16 | - (void)navigation_setDismiss:(void (^_Nullable)())dismiss rootController:( UIViewController * _Nullable )rootController;
17 | - (void)state_setNeedsStatusBarAppearanceUpdate:(void (^_Nullable)())block;
18 |
19 | @end
20 |
21 | @interface UIView (Navigation)
22 |
23 | @property (nonatomic) bool disablesInteractiveTransitionGestureRecognizer;
24 | @property (nonatomic, copy) bool (^ disablesInteractiveTransitionGestureRecognizerNow)();
25 |
26 | @property (nonatomic) UIResponderDisableAutomaticKeyboardHandling disableAutomaticKeyboardHandling;
27 |
28 | @property (nonatomic, copy) BOOL (^_Nullable interactiveTransitionGestureRecognizerTest)(CGPoint);
29 |
30 | - (void)input_setInputAccessoryHeightProvider:(CGFloat (^_Nullable)())block;
31 | - (CGFloat)input_getInputAccessoryHeight;
32 |
33 | @end
34 |
35 | void applyKeyboardAutocorrection();
36 |
--------------------------------------------------------------------------------
/Display/UIWindow+OrientationChange.h:
--------------------------------------------------------------------------------
1 | #import
2 |
3 | @interface UIWindow (OrientationChange)
4 |
5 | - (bool)isRotating;
6 | + (void)addPostDeviceOrientationDidChangeBlock:(void (^)())block;
7 | + (bool)isDeviceRotating;
8 |
9 | - (void)_updateToInterfaceOrientation:(int)arg1 duration:(double)arg2 force:(BOOL)arg3;
10 |
11 | @end
12 |
--------------------------------------------------------------------------------
/Display/UIWindow+OrientationChange.m:
--------------------------------------------------------------------------------
1 | #import "UIWindow+OrientationChange.h"
2 |
3 | #import "RuntimeUtils.h"
4 | #import "NotificationCenterUtils.h"
5 |
6 | static const void *isRotatingKey = &isRotatingKey;
7 |
8 | static NSMutableArray *postDeviceDidChangeOrientationBlocks() {
9 | static NSMutableArray *array = nil;
10 | static dispatch_once_t onceToken;
11 | dispatch_once(&onceToken, ^{
12 | array = [[NSMutableArray alloc] init];
13 | });
14 | return array;
15 | }
16 |
17 | static bool _isDeviceRotating = false;
18 |
19 | @implementation UIWindow (OrientationChange)
20 |
21 | + (void)load {
22 | static dispatch_once_t onceToken;
23 | dispatch_once(&onceToken, ^ {
24 | [NotificationCenterUtils addNotificationHandler:^bool(NSString *name, id object, NSDictionary *userInfo, void (^passNotification)()) {
25 | if ([name isEqualToString:@"UIWindowWillRotateNotification"]) {
26 | [(UIWindow *)object setRotating:true];
27 |
28 | if (NSClassFromString(@"NSUserActivity") == NULL) {
29 | UIInterfaceOrientation orientation = [userInfo[@"UIWindowNewOrientationUserInfoKey"] integerValue];
30 | CGSize screenSize = [UIScreen mainScreen].bounds.size;
31 | if (screenSize.width > screenSize.height)
32 | {
33 | CGFloat tmp = screenSize.height;
34 | screenSize.height = screenSize.width;
35 | screenSize.width = tmp;
36 | }
37 | CGSize windowSize = CGSizeZero;
38 | CGFloat windowRotation = 0.0;
39 | bool landscape = false;
40 | switch (orientation) {
41 | case UIInterfaceOrientationPortrait:
42 | windowSize = screenSize;
43 | break;
44 | case UIInterfaceOrientationPortraitUpsideDown:
45 | windowRotation = (CGFloat)(M_PI);
46 | windowSize = screenSize;
47 | break;
48 | case UIInterfaceOrientationLandscapeLeft:
49 | landscape = true;
50 | windowRotation = (CGFloat)(-M_PI / 2.0);
51 | windowSize = CGSizeMake(screenSize.height, screenSize.width);
52 | break;
53 | case UIInterfaceOrientationLandscapeRight:
54 | landscape = true;
55 | windowRotation = (CGFloat)(M_PI / 2.0);
56 | windowSize = CGSizeMake(screenSize.height, screenSize.width);
57 | break;
58 | default:
59 | break;
60 | }
61 |
62 | [UIView animateWithDuration:0.3 animations:^
63 | {
64 | CGAffineTransform transform = CGAffineTransformIdentity;
65 | transform = CGAffineTransformRotate(transform, windowRotation);
66 | ((UIWindow *)object).transform = transform;
67 | ((UIWindow *)object).bounds = CGRectMake(0.0f, 0.0f, windowSize.width, windowSize.height);
68 | }];
69 | }
70 |
71 | passNotification();
72 |
73 | return true;
74 | } else if ([name isEqualToString:@"UIWindowDidRotateNotification"]) {
75 | [(UIWindow *)object setRotating:false];
76 | } else if ([name isEqualToString:UIDeviceOrientationDidChangeNotification]) {
77 | //NSLog(@"notification start: %@", name);
78 |
79 | _isDeviceRotating = true;
80 |
81 | passNotification();
82 |
83 | if (postDeviceDidChangeOrientationBlocks().count != 0) {
84 | NSArray *blocks = [postDeviceDidChangeOrientationBlocks() copy];
85 | [postDeviceDidChangeOrientationBlocks() removeAllObjects];
86 | for (dispatch_block_t block in blocks) {
87 | block();
88 | }
89 | }
90 |
91 | _isDeviceRotating = false;
92 |
93 | //NSLog(@"notification end: %@", name);
94 |
95 | return true;
96 | }
97 |
98 | return false;
99 | }];
100 | });
101 | }
102 |
103 | + (void)addPostDeviceOrientationDidChangeBlock:(void (^)())block {
104 | [postDeviceDidChangeOrientationBlocks() addObject:[block copy]];
105 | }
106 |
107 | - (void)setRotating:(bool)rotating
108 | {
109 | [self setAssociatedObject:@(rotating) forKey:isRotatingKey];
110 | }
111 |
112 | - (bool)isRotating
113 | {
114 | return [[self associatedObjectForKey:isRotatingKey] boolValue];
115 | }
116 |
117 | + (bool)isDeviceRotating {
118 | return _isDeviceRotating;
119 | }
120 |
121 | @end
122 |
--------------------------------------------------------------------------------
/Display/UniversalMasterController.swift:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/peter-iakovlev/Display/ffb131ac55bed21c37052a09c27150d5d380321e/Display/UniversalMasterController.swift
--------------------------------------------------------------------------------
/Display/UniversalTapRecognizer.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 | import UIKit.UIGestureRecognizerSubclass
3 |
4 | private class TimerTargetWrapper: NSObject {
5 | let f: () -> Void
6 |
7 | init(_ f: @escaping () -> Void) {
8 | self.f = f
9 | }
10 |
11 | @objc func timerEvent() {
12 | self.f()
13 | }
14 | }
15 |
16 | class UniversalTapRecognizer: UITapGestureRecognizer {
17 | private let tapMaxDelay: Double = 0.15
18 |
19 | private var timer: Timer?
20 |
21 | deinit {
22 | self.timer?.invalidate()
23 | }
24 |
25 | override func reset() {
26 | super.reset()
27 |
28 | self.timer?.invalidate()
29 | }
30 |
31 | override func touchesBegan(_ touches: Set, with event: UIEvent) {
32 | super.touchesBegan(touches, with: event)
33 |
34 | let timer = Timer(timeInterval: self.tapMaxDelay, target: TimerTargetWrapper({ [weak self] in
35 | if let strongSelf = self {
36 | if strongSelf.state != .ended {
37 | strongSelf.state = .failed
38 | }
39 | }
40 | }), selector: #selector(TimerTargetWrapper.timerEvent), userInfo: nil, repeats: false)
41 | self.timer = timer
42 | RunLoop.main.add(timer, forMode: .commonModes)
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/Display/ViewControllerPreviewing.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 | import SwiftSignalKit
4 |
5 | @available(iOSApplicationExtension 9.0, *)
6 | private final class ViewControllerPeekContent: PeekControllerContent {
7 | private let controller: ViewController
8 | private let menu: [PeekControllerMenuItem]
9 |
10 | init(controller: ViewController) {
11 | self.controller = controller
12 | var menu: [PeekControllerMenuItem] = []
13 | for item in controller.previewActionItems {
14 | menu.append(PeekControllerMenuItem(title: item.title, color: .accent, action: { [weak controller] in
15 | if let controller = controller, let item = item as? UIPreviewAction {
16 | item.handler(item, controller)
17 | }
18 | }))
19 | }
20 | self.menu = menu
21 | }
22 |
23 | func presentation() -> PeekControllerContentPresentation {
24 | return .contained
25 | }
26 |
27 | func menuActivation() -> PeerkControllerMenuActivation {
28 | return .drag
29 | }
30 |
31 | func menuItems() -> [PeekControllerMenuItem] {
32 | return self.menu
33 | }
34 |
35 | func node() -> PeekControllerContentNode & ASDisplayNode {
36 | return ViewControllerPeekContentNode(controller: self.controller)
37 | }
38 |
39 | func topAccessoryNode() -> ASDisplayNode? {
40 | return nil
41 | }
42 |
43 | func isEqual(to: PeekControllerContent) -> Bool {
44 | if let to = to as? ViewControllerPeekContent {
45 | return self.controller === to.controller
46 | } else {
47 | return false
48 | }
49 | }
50 | }
51 |
52 | private final class ViewControllerPeekContentNode: ASDisplayNode, PeekControllerContentNode {
53 | private let controller: ViewController
54 | private var hasValidLayout = false
55 |
56 | init(controller: ViewController) {
57 | self.controller = controller
58 |
59 | super.init()
60 | }
61 |
62 | func updateLayout(size: CGSize, transition: ContainedViewLayoutTransition) -> CGSize {
63 | if !self.hasValidLayout {
64 | self.hasValidLayout = true
65 | self.controller.view.frame = CGRect(origin: CGPoint(), size: size)
66 | self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: .immediate)
67 | self.controller.setIgnoreAppearanceMethodInvocations(true)
68 | self.view.addSubview(self.controller.view)
69 | self.controller.setIgnoreAppearanceMethodInvocations(false)
70 | self.controller.viewWillAppear(false)
71 | self.controller.viewDidAppear(false)
72 | } else {
73 | self.controller.containerLayoutUpdated(ContainerViewLayout(size: size, metrics: LayoutMetrics(), intrinsicInsets: UIEdgeInsets(), safeInsets: UIEdgeInsets(), statusBarHeight: nil, inputHeight: nil, standardInputHeight: 216.0, inputHeightIsInteractivellyChanging: false, inVoiceOver: false), transition: transition)
74 | }
75 |
76 | return size
77 | }
78 |
79 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
80 | if self.bounds.contains(point) {
81 | return self.view
82 | }
83 | return nil
84 | }
85 | }
86 |
87 | @available(iOSApplicationExtension 9.0, *)
88 | final class SimulatedViewControllerPreviewing: NSObject, UIViewControllerPreviewing {
89 | weak var delegateImpl: UIViewControllerPreviewingDelegate?
90 | var delegate: UIViewControllerPreviewingDelegate {
91 | return self.delegateImpl!
92 | }
93 | let recognizer: PeekControllerGestureRecognizer
94 | var previewingGestureRecognizerForFailureRelationship: UIGestureRecognizer {
95 | return self.recognizer
96 | }
97 | let sourceView: UIView
98 | let node: ASDisplayNode
99 |
100 | var sourceRect: CGRect = CGRect()
101 |
102 | init(theme: PeekControllerTheme, delegate: UIViewControllerPreviewingDelegate, sourceView: UIView, node: ASDisplayNode, present: @escaping (ViewController, Any?) -> Void) {
103 | self.delegateImpl = delegate
104 | self.sourceView = sourceView
105 | self.node = node
106 | var contentAtPointImpl: ((CGPoint) -> Signal<(ASDisplayNode, PeekControllerContent)?, NoError>?)?
107 | self.recognizer = PeekControllerGestureRecognizer(contentAtPoint: { point in
108 | return contentAtPointImpl?(point)
109 | }, present: { content, sourceNode in
110 | let controller = PeekController(theme: theme, content: content, sourceNode: {
111 | return sourceNode
112 | })
113 | present(controller, nil)
114 | return controller
115 | })
116 |
117 | node.view.addGestureRecognizer(self.recognizer)
118 |
119 | super.init()
120 |
121 | contentAtPointImpl = { [weak self] point in
122 | if let strongSelf = self, let delegate = strongSelf.delegateImpl {
123 | if let controller = delegate.previewingContext(strongSelf, viewControllerForLocation: point) as? ViewController {
124 | return .single((strongSelf.node, ViewControllerPeekContent(controller: controller)))
125 | }
126 | }
127 | return nil
128 | }
129 | }
130 | }
131 |
--------------------------------------------------------------------------------
/Display/ViewControllerTracingNode.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import AsyncDisplayKit
3 |
4 | private final class ViewControllerTracingNodeView: UITracingLayerView {
5 | private var inHitTest = false
6 | var hitTestImpl: ((CGPoint, UIEvent?) -> UIView?)?
7 |
8 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
9 | if self.inHitTest {
10 | return super.hitTest(point, with: event)
11 | } else {
12 | self.inHitTest = true
13 | let result = self.hitTestImpl?(point, event)
14 | self.inHitTest = false
15 | return result
16 | }
17 | }
18 | }
19 |
20 | open class ViewControllerTracingNode: ASDisplayNode {
21 | override public init() {
22 | super.init()
23 |
24 | self.setViewBlock({
25 | return ViewControllerTracingNodeView()
26 | })
27 | }
28 |
29 | override open func didLoad() {
30 | super.didLoad()
31 |
32 | (self.view as! ViewControllerTracingNodeView).hitTestImpl = { [weak self] point, event in
33 | return self?.hitTest(point, with: event)
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/Display/WindowCoveringView.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | open class WindowCoveringView: UIView {
5 | open func updateLayout(_ size: CGSize) {
6 | }
7 | }
8 |
--------------------------------------------------------------------------------
/Display/WindowInputAccessoryHeightProvider.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 | import UIKit
3 |
4 | public protocol WindowInputAccessoryHeightProvider: class {
5 | func getWindowInputAccessoryHeight() -> CGFloat
6 | }
7 |
--------------------------------------------------------------------------------
/Display/WindowPanRecognizer.swift:
--------------------------------------------------------------------------------
1 | import Foundation
2 |
3 | public final class WindowPanRecognizer: UIGestureRecognizer {
4 | public var began: ((CGPoint) -> Void)?
5 | public var moved: ((CGPoint) -> Void)?
6 | public var ended: ((CGPoint, CGPoint?) -> Void)?
7 |
8 | private var previousPoints: [(CGPoint, Double)] = []
9 |
10 | override public func reset() {
11 | super.reset()
12 |
13 | self.previousPoints.removeAll()
14 | }
15 |
16 | private func addPoint(_ point: CGPoint) {
17 | self.previousPoints.append((point, CACurrentMediaTime()))
18 | if self.previousPoints.count > 6 {
19 | self.previousPoints.removeFirst()
20 | }
21 | }
22 |
23 | private func estimateVerticalVelocity() -> CGFloat {
24 | let timestamp = CACurrentMediaTime()
25 | var sum: CGFloat = 0.0
26 | var count = 0
27 | if self.previousPoints.count > 1 {
28 | for i in 1 ..< self.previousPoints.count {
29 | if self.previousPoints[i].1 >= timestamp - 0.1 {
30 | sum += (self.previousPoints[i].0.y - self.previousPoints[i - 1].0.y) / CGFloat(self.previousPoints[i].1 - self.previousPoints[i - 1].1)
31 | count += 1
32 | }
33 | }
34 | }
35 |
36 | if count != 0 {
37 | return sum / CGFloat(count * 5)
38 | } else {
39 | return 0.0
40 | }
41 | }
42 |
43 | override public func touchesBegan(_ touches: Set, with event: UIEvent) {
44 | super.touchesBegan(touches, with: event)
45 |
46 | if let touch = touches.first {
47 | let location = touch.location(in: self.view)
48 | self.addPoint(location)
49 | self.began?(location)
50 | }
51 | }
52 |
53 | override public func touchesMoved(_ touches: Set, with event: UIEvent) {
54 | super.touchesMoved(touches, with: event)
55 |
56 | if let touch = touches.first {
57 | let location = touch.location(in: self.view)
58 | self.addPoint(location)
59 | self.moved?(location)
60 | }
61 | }
62 |
63 | override public func touchesEnded(_ touches: Set, with event: UIEvent) {
64 | super.touchesEnded(touches, with: event)
65 |
66 | if let touch = touches.first {
67 | let location = touch.location(in: self.view)
68 | self.addPoint(location)
69 | self.ended?(location, CGPoint(x: 0.0, y: self.estimateVerticalVelocity()))
70 | }
71 | }
72 |
73 | override public func touchesCancelled(_ touches: Set, with event: UIEvent) {
74 | super.touchesCancelled(touches, with: event)
75 |
76 | if let touch = touches.first {
77 | self.ended?(touch.location(in: self.view), nil)
78 | }
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/DisplayTests/DisplayTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // DisplayTests.swift
3 | // DisplayTests
4 | //
5 | // Created by Peter on 29/07/15.
6 | // Copyright © 2015 Telegram. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import Display
11 |
12 | class DisplayTests: XCTestCase {
13 |
14 | override func setUp() {
15 | super.setUp()
16 | // Put setup code here. This method is called before the invocation of each test method in the class.
17 | }
18 |
19 | override func tearDown() {
20 | // Put teardown code here. This method is called after the invocation of each test method in the class.
21 | super.tearDown()
22 | }
23 |
24 | func testExample() {
25 | // This is an example of a functional test case.
26 | // Use XCTAssert and related functions to verify your tests produce the correct results.
27 | }
28 |
29 | func testPerformanceExample() {
30 | // This is an example of a performance test case.
31 | self.measure {
32 | // Put the code you want to measure the time of here.
33 | }
34 | }
35 |
36 | }
37 |
--------------------------------------------------------------------------------
/DisplayTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | BNDL
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleSignature
20 | ????
21 | CFBundleVersion
22 | 1
23 |
24 |
25 |
--------------------------------------------------------------------------------