MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

SwiftUI导航与路由实践

2023-12-115.3k 阅读

SwiftUI 导航基础

导航视图类型

在 SwiftUI 中,最常用的导航视图是 NavigationView。它提供了一种简单且直观的方式来管理屏幕之间的层次结构。NavigationView 通常有两种风格:堆叠式(适用于 iPhone 等小屏幕设备)和并列式(适用于 iPad 等大屏幕设备)。

例如,以下代码创建了一个基本的 NavigationView

import SwiftUI

struct ContentView: View {
    var body: some View {
        NavigationView {
            List {
                Text("Item 1")
                Text("Item 2")
                Text("Item 3")
            }
          .navigationTitle("My List")
        }
    }
}

在这个例子中,List 被包含在 NavigationView 内。navigationTitle 修饰符用于设置导航栏的标题。

导航栏的定制

  1. 标题与样式
    • 除了基本的标题设置,还可以对导航栏标题的样式进行定制。例如,可以改变字体、颜色等。
    struct ContentView: View {
        var body: some View {
            NavigationView {
                List {
                    Text("Item 1")
                    Text("Item 2")
                    Text("Item 3")
                }
              .navigationTitle(
                   Text("My Custom List")
                   .font(.title)
                   .foregroundColor(.blue)
               )
            }
        }
    }
    
  2. 导航栏按钮
    • 可以在导航栏的左右两侧添加按钮。例如,添加一个返回按钮和一个编辑按钮:
    struct ContentView: View {
        @State private var isEditing = false
        var body: some View {
            NavigationView {
                List {
                    Text("Item 1")
                    Text("Item 2")
                    Text("Item 3")
                }
              .navigationTitle("My List")
              .navigationBarItems(
                   leading: Button("Back") {
                       // 处理返回逻辑
                   },
                   trailing: Button(isEditing? "Done" : "Edit") {
                       isEditing.toggle()
                   }
               )
            }
        }
    }
    

基于栈的导航

导航栈原理

SwiftUI 的 NavigationView 基于导航栈来管理视图。当使用 NavigationLink 导航到新视图时,新视图被压入栈中,而当用户点击返回按钮时,当前视图从栈中弹出。

使用 NavigationLink 导航

  1. 基本导航 假设我们有两个视图 ContentViewDetailView,可以通过 NavigationLink 实现从 ContentViewDetailView 的导航:
    struct ContentView: View {
        var body: some View {
            NavigationView {
                VStack {
                    NavigationLink("Go to Detail", destination: DetailView())
                }
              .navigationTitle("Main")
            }
        }
    }
    
    struct DetailView: View {
        var body: some View {
            Text("This is the detail view")
          .navigationTitle("Detail")
        }
    }
    
  2. 带参数导航 很多时候,我们需要将数据从一个视图传递到另一个视图。可以通过在 NavigationLink 中传递参数来实现。
    struct ContentView: View {
        var items = ["Item 1", "Item 2", "Item 3"]
        var body: some View {
            NavigationView {
                List(items, id: \.self) { item in
                    NavigationLink(item, destination: DetailView(item: item))
                }
              .navigationTitle("Main")
            }
        }
    }
    
    struct DetailView: View {
        let item: String
        var body: some View {
            Text("You selected: \(item)")
          .navigationTitle("Detail")
        }
    }
    

动态导航

  1. 条件导航 有时候,导航行为可能需要根据某些条件来决定。可以使用 @State 变量结合 NavigationLinkisActive 参数来实现。
    struct ContentView: View {
        @State private var shouldNavigate = false
        var body: some View {
            NavigationView {
                VStack {
                    Button("Navigate Conditionally") {
                        shouldNavigate = true
                    }
                    NavigationLink("Go to Detail", destination: DetailView(), isActive: $shouldNavigate)
                }
              .navigationTitle("Main")
            }
        }
    }
    
    struct DetailView: View {
        var body: some View {
            Text("This is the detail view")
          .navigationTitle("Detail")
        }
    }
    
  2. 通过视图模型控制导航 在更复杂的应用中,可能会将导航逻辑封装在视图模型中。例如:
    class ViewModel: ObservableObject {
        @Published var shouldNavigate = false
    }
    
    struct ContentView: View {
        @StateObject var viewModel = ViewModel()
        var body: some View {
            NavigationView {
                VStack {
                    Button("Navigate via ViewModel") {
                        viewModel.shouldNavigate = true
                    }
                    NavigationLink("Go to Detail", destination: DetailView(), isActive: $viewModel.shouldNavigate)
                }
              .navigationTitle("Main")
            }
        }
    }
    
    struct DetailView: View {
        var body: some View {
            Text("This is the detail view")
          .navigationTitle("Detail")
        }
    }
    

模态导航

模态视图的概念

模态视图是一种临时覆盖在当前视图之上的视图,通常用于显示一些需要用户关注的信息或进行特定操作。在 SwiftUI 中,可以使用 sheetfullScreenCover 等修饰符来呈现模态视图。

使用 sheet 呈现模态视图

  1. 基本使用
    struct ContentView: View {
        @State private var isSheetPresented = false
        var body: some View {
            VStack {
                Button("Show Sheet") {
                    isSheetPresented = true
                }
            }
          .sheet(isPresented: $isSheetPresented) {
               Text("This is a sheet")
            }
        }
    }
    
  2. 带参数的 sheet 类似于 NavigationLinksheet 也可以传递参数。
    struct ContentView: View {
        @State private var isSheetPresented = false
        var body: some View {
            VStack {
                Button("Show Sheet with Data") {
                    isSheetPresented = true
                }
            }
          .sheet(isPresented: $isSheetPresented) {
               DetailSheetView(data: "Some data")
            }
        }
    }
    
    struct DetailSheetView: View {
        let data: String
        var body: some View {
            Text("Received data: \(data)")
        }
    }
    

fullScreenCover 的应用

  1. 全屏模态 fullScreenCover 用于呈现全屏的模态视图。这在显示一些重要信息或进行需要用户高度专注的操作时非常有用。
    struct ContentView: View {
        @State private var isFullScreenPresented = false
        var body: some View {
            VStack {
                Button("Show Full - Screen Cover") {
                    isFullScreenPresented = true
                }
            }
          .fullScreenCover(isPresented: $isFullScreenPresented) {
               Text("This is a full - screen cover")
            }
        }
    }
    
  2. 与 sheet 的区别
    • sheet 通常用于显示一些相对较小的信息或进行简单的操作,它不会完全覆盖当前视图,而是从底部滑出或在当前视图上以弹窗形式呈现。
    • fullScreenCover 则是完全覆盖当前视图,适用于需要用户专注处理的场景,如设置页面、新内容创建等。

路由模式与实践

路由的概念

在应用开发中,路由是指根据不同的条件或用户操作,决定导航到哪个视图的过程。在 SwiftUI 中,虽然没有像一些 Web 框架那样明确的路由表概念,但可以通过合理的设计和代码组织来实现类似的功能。

基于枚举的路由

  1. 定义路由枚举 首先,定义一个枚举来表示不同的路由路径。
    enum AppRoute {
        case home
        case detail(String)
        case settings
    }
    
  2. 管理路由状态 使用 @State 变量来跟踪当前的路由状态。
    struct ContentView: View {
        @State private var currentRoute: AppRoute? = .home
        var body: some View {
            if let route = currentRoute {
                switch route {
                case .home:
                    HomeView()
                case .detail(let item):
                    DetailView(item: item)
                case .settings:
                    SettingsView()
                }
            }
        }
    }
    
    struct HomeView: View {
        var body: some View {
            Text("This is the home view")
        }
    }
    
    struct DetailView: View {
        let item: String
        var body: some View {
            Text("You selected: \(item)")
        }
    }
    
    struct SettingsView: View {
        var body: some View {
            Text("This is the settings view")
        }
    }
    
  3. 导航到不同路由 可以通过按钮等交互元素来改变 currentRoute 的值,从而实现导航。
    struct ContentView: View {
        @State private var currentRoute: AppRoute? = .home
        var body: some View {
            VStack {
                if let route = currentRoute {
                    switch route {
                    case .home:
                        HomeView()
                    case .detail(let item):
                        DetailView(item: item)
                    case .settings:
                        SettingsView()
                    }
                }
                Button("Go to Detail") {
                    currentRoute = .detail("Some item")
                }
                Button("Go to Settings") {
                    currentRoute = .settings
                }
            }
        }
    }
    

结合视图模型的路由管理

  1. 将路由逻辑封装到视图模型 创建一个视图模型来管理路由状态和导航逻辑。
    class AppRouter: ObservableObject {
        @Published var currentRoute: AppRoute? = .home
        func navigate(to route: AppRoute) {
            currentRoute = route
        }
    }
    
    struct ContentView: View {
        @StateObject var router = AppRouter()
        var body: some View {
            VStack {
                if let route = router.currentRoute {
                    switch route {
                    case .home:
                        HomeView()
                    case .detail(let item):
                        DetailView(item: item)
                    case .settings:
                        SettingsView()
                    }
                }
                Button("Go to Detail") {
                    router.navigate(to:.detail("Some item"))
                }
                Button("Go to Settings") {
                    router.navigate(to:.settings)
                }
            }
        }
    }
    
  2. 优点与应用场景
    • 将路由逻辑封装到视图模型中,使得视图代码更加简洁,同时也便于在不同的视图中复用导航逻辑。这种方式在大型应用中非常有用,因为可以集中管理路由,并且更容易进行测试和维护。

