SwiftUI "Push" NavigationView/NavigationLink not working when trying to create multi-screen flow - swiftui

In SwiftUI, I set up a 4 screen flow (1>2>3>4) where the user would hit "next" on each to navigate to the next screen - just like a typical push flow in UIKit. Im using "programmatic" NavigationLinks (e.g. isActive parameter) for flexibility. It gets to screen 3, but for some reason hitting next on screen 3 doesn't navigate to screen 4. Can't figure it out.
struct FlowView: View {
#State var navigateToScreen2 = false
#State var navigateToScreen3 = false
#State var navigateToScreen4 = false
var body: some View {
NavigationView {
VStack {
Text("Screen 1")
Button(action: { self.navigateToScreen2 = true }, label: { Text("Next") })
NavigationLink(destination:
VStack {
Text("Screen 2")
Button(action: { self.navigateToScreen3 = true }, label: { Text("Next") })
NavigationLink(destination:
VStack {
Text("Screen 3")
Button(action: { self.navigateToScreen4 = true}, label: { Text("Next") })
NavigationLink(destination:
Text("Screen 4"),
isActive: self.$navigateToScreen4,
label: { EmptyView() }
)
},
isActive: self.$navigateToScreen3,
label: { EmptyView() }
)
},
isActive: self.$navigateToScreen2,
label: { EmptyView() }
)
}
}
}
}

I would do it like this. It works and can be much better read:
struct ContentView: View {
#State private var navigateToScreen2 = false
var body: some View {
NavigationView {
NavigationLink(destination: View2(), isActive: $navigateToScreen2) {
Text("View1")
}
}
}
}
struct View2: View {
#State private var navigateToScreen3 = false
var body: some View {
NavigationLink(destination: View3(), isActive: $navigateToScreen3) {
Text("View2")
}
}
}
struct View3: View {
#State private var navigateToScreen4 = false
var body: some View {
NavigationLink(destination: View4(), isActive: $navigateToScreen4) {
Text("View3")
}
}
}
struct View4: View {
var body: some View {
Text("View4")
}
}

Related

SwiftUI: two column NavigationView not loading details after rotation?

I have the following SwiftUI code:
struct ContentView: View {
var body: some View {
NavigationView {
Form {
Section {
NavigationLink {
DetailsView()
} label: {
Text("Show details")
}
}
}
Text("Select details")
}
}
}
struct DetailsView: View {
#State var showModal = false
var body: some View {
ZStack {
Color.gray.ignoresSafeArea()
VStack {
Spacer()
Button {
showModal.toggle()
} label: {
Text("Show modal")
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
}
}
}
.fullScreenCover(isPresented: $showModal) {
MyModalView {
showModal = false
}
}
}
}
struct MyModalView: View {
var someAction:()->()
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
Button {
someAction()
} label: {
Text("some action")
.foregroundColor(.white)
}
}
}
}
I am experiencing the following bug, where tapping on "Show details" won't show the DetailsView anymore after rotation...
How can I fix this?
Navigation view is really problematical but will be improved with IOS 16. Here is my solution
struct ContentView: View {
#State var navigate = false
var body: some View {
NavigationView {
Form {
Section {
NavigationLink(destination: DetailsView(navigate: $navigate), isActive: $navigate){
Text("Show details")
}
}
}
Text("Select details")
}
.navigationViewStyle(.automatic)
}
}
struct DetailsView: View {
#Binding var navigate: Bool
#State var showModal = false
var body: some View {
ZStack {
Color.gray.ignoresSafeArea()
VStack {
Spacer()
Button {
showModal.toggle()
} label: {
Text("Show modal")
.foregroundColor(.black)
.font(.system(size: 20, weight: .bold))
}
}
}
.fullScreenCover(isPresented: $showModal) {
MyModalView {
showModal = false
}
}
.onAppear(){
navigate = false
}
}}
struct MyModalView: View {
var someAction:()->()
var body: some View {
ZStack {
Color.black.ignoresSafeArea()
Button {
someAction()
} label: {
Text("some action")
.foregroundColor(.white)
}
}
}}

