【问题标题】:SwiftUI macOS Commands (menu bar) and ViewSwiftUI macOS 命令(菜单栏)和视图
【发布时间】:2021-03-10 12:11:51
【问题描述】:

您好,我开始学习 SwiftUI 和 macOS 开发。我正在使用 SwiftUI 生命周期。如何从菜单栏的焦点窗口调用函数。

除了 Apple 文档之外,我还找到了 this reference,并且能够使用命令创建菜单项,但我不知道如何从我的角度调用函数。

例如:

假设这是我的 App 结构:

import SwiftUI

@main
struct ExampleApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
    }.commands {
        CommandMenu("First menu") {
            Button("Action!") {
                // How do I call the views action function?
            }
        }
    }
}

这是我的观点:

struct ContentView: View {
    public func action() {
        print("It works")
    }
    var body: some View {
        Text("Example")
    }
}

我刚刚输入了示例代码,如果有任何拼写错误,我很抱歉,但我希望你能明白。

【问题讨论】:

    标签: swift macos swiftui menubar


    【解决方案1】:

    因为 SwiftUI 中的 Views 是瞬态的,所以您不能持有对 ContentView 特定实例的引用来调用它的函数。但是,您可以做的就是更改传递给内容视图的部分状态。

    例如:

    @main
    struct ExampleApp: App {
        @StateObject var appState = AppState()
        
        var body: some Scene {
            WindowGroup {
                ContentView(appState: appState)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }.commands {
                CommandMenu("First menu") {
                    Button("Action!") {
                        appState.textToDisplay = "\(Date())"
                    }
                }
            }
        }
    }
    
    class AppState : ObservableObject {
        @Published var textToDisplay = "(not clicked yet)"
    }
    
    struct ContentView: View {
        @ObservedObject var appState : AppState
        
        var body: some View {
            Text(appState.textToDisplay)
        }
    }
    
    

    注意.commands 修饰符在WindowGroup { }

    在此示例中,AppState 是一个 ObservableObject,它保存了应用程序的某些状态。它使用参数传递给ContentView。您也可以通过环境对象 (https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views) 传递它

    单击菜单项时,它会设置textToDisplay,这是AppState 上的@Published 属性。每当AppState 的@Published 属性更新时,ContentView 就会更新。

    这是您要使用的模式的一般概念。如果您有此模式未涵盖的用例,请在 cmets 中告诉我。

    更新,基于您的 cmets

    import SwiftUI
    import Combine
    
    @main
    struct ExampleApp: App {
        @StateObject var appState = AppState()
        
        var body: some Scene {
            WindowGroup {
                ContentView(appState: appState)
                    .frame(maxWidth: .infinity, maxHeight: .infinity)
            }.commands {
                CommandMenu("First menu") {
                    Button("Action!") {
                        appState.textToDisplay = "\(Date())"
                    }
                    Button("Change background color") {
                        appState.contentBackgroundColor = Color.green
                    }
                    Button("Toggle view") {
                        appState.viewShown.toggle()
                    }
                    Button("CustomCopy") {
                        appState.customCopy.send()
                    }
                }
            }
        }
    }
    
    class AppState : ObservableObject {
        @Published var textToDisplay = "(not clicked yet)"
        @Published var contentBackgroundColor = Color.clear
        @Published var viewShown = true
        
        var customCopy = PassthroughSubject<Void,Never>()
    }
    
    class ViewModel : ObservableObject {
        @Published var text = "The text I have here"
        var cancellable : AnyCancellable?
    
        func connect(withAppState appState: AppState) {
            cancellable = appState.customCopy.sink(receiveValue: { _ in
                print("Do custom copy based on my state: \(self.text) or call a function")
            })
        }
    }
    
    struct ContentView: View {
        @ObservedObject var appState : AppState
        @State var text = "The text I have here"
        @StateObject private var viewModel = ViewModel()
        
        var body: some View {
            VStack {
                Text(appState.textToDisplay)
                    .background(appState.contentBackgroundColor)
                if appState.viewShown {
                    Text("Shown?")
                }
            }
            .onReceive(appState.$textToDisplay) { (newText) in
                print("Got new text: \(newText)")
            }
            .onAppear {
                viewModel.connect(withAppState: appState)
            }
        }
    }
    

    在我的更新中,您可以看到我已经解决了背景颜色问题,显示隐藏视图,甚至在 @Published 属性之一发生更改时收到通知(通过onReceive)。

    您还可以看到我如何使用自定义发布者 (customCopy) 将操作传递给 ContentViewViewModel

    【讨论】:

    • 非常感谢您的回答。我收到的关于堆栈溢出的最佳答案! SwiftUI 和 macOS 对我来说都很陌生,我还有 2 个问题
    • AppState 持有许多视图的状态,对吗?如何更改前景视图的状态?例如,我有一个隐藏/显示它的一些元素的状态。但我可以有多个视图窗口。当我单击按钮时如何切换焦点视图的隐藏/显示?
    • 第二个问题也是关于前景视图。如何调用函数而不是更改状态?例如,我的视图中有一个执行 X() 的按钮,并且 X() 代码位于视图结构中(因为它处理该视图的内容,但它应该在其他地方吗?)如何创建菜单项调用 X()?
    • 关于你的第二个问题,这就是我在最初的回答中试图解决的问题。我认为你需要考虑X() 中发生了什么——它可能会改变一些状态,对吧?对于您的第一个问题,您将根据 AppState 中发生的情况确定您的视图状态。请参阅我的更新答案。
    • X() 将基于视图控件的内容(即与应用程序状态无关的视图状态)复制到粘贴板
    猜你喜欢
    • 1970-01-01
    • 2021-10-10
    • 1970-01-01
    • 1970-01-01
    • 2021-09-27
    • 1970-01-01
    • 2011-02-16
    • 1970-01-01
    • 2021-02-25
    相关资源
    最近更新 更多