SwiftUI 页面导航最佳实践

Share
SwiftUI 页面导航最佳实践
Photo by Chris Lawton / Unsplash

通过全局 Router

  1. 定义一个全局 Router 对象,维护页面跳转类型和参数。
@Observable
final class Router {
    
    public enum Destination: Codable, Hashable {
        case pageA(models: [Model])
        case pageB
    }
    
    var navPath = NavigationPath()
    
    func navigate(to destination: Destination) {
        navPath.append(destination)
    }
    
    func navigateBack() {
        navPath.removeLast()
    }
    
    func navigateToRoot() {
        navPath.removeLast(navPath.count)
    }
}

枚举 Destination 可以指定要跳转到哪个页面以及携带的参数。

  1. 将 Router 和 NavigationStack 绑定在一起。
@Bindable var router = Router()    // 定义 router 变量   

NavigationStack(path: $router.navPath) {
    ContentView()
        .navigationDestination(for: Router.Destination.self) { destination in
            switch destination {
            case .pageA(let models):
                PageAView(models: models)
            case .pageB:
                PageBView()
            }
        }
}
.frame(width: CONTENT_VIEW_WIDTH, height: CONTENT_VIEW_HEIGHT)
.environment(router)

NavigationStack 的 path 传入的是 Router 的 path,这样 Router 就可以控制 NavigationStack 的页面跳转。在 navigationDestination 里处理各种页面的跳转。

.environment(router) 让 Router 传递给视图上的子视图,任意分支的子视图(包括子视图的子视图…)都可以拿到 Router 实例,控制页面跳转。

struct SubView: View {
    @Environment(Router.self) var router: Router
        
    private func gotoPageA() {
        router.navigate(to: .pageA(models: models))
    }
}

进阶版

利用泛型和协议,让路由支持自定义。

public protocol Destination: Hashable, Codable {
    
}

@Observable
final public class Router<Dest: Destination> {
    public var navPath = NavigationPath()
    
    public init() {
        
    }
}

public extension Router {
    func navigate(to destination: Dest) {
        navPath.append(destination)
    }
    
    func navigateBack() {
        navPath.removeLast()
    }
    
    func navigateToRoot() {
        navPath.removeLast(navPath.count)
    }
}

这样不同的业务就可以创建不同类别的页面枚举了。

enum BusinessDestination: Destination {
    case pageA(models: [Model])
    case pageB
}

struct ContainerView: View {
    @Bindable var router = Router<BusinessDestination>()
}