In many macOS apps, if you open a menu from the main menubar, you can press the option key to change some of the items.
For example in Safari, I can open the File menu, and there is a "Close Tab" item. Pressing option changes it to "Close Other Tabs".
Is there a way to do this with SwiftUI?
I know how to create basic menus, but I don't see a way to detect the option key.
I think in AppKit you use NSMenuItem's isAlternate property.
struct MyApp: App {
var body: some Scene {
WindowGroup {
...
}.commands {
CommandGroup(replacing: .newItem) {
Button { newFolder() } label: { Text("New Folder") }
.keyboardShortcut("n")
...
}
}
Update to Question
I've tried a few more things, and run into two problems.
I can connect a custom object to the end of the NSResponder chain, and the system will call its flagsChanged method when the user presses the option key. However, it will not call this method when a menu is open.
Even if I find a way to observe the option key while the menu is open, changing state used to build a CommandMenu causes the menu to disappear, not rebuild and stay open.
var body: some Commands {
// If the `isOptionDown` property changes when this menu is open,
// SwiftUI doesn't change the menu, it simply closes it.
// That's not how macOS apps usually behave.
CommandGroup(replacing: .pasteboard) {
Button(...)
if optionKeyWatcher.isOptionDown {
Button(...)
} else {
Button(...)
}
Related
Context
I have an app that runs only from the macOS menubar. (The LSUIElement property in info.plist is set to YES).
Instead of a menu, this app shows an NSPopover when the menubar button is clicked. The popover holds an NSHostingView which has an extremely simple SwiftUI view:
struct PopoverContentView: View
{
#State private var color: CGColor = .white
var body: some View
{
ColorPicker(selection: $color) {
Text("Pick a Color:")
}
}
}
Problem
Clicking on the ColorPicker() does not open the macOS color picker window. The UI of the ColorPicker() button changes, to show the "selected" border state but the color-picker window never appears.
However, if I change LSUIElement to be NO and then make the app active by clicking its Dock icon (so that it takes over the menubar), THEN clicking on the ColorPicker() in the popover actually reveals the color-picker window.
Do you know of a way to force macOS to show the color-picker window for a background application?
The answer turned out to be simple. In the AppKit ViewController that opens the popover when the menubar button is clicked (PopoverController, for me), I simply did this:
extension PopoverController: NSPopoverDelegate
{
func popoverWillShow(_ notification: Notification)
{
NSApp.activate(ignoringOtherApps: true)
}
}
The ColorPicker now correctly shows the standard macOS system color panel on click.
Writing a Macos app. The following code just puts up a simple navigation list. Everything is fine with single clicking the Row links and displaying the Detail row.
If you double click the NavigationLink, another window opens with only the Text view on it. During my testing, there was a button to dismiss the view on the detail window, and if clicked, the original window would close leaving this stripped down view open.
I have to assume that no one else is seeing this since I cannot see anything from other people.
Does anyone have any ideas what would cause this to happen?
import SwiftUI
struct ContentView: View {
var body: some View {
HStack {
NavigationView {
List(0..<100) { row in
NavigationLink(destination: Text("Detail \(row)")) {
Text("Row \(row)")
}
}
}
}
}
}
Just use sidebar list style explicitly
List(0..<100) { row in
// ... content here
}
.listStyle(SidebarListStyle()) // << here !1
Tested with Xcode 13.2 / macOS 12.1
Does anyone know how to use onCopyCommand / onPasteCommand modifier in SwiftUI on macOS?
I can't make it being called.
No matter where I put it, 'copy / cut / paste` menu items are disabled.
In case you are using a custom view, you need to make sure it is focused.
Currently, the only way I know to focus a view (in SwiftUI) is with the focusable modifier
import SwiftUI
struct MyView {
var body: some View {
VStack {
Text("can be focused by pressing 'TAB'")
}
.focusable()
.onCopyCommand {
return [NSItemProvider(/* some data */)]
}
}
}
Note:
The only way to focus a custom View with focusable() modifier, is with the TAB key. I guess that mouse clicks are not yet supported.
I have got more Info Here
I would like to create a SwiftUI Menu that has a dynamic content (very easy to do), but I would like to sort the items if the option-key was pressed when the Menu was clicked. I have explored using gestures, but I can’t figure out how to change my state from the gesture in a way that won’t interfere with the operation of the Menu itself.
Is there a way to update a State variable based on tap+modifier key, that will occur before the Menu subviews are created, and won’t interfere with the normal operation of the Menu?
The Menu will have “recents” and might contain a few dozen entries. Being able to sort the history will make it faster to find a specific entry. I want to use a Menu because it’s very compact when not engaged.
The skeleton of what I am looking for is:
Menu("My Menu") {
if <option-key pressed?> {
// sorted order
Button("A") { }
Button("B") { }
Button("C") { }
Button("D") { }
} else {
// natural order
Button("B") { }
Button("D") { }
Button("A") { }
Button("C") { }
}
}
This is for MacOS, thanks!
Specifically, I want my "New" menu item to respond to both Cmd+N and Cmd+T since it will open a new document in a tab.* How can I do this either in Interface Builder or programmatically?
* I can explain the reasoning further if needed, but I'm hoping to avoid a discussion of the merits and rather focus on how to do it, not why to do it.
Make a second one (easiest way being to duplicate it) and set it as hidden. It won't show up when the user pulls open the menu, but as long as it's enabled, its key equivalents should still be in effect.
A simple way to have two or more Key Equivalents for an action is to duplicate the NSMenuItem and add a special Tag for these "alternatives" menu items.
Then set the AppDelegate the delegate (NSMenuDelegate) of the corresponding enclosing NSMenu (where the inner items need the visibility to be updated).
Hidden menu items (or items with a hidden superitem) do not appear in
a menu and do not participate in command key matching.
When the NSMenu open, hides this alternates NSMenuItem, when it close, display them.
Example in Swift 3:
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) {
NSApp.mainMenu?.item(withTitle: "View")?.submenu?.item(withTitle: "Zoom")?.submenu?.delegate = self
}
func toggleVisibility(_ visible: Bool, ofAlternatesKeyEquivalentsItems items: [NSMenuItem]) {
for item in items.filter({ $0.tag == 2 }) {
item.isHidden = !visible
}
}
func menuWillOpen(_ menu: NSMenu) {
if menu.title == "Zoom" {
toggleVisibility(false, ofAlternatesKeyEquivalentsItems: menu.items)
}
}
func menuDidClose(_ menu: NSMenu) {
if menu.title == "Zoom" {
toggleVisibility(true, ofAlternatesKeyEquivalentsItems: menu.items)
}
}
}