SwiftUI Toggle onChange not called for macOS menu item - macos

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()
}
}
}

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

Stepper not showing in ToolBar on macOS

On macOS the Stepper is not showing in the toolbar. It works on iOS. Does anybody know a workaround?
struct ContentView: View {
#State private var value = 1
var body: some View {
NavigationStack {
Text("Hello")
.toolbar {
// does not show
ToolbarItem {
Stepper("Test \(value)", value: $value)
}
}
}
}
}

Xcode: Button action between files/views

Xcode complete beginner
Trying to make searchbar appear in different view than where the button is placed. When pressing magnifying-glass (button placed in ContentView), I want the text to appear in FeedView. How? #Published/ObservedObject/State...?? I have no knowledge just like to play around and learn. So 1. press magnifying-glass 2. makes the text appear (needs to be in different views)
ContentView:
import SwiftUI
struct ContentView: View {
#State private var doSearch = false
var body: some View {
Button {
doSearch.toggle()
} label: {
Image(systemName: "magnifyingglass")
.foregroundColor(Color(.black))
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
FeedView:
import SwiftUI
struct FeedView: View {
#State private var doSearch = false
var body: some View {
HStack {
if doSearch {
Text("search bar here")
}
}
}
}
Images:
ContentView
FeedView

Highlight Navigation View At Start

When I start my app, the start page is "Kunde" but the whole thing is not highlighted in blue in the navigation. It just turns blue (system color) when I click on it.
I want it to be highlighted blue when I open the app.
import SwiftUI
struct ContentView: View {
var body: some View {
NavigationView {
List {
NavigationLink(destination: ListView()) {
Text("Kunde")
}
}
ListView()
}
}
}
struct ListView: View {
var body: some View {
Text("Hello.")
}
}
you could try something like this approach:
struct ContentView: View {
#State var selection: String?
#State var listData = ["Kunde", "xxxx", "zzzz"]
var body: some View {
NavigationView {
List(listData, id: \.self) { item in
NavigationLink(destination: ListView()) {
Text(item)
}
.listRowBackground(selection == item ? Color.blue : Color.clear)
}
}
.onAppear {
selection = "Kunde"
}
}
}

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.
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)
}
}

SwiftuUI NavigationLink inside touchBar

I'm trying to create NavigationLink in MacBook touchBar with help of SwiftUI. Actually with my piece of code, the button is shown in touchbar, but unfortunately the link doesn't work.
NavigationView {
.touchBar {
NavigationLink(destination: BookView()) {
Text("GoToBook")
}
}
}
struct BookView: View {
var body: some View {
Text("Hello")
}
}
Try instead with Button in touchBar activating NavigationLink programmatically, like below
#State private var isActive = false
...
// below in body
NavigationView {
SomeView() // << your view here
.background(NavigationLink(destination: BookView(), isActive: $isActive) {
EmptyView()
} // hidden link
)
.touchBar {
Button("GoToBook") { self.isActive.toggle() } // activate link
}
}

Resources