布景

SwiftUI 中 View 能够理解为 State 的运算成果,View = f(State),在处理映射联系中,比:在一篇剖析文章中定义了如下 ViewState 类型,并试图通过扩展的方法映射到 SwiftUI View。


typealias BuilderWidget<T> = (T) -> some View
enum ViewState<T: Codable> {
    case loading
    case error
    case success(ViewSuccess)
    struct HttpRespone<T> {
        let data: T
    }
    enum ViewSuccess {
        case noData
        case content(BuilderWidget<T>, HttpRespone<T>)
    }
}
extension ViewState: View {
/// 这个some View 回来的是some View
/// 但是有必要是一个仅有确认的类型,比如你在.error中回来EmptyView(),那么就会立刻报错,一旦确认是回来是Text,那么有必要都是Text, 这也导致了BuilderView这闭包无法运用  
  var body: some View {
    switch self {
    case .error:
      return Text("")
    case .loading:
      return Text("")
    case .success(let successState):
      switch successState {
      case .noData:
        return Text("")
      case .content(let builder, let resp):
        return builder(resp.data)
      }
    }
  }
}

此处因为 SwiftUi 对 body 的 some View 不透明回来值类型的设定,不同的 case 要求的回来值类型需求保持一致,从语法层面当前的扩展无法编译通过。

解决方案

方案一:AnyView 的运用

运用 AnyView 类型,这就满足了那篇文章中的 ContainerView 的需求,即每一个回来的当地都运用 AnyView 进行包裹。


func createAnyView<T>(_ value: T) -> AnyView {
    return AnyView(Text("value"))
}

留心:这个方法是不太可取的,AnyView 会擦除自身的 View 类型,失去了 SwiftUI 的清晰的结构,不利于视图的刷新和动画,直接影响视图功能

方案二:正确理解和运用 ViewBuilder

SwiftUI 的 some View 的运用,因为 ViewBuilder 这一 resultBuilder 的特性,使得 body 的结构有充沛的灵活性和组合才干。
例如将 BuilderWidget 的声明新增一个 View 的泛型参数,一起也对 ViewState 新增 View 类型参数,上述的 body 代码就能够编译通过。
留心,SwiftUI 对 switch 的支撑要求是同类型,下面代码中运用的 if case 的形式,ViewBuilder 有更好的支撑


typealias BuilderWidget<T, V: View> = (T) -> V
enum ViewState<T: Codable, V: View> {
    case loading
    case error
    case success(ViewSuccess)
    struct HttpRespone<T> {
        let data: T
    }
    enum ViewSuccess {
        case noData
        case content(BuilderWidget<T, V>, HttpRespone<T>)
    }
}
extension ViewState: View {
    var body: some View {
        if case .success(let result) = self {
            if case .content(let builder, let resp) = result {
                builder(resp.data)
            } else {
                Text("no data")
            }
        }
        else if case .loading = self {
            ProgressView()
        }
        else {
            EmptyView()
        }
    }
}

而 body 的实践类型,不是某一个分支的 view,而是一个组合后的类型,通过打印得知如下:


_ConditionalContent<_ConditionalContent<_ConditionalContent<Button<Text>, Text>, ProgressView<EmptyView, EmptyView>>, EmptyView>

不过这样以来, VIewState 就包含了 View 的信息,不符合架构上的责任隔绝,ViewState 不负责 BuilderWidget 更适宜,能够抽象一个新的结构进行 view 的结构,例如:


typealias BuilderWidget<T: Codable, V: View> = (T) -> V
enum ViewState<T: Codable> {
    case loading
    case error
    case success(ViewSuccess)
    struct HttpRespone<T> {
        let data: T
    }
    enum ViewSuccess {
        case noData
        case content(HttpRespone<T>)
    }
}
struct ViewMaker<T: Codable, V: View>: View {
    let viewState: ViewState<T>
    let builder: BuilderWidget<T, V>
    var body: some View {
        if case .success(let result) = viewState {
            if case .content(let resp) = result {
                builder(resp.data)
            } else {
                Text("no data")
            }
        }
        else if case .loading = viewState {
            ProgressView()
        }
        else {
            EmptyView()
        }
    }
}

小结

关于 SwiftUI 的语法特性,对 ViewBuilder 和泛型有很好的使用,能够从其声明中进一步学习,先从 SwiftUI 完善的官方教程初步。

参阅文章

  • UI = f(State),在Swift中的一点考虑

  • ViewBuilder

  • resultBuilder