Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SwiftUI - Is there a popViewController equivalent in SwiftUI?

Tags:

swift

swiftui

I was playing around with SwiftUI and want to be able to come back to the previous view when tapping a button, the same we use popViewController inside a UINavigationController. Is there a provided way to do it so far ?

I've also tried to use NavigationDestinationLink to do so without success.

struct AView: View {
    var body: some View {
        NavigationView {
            NavigationButton(destination: BView()) {
                Text("Go to B")
            }
        }
    }
}

struct BView: View {
    var body: some View {
        Button(action: {
            // Trying to go back to the previous view
            // previously: navigationController.popViewController(animated: true)
        }) {
            Text("Come back to A")
        }
    }
}
like image 911
Alexandre Legent Avatar asked Jun 07 '19 10:06

Alexandre Legent


People also ask

Does SwiftUI use Viewcontrollers?

SwiftUI works seamlessly with the existing UI frameworks on all Apple platforms. For example, you can place UIKit views and view controllers inside SwiftUI views, and vice versa.

Does SwiftUI have Viewdidload?

SwiftUI gives us equivalents to UIKit's viewDidAppear() and viewDidDisappear() in the form of onAppear() and onDisappear() . You can attach any code to these two events that you want, and SwiftUI will execute them when they occur.

What is a NavigationView in SwiftUI?

NavigationView is one of the most important components of a SwiftUI app, allowing us to push and pop screens with ease, presenting information in a clear, hierarchical way for users.

What is identifiable view in SwiftUI?

In general, identifiable allows the SwiftUI engine to optimize the memory by identifying if the given view needs to be created from scratch or if the SwiftUI engine can reuse already created View. The second element of this puzzle is CircleButton which is a little more advanced View then PageView.

What's new in SwiftUI?

SwiftUI was first introduced at WWDC 2019, and the API has rapidly improved over the last two years. Apple has really made significant improvements to SwiftUI, including UI components, layouts, and animation. Until WWDC21, SwiftUI had not yet included a dedicated CollectionView or Compositional Layouts to replace the UICollectionView in UIKit.

How to animate pop-ups in SwiftUI?

In SwiftUI animations can be as simple as adding a one-line modifier, as we do in the end of building the sheet view. Of course, you can instead animate it in any way you want - in fact, changing the position and the animation of the popup could be enough to make another type of a UI element (for example, a toast).

Is there a better way to present a sfsafariviewcontroller in SwiftUI?

A better way to present a SFSafariViewController or start a ASWebAuthenticationSession in SwiftUI. SwiftUI is a strong, intuitive way to build user interfaces, but was released with some part of existing elements missing. One example of those missing elements is the SFSafariViewController.


10 Answers

Modify your BView struct as follows. The button will perform just as popViewController did in UIKit.

struct BView: View {
    @Environment(\.presentationMode) var mode: Binding<PresentationMode>
    var body: some View {
        Button(action: { self.mode.wrappedValue.dismiss() })
        { Text("Come back to A") }
    }
}
like image 135
Chuck H Avatar answered Oct 14 '22 02:10

Chuck H


Use @Environment(\.presentationMode) var presentationMode to go back previous view. Check below code for more understanding.

import SwiftUI

struct ContentView: View {


    var body: some View {

        NavigationView {
            ZStack {
                Color.gray.opacity(0.2)

                NavigationLink(destination: NextView(), label: {Text("Go to Next View").font(.largeTitle)})
            }.navigationBarTitle(Text("This is Navigation"), displayMode: .large)
                .edgesIgnoringSafeArea(.bottom)
        }
    }
}

struct NextView: View {
    @Environment(\.presentationMode) var presentationMode
    var body: some View {
        ZStack {
            Color.gray.opacity(0.2)
        }.navigationBarBackButtonHidden(true)
            .navigationBarItems(leading: Button(action: {
                self.presentationMode.wrappedValue.dismiss()
            }, label: { Image(systemName: "arrow.left") }))
            .navigationBarTitle("", displayMode: .inline)
    }
}


