SwiftUI: Changing default Command Menus on macOS
Use CommandGroup, which has init options to append or replace existing menus:
.commands {
CommandGroup(before: CommandGroupPlacement.newItem) {
Button("before item") {
print("before item")
}
}
CommandGroup(replacing: CommandGroupPlacement.appInfo) {
Button("Custom app info") {
// show custom app info
}
}
CommandGroup(after: CommandGroupPlacement.newItem) {
Button("after item") {
print("after item")
}
}
}
Nice tutorial: https://swiftwithmajid.com/2020/11/24/commands-in-swiftui/
SwiftUI: How to implement Edit menu in macOS app
Have a look at the commands modifier, the CommandGroup and CommandMenu.
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}.commands {
// for example
CommandGroup(replacing: .help) {
Button(action: {someActionHere()}) {
Text("MyApp Help")
}
}
CommandMenu("Edit") {
// ...
}
}
}
}
Hiding Edit Menu of a SwiftUI / MacOS app
To my knowledge you cannot hide the whole menu, you can just hide element groups inside of it:
.commands {
CommandGroup(replacing: .pasteboard) { }
CommandGroup(replacing: .undoRedo) { }
}
SwiftUI macOS Commands (menu bar) and View
Because View
s in SwiftUI are transient, you can't hold a reference to a specific instance of ContentView
to call a function on it. What you can do, though, is change part of your state that gets passed down to the content view.
For example:
@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)
}
}
Note that the .commands
modifier goes on WindowGroup { }
In this example, AppState
is an ObservableObject
that holds some state of the app. It's passed through to ContentView
using a parameter. You could also pass it via an Environment Object (https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-environmentobject-to-share-data-between-views)
When the menu item is clicked, it sets textToDisplay
which is a @Published
property on AppState
. ContentView
will get updated any time a @Published property of AppState
gets updated.
This is the general idea of the pattern you'd use. If you have a use case that isn't covered by this pattern, let me know in the comments.
Updates, based on your comments:
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)
}
}
}
In my updates, you can see that I've addressed the question of the background color, showing hiding a view, and even getting a notification (via onReceive
) when one of the @Published properties changes.
You can also see how I use a custom publisher (customCopy
) to pass along an action to ContentView
's ViewModel
Related Topics
Swift: How to Add a Class Method in 'string" Extension
How to Get The Push Notifications Displayed in The Notification Center
Combine Sink: Ignore Receivevalue, Only Completion Is Needed
Animation Delay on Left Side of Screen in iOS Keyboard Extension
Swift: Rsa Encrypt a String with a Specific Private Key
Using UIdropinteractiondelegate and Movies
Get Applicationdidfinishlaunching Call in a View Controller. Parse Not Initialized Yet
More Precision Than Double in Swift
Nsdatepicker in Nsstatusbar Nssmenuitem Not Receiving Input
How to Draw a Line Between Two Points Over an Image in Swift 3
Set an Horizontal Scroll to My Barchart in Swift
Using Getters and Setters to Modify Values W/O Subclassing in Swift
Swift Switch Case Compiler Error
+' Is Deprecated: Mixed-Type Addition Is Deprecated in Swift 3.1