Swift应用中.commands菜单调用外部类方法无效的问题
嗨,我看了你的代码,一下子就找到问题根源啦!你现在遇到的问题,核心原因是每次点击Command菜单里的按钮时,你都创建了一个全新的WebViewModel实例,而这个临时实例和你的ContentView里正在使用的ViewModel完全不是同一个对象——你修改的只是这个短命实例的属性,自然不会对界面产生任何影响。
举个例子,你写的WebViewModel().docShown = true,相当于每次点按钮都新建一个ViewModel,改完它的docShown,这个对象马上就被系统回收了,和你界面上绑定的那个ViewModel半毛钱关系都没有,所以界面根本不会有变化。
那怎么解决呢?我们需要让WebViewModel成为整个App共享的实例,这样Commands和ContentView都能访问到同一个对象。具体步骤如下:
第一步:在App结构体中创建共享的ViewModel实例
修改你的Ion_MacOSApp代码,用@StateObject声明一个全局共享的WebViewModel,然后把它传递给ContentView:
import SwiftUI import WebKit @main struct Ion_MacOSApp: App { // 用@StateObject创建唯一的共享ViewModel实例 @StateObject private var webViewModel = WebViewModel() var body: some Scene { WindowGroup { // 将共享实例传递给ContentView ContentView(viewModel: webViewModel) } .commands { CommandMenu("Visibility") { Button("Show") { // 直接使用共享实例修改属性 webViewModel.docShown = true } .keyboardShortcut("S") Button("Hide") { // 调用共享实例的方法 webViewModel.hideDoc() } .keyboardShortcut("H") } } } }
第二步:修改ContentView接收共享实例
更新你的ContentView,让它通过@ObservedObject接收传入的ViewModel(这样就能和共享实例绑定,响应属性变化):
struct ContentView: View { // 接收外部传入的共享ViewModel @ObservedObject var viewModel: WebViewModel var body: some View { // 这里是你的原有界面代码,记得把所有用到WebViewModel的地方替换成viewModel VStack { if viewModel.docShown { // 假设你有一个WebView的NSViewRepresentable封装 WebView(webView: viewModel.webView) } // 其他界面元素... } } } // 如果你的WebView是这样封装的,确保它使用传入的webView实例 struct WebView: NSViewRepresentable { let webView: WKWebView func makeNSView(context: Context) -> WKWebView { webView } func updateNSView(_ nsView: WKWebView, context: Context) {} }
为什么这样能解决问题?
@StateObject会在App的整个生命周期内保持唯一的实例,不管是ContentView还是Commands里的按钮,操作的都是同一个WebViewModel对象。当你修改docShown或者调用hideDoc()时,@Published属性会自动触发界面更新,你想要的“显示/隐藏”效果就能正常生效了。
以后再遇到这种跨组件共享数据的问题,记得要保证操作的是同一个对象实例,而不是每次都新建哦!
备注:内容来源于stack exchange,提问作者AtomSys

