Reputation: 999
I'm (attempting) switching over my AppDelegate macOS app to the SwiftUI lifecycle - but can't seem to find out how to handle the CommandMenu. I just want to delete these default menu items (Fie, Edit, View, etc...). In the past, I would just delete them from the Storyboard - but I'm not using a storyboard here. Is there a way to delete these items in SwiftUI?
The items I want to delete:
I know how to add new items via:
.commands {
MyAppMenus()
}
But that just adds them inline with the existing menu items.
Upvotes: 7
Views: 2704
Reputation: 10410
Working off of the above, but using NotificationCenter vs KVO.
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ notification: Notification) {
let unwantedMenus = ["File", "Edit"]
let removeMenus = {
unwantedMenus.forEach {
guard let menu = NSApp.mainMenu?.item(withTitle: $0) else { return }
NSApp.mainMenu?.removeItem(menu)
}
}
NotificationCenter.default.addObserver(
forName: NSMenu.didAddItemNotification,
object: nil,
queue: .main
) { _ in
// Must refresh after every time SwiftUI re adds
removeMenus()
}
removeMenus()
}
}
Upvotes: 3
Reputation: 11427
swiftUI -- override AppDelegate with your custom:
@main
struct PixieApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
///........
}
code of appDelegate:
final class AppDelegate: NSObject, NSApplicationDelegate {
func applicationWillUpdate(_ notification: Notification) {
DispatchQueue.main.async {
if let menu = NSApplication.shared.mainMenu {
menu.items.removeFirst{ $0.title == "Edit" }
menu.items.removeFirst{ $0.title == "File" }
menu.items.removeFirst{ $0.title == "Window" }
menu.items.removeFirst{ $0.title == "View" }
}
}
}
}
result:
But i didn't found better one. =(
Upvotes: 8
Reputation: 2854
Until SwiftUI adds more support for adjusting menus, I think you have to worry about SwiftUI reseting the NSApp.mainMenu
whenever it updates a window.body
. I haven't tried every method for adjusting the mainMenu
, but of the methods I tried, the flaw was that SwiftUI seems to have no check for whether it last set NSApp.mainMenu
or if something else did.
So however you are managing the menu, update it after SwiftUI has.
Use KVO and watch the NSApp for changes on .mainMenu. Then make your changes with a xib, or reseting the whole thing, or editing SwiftUI's menus.
Example:
@objc
class AppDelegate: NSObject, NSApplicationDelegate {
var token: NSKeyValueObservation?
func applicationDidFinishLaunching(_ notification: Notification) {
// Adjust a menu initially
if let m = NSApp.mainMenu?.item(withTitle: "Edit") {
NSApp.mainMenu?.removeItem(m)
}
// Must refresh after every time SwiftUI re adds
token = NSApp.observe(\.mainMenu, options: .new) { (app, change) in
// Refresh your changes
guard let menu = app.mainMenu?.item(withTitle: "Edit") else { return }
app.mainMenu?.removeItem(menu)
}
}
}
struct MarblesApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some View {
//...
}
}
This seems to work in Xcode 13.4.1 with Swift 5 targeting macOS 12.3.
Hopefully Apple adds greater control soon. It seems Catalyst has other options. Or you can create a traditional AppKit app and insert the SwiftUI views into it.
Upvotes: 2
Reputation: 11
CommandGroup(replacing: CommandGroupPlacement.appVisibility, addition: {})
Upvotes: -3
Reputation: 1460
You can remove command menu items through the AppDelegate file:
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
builder.remove(menu: .services)
builder.remove(menu: .format)
builder.remove(menu: .toolbar)
}
This thread on the Apple Developer forum might help as well: https://developer.apple.com/forums/thread/649096
Upvotes: 0