Note: This is a simplified version of my original problem.
Problem: Why when I tap anywhere within my view, I activate the NavigationLink when the 'isActive' parameter is set to false?
Expectation: *** No Action ***
I believe this is a bug.
import SwiftUI
struct ContentView: View {
#State private var isDataResultViewActive = false
var body: some View {
NavigationView {
NavigationLink(destination: EmptyView(), isActive: $isDataResultViewActive) {
Text("Hello, world!")
.padding()
}
}
}
}
I had applied the NavigationLink template INCORRECTLY.
Solution:
I should wrap the NavigationLink around a button that toggles it; NOT the entire container view (which doing so, causes trigger when user taps ANYWHERE with the container view, which I don't want). ☝️
Related
When trying to navigate back from a view using the environment Dismiss value while also focussing on an empty searchable modifier the view you navigated back to becomes unresponsive. This is due to an empty UIView blocking any interaction with the view as seen in this screenshot:
Empty UIView blocking view after navigating back
This only occurs when the searchbar is focussed and empty when trying to navigate back. When there's a value in the searchbar everything works:
GIF of the bug
Am I doing something wrong here?
Tested on Xcode 14.2 iPhone 14 Pro (iOS 16.0) simulator.
import SwiftUI
struct MainPage: View {
var body: some View {
if #available(iOS 16.0, *) {
NavigationStack {
Text("Main view")
NavigationLink(destination: DetailView()) {
Text("Click me")
}
}
}
}
}
struct DetailView: View {
#Environment(\.dismiss) private var dismiss
#State private var searchText = ""
var body: some View {
VStack {
Text("Detail view")
Button("Go back") {
dismiss()
}
}
.searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .always))
}
}
This bug only seems to happen when using NavigationStack or NavigationView with a .navigationViewStyle(.stack). When using NavigationView without a navigationViewStyle it seems to work fine. Currently I can work around this using the latter but I would prefer to use NavigationStack as NavigationView has become deprecated since iOS 16.0.
Any help is appreciated.
I have an app which presents a sheet. It works for the first time but when I click on it again it does not work. I am making isPresented false when you dismiss a sheet but when I tap on the Filter button again, it does not show the sheet.
ContentView
struct ContentView: View {
#State private var isPresented: Bool = false
var body: some View {
NavigationView {
List(1...20, id: \.self) { index in
Text("\(index)")
}.listStyle(.plain)
.navigationTitle("Hotels")
.toolbar {
Button("Filters") {
isPresented = true
}
}
.sheet(isPresented: $isPresented) {
isPresented = false
} content: {
FilterView()
}
}
}
}
FilterView:
import SwiftUI
struct FilterView: View {
#Environment(\.presentationMode) private var presentationMode
var body: some View {
ZStack {
Text("FilterView")
Button {
// action
presentationMode.wrappedValue.dismiss()
} label: {
Text("Dismiss")
}
}
}
}
struct FilterView_Previews: PreviewProvider {
static var previews: some View {
FilterView()
}
}
A couple of things to note from my experience.
Firstly, when using the isPresented binding to show a sheet, you don't need to reset the bound value in a custom onDismiss handler to reset it to false - that's handled for you internally by SwiftUI as part of the dismiss action.
So your modifier can be simplified a little:
.sheet(isPresented: $isPresented) {
FilterView()
}
Secondly, when running an app in the Simulator I've noticed that when you come back to the main view after dismissing a sheet you have to interact with the app somehow before clicking on the toolbar button, or the action won't trigger.
In cases like this, just scrolling the list up or down a little would be enough, and then the toolbar button works as you'd expect.
I've not encountered the same thing while running apps on a physical device – whether that's because the bug isn't present, or just that it's a lot easier to interact with the app in some microscopic form of gesture, I couldn't say.
I am quite new to swiftUI. I have created a grid view on tapping on which I want to go to next screen. But somehow I am not able to manage to push to next screen. I am doing like this:
var body: some View {
NavigationView {
ScrollView {
LazyVGrid(columns: gridItems, spacing: 16) {
ForEach(viewModel.pokemon) { pokemon in
PokemonCell(pokemon: pokemon, viewModel: viewModel)
.onTapGesture {
NavigationLink(destination: PokemonDetailView(pokemon: pokemon)) {
Text(pokemon.name)
}
}
}
}
}
.navigationTitle("Pokedex")
}
}
Upon doing like this, I am getting a warning stating
Result of 'NavigationLink<Label, Destination>' initializer is unused
Can someone please guide me, how to do this?
.onTapGesture adds an action to perform when the view recognizes a tap gesture. In your case you don't need to use .onTapGesture. If you want to go to another view when cell is tapped you need to write NavigationLink as below.
NavigationLink(destination: PokemonDetailView(pokemon: pokemon)) {
PokemonCell(pokemon: pokemon, viewModel: viewModel)
}
If you want to use .onTapGesture, another approach is creating #State for your tapped cell's pokemon and using NavigationLink's isActive binding. So when user tap the cell it will change the #State and toggle the isActive in .onTapGesture. You may need to add another Stack (ZStack etc.) for this.
NavigationView {
ZStack {
NavigationLink("", destination: PokemonDetailView(pokemon: pokemon), isActive: $isNavigationActive).hidden()
ScrollView {
// ...
Discovered in my app that navigation bar items in some views disappear when orientation of the device changes. This seems to occur only in a view that is opened using NavigationLink, on main view navigation bar items work as expected. It appears that something has changed between iOS 13.7 and iOS 14.2 related to this. Also, it does not seem to matter whether using leading or trailing items, both disappear.
Example snippet where this occurs:
struct ContentView: View {
var detailView: some View {
Text("This is detail view")
.navigationBarTitle("Detail view title", displayMode: .inline)
.navigationBarItems(trailing: Button(action: {}, label: {
Image(systemName: "pencil.circle.fill")
}))
}
var body: some View {
NavigationView {
NavigationLink(
destination: detailView,
label: {
Text("Open detail view")
})
.navigationBarTitle("Main view")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
The issue occurs only when running on a real device. (iPhone 11 in my case) On simulator everything works as expected.
Anyone else seen similar issues? Workarounds/fixes?
.navigationBarTitle and .navigationBarItems are being deprecated. I think that the best "fix" is to switch to .toolbar
It's a weird issue but I guess there is a hack to make it work.
SwiftUI doesn't call body property when rotations happen. So you can add #State UIDeviceOrientation property to your view and update it every time orientation changes. One tricky thing is to use that property somewhere in the body of the view since SwiftUI smart enough to ignore #State that is not used in the body.
struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink(
destination: DetailsView(),
label: {
Text("Open detail view")
}).navigationBarTitle("Main view")
}.navigationViewStyle(StackNavigationViewStyle())
}
}
struct DetailsView: View {
#State var orientation: UIDeviceOrientation = UIDevice.current.orientation
var body: some View {
Text("This is detail view")
.navigationBarTitle("Detail view title")
.navigationBarItems(trailing: button)
.onReceive(NotificationCenter.default.publisher(for: UIDevice.orientationDidChangeNotification)) { _ in
orientation = UIDevice.current.orientation
}.background(Text("\(orientation.rawValue)"))
}
var button: some View {
Button(action: { print("123") }, label: {
Image(systemName: "pencil.circle.fill")
}).id("123")
}
}
In my experience when I change a Button in the toolbar from disabled to enabled, they disappear. But if I scroll to the bottom of the View, they re-appear. If I am already at the end of the View when the button is enabled, it acts normally, until I then scroll away from the bottom, the button again disappears.
Try scrolling to the bottom of your view, if you use landscape.
I'm trying to use .alert and .onAppear for one of my views, and I want .onAppear to be called first, because .alert is dependent on a variable in .onAppear. Any way to specifically order ui controls?
SwiftUI provides a declarative user interface rather than the more imperative paradigm used in UIKit. So instead of using a sequence of commands to modify state and views separately, a SwiftUI view is defined as a function of its state.
So in this case the order of .onAppear and .alert does not change the presentation of the alert.
struct Demo: View {
#State var isAlertPresented = false
var body: some View {
Text("Hello, World!")
.onAppear() {
self.isAlertPresented = true
}
.alert(isPresented: self.$isAlertPresented) {
Alert(title: Text("Alert!"))
}
}
}
In the example above, when the Text view appears, .isAlertPresented changes to true. Separately, an Alert is added that will display whenever .isAlertPresented is true.
The example below has the same result, even changing the order of the two ViewModifiers.
struct Demo: View {
#State var isAlertPresented = false
var body: some View {
Text("Hello, World!")
.alert(isPresented: self.$isAlertPresented) {
Alert(title: Text("Alert!"))
}
.onAppear() {
self.isAlertPresented = true
}
}
}