How to close Half sheet in this situation?

I want to close the half sheet and back to the root view In swiftUI when with navigationLink we go to the another view. dismiss() doesn't work and turn us back to the previous view not the root view.
struct ContentView: View {
#State var showFirstSheetView = false
var body: some View {
NavigationView {
VStack {
Text("half sheet")
.onTapGesture {
showFirstSheetView.toggle()
}
}
.navigationTitle("Root view")
.sheet(isPresented: $showFirstSheetView) {
halfSheet()
}
}
}
}
struct halfSheet : View {
#State var showSecondView = false
var body: some View {
NavigationView {
VStack {
}
.background(
NavigationLink(isActive: $showSecondView, destination: {
SecondView()
}, label: {
EmptyView()
})
)
.navigationTitle("First view")
.navigationBarTitleDisplayMode(.inline)
.navigationBarItems( trailing: Button(action: {
showSecondView.toggle()
}) {
Text("Next")
}
)
}
}
}
struct SecondView : View {
#Environment(\.dismiss) var dismiss
var body: some View {
VStack {
}
.navigationTitle("Second view")
.navigationBarTitleDisplayMode(.inline)
.navigationBarBackButtonHidden(true)
.navigationBarItems(leading: Button(action: {
dismiss()
}) {
HStack(spacing : 5) {
Image(systemName: "chevron.backward")
Text("Back")
}
}, trailing: Button(action: {
// How back to the root View?
// below code works but not compatible with ios 15 and gives a warning
// UIApplication.shared.windows.first?.rootViewController?.dismiss(animated: true)
}) {
Text("Done")
})
}
}

Programmatically Presenting and Dismissing Views SwiftUI

I am working on a project that is attempting to present and dismiss views in a NavigationView using state and binding. The reason I am doing this is there is a bug in the #Environment(.presentationMode) var presentaionMode: Binding
model. It's causing odd behavior. It's discussed in this post here.
The example below has three views that are progressively loaded on to the view. The first two ContentView to NavView1 present and dismiss perfectly. However, once NavView2 is loaded, the button that is used to toggle the state of presentNavView2 ends up adding another NavView2 view on the stack and does not dismiss it as expected. Any thoughts as to why this would be?
ContentView
struct ContentView: View {
#State private var presentNavView1 = false
var body: some View {
NavigationView {
List {
NavigationLink(destination: NavView1(presentNavView1: self.$presentNavView1), isActive: self.$presentNavView1, label: {
Button(action: {
self.presentNavView1.toggle()
}, label: {
Text("To NavView1")
}) // Button
}) // NavigationLink
} // List
.navigationTitle("Home")
} // NavigationView
} // View
}
NavView1
struct NavView1: View {
#State private var presentNavView2 = false
#Binding var presentNavView1: Bool
var body: some View {
List {
NavigationLink(destination: NavView2(presentNavView2: self.$presentNavView2), isActive: self.$presentNavView2, label: {
Button(action: {
self.presentNavView2.toggle()
}, label: {
Text("To NavView2")
}) // Button
}) // NavigationLink
Button(action: {
self.presentNavView1.toggle()
}, label: {
Text("Back")
})
} // List
.navigationTitle("NavView1")
} // View
}
NavView2
struct NavView2: View {
#Binding var presentNavView2: Bool
var body: some View {
VStack {
Text("NavView2")
Button(action: {
self.presentNavView2.toggle()
}, label: {
Text("Back")
}) // Button
} // VStack
.navigationTitle("NavView2")
}
}
You can use DismissAction, because PresentationMode will be deprecated. I tried the code and it works perfectly! Here you go!
import SwiftUI
struct MContentView: View {
#State private var presentNavView1 = false
var body: some View {
NavigationView {
List {
NavigationLink(destination: NavView1(), isActive: self.$presentNavView1, label: {
Button(action: {
self.presentNavView1.toggle()
}, label: {
Text("To NavView1")
})
})
}
.navigationTitle("Home")
}
}
}
struct NavView1: View {
#Environment(\.dismiss) private var dismissAction: DismissAction
#State private var presentNavView2 = false
var body: some View {
List {
NavigationLink(destination: NavView2(), isActive: self.$presentNavView2, label: {
Button(action: {
self.presentNavView2.toggle()
}, label: {
Text("To NavView2")
})
})
Button(action: {
self.dismissAction.callAsFunction()
}, label: {
Text("Back")
})
}
.navigationTitle("NavView1")
}
}
struct NavView2: View {
#Environment(\.dismiss) private var dismissAction: DismissAction
var body: some View {
VStack {
Text("NavView2")
Button(action: {
self.dismissAction.callAsFunction()
}, label: {
Text("Back")
})
}
.navigationTitle("NavView2")
}
}
struct MContentView_Previews: PreviewProvider {
static var previews: some View {
MContentView()
}
}

