I trying to build a menu like the Notes app on iOS. On press of the ellipsis-circle button, it should release focus on the text and hide the Done toolbar item.
This is my code:
#FocusState private var isEditing: Bool
var body: some View {
AnnotatedTextEditor(text: $text)
.focused($isEditing)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
Button(role: .destructive, action: delete) {
Label("Delete", systemImage: .trash)
}
} label: {
Label("Menu", systemImage: .ellipsisCircle)
}
.onPress {
isEditing = false
}
if (isEditing) {
Button("Done", action: done)
}
}
}
}
The AnnotatedTextEditor is a UITextView wrapped in a UITextViewRepresentable for SwiftUI compatibility.
Working: The text view is unfocused and the done toolbar item disappears when the menu toolbar item is presses.
Not working: the menu does not open.
When I show the Done button un-conditionally, it works as expected, so I assume the issue is that the toolbar changes on the state change.
Is there any workaround to achieve hiding the toolbar's done button on menu open?
Screenshots:
My app (not working as expected):
iOS Notes app (working as expected):
Related
I want to achieve behavior close to native iOS reminders app on iPad.
There is dropdown Menu in NavigationBar that shows tags in it. I thought that I can do it with Picker but as I see there is now multiple selection support in it.
I did it using
...
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Menu {
MenuView()
} label: {
Image(systemName: "number")
}
Button("Done") {}
}
}
And MenuView's code is
struct MenuView: View {
#State var tags = ["#tag1", "#tag2", "#tag3", "#tag4"]
var body: some View {
ForEach(tags, id:\.self) { tag in
Button {
} label: {
if selectedTags.contains(tag) {
Label(tag, systemImage: "checkmark")
} else {
Text(tag)
}
}
}
Section {
Button {
} label: {
Label("Configure", systemImage: "ellipsis")
}
}
}
}
The main problem right now is when I press any of this buttons, drop down is closed, while I want this button to only toggle selected status.
I tried to use Text instead of buttons but didn't find way to handle tap gestures from them, they look like being disabled by some view modifier.
What is the optimal way to handle it?
I want to hide the navigationbarTitle for my views, but keep a custom value for the back button in the child views. So the NavigationBar should be visible in every screen, but should not have a title. But i want to change the text 'Back' to a custom text.
NavigationLink {
SomeChildView()
} label: {
SomeView()
}.navigationBarTitle("Text for back button in child view")
If I set the title on the NavigationLink this gives me my custom back button text but also displays the title in the Parent View.
You can achieve what you need by using the environment value of presentationMode to dismiss the screen you are in by code, and for changing the back button label and style you can simply do
hide the back button by using .navigationBarBackButtonHidden(true) modifier
use toolbar and toolbarItem to add your custom back button to the NavigationBar
here an example of how you can use it
// FirstScreen
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink {
SecondScreen()
} label: {
Text("Go To Second Screen")
}
}
}
}
// SecondScreen
struct SecondScreen: View {
#Environment(\.presentationMode) var presentationMode
var body: some View {
Text("Second Screen")
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
Button {
presentationMode.wrappedValue.dismiss()
} label: {
Label("Custom Back Button", systemImage: "command")
.labelStyle(.titleAndIcon)
}
}
}
}
}
I want to push a view when tapping the trailing button of the navigation bar, so I tried this:
struct ContentView: View {
var body: some View {
NavigationView {
MyListView()
.navigationBarTitle(Text("Main list title"))
.navigationBarItems(trailing: Button("Add", action: {
NavigationLink(destination: MyFormView()) {
Text("Hello!")
}
}))
}
}
}
But nothing happens when I tap the button and I end up having an issue on the NavigationLink line:
Result of 'NavigationLink<Label, Destination>' initializer is unused
Thank you for helping me
I'm running into some weird behavior, trying to get a simple modal to pop after it has been dismissed.
I have an Add button in the NavigationBar that pops a modal. The modal has a button that will dismiss it, which works. However, I cannot interact with the Add button in the NavigationBar again until I interact with something else on the screen, such as scrolling the List below.
I have also placed another Add button, just for kicks, in the List itself, which always works.
Here's the code for the main view:
import SwiftUI
struct ContentView: View {
#State var displayModal: Bool = false
var body: some View {
NavigationView {
List {
Text("Hello again.")
Button(action: { self.displayModal = true }) {
Text("Add")
}
}
.sheet(isPresented: $displayModal) {
Modal(isPresented: self.$displayModal)
}
.navigationBarTitle("The Title")
.navigationBarItems(trailing: Button(action: { self.displayModal = true }) {
Text("Add")
})
}
}
}
And the modal, for completeness:
import SwiftUI
struct Modal: View {
#Binding var isPresented: Bool
var body: some View {
VStack {
HStack {
Button(action: {
self.isPresented = false
}) {
Text("Cancel")
}
.padding()
Spacer()
}
Text("I am the modal")
Spacer()
}
}
}
The only thing I can think of is that something invisible is preventing me from working with the NavigationBar button. So I fired up the UI Debugger, and here's what the ContentView looks like. Note the NavigationBar button.
Now, after I tap the button and display the modal, and then use the UI Debugger to see the ContentView again, all the same elements are in place, but the Button parent views are offset a bit, like this:
Once I drag the List up and down, the UI Debugger shows a view hierarchy identical to the first image.
Does anyone have any idea what's going on here?
I'm using Xcode 11.2.1 and iOS 13 on an iPhone 11 Pro simulator, but have also observed this on my iPhone.
It is really a bug. The interesting thing is that after 'drag to dismiss' the issue is not observed, so it is a kind of 'sync/async' state changing or something.
Workaround (temporary of course, decreases visibility almost completely)
.navigationBarItems(trailing: Button(action: { self.displayModal = true }) {
Text("Add").padding([.leading, .vertical], 4)
})
I ran into the same issue, and for me the workaround was to use an inline-style navigation bar title on the presenter.
.navigationBarTitle(Text("The Title"), displayMode: .inline)
HOWEVER, if you use a custom accent color on your ContentView (like .accentColor(Color.green)), this workaround no longer works.
Edit: the bug seems fixed in 13.4, and no workarounds are needed anymore.
I have a list in a Navigation View, with a trailing navigation button to add a list item. The button opens a modal sheet. When I dismiss the sheet (by pulling it down), the sheet pops right back up again automatically and I can't get back to the first screen. Here's my code.
struct ListView: View {
#ObservedObject var listVM: ListViewModel
#State var showNewItemView: Bool = false
init() {
self.listVM = ListViewModel()
}
var body: some View {
NavigationView {
List {
ForEach(listVM.items, id: \.dateCreated) { item in
HStack {
Text(item.name)
Spacer()
Image(systemName: "arrow.right")
}
}
}
.navigationBarTitle("List Name")
.navigationBarItems(trailing: AddNewItemBtn(isOn: $showNewItemView))
}
}
}
struct AddNewItemBtn: View {
#Binding var isOn: Bool
var body: some View {
Button(
action: { self.isOn.toggle() },
label: { Image(systemName: "plus.app") })
.sheet(
isPresented: self.$isOn,
content: { NewItemView() })
}
}
I am getting this error:
Warning: Attempt to present <_TtGC7SwiftUIP13$7fff2c603b7c22SheetHostingControllerVS_7AnyView_: 0x7fc5e0c1f8f0> on which is already presenting (null)
I've tried toggling the bool within "onDismiss" on the button itself, but that doesn't work either. Any ideas?
Turns out putting the button in the navigationBarItems(trailing:) modifier is the problem. I just put the button in the list itself instead of in the nav bar and it works perfectly fine. Must be some kind of bug.