Reputation: 213
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))
}
}
}
}
}
}
Upvotes: 7
Views: 5433
Reputation: 4698
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)
}
Upvotes: 0
Reputation: 611
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.
Upvotes: 1
Reputation: 2264
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)
}
}
}
Upvotes: 1
Reputation: 257523
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
}
Upvotes: 2