7
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))
                    }
                }
            }
        }
    }
}

4 Answers 4

2

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
}
Sign up to request clarification or add additional context in comments.

2 Comments

Thank you for the advice! I have tried your advice. Plan A had no effect. However, when I use "DispatchQueue.main.asyncAfter (deadline: .now () + 0.01)" instead of "DispatchQueue.main.async", it now works as advised. Plan B worked as advised. When I use this plan, I need to add a new status variable that can determine whether to return to FirstView at ".onDismiss". In any case, both plan is a step-by-step transition to return to the FirstView, not a direct transition. I want to wait for the other person's solution.
I checked again. planA worked with the code as shown. When I checked it earlier, I omitted the DispatchQueue before setting showThird. I have added a code based on this advice to the question area.
1

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)
        }
    }
}

2 Comments

Z-stacking multiple animating views that render based upon @State booleans might be a way to make make synthesised unwind segues several layers "deep". It's not exactly elegant, but... yeah
Thank you for the advice! Your advice on overlaying views using zstack was very helpful. This method worked very well if we weren't concerned with using a sheet to animate a modal transition.I have added a code based on this advice to the question area.
1

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.

Comments

0

It's not pretty, but you could fall-back to UIKit like this (n is the number of modal views you would like to dismiss at same time):

private func dismiss(_ n: Int) {
    let rootViewController = UIApplication.shared.connectedScenes
        .filter { $0.activationState == .foregroundActive }
        .map {$0 as? UIWindowScene }
        .compactMap { $0 }
        .first?.windows
        .filter({ $0.isKeyWindow }).first?.rootViewController
    guard let rootViewController = rootViewController else { return }

    var leafFlound = false
    var viewStack: [UIViewController] = [rootViewController]
    while(!leafFlound) {
        if let presentedViewController = viewStack.last?.presentedViewController {
            viewStack.append(presentedViewController)
        } else {
            leafFlound = true
        }
    }
    let presentingViewController = viewStack[max(0, viewStack.count - n - 1)]
    presentingViewController.dismiss(animated: true)
}

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.