struct NameRow: View {
    var name: String
    var body: some View {
        HStack {
            Image(systemName: "circle.fill").foregroundColor(Color.green)
            Text(name)
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
like image 31
Ashish Avatar answered Oct 14 '22 02:10

Ashish


With State Variables. Try that.

struct ContentViewRoot: View {
    @State var pushed: Bool = false
    var body: some View {
        NavigationView{
            VStack{
                NavigationLink(destination:ContentViewFirst(pushed: self.$pushed), isActive: self.$pushed) { EmptyView() }
                    .navigationBarTitle("Root")
                Button("push"){
                    self.pushed = true
                }
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}


struct ContentViewFirst: View {
    @Binding var pushed: Bool
    @State var secondPushed: Bool = false
    var body: some View {
        VStack{
            NavigationLink(destination: ContentViewSecond(pushed: self.$pushed, secondPushed: self.$secondPushed), isActive: self.$secondPushed) { EmptyView() }
                .navigationBarTitle("1st")
            Button("push"){
                self.secondPushed = true;
            }
        }
    }
}



struct ContentViewSecond: View {
    @Binding var pushed: Bool
    @Binding var secondPushed: Bool

    var body: some View {
        VStack{
            Spacer()
            Button("PopToRoot"){
                self.pushed = false
            } .navigationBarTitle("2st")

            Spacer()
            Button("Pop"){
                         self.secondPushed = false
                     } .navigationBarTitle("1st")
            Spacer()
        }
    }
}

enter image description here

like image 40
Andreas Stokidis Avatar answered Oct 14 '22 00:10

Andreas Stokidis


There is now a way to programmatically pop in a NavigationView, if you would like. This is in beta 5.

Notice that you don't need the back button. You could programmatically trigger the showSelf property in the DetailView any way you like. And you don't have to display the "Push" text in the master. That could be an EmptyView(), thereby creating an invisible segue.

(The new NavigationLink functionality takes over the deprecated NavigationDestinationLink)

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            MasterView()
        }
    }
}

struct MasterView: View {
    @State var showDetail = false

    var body: some View {
        VStack {
            NavigationLink(destination: DetailView(showSelf: $showDetail), isActive: $showDetail) {
                Text("Push")
            }
        }
    }
}

struct DetailView: View {
    @Binding var showSelf: Bool

    var body: some View {
        Button(action: {
            self.showSelf = false
        }) {
            Text("Pop")
        }
    }
}

#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif
like image 33
MScottWaller Avatar answered Oct 14 '22 01:10

MScottWaller


This seems to work for me on watchOS (haven't tried on iOS):

@Environment(\.presentationMode) var presentationMode

And then when you need to pop

self.presentationMode.wrappedValue.dismiss()
like image 20
Cherpak Evgeny Avatar answered Oct 14 '22 00:10

Cherpak Evgeny


It seems that a ton of basic navigation functionality is super buggy, which is disappointing and may be worth walking away from for now to save hours of frustration. For me, PresentationButton is the only one that works. TabbedView tabs don't work properly, and NavigationButton doesn't work for me at all. Sounds like YMMV if NavigationButton works for you.

I'm hoping that they fix it at the same time they fix autocomplete, which would give us much better insight as to what is available to us. In the meantime, I'm reluctantly coding around it and keeping notes for when fixes come out. It sucks to have to figure out if we're doing something wrong or if it just doesn't work, but that's beta for you!

like image 29
Sean Avatar answered Oct 14 '22 00:10

Sean


Update: the NavigationDestinationLink API in this solution has been deprecated as of iOS 13 Beta 5. It is now recommended to use NavigationLink with an isActive binding.

I figured out a solution for programmatic pushing/popping of views in a NavigationView using NavigationDestinationLink.

Here's a simple example:

import Combine
import SwiftUI

struct DetailView: View {
    var onDismiss: () -> Void

    var body: some View {
        Button(
            "Here are details. Tap to go back.",
            action: self.onDismiss
        )
    }
}

struct MainView: View {
    var link: NavigationDestinationLink<DetailView>
    var publisher: AnyPublisher<Void, Never>

    init() {
        let publisher = PassthroughSubject<Void, Never>()
        self.link = NavigationDestinationLink(
            DetailView(onDismiss: { publisher.send() }),
            isDetail: false
        )
        self.publisher = publisher.eraseToAnyPublisher()
    }

    var body: some View {
        VStack {
            Button("I am root. Tap for more details.", action: {
                self.link.presented?.value = true
            })
        }
            .onReceive(publisher, perform: { _ in
                self.link.presented?.value = false
            })
    }
}

struct RootView: View {
    var body: some View {
        NavigationView {
            MainView()
        }
    }
}

I wrote about this in a blog post here.

like image 25
Ryan Ashcraft Avatar answered Oct 14 '22 01:10

Ryan Ashcraft


You can also do it with .sheet

.navigationBarItems(trailing: Button(action: {
            self.presentingEditView.toggle()
        }) {
            Image(systemName: "square.and.pencil")
        }.sheet(isPresented: $presentingEditView) {
            EditItemView()
        })

In my case I use it from a right navigation bar item, then you have to create the view (EditItemView() in my case) that you are going to display in that modal view.

https://developer.apple.com/documentation/swiftui/view/sheet(ispresented:ondismiss:content:)

like image 23
Alejandro L.Rocha Avatar answered Oct 14 '22 02:10

Alejandro L.Rocha


EDIT: This answer over here is better than mine, but both work: SwiftUI dismiss modal

What you really want (or should want) is a modal presentation, which several people have mentioned here. If you go that path, you definitely will need to be able to programmatically dismiss the modal, and Erica Sadun has a great example of how to do that here: https://ericasadun.com/2019/06/16/swiftui-modal-presentation/

Given the difference between declarative coding and imperative coding, the solution there may be non-obvious (toggling a bool to false to dismiss the modal, for example), but it makes sense if your model state is the source of truth, rather than the state of the UI itself.

Here's my quick take on Erica's example, using a binding passed into the TestModal so that it can dismiss itself without having to be a member of the ContentView itself (as Erica's is, for simplicity).

struct TestModal: View {
    @State var isPresented: Binding<Bool>

    var body: some View {
        Button(action: { self.isPresented.value = false }, label: { Text("Done") })
    }
}

struct ContentView : View {
    @State var modalPresented = false

    var body: some View {
        NavigationView {
            Text("Hello World")
            .navigationBarTitle(Text("View"))
            .navigationBarItems(trailing:
                Button(action: { self.modalPresented = true }) { Text("Show Modal") })
        }
        .presentation(self.modalPresented ? Modal(TestModal(isPresented: $modalPresented)) {
            self.modalPresented.toggle()
        } : nil)
    }
}
like image 33
Brad Avatar answered Oct 14 '22 01:10

Brad


Below works for me in XCode11 GM

self.myPresentationMode.wrappedValue.dismiss()
like image 35
guru Avatar answered Oct 14 '22 02:10

guru