Search code examples
macosswiftuimenumenuitem

SwiftUI how to alternate a menu item based on modifiers


In SwiftUI how to to alternate between 2 menu items, when holding down the Options key

like when you open a Finder File menu and press down the Options key, some items alternate

sadly my approach still shows both options

        Button("Alternative option") {
            
        }
        .keyboardShortcut(KeyEquivalent("i"), modifiers: [.command, .option])

        Button("Regular option") {
            
        }
        .keyboardShortcut(KeyEquivalent("i"), modifiers: [.command])

enter image description here


Solution

  • UPDATE

    I just found this modifierkeyalternate

    might be just what you're looking for take a look at the Apple sample code.

    // File 
    
    // Save
    Button("Save", ...) // ⌘ S
        .keyboardShortcut("s")
        .modifierKeyAlternate(.option) {
            Button("Save All", ...) // ⌥⌘ S
        }
    

    This is because you are providing two views. You need to use an if statement to decide which one you want to display.

    here is an example from one of my apps where I alter menu buttons on changes to the logged in user

    import SwiftUI
    

    struct LoginCommandsView: View {

    @Environment(\.openWindow) private var openWindow
    @Environment(\.dismiss) private var dismiss
    
    @State private var loginText: String = "Login as User"
    @State private var userIsAdmin: Bool = false
    @State private var userIsLoggedIn: Bool = false
    
    
    var body: some View {
        //FIXME: - see bug notes
        loginButton
            .onAppear {
                setParameters(Company.shared.currentUser)
            }
            //bug fix on change not always called
            .onReceive(NotificationCenter.default.publisher(for: .authenticationState), perform: { _ in
                setParameters(Company.shared.currentUser)
            })
            //bug fix on receive not always called
            .onChange(of: Company.shared.currentUser) { _, newValue in
                setParameters(newValue)
            }
        
        if userIsLoggedIn {
            changePassword
        }
        
        if userIsAdmin {
            createUser
        }
        
        if userIsLoggedIn {
            logoutButton
        }
    }
    
    @ViewBuilder
    private var loginButton: some View {
        
        Button(loginText) {
            Company.shared.currentUser = nil
            CompanyData.shared.save()
            openWindow(id: WindowConstants.loginView)
        }
    }
    
    private var changePassword: some View {
        Button("Change Password") {
            openWindow(id: WindowConstants.changePassword)
        }
    }
    
    private var createUser: some View {
        Button("Create New User") {
            openWindow(id: WindowConstants.userCreate)
        }
    }
    
    private var logoutButton: some View {
        Button("Logout") {
            logout()
        }
    }
    
    private func logout() {
        Company.shared.currentUser = nil
        CompanyData.shared.save()
        openWindow(id: WindowConstants.loginView)
        dismiss()
    }
    
    private func setParameters(_ user: User?) {
        if user == nil {
            loginText = "Login"
            userIsAdmin = false
            userIsLoggedIn = false
        } else {
            userIsLoggedIn = true
            loginText = "Switch User"
            userIsAdmin = user!.userIsAdmin 
        }
    }
    }
    

    in your case you could just do

    if someCondition {
                Button("Alternative option") {
            
        }
        .keyboardShortcut(KeyEquivalent("i"), modifiers: [.command, .option])
    } else {
        Button("Regular option") {
            
        }
        .keyboardShortcut(KeyEquivalent("i"), modifiers: [.command])
    }
    

    note that this view is utilized in .commands of the window group to which it applies "in my case all of them"

    .commands {
                //MARK: - commands
            Group {
                    //MARK: New Item Commands
                CommandGroup(replacing: .newItem) {
                    LoginCommandsView()
                        .onReceive(NotificationCenter.default.publisher(for: .authenticationState)) { _ in
                            showFullMenu = Company.shared.currentUser != nil
                        }
                }
    }
    

    and that show full menu just adds the other menu items available if the user is authenticated. from inside the .commands closure.