如何在SwiftUI中使用pop到根视图?

183

最终,现在有了 Beta 5 版本,我们可以以编程方式回到父视图了。但是,在我的应用程序中有几个地方,一个视图有一个“保存”按钮,它结束了一个多步骤的过程并返回到开头。在 UIKit 中,我使用 popToRootViewController(),但我一直无法找出在 SwiftUI 中实现相同功能的方法。

下面是我试图实现的简单示例。

我该怎么做?

import SwiftUI

struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var body: some View {
        VStack {
            Text("This is Detail View B.")

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop to Detail View A.") }

            Button(action: { /* How to do equivalent to popToRootViewController() here?? */ } )
            { Text("Pop two levels to Master View.") }

        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var body: some View {
        VStack {
            Text("This is Detail View A.")

            NavigationLink(destination: DetailViewB() )
            { Text("Push to Detail View B.") }

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop one level to Master.") }
        }
    }
}

struct MasterView: View {
    var body: some View {
        VStack {
            Text("This is Master View.")

            NavigationLink(destination: DetailViewA() )
            { Text("Push to Detail View A.") }
        }
    }
}

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

2
我会接受一种解决方案,它要么弹出到根目录,要么弹出比当前层级高指定数量的层级。谢谢。 - Chuck H
另一种方法:https://dev59.com/uLbna4cB1Zd3GeqPUAJX#57717462 - Sajjon
看看这个开源项目:https://github.com/biobeats/swiftui-navigation-stack 我在下面发了一个关于它的答案。 - superpuccio
我为那些新手偷了一个更好的方法,阅读此内容:https://dev59.com/21MH5IYBdhLWcg3w_Wd_#63760934 - Super Noob
https://github.com/canopas/UIPilot 可以轻松实现此功能,下面附上答案 https://dev59.com/21MH5IYBdhLWcg3w_Wd_#71259665 - jimmy0251
iOS 16+已经内置了此功能。您可以在https://dev59.com/WLDxpogBymzxlkE8r9pY#76080377上找到相关信息。 - lorem ipsum
32个回答

250
NavigationLink 上的视图修饰符 isDetailLink 设置为 false 是使弹出到根视图工作的关键。 默认情况下,isDetailLinktrue 并且适应所包含的视图。例如,在 iPad 横向时,拆分视图被分开,并且 isDetailLink 确保目标视图将显示在右侧。因此,将 isDetailLink 设置为 false 意味着目标视图将始终被推入导航栈,从而可以始终被弹出。
除了在 NavigationLink 上设置 isDetailLinkfalse ,还需要将 isActive 绑定传递给每个后续的目标视图。最后,当您想要弹出到根视图时,将该值设置为 false ,它会自动弹出所有内容:
import SwiftUI

struct ContentView: View {
    @State var isActive : Bool = false

    var body: some View {
        NavigationView {
            NavigationLink(
                destination: ContentView2(rootIsActive: self.$isActive),
                isActive: self.$isActive
            ) {
                Text("Hello, World!")
            }
            .isDetailLink(false)
            .navigationBarTitle("Root")
        }
    }
}

struct ContentView2: View {
    @Binding var rootIsActive : Bool

    var body: some View {
        NavigationLink(destination: ContentView3(shouldPopToRootView: self.$rootIsActive)) {
            Text("Hello, World #2!")
        }
        .isDetailLink(false)
        .navigationBarTitle("Two")
    }
}

struct ContentView3: View {
    @Binding var shouldPopToRootView : Bool

    var body: some View {
        VStack {
            Text("Hello, World #3!")
            Button (action: { self.shouldPopToRootView = false } ){
                Text("Pop to root")
            }
        }.navigationBarTitle("Three")
    }
}

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

屏幕截图


14
这是最佳答案,现在应该被采纳。它完全符合我的要求,而且不是黑客技巧。谢谢。 - Chuck H
16
如果您在视图上使用自定义初始化程序并且无法使它们正常工作,请确保在init参数中使用Binding<Type>(例如"init(rootIsActive: Binding<Bool>)"),同时在初始化程序内部不要忘记使用下划线来声明局部绑定变量(例如self._rootIsActive = rootIsActive)。当您的预览出现问题时,只需将.constant(true)用作参数即可。 - Repose
21
它运作正常,但是"shouldPopToRootView"的命名不够清晰。该属性实际上会禁用根视图的导航功能。而且,最好使用环境对象来观察更改,而不是将绑定布尔传递给子视图中的每个视图。 - imthath
10
如果您在根视图中有多个导航链接,则这种解决方案可能会变得有些棘手。不要只是将相同的布尔绑定传递给所有导航链接(在根视图中)。否则,当您进行导航时,所有导航链接都会同时变为活动状态。棘手。 - Scott Marchant
9
谢谢你的灵感和代码。我的两个要点:
  • ContentView中的指令.isDetailLink(false)是不必要的(因为它是根视图)。
  • booleans rootIsActive和shouldPopToRootView命名非常糟糕。由于它们,我在理解代码时遇到了很多困难。特别是self.shouldPopToRootView = false看起来非常反直觉(false...?真的吗...?我们实际上想要弹回到根视图,你知道...)。 我的做法是用一个名为stackingPermitted的单一布尔值替换它们(连同ContentView中的isActive)。
- Florin Odagiu
显示剩余21条评论

57

毫无疑问,malhal已经找到了解决方案的关键,但对我来说,将Binding作为参数传递给View不是实际可行的方法。正如Imthath所指出的那样,环境变量是更好的方法。

这里有另一种方法,它模仿了苹果发布的dismiss()方法以弹出上一个View。

定义一个对环境变量的扩展:

struct RootPresentationModeKey: EnvironmentKey {
    static let defaultValue: Binding<RootPresentationMode> = .constant(RootPresentationMode())
}

extension EnvironmentValues {
    var rootPresentationMode: Binding<RootPresentationMode> {
        get { return self[RootPresentationModeKey.self] }
        set { self[RootPresentationModeKey.self] = newValue }
    }
}

typealias RootPresentationMode = Bool

extension RootPresentationMode {
    
    public mutating func dismiss() {
        self.toggle()
    }
}

用法:

  1. .environment(\.rootPresentationMode, self.$isPresented)添加到根NavigationView中,其中isPresented是用于呈现第一个子视图的Bool

  2. 要么将 .navigationViewStyle(StackNavigationViewStyle()) 修饰符添加到根NavigationView中,或将 .isDetailLink(false) 添加到第一个子视图的NavigationLink 中。

  3. 在任何应该返回到根视图的子视图中添加 @Environment(\.rootPresentationMode) private var rootPresentationMode

  4. 最后,在该子视图中调用 self.rootPresentationMode.wrappedValue.dismiss() 将返回到根视图。

我在GitHub上发布了完整的工作示例


2
这真的帮了我很多。谢谢Chuck和Nikola。 - Parth Patel
1
这确实是一个优雅、可重用的解决方案。我花了一些时间才理解它的工作原理,但多亏了你的示例,我明白了。任何尝试这个的人:尝试将示例最小化以更好地理解它。 - brainray
这就是应该做的方式。使用绑定与 DI 不兼容,这样非常完美。 - Lukasz D
3
你如何使它与TabView和多个不同的“根”屏幕一起工作? - Bjørn Olav Jalborg
我打算尝试使用NavigationStack。我的应用程序非常复杂,有多个视图,如果NavigationStack能够正常工作,将会让我的工作变得更加轻松,因为我就不必在所有视图中传递对象了。 - undefined
显示剩余6条评论

44
由于目前SwiftUI仍然在后台使用UINavigationController,因此也可以调用其popToRootViewController(animated:)函数。您只需要在视图控制器层次结构中搜索UINavigationController即可:
struct NavigationUtil {
    static func popToRootView(animated: Bool = false) {
        findNavigationController(viewController: UIApplication.shared.connectedScenes.flatMap { ($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow }?.rootViewController)?.popToRootViewController(animated: animated)
    }
    
    static func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
        guard let viewController = viewController else {
            return nil
        }
        
        if let navigationController = viewController as? UITabBarController {
            return findNavigationController(viewController: navigationController.selectedViewController)
        }
        
        if let navigationController = viewController as? UINavigationController {
            return navigationController
        }
        
        for childViewController in viewController.children {
            return findNavigationController(viewController: childViewController)
        }
        
        return nil
    }
}

然后像这样使用:

struct ContentView: View {
    var body: some View {
      NavigationView { DummyView(number: 1) }
    }
}

struct DummyView: View {
  let number: Int

  var body: some View {
    VStack(spacing: 10) {
      Text("This is view \(number)")
      NavigationLink(destination: DummyView(number: number + 1)) {
        Text("Go to view \(number + 1)")
      }
      Button(action: { NavigationUtil.popToRootView() }) {
        Text("Or go to root view!")
      }
    }
  }
}

1
在我的端上工作了!谢谢。 - vidalbenjoe
1
仍然有效。也许将来可能不行了。但为什么不现在过一种轻松的生活呢?感觉这是最自然的方式。 - brainray
1
由于某种原因,这里停止工作了... - brainray
8
似乎只能在一个带有NavigationView的视图中使用。如果您有一个包含多个带有NavigationView的视图的TabView,则只能在第一个视图中使用。 - fballjeff
1
@WaitsAtWork,能否请您分享一下您的更新解决方案?它是否适用于嵌套在TabView中的NavigationView? - Olivia Brown
显示剩余3条评论

26

介绍苹果公司解决这个问题的方法

此方法也是通过HackingWithSwift(我从那里偷来的,哈哈)在编程导航下向您展示:

(在Xcode 12和iOS 14上测试过)

基本上,您可以在NavigationLink中使用tagselection直接转到您想要的页面。

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

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Second View"), tag: "Second", selection: $selection) { EmptyView() }
                NavigationLink(destination: Text("Third View"), tag: "Third", selection: $selection) { EmptyView() }
                Button("Tap to show second") {
                    self.selection = "Second"
                }
                Button("Tap to show third") {
                    self.selection = "Third"
                }
            }
            .navigationBarTitle("Navigation")
        }
    }
}
你可以在ContentView()中注入一个@EnvironmentObject来处理选择:
class NavigationHelper: ObservableObject {
    @Published var selection: String? = nil
}

