This seems like a SwiftUI bug, and I have filed a bug report with Feedback Assistant.
The issue description is in below’s code Text View’s string(there is only one Text View), please read it and try to run the code.
I have also posted another question on stackoverflow before, which seems like a SwiftUI bug too, and that may related to this issue: SwiftUI .keyboardShortcut unexpectedly captured value inside Button's action
Here is the minimal code for reproducing the issue, I run on a new mac app project, macOS version 15.1 Beta (24B5055e), Xcode Version 16.1 beta (16B5001e):
import SwiftUI
@main struct TestDotKeyboardShortcutBugApp: App {
@Environment(\.openWindow) var openWindow
let windowId = "ContentView"
var body: some Scene {
WindowGroup {
Button("Open Window") {
openWindow(id: windowId, value: "")
// openWindow(id: windowId)
}
}
WindowGroup(id: windowId, for: String.self) { _ in
// WindowGroup(id: windowId) {
ContentView()
}
.restorationBehavior(.disabled)
}
}
let key = "Key"
struct ContentView: View {
@AppStorage(key) var appStorageValue: Bool = false
var userDefaultValue: Bool {
UserDefaults.standard.bool(forKey: key)
}
var body: some View {
ScrollViewReader { proxy in
Text("The bug:\nafter switching the value of below Toggle one time, and then press 'a', different values will be printed on the console,\nbut if you click the 'print value' button, same values will be printed.\nHowever, comment out line 9 & 13, then uncomment line 10 & 14, the issue will be gone.")
.padding()
Button("print value") {
print("appStorageValue: \(appStorageValue), userDefaultValue: \(userDefaultValue)")
}
.keyboardShortcut("a", modifiers: [])
Toggle(isOn: $appStorageValue) {
Text("toggle appStorageValue")
}
}
}
}
Is this a SwiftUI bug? If it is, any workaround?
View identity is an important concept, which I don't fully understand at times. There are various articles on this, including one at: SwiftUI id.
I sometimes think of it as refreshing the View when nothing else
triggers a change. The appStorageValue
changes in the
Toggle
, but the View already knows about this, and the
side effect print
does not contribute to the view.
Example code that works for me on MacOS 15.3.
@main struct TestDotKeyboardShortcutBugApp: App {
@Environment(\.openWindow) var openWindow
let windowId = "ContentView"
var body: some Scene {
WindowGroup {
Button("Open Window") {
openWindow(id: windowId, value: "")
}
}
WindowGroup(id: windowId, for: String.self) { _ in
ContentView()
}
.restorationBehavior(.disabled)
}
}
struct ContentView: View {
@State private var id = UUID()
@AppStorage("Key") var appStorageValue: Bool = false
var userDefaultValue: Bool {
UserDefaults.standard.bool(forKey: "Key")
}
var body: some View {
Group {
Text("The bug:\nafter switching the value of below Toggle one time, and then press 'a', different values will be printed on the console,\nbut if you click the 'print value' button, same values will be printed.\nHowever, comment out line 9 & 13, then uncomment line 10 & 14, the issue will be gone.")
.padding()
Button("print value") {
print("appStorageValue: \(appStorageValue), userDefaultValue: \(userDefaultValue)")
}
.keyboardShortcut("a", modifiers: [])
Toggle(isOn: $appStorageValue) {
Text("toggle appStorageValue")
}
.onChange(of: appStorageValue) {
id = UUID()
}
}
.id(id)
}
}