1-
21/// A container display node that handles itself appears on the device screen.
32/// Underlying, using CATiledLayer to hook the signal of appearing.
43public 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