Calling a sheet from a menu item - macos

I have a macOS app that has to display a small dialog with some information when the user presses the menu item "Info".
I've tried calling doing this with a .sheet but can't get it to display the sheet. Code:
#main
struct The_ThingApp: App {
private let dataModel = DataModel()
#State var showsAlert = false
#State private var isShowingSheet = false
var body: some Scene {
WindowGroup {
ContentView()
.environmentObject(self.dataModel)
}
.commands {
CommandMenu("Info") {
Button("Get Info") {
print("getting info")
isShowingSheet.toggle()
}
.sheet(isPresented: $isShowingSheet) {
VStack {
Text("Some stuff to be shown")
.font(.title)
.padding(50)
Button("Dismiss",
action: { isShowingSheet.toggle() })
}
}
}
}
}
}
How would I display a sheet from a menu item?
However, if a sheet is not the way to do it (I think given the simplicity of what I need to show, it would be it), how would you suggest I do it? I tried creating a new view, like I did with the preferences window, but I can't call it either from the menu.

put the sheet directly on ContentView:
#main
struct The_ThingApp: App {
#State private var isShowingSheet = false
var body: some Scene {
WindowGroup {
ContentView()
// here VV
.sheet(isPresented: $isShowingSheet) {
VStack {
Text("Some stuff to be shown")
.font(.title)
.padding(50)
Button("Dismiss",
action: { isShowingSheet.toggle() })
}
}
}
.commands {
CommandMenu("Info") {
Button("Get Info") {
print("getting info")
isShowingSheet.toggle()
}
}
}
}
}

Related

SwiftUI: animating tab item addition/removal in tab bar

In my app I add/remove a subview to/from a TabView based on some condition. I'd like to animate tab item addition/removal in tab bar. My experiment (see code below) shows it's not working. I read on the net that TabView support for animation is quite limited and some people rolled their own implementation. But just in case, is it possible to implement it?
import SwiftUI
struct ContentView: View {
#State var showBoth: Bool = false
var body: some View {
TabView {
Button("Test") {
withAnimation {
showBoth.toggle()
}
}
.tabItem {
Label("1", systemImage: "1.circle")
}
if showBoth {
Text("2")
.tabItem {
Label("2", systemImage: "2.circle")
}
.transition(.slide)
}
}
}
}
Note: moving transition() call to the Label passed to tabItem() doesn't work either.
As commented Apple wants the TabBar to stay unchanged throughout the App.
But you can simply implement your own Tabbar with full control:
struct ContentView: View {
#State private var currentTab = "One"
#State var showBoth: Bool = false
var body: some View {
VStack {
TabView(selection: $currentTab) {
// Tab 1.
VStack {
Button("Toggle 2. Tab") {
withAnimation {
showBoth.toggle()
}
}
} .tag("One")
// Tab 2.
VStack {
Text("Two")
} .tag("Two")
}
// custom Tabbar buttons
Divider()
HStack {
OwnTabBarButton("One", imageName: "1.circle")
if showBoth {
OwnTabBarButton("Two", imageName: "2.circle")
.transition(.scale)
}
}
}
}
func OwnTabBarButton(_ label: String, imageName: String) -> some View {
Button {
currentTab = label
} label: {
VStack {
Image(systemName: imageName)
Text(label)
}
}
.padding([.horizontal,.top])
}
}

Enabling and Disabling CommandGroup Menu Items

