Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to dismiss multiple view with SwiftUI

Tags:

swift

swiftui

import SwiftUI

struct ContentView: View {
    @State var showSecond = false
    @State var showThird = false

    var body: some View {
        VStack(spacing: 50) {
            Text("FirstView")
            Button("to SecondView") {
                self.showSecond = true
            }
            .sheet(isPresented: $showSecond) {
                VStack(spacing: 50) {
                    Text("SecondView")
                    Button("to ThirdView") {
                        self.showThird = true
                    }
                    .sheet(isPresented: self.$showThird) {
                        VStack(spacing: 50) {
                            Text("ThirdView")
                            Button("back") {
                                self.showThird = false
                            }
                            Button("back to FirstView") {
                                self.showThird = false
                                self.showSecond = false
                            }
                        }
                    }
                    Button("back") {
                        self.showSecond = false
                    }
                }
            }
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

The above code transitions from FirstView to SecondView, and transitions from SecondView to ThirdView. And the "back" button in SecondView and ThirdView returns to the previous screen normally.

However, if you tap the "back to FirstView" button in the ThirdView, SecondView will be displayed without returning to FirstView. And after this operation, when you tap the "back" button of SecondView, it does not return to FirstView.

How can I return directly from ThirdView to FirstView?


Added February 19, 2020

I have added the solution code based on answers.

Solution1: based on Asperi's plan A.

struct ContentView: View {
    @State var showSecond = false
    @State var showThird = false

    var body: some View {
        VStack(spacing: 50) {
            Text("FirstView")
            Button("to SecondView") {
                self.showSecond = true
            }
            .sheet(isPresented: $showSecond) {
                VStack(spacing: 50) {
                    Text("SecondView")
                    Button("to ThirdView") {
                        self.showThird = true
                    }
                    .sheet(isPresented: self.$showThird) {
                        VStack(spacing: 50) {
                            Text("ThirdView")
                            Button("back") {
                                self.showThird = false
                            }
                            Button("back to FirstView") {
                                DispatchQueue.main.async {
                                    self.showThird = false
                                    DispatchQueue.main.async {
                                        self.showSecond = false
                                    }
                                }
                            }
                        }
                    }
                    Button("back") {
                        self.showSecond = false
                    }
                }
            }
        }
    }
}

Solution2: based on Asperi's plan B.

struct ContentView: View {
    @State var showSecond = false
    @State var showThird = false
    @State var backToFirst = false

    var body: some View {
        VStack(spacing: 50) {
            Text("FirstView")
            Button("to SecondView") {
                self.showSecond = true
            }
            .sheet(isPresented: $showSecond) {
                VStack(spacing: 50) {
                    Text("SecondView")
                    Button("to ThirdView") {
                        self.showThird = true
                    }
                    .sheet(isPresented: self.$showThird, onDismiss: {
                        if self.backToFirst {
                            self.showSecond = false
                        }
                    }) {
                        VStack(spacing: 50) {
                            Text("ThirdView")
                            Button("back") {
                                self.showThird = false
                                self.backToFirst = false
                            }
                            Button("back to FirstView") {
                                self.showThird = false
                                self.backToFirst = true
                            }
                        }
                    }
                    Button("back") {
                        self.showSecond = false
                    }
                }
            }
        }
    }
}

Solution3: based on Joseph's advice.

struct ContentView: View {
    @State var showSecond = false
    @State var showThird = false

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                VStack(spacing: 50) {
                    Text("FirstView")
                    Button("to SecondView") {
                        self.showSecond = true
                    }
                }
                .frame(width: geometry.size.width, height: geometry.size.height)
                .background(Rectangle().foregroundColor(.white))
                if self.showSecond {
                    VStack(spacing: 50) {
                        Text("SecondView")
                        Button("to ThirdView") {
                            self.showThird = true
                        }
                        Button("back") {
                            self.showSecond = false
                        }
                    }
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .background(Rectangle().foregroundColor(.white))
                    if self.showThird {
                        VStack(spacing: 50) {
                            Text("ThirdView")
                            Button("back") {
                                self.showThird = false
                            }
                            Button("back to FirstView") {
                                self.showThird = false
                                self.showSecond = false
                            }
                        }
                        .frame(width: geometry.size.width, height: geometry.size.height)
                        .background(Rectangle().foregroundColor(.white))
                    }
                }
            }
        }
    }
}
like image 844
TakeOne Avatar asked Dec 24 '19 09:12

TakeOne


3 Answers

Directly no way for now (and IMO will never be - it is two modal sessions)... I found two possible approaches that maybe worth considering:

A. sequential close from one place

Button("back to FirstView") {
    DispatchQueue.main.async {
        self.showThird = false
        DispatchQueue.main.async {
            self.showSecond = false
        }
    }
}

B. sequential close from different places

.sheet(isPresented: self.$showThird, onDismiss: {
    self.showSecond = false // with some additional condition for forced back
})...

...

Button("back to FirstView") {
    self.showThird = false
}
like image 103
Asperi Avatar answered Oct 12 '22 11:10

Asperi


Another /cough/ "solution" - possibly to a slightly different scenario - but anyway:

struct ContentView: View {
    var body: some View {
        NavigationView {
            NavigationLink(destination: SecondView()) {
                Text("Show Second View")
                    .font(.largeTitle)
            }
        }
        .navigationViewStyle(StackNavigationViewStyle())
    }
}

struct SecondView: View {
    @Environment(\.presentationMode) var presentationMode

    @State private var showModal = false

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                Rectangle()
                    .foregroundColor(.gray)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                Button(action: {
                    withAnimation {
                        self.showModal.toggle()
                    }
                }) {
                    Text("Show Third View")
                        .font(.largeTitle)
                        .foregroundColor(.white)
                }

                if self.showModal {
                    VStack {
                        Button(action: {
                            self.presentationMode.wrappedValue.dismiss()
                        } ) {
                            Text("Show First View")
                                .font(.largeTitle)
                                .foregroundColor(.white)
                        }
                    }
                        .frame(width: geometry.size.width, height: geometry.size.height)
                        .background(Rectangle().foregroundColor(.orange))
                        .transition(.move(edge: .trailing))
                }
            }
            .frame(width: geometry.size.width, height: geometry.size.height)
        }
    }
}
like image 1
Joseph Beuys' Mum Avatar answered Oct 12 '22 10:10

Joseph Beuys' Mum


Asperi's solutions work for dismissing two screens at once, but don't work for more screens. In such cases, we need to add DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) in both approaches like this:

self.showFourth = false
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
    self.showThird = false
    DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300)) {
        self.showSecond = false
    }
}

.milliseconds(200) is not enought.

like image 1
Ihor Vovk Avatar answered Oct 12 '22 12:10

Ihor Vovk