SwitfUI:在 macOS 上访问特定场景的 ViewModel [英] SwitfUI: access the specific scene's ViewModel on macOS
问题描述
在这个简单的示例应用程序中,我有以下要求:
In this simple example app, I have the following requirements:
- 有多个窗口,每个窗口都有自己的
ViewModel
- 在一个窗口中切换
Toggle
应该不更新另一个窗口的 - 我还希望能够通过菜单进行切换
- have multiple windows, each having it's own
ViewModel
- toggling the
Toggle
in one window should not update the other window's - 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屋!