SwitfUI:在 macOS 上访问特定场景的 ViewModel [英] SwitfUI: access the specific scene's ViewModel on macOS

查看:20
本文介绍了SwitfUI:在 macOS 上访问特定场景的 ViewModel的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

In this simple example app, I have the following requirements:

  1. 有多个窗口,每个窗口都有自己的ViewModel
  2. 在一个窗口中切换 Toggle 应该更新另一个窗口的
  3. 我还希望能够通过菜单进行切换
  1. have multiple windows, each having it's own ViewModel
  2. toggling the Toggle in one window should not update the other window's
  3. I want to also be able to toggle via menu

就目前而言,前两点没有给出,但最后一点有效.我已经知道,当我将 ViewModel 的单一事实来源移动到 ContentView 时,前两点适用,但随后我将无法访问WindowGroup 级别,我在其中注入命令.

As it is right now, the first two points are not given, the last point works though. I do already know that when I move the ViewModel's single source of truth to the ContentView works for the first two points, but then I wouldn't have access at the WindowGroup level, where I inject the commands.

import SwiftUI

@main
struct ViewModelAndCommandsApp: App {
    var body: some Scene {
        ContentScene()
    }
}

class ViewModel: ObservableObject {
    @Published var toggleState = true
}

struct ContentScene: Scene {
    @StateObject private var vm = ViewModel()// injecting here fulfills the last point only…
    
    var body: some Scene {
        WindowGroup {
            ContentView()
                .environmentObject(vm)
                .frame(width: 200, height: 200)
        }
        .commands {
            ContentCommands(vm: vm)
        }
    }
}

struct ContentCommands: Commands {
    @ObservedObject var vm: ViewModel
    
    var body: some Commands {
        CommandGroup(before: .toolbar) {
            Button("Toggle Some State") {
                vm.toggleState.toggle()
            }
        }
    }
}

struct ContentView: View {
    @EnvironmentObject var vm: ViewModel//injecting here will result in window independant ViewModels, but make them unavailable in `ContactScene` and `ContentCommands`…
    
    var body: some View {
        Toggle(isOn: $vm.toggleState, label: {
            Text("Some State")
        })
    }
}

我如何才能满足这些要求——是否有 SwiftUI 解决方案,或者我是否必须实现一个 SceneDelegate(无论如何这是解决方案?)?

How can I fulfill theses requirements–is there a SwiftUI solution to this or will I have to implement a SceneDelegate (is this the solution anyway?)?

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

To be more specific: I'd like to know how I can go about instantiating a ViewModel for each individual scene and also be able to know from the menu bar which ViewModel is meant to be changed.

推荐答案

长话短说,请看下面的代码.该项目名为 WindowSample,它需要与您在 URL 注册中的应用名称相匹配.

Long story short, see the code below. The project is called WindowSample this needs to match your app name in the URL registration.

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

这篇关于SwitfUI:在 macOS 上访问特定场景的 ViewModel的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