将其注入应用程序:

@main
struct YourApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView().environmentObject(NavigationHelper())
        }
    }
}

并使用它:

struct ContentView: View {
    @EnvironmentObject var navigationHelper: NavigationHelper

    var body: some View {
        NavigationView {
            VStack {
                NavigationLink(destination: Text("Second View"), tag: "Second", selection: $navigationHelper.selection) { EmptyView() }
                NavigationLink(destination: Text("Third View"), tag: "Third", selection: $navigationHelper.selection) { EmptyView() }
                Button("Tap to show second") {
                    self.navigationHelper.selection = "Second"
                }
                Button("Tap to show third") {
                    self.navigationHelper.selection = "Third"
                }
            }
            .navigationBarTitle("Navigation")
        }
    }
}

要返回子导航链接的内容视图,只需设置 navigationHelper.selection = nil

请注意,如果您不想使用标签和选择器来处理后续的子级导航链接,那么它们将无法具有转到该特定导航链接的功能。


是的,这个解决方案确实有效。我刚刚找到了为什么最初它没有起作用的答案。 - JLively
1
只有根视图 > 子视图1 可以正常工作。当子视图1 > 子视图2 时,它会自动返回到根视图。 - Kenan Karakecili
1
@KenanKarakecili,是的,我也不知道为什么会这样..但是在child1中删除tag:selection:将防止它在弹出到child2时返回到根(nil)..然而这意味着你不能通过将child2的tag设置为navigationHelper.selection来进入child2。 - Super Noob
你如何在根 ViewController 上设置标签? - ScottyBlades
当有3个视图时,第三个视图会将我弹回到根视图。你知道为什么吗? - Foriger
显示剩余8条评论

