SwiftUI: Problem with Picker inside ContextMenu of NavigationBarItem - swiftui

I'm trying to use Picker on .contextMenu of navigationBarItems. However, the behavior is not as I would expect and I can't tell if this is Apple bug or if I'm doing something wrong (XCode 13.4).
After the user selects an item from the dropdown, the variable value do change but reopening the context menu, the wrong item is marked as "selected".
I've created the most basic demo to illustrate it. Same behavior on real device (iPhone 11 / iOS 15.4.1).
struct ContentView: View {
#State private var isAutoRefresh = true
var body: some View {
NavigationView {
Text("Auto refresh is: \(String(isAutoRefresh))")
.navigationBarTitle("Demo")
.navigationBarItems(
trailing:
Button(action: {}, label: {
Image(systemName: "arrow.clockwise")
})
.contextMenu {
Picker(selection: self.$isAutoRefresh, label: Text("")) {
Text("Manual refresh").tag(false)
Text("Auto refresh").tag(true)
}
.pickerStyle(InlinePickerStyle())
}
)
}
}
}
Is this a bug? Is there any workaround I can use?

Context menu is created once and cached, so we need to rebuild it once anything changed outside.
Here is a fix. Tested with Xcode 13.3 / iOS 15.4
.contextMenu {
Picker(selection: self.$isAutoRefresh, label: Text("")) {
Text("Manual refresh").tag(false)
Text("Auto refresh").tag(true)
}
.pickerStyle(InlinePickerStyle())
}.id(isAutoRefresh) // << here !!
Alternate: Just to use Menu instead of Button with context menu (if applicable by design), like
Menu {
Picker(selection: self.$isAutoRefresh, label: Text("")) {
Text("Manual refresh").tag(false)
Text("Auto refresh").tag(true)
}
.pickerStyle(InlinePickerStyle())
} label: {
Image(systemName: "arrow.clockwise")
}

Related

SwiftUI extra space at top of list above section header. On testing device only

I have been trying to figure out what is causing the space at the top of the screen in my production app, so I made this test app to see if it is a bug or not. The code works as intended on a simulator but when a testing device runs the code it adds extra space. The space goes away after you start scrolling, and does not comeback until the view reloads. I have tried restarting the device and other devices. I took out .navigationTitle and .navigationBarTitleDisplayMode and it did not fix the problem. So far my best guess is that there is some problem with changing the section header in .onAppear(). Changing it to .task() seems to be a workaround for now.
struct DetailView: View {
#State var item: Item
#State private var headerText = "Header"
var body: some View {
List {
Section(header: Text("\(headerText)")) {
Text("Text")
}
HStack {
Text("Red Text")
}.listRowBackground(Color.red)
// Change to .task instead
}.onAppear {
headerText = "Change Header"
}
}
}
Edit: Here is the code for the list view, it is the default new project setup.
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink(destination: DetailView(item: item), label: {
Text(item.timestamp!, formatter: itemFormatter)
})
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
In my case this behaviour was caused by .navigationViewStyle(StackNavigationViewStyle()).
Looks like it's a bug with NavigationView in SwiftUI 4 since it wasn't like this before.
If you are on iOS 16 use NavigationStack instead NavigationView. This fixed all my problems and I didn't even need to use the navigationViewStyle anymore.
NavigationView is deprecated in favor of NavigationStack.

Adding a Navigation Link inside a menu [duplicate]

Can you use a NavigationLink as a Menu's item in swiftUI?
It seems to do just nothing:
Menu {
NavigationLink(destination: Text("test1")) {
Text("item1")
}
NavigationLink(destination: Text("test2")) {
Text("item2")
}
} label: {
Text("open menu")
}
In case it is meant to not work as tried above, is there an alternative way of achiving the intended reult?
init(destination:isActive:label:) is deprecated since iOS 16
'init(destination:isActive:label:)' was deprecated in iOS 16.0: use
NavigationLink(value:label:) inside a NavigationStack or
NavigationSplitView
NavigationLink should be inside NavigationView hierarchy. The Menu is outside navigation view, so put buttons inside menu which activate navigation link placed inside navigation view, eg. hidden in background.
Here is a demo of possible approach (tested with Xcode 12.1 / iOS 14.1)
struct DemoNavigateFromMenu: View {
#State private var navigateTo = ""
#State private var isActive = false
var body: some View {
NavigationView {
Menu {
Button("item1") {
self.navigateTo = "test1"
self.isActive = true
}
Button("item2") {
self.navigateTo = "test2"
self.isActive = true
}
} label: {
Text("open menu")
}
.background(
NavigationLink(destination: Text(self.navigateTo), isActive: $isActive) {
EmptyView()
})
}
}
}
I can say that Asperi's answer is great solution. It helped a lot. But we need a custom view to hold a reference inside the destination property right? not a string.
#State var navigateTo: AnyView?
#State var isNavigationActive = false
We can hold a reference AnyView type and then call the view like this:
Menu {
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create an Item", systemImage: "doc")
}
Button {
navigateTo = AnyView(CreateItemView())
isNavigationActive = true
} label: {
Label("Create a category", systemImage: "folder")
}
} label: {
Label("Add", systemImage: "plus")
}
For more detail please see this post:
https://developer.apple.com/forums/thread/119583

SwiftUI iOS 15 Keyboard toolbar doesn't show for textfield