SwiftUI dismiss all active presented view controls and pop to root view controller

I'm trying to dismiss all active presented view controllers and pushed view controllers, but I'm facing some issue and not getting expected output.
ContentView -> Pushed(LoginView) -> Presented(HomeView) -> Expected back to (ContentView) directly without any animation lags and without showing intermediate animations. It should directly show content view.
here is my code:
import SwiftUI
struct FirstView: View {
#State var showLoginView = false
var body: some View {
NavigationView {
VStack(spacing: 16) {
Text("FirstView View")
.font(.largeTitle)
.foregroundColor(.red)
NavigationLink( destination: SecondView(), isActive: $showLoginView, label: {
Text("Show SecondView")
})
}
}
}
}
struct SecondView: View {
#State var showHomeView = false
#Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
var body: some View {
VStack(spacing: 16) {
Text("Second View")
.font(.largeTitle)
.foregroundColor(Color(.systemPurple))
Button(action: { showHomeView = true }, label: {
Text("Show ThirdView")
.padding()
})
}
.sheet(isPresented: $showHomeView, onDismiss: { presentationMode.wrappedValue.dismiss() }) {
ThirdView()
}
}
}
struct ThirdView: View {
#Environment(\.presentationMode) var presentationMode
#State var showFormView = false
var body: some View {
VStack(spacing: 16) {
Text("Third View")
.font(.largeTitle)
.foregroundColor(Color(.systemTeal))
Button(action: {
showFormView = true
presentationMode.wrappedValue.dismiss()
}, label: {
Text("Back to FirstView")
.padding()
})
}
}
}

How can I check a if screen is on the navigation stack?

Given this...
NavigationLink(destination: Text("Hello")) {
Text("Press")
}
And this...
.sheet(isPresented: $viewModel.showComplete) {
Text("Hello")
}
How can I make the sheet only open if a view opened by the NavigationLink doesn't currently exist?
You may access the isActive parameter of NavigationLink and use it in a custom binding to determine whether to open the sheet.
Here is a simple demo:
struct ContentView: View {
#State var showSheet = false
#State var linkActive = false
var binding: Binding<Bool> {
.init(get: {
showSheet && !linkActive
}, set: {
showSheet = $0
})
}
var body: some View {
NavigationView {
VStack {
NavigationLink(
destination: DetailView(showSheet: $showSheet),
isActive: $linkActive
) {
Text("Go to...")
}
Button("Open sheet") {
self.showSheet.toggle()
}
}
}
.sheet(isPresented: binding) {
Text("Hello")
}
}
}
struct DetailView: View {
#Binding var showSheet: Bool
var body: some View {
Button("Open sheet") {
self.showSheet.toggle()
}
}
}