11

iOS 16解决方案

现在,您可以使用新添加的NavigationStack轻松地弹出到根视图了!!!

struct DataObject: Identifiable, Hashable {
    let id = UUID()
    let name: String
}

@available(iOS 16.0, *)
struct ContentView8: View {
    @State private var path = NavigationPath()
    
    var body: some View {
        NavigationStack(path: $path) {
            Text("Root Pop")
                .font(.largeTitle)
                .foregroundColor(.primary)
            
            NavigationLink("Click Item", value: DataObject.init(name: "Item"))
            
            .listStyle(.plain)
            .navigationDestination(for: DataObject.self) { course in
                Text(course.name)
                NavigationLink("Go Deeper", value: DataObject.init(name: "Item"))
                Button("Back to root") {
                    path = NavigationPath()
                }
            }
        }
        .padding()
    }
}

1
在我看来,这是最正确的方法,并且与苹果关于NavigationStack和NavigationLink的当前版本文档保持一致。请注意,您可以将“path”状态变量作为绑定传递给嵌套视图,这将允许它们通过操作其中的元素(如上面的答案中删除所有元素)来弹出到任何视图(在堆栈中)。 - DCDC
有没有一种方法可以使用环境对象来做到这一点?我正在开发一个复杂的应用程序,其中包含多个视图,我的理解是我将不得不将“路径”对象传递给每个视图。 - undefined
@tech_human 路径可以存在于一个环境对象上,但你必须使用路径进行导航。我喜欢把navigationDestination想象成一个路由器。这里有一个例子https://stackoverflow.com/questions/73723771/navigationstack-not-affected-by-environmentobject-changes - undefined

