VDFlow

描述

这个仓库提供了一种新的、简单的方法来描述路由器。
我将应用程序流程视为所有可能的屏幕状态的树。从这个角度来看,导航就是选择这棵树的一个节点。

示例

例如,有一个具有如下屏幕层次结构的应用程序

             TabView          
   ┌────────────┼────────────┐
  Tab1         Tab2    NavigationView
                    ┌────────┴────────┐
                 RootView         Push1View
                                      
                                  PickerView
                              ┌───────┴───────┐
                            Text1           Text2

PickerView 在这里演示导航不仅可以意味着更改屏幕,还可以意味着更改任何视图的任何状态。

将你的流程描述为一个具有 Step 属性的结构体

@Steps
struct TabSteps {

  var tab1
  var tab2: SomeTab2Data = .init()
  var tab3: NavigationSteps = .screen1
  var none
}

@Steps
struct NavigationSteps {

  var screen1
  var screen2: PickerSteps = .none
}

@Steps
struct PickerSteps {

  var text1
  var text2
  var none
}
var steps: TabSteps = .tab1

如果你想打开 Tab2,你需要将 tab2 标记为已选择。 你有几种方法可以做到这一点

  1. 设置 selected 属性
steps.selected = .tab2
  1. 使用自动生成的静态函数
steps = .tab2(SomeTab2Data())

你可以检查哪个属性被选中

  1. 使用 selected 属性
$steps.selected == .tab2

你也可以设置初始选中的属性

var screen3: PickerSteps = .text1

深层链接 (Deeplink)

然后你得到了一个深层链接,例如,你需要将 Tab2 更改为具有 NavigationView 的第三个标签,推送到 Push2View,并在 PickerView 中选择 Text2

steps.tab3.$screen2.select(with: .text2)

现在 tab3screen3text2 属性被标记为已选中。

与 UI 集成

SwiftUI 是一个状态驱动的框架,因此很容易使用 Step 实现导航。

1. StateStep 属性包装器。

StateStep 更新视图,存储你的流程结构体,或者从父视图将其绑定为环境变量。 要将流程向下绑定到视图层次结构,你需要使用 .step(...).stepEnvironment(...) 视图修饰符,或者使用 Binding<Step<...>> 初始化 StateStep
stepEnvironment 将当前 step 向下绑定到视图层次结构,用于嵌入的 StateStep 属性。 step 修饰符只是 tagstepEnvironment 修饰符的组合。

struct RootTabView: View {

  @StateStep var step: TabSteps = .tab1
  
  var body: some View {
    TabView(selection: $step.selected) {
      Tab1()
        .step(_step.$tab1)
      
      Tab2()
        .step(_step.$tab2)
      
      EmbededNavigation()
        .step(_step.$tab3)
    }
    .tabViewStyle(PageTabViewStyle(indexDisplayMode: .always))
  }
}

struct EmbededNavigation: View {
  
  @StateStep var step = NavigationSteps()
  
  var body: some View {
    NavigationView {
      RootView {
        NavigationLink(isActive: $step.isSelected(.screen3)) {
          EmbededPicker()
            .stepEnvironment($step.$screen2)
        } label: {
          Text("push")
        }
      }
    }
  }
}

struct EmbededPicker: View {
  
  @StateStep var step = PickerSteps()
  
  var body: some View {
    Picker("3", selection: $step.selected) {
      Text("\(step.prefixString) 0")
        .tag(PickerSteps.Steps.text1)
      
      Text("\(step.prefixString) 1")
        .tag(PickerSteps.Steps.text2)
    }
    .pickerStyle(WheelPickerStyle())
  }
}

4. 绑定

你可以直接使用 Step,而无需 StateStep 包装器,可以在 ObservableObject 视图模型中,或者作为 TCA Store 中的状态的一部分等等。

5. UIKit

对于 UIKit 没有任何特殊的工具,因为 UIKit 不支持状态驱动的导航,但是可以使用 Combine 来订阅 Step 的更改

let stepsSubject = CurrentValueSubject(TabSteps(.tab1))

stepsSubject
  .map(\.selected)
  .removeDublicates()
  .sink { selected in
    switch selected {
    case .tab1:
      ... 
    }
  }

stepsSubject.value.$tab2.select()

或者使用 didSet

var steps = TabSteps(.tab1) {
  didSet {
    guard oldValue.selected != steps.selected else { return }
    ... 
  }
}

工具

NavigationLink 便捷初始化器

@StateStep var steps = Steps()
...
NavigationLink(step: _steps.$link) {
  ...
} label: {
  ...
}

navigationPath()Binding<Step<...>> 的扩展和两个 navigationDestination 方法

@StateStep var steps = Steps()
    
var body: some View {
    NavigationStack(path: $steps.navigationPath) {
        RootView()
            .navigationDestination(step: _steps.$link) {
                PushView()
            }
            // or
            .navigationDestination(for: _steps) {
                switch $0 {
                case .link:
                    PushView()
                    	.step(_step.$link)
                default:
                    EmptyView()
                }
            }
    }
    
}

安装

  1. Swift 包管理器

创建一个 Package.swift 文件。

// swift-tools-version:5.9
import PackageDescription

let package = Package(
  name: "SomeProject",
  dependencies: [
    .package(url: "https://github.com/dankinsoid/VDFlow.git", from: "4.31.0")
  ],
  targets: [
    .target(name: "SomeProject", dependencies: ["VDFlow"])
  ]
)
$ swift build

作者

Daniil Voidilov, voidilov@gmail.com

许可证

VDFlow 基于 MIT 许可证发布。 详细信息请参见 LICENSE 文件。