macos - SwitfUI:在 macOS 上访问特定场景的 ViewModel
问题描述
在这个简单的示例应用程序中,我有以下要求:
- 有多个窗口,每个窗口都有自己的
ViewModel
Toggle
在一个窗口中切换不应更新另一个窗口的- 我也希望能够通过菜单切换
就像现在一样,前两点没有给出,但最后一点有效。我已经知道,当我将ViewModel
's 的单一事实来源移至ContentView
前两点的作品时,但随后我将无法在WindowGroup
注入命令的级别访问。
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
(这仍然是解决方案吗?)?
编辑:
更具体地说:我想知道如何为每个单独的场景实例化一个 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)")
})
}
}
}
推荐阅读
- javascript - 如何在grapesjs中为js代码添加选项?
- mysql - 如何使用spring boot通过邮递员操作数据
- firebase - Firebase 部署错误 - 尝试解析函数触发器时出现未知问题。请确保您使用的是 Node.js v6 或更高版本
- c++ - 为什么即使语句评估为假,我的循环也会在睡眠后连续循环
- mysql - MySQL 分析查询 - 提高性能
- kubernetes - Istio - Kiali - 上游连接错误或在标头之前断开/重置
- php - 如何允许特定域访问 drupal 7 API?
- python - NOT NULL 约束失败:baseapi_movie.admin_id 不使用 django 表单
- html - 现在(2020 年)自由使用 Flexbox 和 Grid 是否安全?
- javascript - 如何在 IBM Carbon(Angular)中的表上应用搜索过滤器?