11

据我所知,目前的beta 5没有简单的方法来完成它。我发现唯一的方法是非常巧妙的,但是它能起作用。

基本上,在DetailViewA中添加一个发布者,该发布者将从DetailViewB中触发。在DetailViewB中解散视图并通知发布者,发布者本身将关闭DetailViewA。

    struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var publisher = PassthroughSubject<Void, Never>()

    var body: some View {
        VStack {
            Text("This is Detail View B.")

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop to Detail View A.") }

            Button(action: {
                DispatchQueue.main.async {
                self.presentationMode.wrappedValue.dismiss()
                self.publisher.send()
                }
            } )
            { Text("Pop two levels to Master View.") }

        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>
    var publisher = PassthroughSubject<Void, Never>()

    var body: some View {
        VStack {
            Text("This is Detail View A.")

            NavigationLink(destination: DetailViewB(publisher:self.publisher) )
            { Text("Push to Detail View B.") }

            Button(action: { self.presentationMode.value.dismiss() } )
            { Text("Pop one level to Master.") }
        }
        .onReceive(publisher, perform: { _ in
            DispatchQueue.main.async {
                print("Go Back to Master")
                self.presentationMode.wrappedValue.dismiss()
            }
        })
    }
}

而Beta 6仍然没有解决方案。

我找到了另一种返回根视图的方法,但这次我失去了动画效果,直接返回到根视图。

这个想法是强制刷新根视图,从而导致导航栈的清理。

但最终只有苹果公司能够提供适当的解决方案,因为SwiftUI中导航栈的管理不可用。

NB:下面的通知简单解决方案适用于iOS,但不适用于watchOS,因为watchOS会在两个导航级别后清除根视图。但是,在watchOS中使用外部类来管理状态应该可以正常工作。

struct DetailViewB: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var fullDissmiss:Bool = false
    var body: some View {
        SGNavigationChildsView(fullDissmiss: self.fullDissmiss){
            VStack {
                Text("This is Detail View B.")

                Button(action: { self.presentationMode.wrappedValue.dismiss() } )
                { Text("Pop to Detail View A.") }

                Button(action: {
                    self.fullDissmiss = true
                } )
                { Text("Pop two levels to Master View with SGGoToRoot.") }
            }
        }
    }
}

struct DetailViewA: View {
    @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

    @State var fullDissmiss:Bool = false
    var body: some View {
        SGNavigationChildsView(fullDissmiss: self.fullDissmiss){
            VStack {
                Text("This is Detail View A.")

                NavigationLink(destination: DetailViewB() )
                { Text("Push to Detail View B.") }

                Button(action: { self.presentationMode.wrappedValue.dismiss() } )
                { Text("Pop one level to Master.") }

                Button(action: { self.fullDissmiss = true } )
                { Text("Pop one level to Master with SGGoToRoot.") }
            }
        }
    }
}

struct MasterView: View {
    var body: some View {
        VStack {
            Text("This is Master View.")
            NavigationLink(destination: DetailViewA() )
            { Text("Push to Detail View A.") }
        }
    }
}

struct ContentView: View {

    var body: some View {
        SGRootNavigationView{
            MasterView()
        }
    }
}
#if DEBUG
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
#endif

struct SGRootNavigationView<Content>: View where Content: View {
    let cancellable = NotificationCenter.default.publisher(for: Notification.Name("SGGoToRoot"), object: nil)

    let content: () -> Content

    init(@ViewBuilder content: @escaping () -> Content) {
        self.content = content
    }

    @State var goToRoot:Bool = false

    var body: some View {
        return
            Group{
            if goToRoot == false{
                NavigationView {
                content()
                }
            }else{
                NavigationView {
                content()
                }
            }
            }.onReceive(cancellable, perform: {_ in
                DispatchQueue.main.async {
                    self.goToRoot.toggle()
                }
            })
    }
}

struct SGNavigationChildsView<Content>: View where Content: View {
    let notification = Notification(name: Notification.Name("SGGoToRoot"))

    var fullDissmiss:Bool{
        get{ return false }
        set{ if newValue {self.goToRoot()} }
    }

    let content: () -> Content

