Skip to content

Commit 63b57e0

Browse files
authored
Improve OnAppearNode (#100)
1 parent 4f0633e commit 63b57e0

1 file changed

Lines changed: 86 additions & 35 deletions

File tree

Sources/Components/Compositions/OnAppearNode.swift

Lines changed: 86 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
21
/// A container display node that handles itself appears on the device screen.
32
/// Underlying, using CATiledLayer to hook the signal of appearing.
43
public final class OnAppearNode<Content: ASDisplayNode>: NamedDisplayCellNodeBase {
54

5+
private struct State: Equatable {
6+
var flagTopLeft = false
7+
var flagBottomRight = false
8+
9+
var hasAppeared = false
10+
11+
}
12+
613
public struct Handlers {
714
/// Runs only once on appear.
815
public var onAppear: () -> Void = {}
@@ -16,13 +23,23 @@ public final class OnAppearNode<Content: ASDisplayNode>: NamedDisplayCellNodeBas
1623
public let content: Content
1724
private var debug: Bool = false
1825

19-
private var hasAppeared = false
2026

2127
#if DEBUG
2228
private lazy var debuggingNode = DebuggingNode()
2329
#endif
2430

25-
private let tiledLayerNode = ViewNode<TiledLayerView> { .init() }
31+
private var topLeftTiledLayerNode: ViewNode<TiledLayerView>?
32+
private var bottomRightTiledLayerNode: ViewNode<TiledLayerView>?
33+
34+
@MainActor
35+
private var state = State() {
36+
didSet {
37+
guard oldValue != state else {
38+
return
39+
}
40+
update(state: state, old: oldValue)
41+
}
42+
}
2643

2744
/// Creates an instance
2845
/// - Parameters:
@@ -46,50 +63,82 @@ public final class OnAppearNode<Content: ASDisplayNode>: NamedDisplayCellNodeBas
4663

4764
public override func didLoad() {
4865
super.didLoad()
66+
67+
reset()
4968

50-
tiledLayerNode.wrappedView.setOnDraw { [weak self] in
51-
guard let self = self else { return }
52-
guard self.hasAppeared == false else { return }
53-
self.hasAppeared = true
54-
self.handlers.onAppear()
69+
}
5570

56-
#if DEBUG
57-
if self.debug {
58-
self.debuggingNode.setText("Appeared")
59-
}
60-
#endif
71+
@MainActor
72+
private func reset() {
73+
74+
state = .init()
75+
76+
bottomRightTiledLayerNode = .init(wrappedView: { .init() })
77+
topLeftTiledLayerNode = .init(wrappedView: { .init() })
78+
79+
setNeedsLayout()
80+
81+
bottomRightTiledLayerNode!.wrappedView.setOnDraw { [weak self] in
82+
guard let self else { return }
83+
guard self.state.flagBottomRight == false else { return }
84+
self.state.flagBottomRight = true
85+
}
86+
87+
topLeftTiledLayerNode!.wrappedView.setOnDraw { [weak self] in
88+
guard let self else { return }
89+
guard self.state.flagTopLeft == false else { return }
90+
self.state.flagTopLeft = true
91+
}
92+
93+
}
94+
95+
@MainActor
96+
private func update(state: State, old: State?) {
97+
98+
#if DEBUG
99+
if self.debug {
100+
self.debuggingNode.setText("[\(state.flagTopLeft), \(state.flagBottomRight)]")
101+
}
102+
#endif
103+
104+
if state.hasAppeared == false, state.flagBottomRight, state.flagTopLeft {
105+
self.state.hasAppeared = true
106+
handlers.onAppear()
61107
}
62108
}
63109

64110
public final override func layoutSpecThatFits(_ constrainedSize: ASSizeRange) -> ASLayoutSpec {
65111

66112
#if DEBUG
67-
return LayoutSpec {
68-
if debug {
69-
ZStackLayout {
70-
content
71-
.overlay(debuggingNode)
72-
tiledLayerNode.preferredSize(.init(width: 1, height: 1))
73-
.relativePosition(horizontal: .start, vertical: .start)
74-
}
75-
} else {
76-
ZStackLayout {
77-
content
78-
tiledLayerNode.preferredSize(.init(width: 1, height: 1))
79-
.relativePosition(horizontal: .start, vertical: .start)
80-
}
81-
}
82-
}
83-
#else
84-
return LayoutSpec {
85-
ZStackLayout {
113+
if debug {
114+
return LayoutSpec {
86115
content
87-
tiledLayerNode.preferredSize(.init(width: 1, height: 1))
88-
.relativePosition(horizontal: .start, vertical: .start)
116+
.overlay(
117+
ZStackLayout {
118+
debuggingNode
119+
topLeftTiledLayerNode.preferredSize(.init(width: 1, height: 1))
120+
.relativePosition(horizontal: .start, vertical: .start)
121+
bottomRightTiledLayerNode.preferredSize(.init(width: 1, height: 1))
122+
.relativePosition(horizontal: .end, vertical: .end)
123+
}
124+
)
89125
}
90126
}
91127
#endif
92128

129+
return LayoutSpec {
130+
content
131+
.overlay(
132+
ZStackLayout {
133+
debuggingNode
134+
topLeftTiledLayerNode.preferredSize(.init(width: 1, height: 1))
135+
.relativePosition(horizontal: .start, vertical: .start)
136+
bottomRightTiledLayerNode.preferredSize(.init(width: 1, height: 1))
137+
.relativePosition(horizontal: .end, vertical: .end)
138+
}
139+
)
140+
}
141+
93142
}
94143

95144
@discardableResult
@@ -145,6 +194,8 @@ private final class TiledLayerView: UIView {
145194

146195
isOpaque = false
147196
backgroundColor = .clear
197+
198+
(layer as! TiledLayer).drawsAsynchronously = true
148199
}
149200

150201
required init?(
@@ -164,7 +215,7 @@ private final class TiledLayerView: UIView {
164215
return view
165216
}
166217

167-
func setOnDraw(_ closure: @escaping () -> Void) {
218+
func setOnDraw(_ closure: @escaping @MainActor () -> Void) {
168219
(layer as! TiledLayer).onDraw = closure
169220
}
170221
}

0 commit comments

Comments
 (0)