SwitfUI:访问特定场景';macOS上的VIEWS模型

SwitfUI:访问特定场景';macOS上的VIEWS模型,macos,mvvm,swiftui,Macos,Mvvm,Swiftui,在这个简单的示例应用程序中,我有以下要求: 有多个窗口,每个窗口都有自己的ViewModel 在一个窗口中切换切换,不应更新另一个窗口的 我想也能够通过菜单切换 就目前而言,前两点没有给出,但最后一点有效。我已经知道,当我将ViewModel的单一真实来源移动到ContentView时,前两点是有效的,但是我将无法访问WindowGroup级别,在那里我插入了命令 导入快捷界面 @主要 结构ViewModelAndCommandsApp:App{ var body:一些场景{ ContentS

在这个简单的示例应用程序中,我有以下要求:

  • 有多个窗口,每个窗口都有自己的
    ViewModel
  • 在一个窗口中切换
    切换
    ,不应更新另一个窗口的
  • 我想也能够通过菜单切换
  • 就目前而言,前两点没有给出,但最后一点有效。我已经知道,当我将
    ViewModel
    的单一真实来源移动到
    ContentView
    时,前两点是有效的,但是我将无法访问
    WindowGroup
    级别,在那里我插入了命令

    导入快捷界面
    @主要
    结构ViewModelAndCommandsApp:App{
    var body:一些场景{
    ContentScene()
    }
    }
    类ViewModel:ObservableObject{
    @已发布的变量toggleState=true
    }
    结构内容场景:场景{
    @StateObject private var vm=ViewModel()//此处注入仅满足最后一点…
    var body:一些场景{
    窗口组{
    ContentView()
    .environmentObject(vm)
    .框架(宽:200,高:200)
    }
    .命令{
    ContentCommands(vm:vm)
    }
    }
    }
    结构内容命令:命令{
    @观察对象变量vm:ViewModel
    var body:一些命令{
    CommandGroup(在:。工具栏之前){
    按钮(“切换某些状态”){
    vm.toggleState.toggle()
    }
    }
    }
    }
    结构ContentView:View{
    @EnvironmentObject var vm:ViewModel//Injection here将生成与窗口无关的ViewModels,但会使它们在“ContactScene”和“ContentCommands”中不可用…
    var body:一些观点{
    切换(isOn:$vm.toggleState,标签:{
    文本(“某些国家”)
    })
    }
    }
    
    我如何满足这些要求?是否有一个快速用户界面解决方案,或者我是否必须实现一个
    SceneDelegate
    (这是解决方案吗?)

    编辑:
    更具体地说:我想知道如何为每个场景实例化ViewModel,并且能够从菜单栏知道要更改哪个ViewModel。

    长话短说,请参见下面的代码。该项目名为
    WindowSample
    ,需要在URL注册中匹配您的应用程序名称

    import SwiftUI
    
    @main
    struct WindowSampleApp: App {
        var body: some Scene {
            ContentScene()
        }
    }
    //This can be done several different ways. You just
    //need somewhere to store multiple copies of the VM
    class AStoragePlace {
        private static var viewModels: [ViewModel] = []
        
        static func getAViewModel(id: String?) -> ViewModel? {
            var result: ViewModel? = nil
            if id != nil{
                result = viewModels.filter({$0.id == id}).first
                
                if result == nil{
                    let newVm = ViewModel(id: id!)
                    viewModels.append(newVm)
                    result = newVm
                }
            }
            return result
        }
    }
    
    struct ContentCommands: Commands {
        @ObservedObject var vm: ViewModel
        
        var body: some Commands {
            CommandGroup(before: .toolbar) {
                Button("Toggle Some State \(vm.id)") {
                    vm.testMenu()
                }
            }
        }
    }
    class ViewModel: ObservableObject, Identifiable {
        let id: String
        @Published var toggleState = true
        
        init(id: String) {
            self.id = id
        }
        func testMenu() {
            toggleState.toggle()
        }
    }
    struct ContentScene: Scene {
        var body: some Scene {
            //Trying to init from 1 windowGroup only makes a copy not a new scene
            WindowGroup("1") {
                ToggleView(vm: AStoragePlace.getAViewModel(id: "1")!)
                    .frame(width: 200, height: 200)
            }
            .commands {
                ContentCommands(vm: AStoragePlace.getAViewModel(id: "1")!)
            }.handlesExternalEvents(matching: Set(arrayLiteral: "1"))
            //To open this go to File>New>New 2 Window
            WindowGroup("2") {
                ToggleView(vm: AStoragePlace.getAViewModel(id: "2")!)
                    .frame(width: 200, height: 200)
            }
            .commands {
                ContentCommands(vm: AStoragePlace.getAViewModel(id: "2")!)
            }.handlesExternalEvents(matching: Set(arrayLiteral: "2"))
            
            
        }
    }
    struct ToggleView: View {
        @Environment(\.openURL) var openURL
        @ObservedObject var vm: ViewModel
        var body: some View {
            VStack{
                //Makes copies of the window/scene
                Button("new-window-of type \(vm.id)", action: {
                    //appname needs to be a registered url in info.plist
                    //Info Property List>Url types>url scheme>item 0 == appname
                    //Info Property List>Url types>url identifier == appname
                    if let url = URL(string: "WindowSample://\(vm.id)") {
                        openURL(url)
                    }
                })
                
                //Toggle the state
                Toggle(isOn: $vm.toggleState, label: {
                    Text("Some State \(vm.id)")
                })
            }
        }
    }
    

    也许可以试着找出在菜单中添加项目的“老”方法是什么。然后将它们添加到
    ToggleView
    .Fair point中。这意味着,我必须使用AppDelegate进行桥接,并在
    ApplicationIDFinishLaunching(:)
    中实例化我的菜单,我想这也意味着纯SwiftUI生命周期目前无法实现这一点……进一步研究后,我似乎仍然不知道如何获得当前活动窗口及其viewmodel的句柄。我已经得出结论,这就是我需要在“切换某些状态”按钮中区分目标viewmodel的原因……我同意。看看下面我的答案。不要使用
    视图模型
    而是将
    视图
    视为
    详细视图
    ,其中切换是可在任何地方使用的
    模型
    的一部分。vs仅针对单个视图。“真相的来源”必须在一个单一的位置,在那里你可以获取/检索它。谢谢你的输入,我很感激。再过了两个小时,我还是不能完全理解。虽然上面的示例代码是一个非常基本的示例,显示了当前的问题,但我必须使用它的真正应用程序是一个使用自定义查询调用api的应用程序,每个窗口的api可能不同。因此,很自然,每个窗口的查询可能不同。我还希望能够通过菜单和Cmd+R重新加载,这就是我需要活动窗口的原因。现在,似乎没有一个简单的方法来实现这一点,所以我想我现在要让步了-(参见新代码。我认为它可能会起作用。它正好落在我正在进行的一个项目中。它太近了,可能有两个单独的窗口暂时就可以了。让我头疼的是它不是动态的。如果
    文件
    新窗口
    只需用一个新的
    U实例化一个新的
    视图模型
    ,那就太完美了。)UID
    用于这个特定的新窗口/场景。我只是不知道应该在哪里创建这个
    UUID
    。可能需要再玩一些才能找到方法…但是它比以前更近了,我感谢你的努力。我会继续思考,但我认为这是不可能的。因为菜单栏似乎没有活力c我找到了一些参考资料,其中提到了
    menuBuilder
    从一开始就运行。为了能够影响切换,它必须从一开始就存在。我没有过多地研究它。但是,
    DocumentGroup
    init
    上创建文档,这就是允许它们为特定文档设置菜单栏的原因这似乎是你正在尝试做的事情,与你正在尝试做的事情类似。好吧,我需要的“唯一”事情是在
    按钮
    级别查看当前活动窗口及其
    ViewModel
    。也许下一步可能是查看
    DocumentGroup
    。。。