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

绑定函数类型FocusedSceneValue导致窗口调整菜单命令消失

SwiftUI函数类型FocusedSceneValue导致窗口菜单命令消失的解决方案

问题现象

开发的SwiftUI应用中:

  • ContentView包含一个矩形,填充色通过定时器每3秒自动切换,也可通过菜单手动切换
  • 矩形上的文本可通过菜单控制显示/隐藏
  • 使用FocusedValues绑定视图状态与菜单,功能正常,但当窗口菜单处于打开状态时,定时器触发视图重渲染会导致「进入全屏」「填充」等系统窗口调整命令消失,需重新打开菜单才会恢复
  • 注释掉函数类型的.focusedSceneValue(\.changeColor, changeColor)后,问题消失

问题原因

直接传递函数类型的changeColor到FocusedValues时,每次视图重渲染都会生成新的函数实例,导致FocusedValues中的对应条目频繁更新。当系统菜单处于打开状态时,这种频繁的状态变更会干扰菜单的内部状态维护,最终导致内置窗口命令丢失。而布尔类型的Binding是引用类型,重渲染时不会生成新实例,因此不会引发问题。

解决方案

将视图状态与操作逻辑封装到ObservableObject中,通过传递对象引用替代直接传递函数,确保FocusedValues中的条目稳定,避免频繁更新干扰菜单状态。

修改后的完整代码

1. 定义ViewModel封装状态与逻辑

import SwiftUI

class AppViewModel: ObservableObject {
    @Published var color: Color = .red
    @Published var infoTextVisible: Bool = false
    
    /// 切换颜色的逻辑封装到ViewModel方法中
    func toggleColor() {
        color = color == .red ? .blue : .red
    }
}

2. 更新ContentView绑定ViewModel

import SwiftUI
import Combine

struct ContentView: View {
    @StateObject private var vm = AppViewModel()
    let timer = Timer.publish(every: 3.0, on: .main, in: .common).autoconnect()
    
    var body: some View {
        ZStack {
            Rectangle()
                .fill(vm.color)
            if vm.infoTextVisible {
                Text("INFO TEXT")
            }
        }
        // 传递ViewModel引用到FocusedValues
        .focusedSceneValue(\.appViewModel, vm)
        .onReceive(timer) { _ in
            vm.toggleColor()
        }
    }
}

3. 更新FocusedValues扩展与MenuCommands

import SwiftUI

extension FocusedValues {
    @Entry var appViewModel: AppViewModel?
}

struct MenuCommands: Commands {
    @FocusedValue(\.appViewModel) var vm

    var body: some Commands {
        CommandMenu("Playback") {
            Button("Change Color") {
                vm?.toggleColor()
            }
            .keyboardShortcut(.rightArrow, modifiers: [])
            .disabled(vm == nil)
        }
        CommandGroup(before: .sidebar) {
            Button("Show/Hide Info Text") {
                vm?.infoTextVisible.toggle()
            }
            .keyboardShortcut("I", modifiers: [])
            .disabled(vm == nil)
        }
    }
}

4. 保持TestApp.swift不变

import SwiftUI

@main
struct CarouselApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            MenuCommands()
        }
    }
}

原理说明

通过ObservableObject封装状态与操作后,传递给FocusedValues的是ViewModel的对象引用,视图重渲染时引用不会改变,FocusedValues中的条目保持稳定,不会触发频繁更新,从而避免干扰系统菜单的状态维护,解决了窗口命令消失的问题。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.03 14:04:52