    init(fullDissmiss:Bool, @ViewBuilder content: @escaping () -> Content) {
        self.content = content
        self.fullDissmiss = fullDissmiss
    }

    var body: some View {
        return Group{
            content()
        }
    }

    func goToRoot(){
        NotificationCenter.default.post(self.notification)
    }
}

谢谢。我很高兴看到它是可以实现的。你说得对,这有点hacky,但它确实有效。如果DetailViewA在返回MasterView时不会闪烁,那将是最好的。我们可以希望苹果在即将推出的beta版本中填补SwiftUI导航模型中的一些缺口。 - Chuck H

9

我找到了一个简单的方法来弹出到根视图。 我发送一个通知,然后监听通知以更改 NavigationView 的ID; 这将刷新 NavigationView。虽然没有动画,但看起来不错。这是一个例子:

@main
struct SampleApp: App {
    @State private var navigationId = UUID()

    var body: some Scene {
        WindowGroup {
            NavigationView {
                Screen1()
            }
            .id(navigationId)
            .onReceive(NotificationCenter.default.publisher(for: Notification.Name("popToRootView"))) { output in
                navigationId = UUID()
            }
        }
    }
}

struct Screen1: View {
    var body: some View {
        VStack {
            Text("This is screen 1")
            NavigationLink("Show Screen 2", destination: Screen2())
        }
    }
}

struct Screen2: View {
    var body: some View {
        VStack {
            Text("This is screen 2")
            Button("Go to Home") {
                NotificationCenter.default.post(name: Notification.Name("popToRootView"), object: nil)
            }
        }
    }
}

Gustavo,感谢您的回答。虽然这种技术可以起作用,但它不是在SwiftUI中使用的最佳技术。在SwiftUI中,首选方法是使用@State变量来实现事情的发生。 - Chuck H
在这里(请见链接),您可以找到类似的解决方案,但是使用@EnvironmentObject而不是NotificationCenter... https://www.cuvenx.com/post/swiftui-pop-to-root-view - Serg
类型“Notification”没有成员“Name”。我在Xcode 14.0中遇到了这个错误。 - Bijender Singh Shekhawat

7

这是对 x0randgat3的回答进行的更新,适用于在TabView内具有多个 NavigationView

struct NavigationUtil {
  static func popToRootView() {
    findNavigationController(viewController: UIApplication.shared.windows.filter { $0.isKeyWindow }.first?.rootViewController)?
      .popToRootViewController(animated: true)
  }

  static func findNavigationController(viewController: UIViewController?) -> UINavigationController? {
    guard let viewController = viewController else {
      return nil
    }

    if let navigationController = viewController as? UITabBarController {
      return findNavigationController(viewController: navigationController.selectedViewController)
    }

    if let navigationController = viewController as? UINavigationController {
      return navigationController
    }

    for childViewController in viewController.children {
      return findNavigationController(viewController: childViewController)
    }

    return nil
  }
}

我尝试了这种方法,但对我没有用。我在网络管理器中调用了同样的方法? - Ramgopal
3
可以运行。但是在iOS 15.0中,“windows”已被弃用。通过更新以下内容进行修复:findNavigationController(viewController:UIApplication.shared.connectedScenes.flatMap {($0 as? UIWindowScene)?.windows ?? []} .first { $0.isKeyWindow }?.rootViewController) - codingCartooningCPA

6

我找到了在SwiftUI中使用复杂导航的方法。诀窍是收集所有视图状态,以确定它们是否显示。

首先定义一个NavigationController。我添加了选项卡视图标签的选择和布尔值来表明特定视图是否显示:

import SwiftUI

final class NavigationController: ObservableObject  {

  @Published var selection: Int = 1

  @Published var tab1Detail1IsShown = false
  @Published var tab1Detail2IsShown = false

  @Published var tab2Detail1IsShown = false
  @Published var tab2Detail2IsShown = false
}

使用两个选项卡设置tabview,并将我们的NavigationController.selection与tabview绑定:

import SwiftUI

struct ContentView: View {

  @EnvironmentObject var nav: NavigationController

  var body: some View {

    TabView(selection: self.$nav.selection) {

      FirstMasterView()
      .tabItem {
        Text("First")
      }
      .tag(0)

      SecondMasterView()
      .tabItem {
        Text("Second")
      }
      .tag(1)
    }
  }

}

作为一个例子,这是一个导航栈(navigationStacks)。
import SwiftUI

struct FirstMasterView: View {

  @EnvironmentObject var nav: NavigationController

