Artur P
Artur P

Reputation: 83

How to activate / deactivate menu items in SwiftUI

I am trying to create custom menu items in SwiftUI app for mac os (SwiftUI App life cycle). I don't quite understand the following behaviour:

In the code below:

import SwiftUI
@main
struct MySQLtoolApp: App {
    @ObservedObject var mysql = MySQL()
    var flags = UtilFlags()
    var selectedDB = "mysql"
    var body: some Scene {
        let mainWindow = WindowGroup(Text("title")) {
            ContentView()
                .environmentObject(mysql)
                .environmentObject(flags)
        }
        .windowStyle(HiddenTitleBarWindowStyle())
        .commands {
            CommandMenu("Tools", content: {
                    Button("Connect...", action: {flags.connectDialogVisibilityFlag = true }).disabled(mysql.connected)
                    Button("Disconnect", action: { mysql.closeBase()
                            mysql.connected = false}).disabled(!mysql.connected)
            })
            CommandGroup(after: .help, addition: {
                Button("Test button", action: {print("Test button pressed")})
                .disabled(!mysql.connected)
            })
        }
        return mainWindow
    }
}

the buttons added via CommandMenu behave as expected (i.e. activate and deactivate according to the changing value of 'mysql.connected'. The button added via CommandGroup gets correctly configured according to the value of 'mysql.connected' at launch, but ignores the changes of 'mysql.connected' and doesn't change its state. What am I missing?

I rewrote the above segment to emphasize the problem. In a nutshell:

import SwiftUI

@main
struct MenuTestApp: App {
    @State var active = false
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .commands(content: {
            CommandMenu("Tools", content: {
                Button("Normally active", action: {active = !active}).disabled(active)
                Button("Normally inactive", action: {active = !active}).disabled(!active)
            })
            CommandGroup(after: .newItem, addition: {
                Button("Normally active", action: {active = !active}).disabled(active)
                Button("Normally inactive", action: {active = !active}).disabled(!active)
            })
        })
    }
}

Buttons added via CommandMenu behave correctly (activate and deactivate according to the value of 'active'. Buttons added via CommandGroup ignore the value of 'active' and keep their initial states.

Upvotes: 2

Views: 1093

Answers (1)

Artur P
Artur P

Reputation: 83

I got a great suggestion in Apple dev. forum (thanks OOPer!). It may not address the root cause (is it a bug in SwiftUI?), but it provides a good workaround. If I wrap my "misbehaving" button into a view and add this, everything is working as expected:

import SwiftUI
@main
struct MySQLtoolApp: App {
    @StateObject var mysql = MySQL()
    var flags = UtilFlags()
    var selectedDB = "mysql"
    var body: some Scene {
        let mainWindow = WindowGroup(Text("title")) {
            ContentView()
                .environmentObject(mysql)
                .environmentObject(flags)
        }
        .windowStyle(HiddenTitleBarWindowStyle())
        .commands {
            CommandMenu("Tools", content: {
                    Button("Connect...", action: {flags.connectDialogVisibilityFlag = true }).disabled(mysql.connected)
                    Button("Disconnect", action: { mysql.closeBase()
                            mysql.connected = false}).disabled(!mysql.connected)
            })
            CommandGroup(after: .newItem, addition: {
                MyButtonsGroup().environmentObject(mysql)
            })
        }
        return mainWindow
    }
}
struct MyButtonsGroup: View {
    @EnvironmentObject var mysql: MySQL
    var body: some View {
        Button("Test button", action: {print("Test button pressed")})
            .disabled(mysql.connected)
    }
}

Upvotes: 1

Related Questions