SwiftUI:NavigationView/NavigationLink:在推送目标的情况下启动?

SwiftUI: NavigationView/NavigationLink: launch with destination pushed?

iOS 邮件应用程序启动时,您最近查看的邮箱已经推送到导航堆栈上,即使它实际上是一个详细信息屏幕并且根视图控制器是所有邮箱的列表。这在 UIKit 中是微不足道的,因为推送到导航控制器是微不足道的,并且可以在例如 viewDidLoad 或 applicationDidFinishLaunching 内部完成。

NavigationView 和 NavigationLink 是用于在 SwiftUI 中执行导航 pushes/pops 的 APIs,NavigationLink 支持几个模仿编程导航的 APIs - 但我不能完全弄清楚我如何支持这个特定的用例。每次屏幕出现时都会调用 onAppear 闭包(更像是 viewDidAppear 而不是 viewDidLoad),因此设置对应于 NavigationLink 的 selection 值将导致重复推送该细节用户尝试弹出时的屏幕。我可以尝试围绕维护 State 来确定屏幕是否已被重新查看,但它似乎并不理想。 (我也有一些关于 NavigationLink 的 selection API 不稳定的问题,但这可能是一个单独的问题。)

有什么建议吗?我怎样才能将应用程序启动到详细信息屏幕上,同时让 NavigationView 在没有任何编程干预的情况下正常运行?

如果您不需要 以模式或动画形式呈现您的视图,您应该通过使用编程导航(NavigationLink with selection binding)获得所需的行为。

例如,如果它是您应用程序的根目录,则以下内容应该有效:

struct Item: Identifiable {
    let id: Int
    let name: String
}

struct NormalNav: View {
    @State var items: [Item] = [
        .init(id: 0, name: "One"),
        .init(id: 1, name: "Two"),
        .init(id: 2, name: "Three")
    ]

    @State private var selection: Int? = 1

    var body: some View {
        NavigationView {
            List {
                ForEach(items) { item in
                    NavigationLink(item.name,
                                   destination: Text(item.name),
                                   tag: item.id,
                                   selection: $selection)
                }
            }
        }
    }
}

上面的视图应该直接打开到第二个项目(“Two”),没有动画。

但是,有一个问题。如果您需要在 Sheet 中呈现此视图,将使用动画推送详细视图,这可能不是您想要的。

this answer 的基础上,您可以在初始创建期间取消动画,但在后续导航时将其重新打开:

struct ContentView: View {
    @State var items: [Item] = [
        .init(id: 0, name: "One"),
        .init(id: 1, name: "Two"),
        .init(id: 2, name: "Three")
    ]

    @State private var selection: Int? = nil
    @State var isAnimationDisabled = true
    @State var isListPresented = false

    var body: some View {
        Button("Show list") {
            isListPresented = true
        }.sheet(isPresented: $isListPresented) {
            NavigationView {
                List {
                    ForEach(items) { item in
                        NavigationLink(item.name,
                                       destination: Text(item.name),
                                       tag: item.id,
                                       selection: $selection)
                    }
                }.animations(disabled: isAnimationDisabled)
            }.onChange(of: selection) { value in
                if value == nil {
                    isAnimationDisabled = false
                }
            }.onAppear {
                selection = 1
            }
        }
    }
}

extension View {
    func animations(disabled: Bool) -> some View {
        transaction { (tx: inout Transaction) in
            guard disabled else {
                return
            }
            tx.disablesAnimations = true
            tx.animation = .none
        }
    }
}