  var body: some View {
    NavigationView {
      VStack {

        NavigationLink(destination: FirstDetailView(), isActive: self.$nav.tab1Detail1IsShown) {
          Text("go to first detail")
        }
      } .navigationBarTitle(Text("First MasterView"))
    }
  }
}

struct FirstDetailView: View {

  @EnvironmentObject var nav: NavigationController
  @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode>

  var body: some View {

    VStack(spacing: 20) {
      Text("first detail View").font(.title)

      NavigationLink(destination: FirstTabLastView(), isActive: self.$nav.tab1Detail2IsShown) {
        Text("go to last detail on nav stack")
      }

      Button(action: {
        self.nav.tab2Detail1IsShown = false // true will go directly to detail
        self.nav.tab2Detail2IsShown = false

        self.nav.selection = 1
      }) {
        Text("Go to second tab")
      }
    }

    // In case of collapsing all the way back
    // there is a bug with the environment object
    // to go all the way back I have to use the presentationMode
    .onReceive(self.nav.$tab1Detail2IsShown, perform: { (out) in
      if out ==  false {
        self.presentationMode.wrappedValue.dismiss()
      }
    })
  }
}

struct FirstTabLastView: View {
  @EnvironmentObject var nav: NavigationController

  var body: some View {
    Button(action: {
      self.nav.tab1Detail1IsShown = false
      self.nav.tab1Detail2IsShown = false
    }) {
      Text("Done and go back to beginning of navigation stack")
    }
  }
}

这种方法非常以SwiftUI状态为导向。

创建一个NavigationController并将其放入EnvironmentObject是一个非常好的想法。我还没有完全让你的示例工作,但我认为它正在正确的轨道上。谢谢。 - Chuck H
我意识到我需要一个变量,以确保最后一个视图在堆栈上不会总是崩溃。我在这里添加了我的项目。https://github.com/gahntpo/NavigationSwiftUI.git - kprater
这是一个很好的想法,但在列表中如何实现呢?对于我来说,由于每个NavigationLink的isActive都设置为true,因此列表中的每个项目都将打开详细视图。 - Thomas Vos
2
如果您想使用列表,方法相当类似。我不会将NavigationLink放在List内(因为这会创建不同的链接,正如您所提到的)。您可以添加一个编程链接(意味着您没有可见的按钮)。NavigationLink(destination: MyView(data: mySelectedDataFromTheList), isActive: $self.nav.isShown) { EmptyView() }。当用户在列表中点击项目时,您可以将mySelectedDataFromTheList设置为选定的项目,并将导航状态isShown更改为true。 - kprater
我终于抽出时间写了一篇关于SwiftUI中导航的博客文章。这篇文章对此进行了更详细的解释,并展示了一些使用案例。https://medium.com/@karinprater/how-to-make-navigation-in-swiftui-a-piece-of-cake-223b2a40c6c1 - kprater
我已经实现了这个功能,它可以返回根视图,但当导航更加复杂且深度较大时,有时会在中间视图显示pop动画,并且在再次导航到详细视图时有时会发现导航顺序错乱。 - green0range

4
在iOS 15中,有一个简单的解决方案,可以使用dismiss()并将其传递给子视图:
struct ContentView: View {
    @State private var showingSheet = false
    var body: some View {
        NavigationView {
            Button("show sheet", action: { showingSheet.toggle()})
                .navigationTitle("ContentView")
        }.sheet(isPresented: $showingSheet) { FirstSheetView() }
    }
}

struct FirstSheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        NavigationView {
            List {
                NavigationLink(destination: SecondSheetView(dismiss: _dismiss)) {
                    Text("show 2nd Sheet view")
                }
                NavigationLink(destination: ThirdSheetView(dismiss: _dismiss)) {
                    Text("show 3rd Sheet view")
                }
                Button("cancel", action: {dismiss()})
            } .navigationTitle("1. SheetView")
        }
    }
}

struct SecondSheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        List {
            NavigationLink(destination: ThirdSheetView(dismiss: _dismiss)) {
                Text("show 3rd SheetView")
            }
            Button("cancel", action: {dismiss()})
        } .navigationTitle("2. SheetView")
    }
}

struct ThirdSheetView: View {
    @Environment(\.dismiss) var dismiss
    var body: some View {
        List {
            Button("cancel", action: {dismiss()})
        } .navigationTitle("3. SheetView")
    }
}

它不起作用,根本无法关闭 :( - Nic Wanavit
唯一的解决方案是简单的,并且在SwiftUI预览中也能正常工作。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接