Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// PreferenceActionModifierExample.swift
// Shared

#if OPENSWIFTUI
import OpenSwiftUI
#else
import SwiftUI
#endif

struct MyKey: PreferenceKey {
static let defaultValue = ""

static func reduce(value: inout String, nextValue: () -> String) {
value = nextValue()
}
}

struct PreferenceActionModifierExample: View {
var body: some View {
VStack {
Color.red
.preference(key: MyKey.self, value: "changed")
}
.onPreferenceChange(MyKey.self) {
print("onPreferenceChange: \($0)")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ extension _PreferenceWritingModifier: _SceneModifier {
inputs: _SceneInputs,
body: @escaping (_Graph, _SceneInputs) -> _SceneOutputs
) -> _SceneOutputs {
var inputs = inputs
inputs.preferences.remove(Key.self)
var outputs = body(_Graph(), inputs)
var newInputs = inputs
newInputs.preferences.remove(Key.self)
var outputs = body(_Graph(), newInputs)
outputs.preferences
.makePreferenceWriter(
inputs: inputs.preferences,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
//
// PreferenceActionModifier.swift
// OpenSwiftUICore
//
// Audited for 6.5.4
// Status: Complete
// ID: 264234112339315C9A664F0B7F8B50C1 (SwiftUICore)

import OpenAttributeGraphShims

extension View {
/// Adds an action to perform when the specified preference key's value
/// changes.
///
/// - Parameters:
/// - key: The key to monitor for value changes.
/// - action: The action to perform when the value for `key` changes. The
/// `action` closure passes the new value as its parameter.
///
/// - Returns: A view that triggers `action` when the value for `key`
/// changes.
@inlinable
nonisolated public func onPreferenceChange<K>(

@augmentcode augmentcode Bot Jul 2, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

onPreferenceChange is being added as a public API, but it doesn’t appear to carry the @available(OpenSwiftUI_v1_0, *) availability annotation pattern used by other preference APIs (e.g. preference, transformPreference). Consider aligning the availability annotations to keep the public surface/versioning consistent.

Severity: low

Fix This in Augment

🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.

_ key: K.Type = K.self,
perform action: @escaping (K.Value) -> Void
) -> some View where K: PreferenceKey, K.Value: Equatable {
return modifier(_PreferenceActionModifier<K>(action: action))
}
}

// MARK: - PreferenceActionModifier

@frozen
public struct _PreferenceActionModifier<K>: MultiViewModifier, PrimitiveViewModifier where K: PreferenceKey, K.Value: Equatable {
public var action: (_ value: K.Value) -> Void

@inlinable
public init(action: @escaping (_ value: K.Value) -> Void) {
self.action = action
}

nonisolated public static func _makeView(
modifier: _GraphValue<Self>,
inputs: _ViewInputs,
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
) -> _ViewOutputs {
var inputs = inputs
inputs.preferences.add(K.self)
let outputs = body(_Graph(), inputs)
guard let keyValue = outputs[K.self] else {
return outputs
}
let binder = Attribute(PreferenceBinder<K>(
modifier: modifier.value,
keyValue: keyValue,
phase: inputs.viewPhase,
lastResetSeed: .zero,
lastValue: nil
))
binder.flags = .transactional
return outputs
}
}

@available(*, unavailable)
extension _PreferenceActionModifier: Sendable {}

// MARK: - PreferenceBinder

private struct PreferenceBinder<K>: StatefulRule, AsyncAttribute where K: PreferenceKey, K.Value: Equatable {
@Attribute var modifier: _PreferenceActionModifier<K>
@Attribute var keyValue: K.Value
@Attribute var phase: _GraphInputs.Phase
var cycleDetector: UpdateCycleDetector
var lastResetSeed: UInt32
var lastValue: K.Value?

init(
modifier: Attribute<_PreferenceActionModifier<K>>,
keyValue: Attribute<K.Value>,
phase: Attribute<_GraphInputs.Phase>,
cycleDetector: UpdateCycleDetector = .init(),
lastResetSeed: UInt32,
lastValue: K.Value?
) {
self._modifier = modifier
self._keyValue = keyValue
self._phase = phase
self.cycleDetector = cycleDetector
self.lastResetSeed = lastResetSeed
self.lastValue = lastValue
}

typealias Value = Void

mutating func updateValue() {
if lastResetSeed != phase.resetSeed {
lastResetSeed = phase.resetSeed
cycleDetector.reset()
lastValue = nil
}
let (newValue, changed) = $keyValue.changedValue()
guard changed || (lastValue == nil && _SemanticFeature_v6_1.isEnabled) else {
return
}
guard lastValue != newValue else {
return
}
lastValue = newValue

guard cycleDetector.dispatch(
label: "Bound preference \(K.self)",
isDebug: true
) else {
return
}
let action = Graph.withoutUpdate {
modifier.action
}
Update.enqueueAction(reason: nil) {
action(newValue)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public struct _PreferenceWritingModifier<Key>: ViewModifier, MultiViewModifier,
inputs: _ViewInputs,
body: @escaping (_Graph, _ViewInputs) -> _ViewOutputs
) -> _ViewOutputs {
var inputs = inputs
inputs.preferences.remove(Key.self)
var outputs = body(_Graph(), inputs)
var newInputs = inputs
newInputs.preferences.remove(Key.self)
var outputs = body(_Graph(), newInputs)
outputs.preferences
.makePreferenceWriter(
inputs: inputs.preferences,
Expand Down