布景
在 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