I'm pretty new to SwiftUI, trying to teach myself a few things here and there. But there's this one issue that's been eating at me for a while... and I can't figure out why the toolbar doesn't work/show for me.
The sample code is below, but the button doesn't show nor is there an actual bar. I have iOS 15.2, with XCode 13.2 beta.
TextField("placeholder", text: $text)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
HStack {
Button(action: {
hideKeyboard()
}) {
Text("Done")
}
}
}
}
EDIT:
Figured out the reason why... it just wouldn't work in a scroll view for some reason. Anyone know why?
Under iOS 15.2 - Xcode 13.2 (not beta).
struct ContentView: View {
#State var text: String = ""
var body: some View {
NavigationView {
VStack {
Text(text)
TextField("Enter your name", text: $text)
}
.padding()
.navigationTitle("SwiftUI")
.toolbar {
// ToolbarItem(placement: .keyboard) {
// Button("Ok") {
// print("ok")
// }
// }
ToolbarItemGroup(placement: .keyboard) {
HStack {
Button("Press Me") {
print("Pressed")
}
Spacer()
Button(action: {
print("done")
}) { Text("Done") }
}
}
}
}
}
}
Make sure you put everything in a VStack or similar.
Xcode 13.3 (release version), in order for the toolbar to show up, the TextField and the .toolbar need to be inside of a NavigationView.
In my particular case I didn't want a NavigationBar, so I ended up with something like this to make it work:
var body: some View {
NavigationView {
VStack {
TextField("Enter your name", text: $text)
// Additional text fields go here, all text fields will get the toolbar.
}
.navigationTitle("")
.navigationBarHidden(true)
.navigationBarBackButtonHidden(true)
.toolbar {
ToolbarItemGroup(placement: .keyboard) {
Spacer()
Button("Dismiss") {
print("Bismiss keyboard...")
}
}
}
}
}

SwiftUI - TabView with nested NavigationViews resets Navigationflow

I'm having a TabView with Navigation Views in it.
struct ContentView: View {
var body: some View {
TabView {
NavigationView {
NavigationLink("Link to my first Navigation Level", destination: MyFirstView())
}
.tabItem {
Image(systemName: "house")
Text("Home")
}.tag(0)
NavigationView {
Text("Second Nav Page")
}
.tabItem {
Image(systemName: "gear")
Text("Settings")
}.tag(1)
}
}
}
struct MyFirstView: View {
#State var selectedTag: String?
var body: some View {
VStack {
Text("My First View")
NavigationLink("(Working) Link to my second Navigation Level", destination: MySecondView())
Text("But the Button in the Navigation Bar doesn't work")
}
.navigationBarTitle("My First View", displayMode: .inline)
.navigationBarItems(
leading: HStack {
NavigationLink(destination: MySecondView(), tag: "xx", selection: $selectedTag ){
Button(action: {
print("Settings button pressed...")
self.selectedTag = "xx"
}) {
Image(systemName: "gearshape.2.fill").imageScale(.large)
}
}
}
)
}
}
struct MySecondView: View {
var body: some View {
Text("My Second View")
}
}
Now I got a super weired behavior. If I click on "Link to my first Navigation Level" and then on "(Working) Link to my second Naviation Level" the journey works. If I click on "Back" while being in the second Navigation Level it goes back to the first Navigation Level.
Issue:
When I click on the gear symbol in the Navbar on the first Navigation Level it kind of escapes from the nested Navigation and sets it to the top level. This has the consequence that when I click on "Back" it brings me from the Second Navigation Level back to the very root screen but my expected behavior is that it should go back to the first Navigation Level.
Any ideas what I'm doing wrong? I'm using Xcode 12.2 beta 3 and iOS 14.2 (not sure if it's a beta bug).
Many Thanks!
This did the trick (thanks #Asperi). Has to be outside tabBarItems
NavigationLink(destination: AddDetailView(existingItem: nil),
isActive: $addMode) { EmptyView() }

SwiftUI ToolbarItem doesn't present a View from a NavigationLink

I don't know if this is a bug or I am doing something wrong here. I've added a new button on the Navigation bar that would present a new view.
struct MyView: View {
#ObservedObject var viewModel = MyViewModel()
var body: some View {
List(viewModel.data, id: \.name) { data in
NavigationLink(destination: MyDetailView(data: data.name)) {
Text(data.name)
}
}
.listStyle(InsetGroupedListStyle())
.edgesIgnoringSafeArea(.all)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
NavigationLink(destination: MyDetailView()) {
Text("New Element")
}
}
}
}
}
This is being tested on the newest iOS 14 beta (beta 6) and Xcode 12 (beta 6). As far as I know a Navigation Link presents fine the new view when on a List but in the toolbar as shown that's not the case. The button on the toolbar it's visible and active but doesn't trigger showing the new view.
I found using an HStack with an empty text as the first element also works, it lets the navigationLink act correctly.
.toolbar {
ToolbarItem(placement: .navigationBarLeading) {
HStack {
Text("")
NavigationLink(destination: SettingsView()) {
Image(systemName: "gear")
.font(.title)
}
}
}
NavigationLink should be inside NavigationView. Toolbar is not in NavigationView, put buttons in it.
So assuming you have somewhere in parent
NavigationView {
MyView()
}
here is a solution:
struct MyView: View {
#ObservedObject var viewModel = MyViewModel()
#State private var showNew = false
var body: some View {
List(viewModel.data, id: \.name) { data in
NavigationLink(destination: MyDetailView(data: data.name)) {
Text(data.name)
}
}
.listStyle(InsetGroupedListStyle())
.background(
NavigationLink(destination: MyDetailView(), isActive: $showNew) {
EmptyView()
}
)
.edgesIgnoringSafeArea(.all)
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
Button("New Element") {
self.showNew = true
}
}
}
}
}
Using Asperi solution may not work if your navigation link directs to a view with keyboard input.
After navigation link, toolbar in the new view loaded correctly but when providing input with keyboard and dismissing keyboard all toolbar items disappears.
The solution is to place NavigationLink not in View but in navigationBarItems, example:
.navigationBarItems(
leading:
NavigationLink(
destination: Creator(),
isActive: $showCreator,
label: {
Text("")
}))