Skip to content

Commit 833728b

Browse files
committed
Add AppearanceActionModifier implementation
1 parent 55314b5 commit 833728b

File tree

2 files changed

+158
-0
lines changed

2 files changed

+158
-0
lines changed

Sources/OpenSwiftUI/Core/View/View/ViewInputs.swift

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ public struct _ViewInputs {
116116
return newInputs
117117
}
118118

119+
// MARK: - base.phase
120+
@inline(__always)
121+
var phase: Attribute<_GraphInputs.Phase> {
122+
base.phase
123+
}
124+
119125
// MARK: - base.changedDebugProperties
120126

121127
@inline(__always)
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
//
2+
// AppearanceActionModifier.swift
3+
// OpenSwiftUI
4+
//
5+
// Audited for RELEASE_2021
6+
// Status: WIP
7+
// ID: 8817D3B1C81ADA2B53E3500D727F785A
8+
9+
// MARK: - AppearanceActionModifier
10+
11+
internal import OpenGraphShims
12+
13+
/// A modifier that triggers actions when its view appears and disappears.
14+
@frozen
15+
public struct _AppearanceActionModifier: PrimitiveViewModifier {
16+
public var appear: (() -> Void)?
17+
public var disappear: (() -> Void)?
18+
19+
@inlinable
20+
public init(appear: (() -> Void)? = nil, disappear: (() -> Void)? = nil) {
21+
self.appear = appear
22+
self.disappear = disappear
23+
}
24+
25+
public static func _makeView(
26+
modifier: _GraphValue<Self>,
27+
inputs: _ViewInputs,
28+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
29+
) -> _ViewOutputs {
30+
let effect = AppearanceEffect(modifier: modifier.value, phase: inputs.phase)
31+
let attribute = Attribute(effect)
32+
attribute.flags = [.active, .removable]
33+
return body(_Graph(), inputs)
34+
}
35+
36+
public static func _makeViewList(
37+
modifier: _GraphValue<Self>,
38+
inputs: _ViewListInputs,
39+
body: @escaping (_Graph, _ViewListInputs) -> _ViewListOutputs
40+
) -> _ViewListOutputs {
41+
fatalError("TODO")
42+
}
43+
}
44+
45+
// MARK: - AppearanceEffect
46+
47+
private struct AppearanceEffect {
48+
@Attribute
49+
var modifier: _AppearanceActionModifier
50+
@Attribute
51+
var phase: _GraphInputs.Phase
52+
var lastValue: _AppearanceActionModifier?
53+
var isVisible: Bool = false
54+
var resetSeed: UInt32 = 0
55+
var node: AnyOptionalAttribute = AnyOptionalAttribute()
56+
57+
mutating func appeared() {
58+
guard !isVisible else { return }
59+
defer { isVisible = true }
60+
guard let lastValue,
61+
let appear = lastValue.appear
62+
else { return }
63+
Update.enqueueAction(appear)
64+
}
65+
66+
mutating func disappeared() {
67+
guard isVisible else { return }
68+
defer { isVisible = false }
69+
guard let lastValue,
70+
let disappear = lastValue.disappear
71+
else { return }
72+
Update.enqueueAction(disappear)
73+
}
74+
}
75+
76+
// MARK: AppearanceEffect + StatefulRule
77+
78+
extension AppearanceEffect: StatefulRule {
79+
typealias Value = Void
80+
81+
mutating func updateValue() {
82+
if node.attribute == nil {
83+
node.attribute = .current
84+
}
85+
86+
if phase.seed != resetSeed {
87+
resetSeed = phase.seed
88+
disappeared()
89+
}
90+
lastValue = modifier
91+
appeared()
92+
}
93+
}
94+
95+
// MARK: AppearanceEffect + RemovableAttribute
96+
97+
extension AppearanceEffect: RemovableAttribute {
98+
static func willRemove(attribute: OGAttribute) {
99+
let appearancePointer = UnsafeMutableRawPointer(mutating: attribute.info.body)
100+
.assumingMemoryBound(to: AppearanceEffect.self)
101+
guard appearancePointer.pointee.lastValue != nil else {
102+
return
103+
}
104+
appearancePointer.pointee.disappeared()
105+
}
106+
107+
static func didReinsert(attribute: OGAttribute) {
108+
let appearancePointer = UnsafeMutableRawPointer(mutating: attribute.info.body)
109+
.assumingMemoryBound(to: AppearanceEffect.self)
110+
guard let nodeAttribute = appearancePointer.pointee.node.attribute else {
111+
return
112+
}
113+
nodeAttribute.invalidateValue()
114+
nodeAttribute.graph.graphHost().graphInvalidation(from: nil)
115+
}
116+
}
117+
118+
// MARK: - View Extension
119+
120+
extension View {
121+
/// Adds an action to perform before this view appears.
122+
///
123+
/// The exact moment that OpenSwiftUI calls this method
124+
/// depends on the specific view type that you apply it to, but
125+
/// the `action` closure completes before the first
126+
/// rendered frame appears.
127+
///
128+
/// - Parameter action: The action to perform. If `action` is `nil`, the
129+
/// call has no effect.
130+
///
131+
/// - Returns: A view that triggers `action` before it appears.
132+
@inlinable
133+
public func onAppear(perform action: (() -> Void)? = nil) -> some View {
134+
modifier(_AppearanceActionModifier(appear: action, disappear: nil))
135+
}
136+
137+
/// Adds an action to perform after this view disappears.
138+
///
139+
/// The exact moment that OpenSwiftUI calls this method
140+
/// depends on the specific view type that you apply it to, but
141+
/// the `action` closure doesn't execute until the view
142+
/// disappears from the interface.
143+
///
144+
/// - Parameter action: The action to perform. If `action` is `nil`, the
145+
/// call has no effect.
146+
///
147+
/// - Returns: A view that triggers `action` after it disappears.
148+
@inlinable
149+
public func onDisappear(perform action: (() -> Void)? = nil) -> some View {
150+
modifier(_AppearanceActionModifier(appear: nil, disappear: action))
151+
}
152+
}

0 commit comments

Comments
 (0)