Skip to content

Commit 9bbefad

Browse files
committed
Add View.onPreferenceChange
1 parent 95e957c commit 9bbefad

4 files changed

Lines changed: 159 additions & 6 deletions

File tree

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
//
2+
// PreferenceActionModifierExample.swift
3+
// Shared
4+
5+
#if OPENSWIFTUI
6+
import OpenSwiftUI
7+
#else
8+
import SwiftUI
9+
#endif
10+
11+
struct MyKey: PreferenceKey {
12+
static let defaultValue = ""
13+
14+
static func reduce(value: inout String, nextValue: () -> String) {
15+
value = nextValue()
16+
}
17+
}
18+
19+
struct PreferenceActionModifierExample: View {
20+
var body: some View {
21+
VStack {
22+
Color.red
23+
.preference(key: MyKey.self, value: "changed")
24+
}
25+
.onPreferenceChange(MyKey.self) {
26+
print("onPreferenceChange: \($0)")
27+
}
28+
}
29+
}

Sources/OpenSwiftUI/Modifier/SceneModifier/PreferenceSceneModifier.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,9 @@ extension _PreferenceWritingModifier: _SceneModifier {
1616
inputs: _SceneInputs,
1717
body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs
1818
) -> _SceneOutputs {
19-
var inputs = inputs
20-
inputs.preferences.remove(Key.self)
21-
var outputs = body(_Graph(), inputs)
19+
var newInputs = inputs
20+
newInputs.preferences.remove(Key.self)
21+
var outputs = body(_Graph(), newInputs)
2222
outputs.preferences
2323
.makePreferenceWriter(
2424
inputs: inputs.preferences,
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
//
2+
// PreferenceActionModifier.swift
3+
// OpenSwiftUICore
4+
//
5+
// Audited for 6.5.4
6+
// Status: Complete
7+
// ID: 264234112339315C9A664F0B7F8B50C1 (SwiftUICore)
8+
9+
import OpenAttributeGraphShims
10+
11+
extension View {
12+
/// Adds an action to perform when the specified preference key's value
13+
/// changes.
14+
///
15+
/// - Parameters:
16+
/// - key: The key to monitor for value changes.
17+
/// - action: The action to perform when the value for `key` changes. The
18+
/// `action` closure passes the new value as its parameter.
19+
///
20+
/// - Returns: A view that triggers `action` when the value for `key`
21+
/// changes.
22+
@inlinable
23+
nonisolated public func onPreferenceChange<K>(
24+
_ key: K.Type = K.self,
25+
perform action: @escaping (K.Value) -> Void
26+
) -> some View where K: PreferenceKey, K.Value: Equatable {
27+
return modifier(_PreferenceActionModifier<K>(action: action))
28+
}
29+
}
30+
31+
// MARK: - PreferenceActionModifier
32+
33+
@frozen
34+
public struct _PreferenceActionModifier<K>: MultiViewModifier, PrimitiveViewModifier where K: PreferenceKey, K.Value: Equatable {
35+
public var action: (_ value: K.Value) -> Void
36+
37+
@inlinable
38+
public init(action: @escaping (_ value: K.Value) -> Void) {
39+
self.action = action
40+
}
41+
42+
nonisolated public static func _makeView(
43+
modifier: _GraphValue<Self>,
44+
inputs: _ViewInputs,
45+
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
46+
) -> _ViewOutputs {
47+
var inputs = inputs
48+
inputs.preferences.add(K.self)
49+
let outputs = body(_Graph(), inputs)
50+
guard let keyValue = outputs[K.self] else {
51+
return outputs
52+
}
53+
let binder = Attribute(PreferenceBinder<K>(
54+
modifier: modifier.value,
55+
keyValue: keyValue,
56+
phase: inputs.viewPhase,
57+
lastResetSeed: .zero,
58+
lastValue: nil
59+
))
60+
binder.flags = .transactional
61+
return outputs
62+
}
63+
}
64+
65+
@available(*, unavailable)
66+
extension _PreferenceActionModifier: Sendable {}
67+
68+
// MARK: - PreferenceBinder
69+
70+
private struct PreferenceBinder<K>: StatefulRule, AsyncAttribute where K: PreferenceKey, K.Value: Equatable {
71+
@Attribute var modifier: _PreferenceActionModifier<K>
72+
@Attribute var keyValue: K.Value
73+
@Attribute var phase: _GraphInputs.Phase
74+
var cycleDetector: UpdateCycleDetector
75+
var lastResetSeed: UInt32
76+
var lastValue: K.Value?
77+
78+
init(
79+
modifier: Attribute<_PreferenceActionModifier<K>>,
80+
keyValue: Attribute<K.Value>,
81+
phase: Attribute<_GraphInputs.Phase>,
82+
cycleDetector: UpdateCycleDetector = .init(),
83+
lastResetSeed: UInt32,
84+
lastValue: K.Value?
85+
) {
86+
self._modifier = modifier
87+
self._keyValue = keyValue
88+
self._phase = phase
89+
self.cycleDetector = cycleDetector
90+
self.lastResetSeed = lastResetSeed
91+
self.lastValue = lastValue
92+
}
93+
94+
typealias Value = Void
95+
96+
mutating func updateValue() {
97+
if lastResetSeed != phase.resetSeed {
98+
lastResetSeed = phase.resetSeed
99+
cycleDetector.reset()
100+
lastValue = nil
101+
}
102+
let (newValue, changed) = $keyValue.changedValue()
103+
guard changed || (lastValue == nil && _SemanticFeature_v6_1.isEnabled) else {
104+
return
105+
}
106+
guard lastValue != newValue else {
107+
return
108+
}
109+
lastValue = newValue
110+
111+
guard cycleDetector.dispatch(
112+
label: "Bound preference \(K.self)",
113+
isDebug: true
114+
) else {
115+
return
116+
}
117+
let action = Graph.withoutUpdate {
118+
modifier.action
119+
}
120+
Update.enqueueAction(reason: nil) {
121+
action(newValue)
122+
}
123+
}
124+
}

Sources/OpenSwiftUICore/Data/Preference/PreferenceWritingModifier.swift

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ public struct _PreferenceWritingModifier<Key>: ViewModifier, MultiViewModifier,
3030
inputs: _ViewInputs,
3131
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
3232
) -> _ViewOutputs {
33-
var inputs = inputs
34-
inputs.preferences.remove(Key.self)
35-
var outputs = body(_Graph(), inputs)
33+
var newInputs = inputs
34+
newInputs.preferences.remove(Key.self)
35+
var outputs = body(_Graph(), newInputs)
3636
outputs.preferences
3737
.makePreferenceWriter(
3838
inputs: inputs.preferences,

0 commit comments

Comments
 (0)