2

How to pop View programmatically in SwiftUI

 1 year ago
source link: https://sarunw.com/posts/how-to-pop-view-from-navigation-view-in-swiftui/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

How to pop View programmatically in SwiftUI

22 Mar 2023 ⋅ 6 min read ⋅ SwiftUI

Table of Contents

There are many ways to pop a view out of a navigation view in SwiftUI.

Which technique to use is based on the iOS version you supported and how you structure your view.

In this article, I will focus on the old version of a navigation view, NavigationView.

In other words, this is an article for an app that supports iOS 15 and lower.

In iOS 16, Apple did a big revamp on a navigation view architecture. They deprecated the NavigationView and replaced it with the NavigationStack.

If you support iOS 16 forward, I have covered it here

There are many ways to pop or dismiss a view from a navigation view.

I will categorize them into two groups.

Binding

The first approach is quite straightforward. We create a binding that represents a showing state of a destination view.

There are two data types that we can use as a binding.

isActive

NavigationLink can take a binding of a boolean value. This boolean value represents the showing state of a view.

  • If the value is true, the view is pushed to a navigation stack.
  • If the value is false, the view is popped or dismissed.
struct ContentView: View {
// 1
@State private var isShowingDetail = false

var body: some View {
NavigationView {
// 2
NavigationLink("Detail", isActive: $isShowingDetail) {
Text("Detail View")
}
.navigationTitle("Home")
}
}
}

1 Create a binding representing a showing state of a detail view.
2 Then assign that to the isActive parameter in a NavigationLink.

Using a binding is a way for a parent view to control the presentation state, but if you want a destination view to be able to dismiss itself, you need to pass this binding to the pushed view.

Destination view to dismiss itself

If you want the pushed view to be able to pop itself from a navigation stack, you need to pass this binding to the pushed view.

DetailView

struct DetailView: View {
// 1
@Binding var isShowing: Bool

var body: some View {
Button("Dismiss") {
// 2
isShowing = false
}
.navigationTitle("Detail Title")

}
}

1 Create a binding in the destination view. In this case, I create a new DetailView to use as a destination view.
2 Set this binding value to false to dismiss itself.

ContentView

struct ContentView: View {
@State private var isShowingDetail = false

var body: some View {
NavigationView {
NavigationLink("Detail", isActive: $isShowingDetail) {
// 1
DetailView(isShowing: $isShowingDetail)
}
.navigationTitle("Home")
}
}
}

1 Passing the binding value from the parent when creating a navigation link.

isActive.

isActive.

Tag and Selection

Creating a boolean value for each destination view can easily get out of hand if you have multiple destinations.

Here is an example where I try to create a binding value for each destination view. As you can see, it can be cumbersome.

@State private var isShowing1 = false
@State private var isShowing2 = false
@State private var isShowing3 = false

var body: some View {
NavigationView {
List {
NavigationLink("1", isActive: $isShowing1) {
Text("View 1")
}
NavigationLink("2", isActive: $isShowing2) {
Text("View 2")
}
NavigationLink("3", isActive: $isShowing3) {
Text("View 3")
}
}
}
}

Luckily, SwiftUI provides a way to control this via a Hashable value and selection.

The concept is simple:

  • We tag each destination view with a unique hashable view.
  • Then, we select which view to present by setting that value to the selection value.

This is how to use it.

struct ContentView: View {
// 1
@State private var selection: String? = nil

var body: some View {
NavigationView {
List {
// 2
NavigationLink("1", tag: "1", selection: $selection) {
Text("View 1")
}
NavigationLink("2", tag: "2", selection: $selection) {
Text("View 2")
}
NavigationLink("3", tag: "3", selection: $selection) {
Text("View 3")
}
}
.navigationTitle("Home")
}
}
}

1 Instead of declaring multiple boolean values, we declare one hashable value. In this case, we use a string. We will use this value to represent the current active destination view.
2 For each NavigationLink, we assign a tag and binding of selection. If the value in the selection matches the tag, that view will get pushed.

Tag and Selection.

Tag and Selection.

Destination view to dismiss itself

If you want the pushed view to be able to pop itself from a navigation stack, you need to pass this binding to the pushed view.

DetailView

struct DetailView: View {
let title: String
// 1
@Binding var selection: String?

var body: some View {
Button("Dismiss") {
// 2
selection = nil
}
.navigationTitle(title)
}
}

1 Create a binding in the destination view.
2 Set this binding value to nil to dismiss itself.

Then, we pass the selection on creating a navigation link.
ContentView

struct ContentView: View {
@State private var selection: String? = nil

var body: some View {
NavigationView {
List {
NavigationLink("1", tag: "1", selection: $selection) {
// 1
DetailView(title: "View 1", selection: $selection)
}
NavigationLink("2", tag: "2", selection: $selection) {
DetailView(title: "View 2", selection: $selection)
}
NavigationLink("3", tag: "3", selection: $selection) {
DetailView(title: "View 3", selection: $selection)
}
}
.navigationTitle("Home")
}
}
}

1 Passing the parent's binding value when creating a navigation link.

Dismiss the view by setting the selection value to nil.

Dismiss the view by setting the selection value to nil.

Environment Value

Another way to pop or dismiss a view from a navigation stack is by using an Environment value.

This approach doesn't require a parent view to create or pass any binding to the destination view.

There are two Environment values to use based on the minimum iOS version you supported.

Before iOS 15: PresentationMode

Before iOS 15, the environment value that we use to control the dismissal is the PresentationMode environment value.

struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("Detail") {
DetailView()
}
.navigationTitle("Home")
}

}
}

struct DetailView: View {
// 1
@Environment(\.presentationMode) var presentationMode

var body: some View {
Button("Dismiss") {
// 2
presentationMode.wrappedValue.dismiss()
}
.navigationTitle("Detail Title")

}
}

1 Declare the presentationMode environment value in the destination view (DetailView).
2 Then call presentationMode.wrappedValue.dismiss() to perform the dismissal. In this case, we add a button that pops a view back.

As you can see, the parent view doesn't need to pass anything to the DetailView.

PresentationMode.

PresentationMode.

iOS 15: DismissAction

In iOS 15, Apple introduced a new environment value, DismissAction, and deprecated the old PresentationMode.

The way we use it is quite the same. Instead of calling presentationMode.wrappedValue.dismiss() to dismiss, we call DismissAction directly.

struct ContentView: View {
var body: some View {
NavigationView {
NavigationLink("Detail") {
DetailView()
}
.navigationTitle("Home")
}

}
}

struct DetailView: View {
// 1
@Environment(\.dismiss) private var dismiss

var body: some View {
Button("Dismiss") {
// 2
dismiss()
}
.navigationTitle("Detail Title")
}
}

1 We declare the dismiss environment value in the destination view (DetailView).
2 Then call dismiss() to perform the dismissal.

The process and result are the same using the PresentationMode.

DismissAction

DismissAction


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK