对于编程导航,您之前可以使用 NavigationLink(isActive:, destination:, label:) 当 isActive 参数为真时,它将触发导航。在 IOS 16 中,这已被弃用,并引入了NavigationStack、NavigationLink(value:, label:)和NavigationPath。
要了解这些的用法,请点击以下链接:
https://developer.apple.com/documentation/swiftui/migrating-to-new-navigation-types https://www.hackingwithswift.com/articles/250/whats-new-in-swiftui-for-ios-16(搜索 NavigationStack)
我的问题是,如果我想在不同的视图和它们的 ViewModel 中使用它,我应该如何使用和维护包含导航堆栈内容的数组(如 NavigationPath 对象)?
正如您在下面的代码中看到的那样,我创建了一个 NavigationPath 对象来将我的导航堆栈保存在 BaseView 或 BaseView.ViewModel 中。这样我就可以从这个 BaseView 到其他页面(Page1,Page2)进行编程导航,这很棒。
但是如果我转到 Page1 并尝试以编程方式从那里导航到 Page2,我需要访问原始的 NavigationPath 对象,我在 BaseView 中使用的对象。
访问这个原始对象的最佳方式是什么?
我可能误解了这个新功能的用法,但如果您有任何可能的从 ViewModel 进行编程导航的解决方案,我会很高兴看到它:)
我尝试过的事情:
struct BaseView: View {
@StateObject var viewModel = ViewModel()
var body: some View {
NavigationStack(path: $viewModel.paths) {
VStack {
Button("Page 1", action: viewModel.goToPage1)
Button("Page 2", action: viewModel.goToPage2)
}
.navigationDestination(for: String.self) { stringParam in
Page1(stringParam: stringParam)
}
.navigationDestination(for: Int.self) { intParam in
Page2(intParam: intParam)
}
}
}
}
extension BaseView {
@MainActor class ViewModel: ObservableObject {
@Published var paths = NavigationPath()
func goToPage1() {
let param = "Some random string" // gets the parameter from some calculation or async network call
paths.append(param)
}
func goToPage2() {
let param = 19 // gets the parameter from some calculation or async network call
paths.append(param)
}
}
}
struct Page1: View {
@StateObject var viewModel = ViewModel()
let stringParam: String
var body: some View {
VStack {
Button("Page 2", action: viewModel.goToPage2)
}
}
}
extension Page1 {
@MainActor class ViewModel: ObservableObject {
func goToPage2() {
// Need to add value to the original paths variable in BaseView.ViewModel
}
}
}
struct Page2: View {
@StateObject var viewModel = ViewModel()
let intParam: Int
var body: some View {
Text("\(intParam)")
}
}
extension Page2 {
@MainActor class ViewModel: ObservableObject {
}
}
SwiftUI 中不需要 MVVM,因为
View
结构加属性包装器已经等同于视图模型对象,但速度更快且不易出错。同样在 SwiftUI 中,我们甚至无法访问传统的视图层 - 它采用我们的 View
数据结构,对它们进行差异化以创建/更新/删除 UIView
/NSView
对象,使用最适合平台的对象/语境。如果您改为使用对象来查看数据,那么您将遇到与 SwiftUI 旨在消除的相同的一致性问题。
可悲的是,网络(和哈佛大学)充斥着 MVVM SwiftUI 文章,这些文章是由那些懒得去正确学习它的人写的。幸运的是,情况正在发生变化:
我错了! MVVM 不是构建 SwiftUI 应用程序的好选择(Azam Sharp)
官方迁移指南提供了很多有用的信息。
修饰符navigationDestination(for:destination:)启用对特定数据类型的自定义处理。
您可以将选定的数据类型“推送”到
NavigationPath
,然后相关的navigationDestination
块将处理它。
我创建了一些辅助函数来简化新的导航系统。
我将这些存储在一个自定义的
AppContext
类中,您会在下面看到提到的(appContext
),但当然可以在最适合您自己的代码库的地方放置并引用它们。
/// The current navigation stack.
@Published public var navStack = NavigationPath()
/// Type-erased keyed data stored for a given view.
var navData = Dictionary<String, Any>()
/// Set to `true` the given "show view" bound Bool (i.e. show that view).
/// Optionally, provide data to pass to that view.
public func navigateTo(_ showViewFlag: Binding<Bool>,
_ navData: Dictionary<String, Any>? = nil) {
if let navData { self.navData = navData }
showViewFlag.wrappedValue = true
}
/// Pop & retrieve navigation data for the given key.
/// (Generics undo the type-erasure produced by `navigateTo`)
public func popNavData<T>(_ key: String) -> T {
navData.removeValue(forKey: key)! as! T
}
这个
destination
修饰符是官方navigationDestination
修饰符的更简洁版本:
@ViewBuilder
func destination(`for` show: Binding<Bool>,
_ destination: @escaping () -> some View ) -> some View {
self.navigationDestination(isPresented: show) { DeferView(destination) }
}
它使用的
DeferView
定义为:
import SwiftUI
public struct DeferView<Content: View>: View {
let content: () -> Content
public init(@ViewBuilder _ content: @escaping () -> Content) { self.content = content }
public var body: some View { content() }
}
现在你可以这样做了:
// Use the standard bound-bool "show view" var format.
@State var showMyView: Bool
// Code to load the view, e.g. in a Button `action`.
navigateTo($showMyView, ["param1": myData1, "param2", myData2])
// Modifier to handle the view load, e.g. on outermost View inside `body`.
.destination(for: $showMyView) {
MyView(param1: appContext.popNavData("param1"),
param2: appContext.popNavData("param2"),
extraParam: $someOtherSource.info)
}