复杂导航场景处理

多层级导航

  1. 多层级 NavigationView 嵌套 在一些复杂应用中,可能需要多层级的导航。例如,在一个电商应用中,可能有商品列表 -> 商品详情 -> 商品规格选择等多层级导航。
    struct RootView: View {
        var body: some View {
            NavigationView {
                List {
                    NavigationLink("Category 1", destination: CategoryView())
                }
              .navigationTitle("Main Categories")
            }
        }
    }
    
    struct CategoryView: View {
        var body: some View {
            NavigationView {
                List {
                    NavigationLink("Product 1", destination: ProductView())
                }
              .navigationTitle("Category 1 Products")
            }
        }
    }
    
    struct ProductView: View {
        var body: some View {
            Text("This is the product view")
          .navigationTitle("Product Details")
        }
    }
    
  2. 处理多层级返回 当存在多层级导航时,有时候需要直接返回到特定层级的视图。可以通过自定义返回按钮或使用 NavigationStack 来实现。例如,在 ProductView 中添加一个按钮,直接返回到 RootView
    struct ProductView: View {
        @Environment(\.dismiss) var dismiss
        var body: some View {
            VStack {
                Text("This is the product view")
                Button("Back to Main") {
                    // 这里可以实现直接返回到 RootView 的逻辑
                    // 例如使用自定义的导航栈管理逻辑
                    dismiss()
                }
            }
          .navigationTitle("Product Details")
        }
    }
    

选项卡与导航结合

  1. 使用 TabView 和 NavigationView 很多应用都有底部选项卡,同时每个选项卡内又有导航功能。可以通过组合 TabViewNavigationView 来实现。
    struct AppContentView: View {
        var body: some View {
            TabView {
                NavigationView {
                    List {
                        NavigationLink("Item 1", destination: DetailView())
                    }
                  .navigationTitle("Tab 1")
                }
              .tabItem {
                   Image(systemName: "house")
                   Text("Home")
               }
                NavigationView {
                    Text("Tab 2 content")
                  .navigationTitle("Tab 2")
                }
              .tabItem {
                   Image(systemName: "gear")
                   Text("Settings")
               }
            }
        }
    }
    
    struct DetailView: View {
        var body: some View {
            Text("This is the detail view")
          .navigationTitle("Detail")
        }
    }
    
  2. 处理选项卡间的导航 有时候可能需要在不同选项卡之间进行导航。这可以通过共享的视图模型或环境对象来实现。例如,创建一个全局的视图模型来管理导航状态,不同选项卡内的视图可以通过这个视图模型来触发导航到其他选项卡的特定视图。
    class GlobalRouter: ObservableObject {
        @Published var shouldNavigateToTab2Detail = false
    }
    
    struct AppContentView: View {
        @StateObject var router = GlobalRouter()
        var body: some View {
            TabView {
                NavigationView {
                    VStack {
                        List {
                            NavigationLink("Item 1", destination: DetailView())
                        }
                        Button("Navigate to Tab 2 Detail") {
                            router.shouldNavigateToTab2Detail = true
                        }
                    }
                  .navigationTitle("Tab 1")
                }
              .tabItem {
                   Image(systemName: "house")
                   Text("Home")
               }
                NavigationView {
                    if router.shouldNavigateToTab2Detail {
                        Tab2DetailView()
                    } else {
                        Text("Tab 2 content")
                    }
                  .navigationTitle("Tab 2")
                }
              .tabItem {
                   Image(systemName: "gear")
                   Text("Settings")
               }
            }
        }
    }
    
    struct DetailView: View {
        var body: some View {
            Text("This is the detail view")
          .navigationTitle("Detail")
        }
    }
    
    struct Tab2DetailView: View {
        var body: some View {
            Text("This is the detail view in Tab 2")
          .navigationTitle("Tab 2 Detail")
        }
    }
    

通过以上对 SwiftUI 导航与路由的详细介绍和实践示例,开发者可以根据应用的具体需求,灵活选择和组合不同的导航方式,构建出用户体验良好的应用程序。无论是简单的基于栈的导航,还是复杂的多层级、选项卡结合的导航场景,SwiftUI 都提供了丰富的工具和方法来实现。