I have a very simple sample macOS application with one custom menu command just in order to test my ideas as follows.
import SwiftUI
#main
struct MenuMonsterMacApp: App {
#State var fileOpenEnabled: Bool = true
var body: some Scene {
WindowGroup {
ContentView()
.frame(width: 480.0, height: 320.0)
}.commands {
CommandGroup(after: .newItem) {
Button {
print("Open file, will you?")
} label: {
Text("Open...")
}
.keyboardShortcut("O")
.disabled(false)
}
}
}
}
And I want to enable and disable this command with a click of a button that is placed in ContentView. So I've created an ObservableObject class to observe the boolean value of the File Open command as follows.
import SwiftUI
#main
struct MenuMonsterMacApp: App {
#ObservedObject var menuObservable = MenuObservable()
#State var fileOpenEnabled: Bool = true
var body: some Scene {
WindowGroup {
ContentView()
.frame(width: 480.0, height: 320.0)
}.commands {
CommandGroup(after: .newItem) {
Button {
print("Open file, will you?")
} label: {
Text("Open...")
}
.keyboardShortcut("O")
.disabled(!fileOpenEnabled)
}
}.onChange(of: menuObservable.fileOpen) { newValue in
fileOpenEnabled = newValue
}
}
}
class MenuObservable: ObservableObject {
#Published var fileOpen: Bool = true
}
In my ContentView, which virtually runs the show, I have the following.
import SwiftUI
struct ContentView: View {
#StateObject var menuObservable = MenuObservable()
var body: some View {
VStack {
Button {
menuObservable.fileOpen.toggle()
} label: {
Text("Click to disable 'File Open'")
}
}
}
}
If I click on the button, the boolean status of the menu command in question won't change. Is this a wrong approach? If it is, how can enable and disable the menu command from ContentView? Thanks.
To enable and disable the command with a click of a button that is placed in ContentView,
try the following approach, using passing environmentObject and a separate View for the
menu Button.
import SwiftUI
#main
struct MenuMonsterMacApp: App {
#StateObject var menuObservable = MenuObservable()
var body: some Scene {
WindowGroup {
ContentView().environmentObject(menuObservable)
.frame(width: 480.0, height: 320.0)
}.commands {
CommandGroup(after: .newItem) {
OpenCommand().environmentObject(menuObservable)
}
}
}
}
struct OpenCommand: View {
#EnvironmentObject var menuObservable: MenuObservable
var body: some View {
Button {
print("Open file, will you?")
} label: {
Text("Open...")
}
.disabled(!menuObservable.fileOpen)
.keyboardShortcut("O")
}
}
class MenuObservable: ObservableObject {
#Published var fileOpen: Bool = true
}
struct ContentView: View {
#EnvironmentObject var menuObservable: MenuObservable
var body: some View {
VStack {
Button {
menuObservable.fileOpen.toggle()
} label: {
Text("Click to disable 'File Open'")
}
}
}
}

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"
}
}
}

SwiftUI for macOS - trigger sheet .onDismiss problem

In a multiplatform app I'm showing a sheet to collect a small amount of user input. On iOS, when the sheet is dismissed, the relevant .onDismiss method is called but not on macOS.
I've read that having the .onDismiss in the List can cause problems so I've attached it to the button itself with no improvement. I've also tried passing the isPresented binding through and toggling that within the sheet itself to dismiss, but again with no success.
I am employing a NavigationView but removing that makes no difference. The following simplified example demonstrates my problem. Any ideas? Should I even be using a sheet for this purpose on macOS?
I just want to make clear that I have no problem closing the sheet. The other questions I found were regarding problems closing the sheet - I can do that fine.
import SwiftUI
#main
struct SheetTestApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
struct ContentView: View {
var body: some View {
NavigationView {
ListView()
}
}
}
The List view.
struct ListView: View {
#State private var isPresented: Bool = false
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
.sheet(isPresented: $isPresented, onDismiss: {
doSomethingAfter()
}) {
TestSheetView()
}
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
}
func doSomethingBefore() {
print("Johnny")
}
func doSomethingAfter() {
print("Cash")
}
}
This is the sheet view.
struct TestSheetView: View {
#Environment(\.presentationMode) var presentationMode
#State private var name = ""
var body: some View {
Form {
TextField("Enter name", text: $name)
.padding()
HStack {
Spacer()
Button("Save") {
presentationMode.wrappedValue.dismiss()
}
Spacer()
}
}
.frame(minWidth: 300, minHeight: 300)
.navigationTitle("Jedward")
}
}
Bad issue.. you are right. OnDismiss is not called. Here is a workaround with Proxybinding
var body: some View {
VStack {
Text("Patterns").font(.title)
Button(action: {
isPresented = true
}, label: {
Text("Add")
})
List {
Text("Bingo")
Text("Bongo")
Text("Banjo")
}
.onAppear(perform: {
doSomethingBefore()
})
}
.sheet(isPresented: Binding<Bool>(
get: {
isPresented
}, set: {
isPresented = $0
if !$0 {
doSomethingAfter()
}
})) {
TestSheetView()
}
}

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