Home > front end >  SwiftUI Toggle onChange not called for macOS menu item
SwiftUI Toggle onChange not called for macOS menu item

Time:04-06

I am adding a sort menu to my macOS SwiftUI app. Some of the items can have checkmarks next to them. For each menu item I create a Toggle item bound to an @State Bool. When an item is clicked I would like to run additional code to update the sort, so I added an .onChange, but it is never called. The checkmark for the item appears and disappears as I would expect, but the .onChange never gets hit.

Here is an example of creating a macOS menu item with checkmark. The "change" is never printed and a breakpoint set there never gets hit.

import SwiftUI

struct ViewMenuCommands: Commands {
    @State
    var isAlphabetical = false
        
    @CommandsBuilder var body: some Commands {
        CommandMenu("ViewTest") {
            Toggle("Alphabetical", isOn: $isAlphabetical)
                .onChange(of: isAlphabetical) { value in
                    print( "change" )
                }
        }
    }
}

And here's where I add the menu to the app:

import SwiftUI

@main
struct MenuToggleTestApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            ViewMenuCommands()
        }
    }
}

CodePudding user response:

The problem is a result of @State not notifying Commands of its change -- @State will only notify a View of a change.

The solution is to wrap your menu item in a view that can respond to changes (via either @State or an ObservableObject -- I've chosen then latter).

Example:

@main
struct MainApp: App {
    @StateObject var menuState = MenuState()
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands {
            CommandMenu("ViewTest") {
                AlphabeticalMenuItem(menuState: menuState)
            }
        }
    }
}

class MenuState : ObservableObject {
    @Published var isAlphabetical = false
}

struct AlphabeticalMenuItem : View {
    @ObservedObject var menuState : MenuState
    
    var body: some View {
        Toggle("Alphabetical", isOn: $menuState.isAlphabetical)
            .onChange(of: menuState.isAlphabetical) { value in
                print( "change" )
            }
    }
}
  • Related