You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

SwiftUI中.keyboardShortcut与@AppStorage联动异常问题排查及方案咨询

这是SwiftUI的Bug吗?有哪些可行的解决办法?

问题背景

在macOS 15.1 Beta(24B5055e)、Xcode 16.1 Beta(16B5001e)的全新Mac App项目中,出现了以下异常行为:通过键盘快捷键触发按钮动作时,@AppStorage的值与UserDefaults中的实际值不一致,但直接点击按钮则能拿到一致的值。该问题仅在使用带关联值的WindowGroup时出现,已通过Feedback Assistant提交Bug报告。

问题复现步骤

  1. 运行下方代码,点击"Open Window"打开目标窗口
  2. 切换一次Toggle的状态
  3. 按下键盘快捷键a触发按钮动作,控制台会打印出不一致的appStorageValue和userDefaultValue
  4. 直接点击"print value"按钮,控制台打印的两个值一致
  5. 若注释掉带关联值的WindowGroup代码,改用无参数的WindowGroup,问题消失

复现代码

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")
            }
        }
    }
}

结论与解决办法

这确实是SwiftUI的Bug,根源在于使用带关联值的WindowGroup时,键盘快捷键触发的按钮动作会捕获旧的@AppStorage状态,无法同步UserDefaults的最新值,而直接点击按钮则能正常获取最新状态。

可行的解决办法:

  • 方案1:改用无关联值的WindowGroup
    注释掉带泛型参数的WindowGroup(id:for:)代码,改用无参数的WindowGroup(id:),这是最直接规避问题的方式,因为问题仅在带关联值的窗口场景下触发。
  • 方案2:直接读取UserDefaults
    在按钮动作中不依赖@AppStorage包装的属性,直接从UserDefaults读取最新值,比如将打印代码改为:
    print("appStorageValue: \(UserDefaults.standard.bool(forKey: key)), userDefaultValue: \(userDefaultValue)")
    
  • 方案3:使用Command绑定快捷键
    放弃按钮的.keyboardShortcut修饰符,改用CommandGroup将快捷键绑定到命令上,绕过状态捕获的问题:
    // 在App结构体的Scene中添加
    CommandGroup(after: .help) {
        Button("print value") {
            print("appStorageValue: \(UserDefaults.standard.bool(forKey: key)), userDefaultValue: \(userDefaultValue)")
        }
        .keyboardShortcut("a", modifiers: [])
    }
    
  • 方案4:手动同步状态
    在Toggle的动作中手动触发视图更新,确保@AppStorage状态及时同步:
    Toggle(isOn: $appStorageValue) {
        Text("toggle appStorageValue")
    }
    .onChange(of: appStorageValue) { _ in
        objectWillChange.send()
    }
    

内容的提问来源于stack exchange,提问作者Jay Tang

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.14 22:49:54