background
In SwiftUI, View can be understood as the operation result of State, View = F (State). In dealing with mapping relations, than: In an analysis article, the following ViewState type is defined, and it tries to map to SwiftUI View in an extended way.
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 {
/// This some View returns some View
If you return EmptyView() in.error, you'll get an error. If you return Text, it must be Text. This also makes the BuilderView closure unusable
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)
}
}
}
}
Copy the code
Because SwiftUi sets some View opaque return value type for body, different cases require the same return value type, so the current extension fails to compile syntactically.
The solution
Scheme 1: The use of AnyView
Use the AnyView type, which satisfies the need for ContainerView in that article to use AnyView for each return location.
func createAnyView<T> (_ value: T) -> AnyView {
return AnyView(Text("value"))}Copy the code
Note: This approach is not desirable, AnyView will erase its own View type, and lose the clear structure of SwiftUI, which is not conducive to View refresh and animation, directly affecting View performance
Plan 2: Understand and use ViewBuilder correctly
The use of some View of SwiftUI, because of ViewBuilder, the resultBuilder feature, makes the construction of body have full flexibility and combination ability. For example, if you add a View generic parameter to the BuilderWidget declaration and a View type parameter to ViewState, the body code will compile. Note that SwiftUI requires the same type of support for Switch, ViewBuilder has better support for the if case form used in the following code
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()}}}Copy the code
The actual type of the body is not a branch view, but a combined type, as shown in the following print:
_ConditionalContent<_ConditionalContent<_ConditionalContent<Button<Text>, Text>, ProgressView<EmptyView, EmptyView>>, EmptyView>
Copy the code
ViewState is not responsible for the BuilderWidget. It is more appropriate to abstract a new structure to construct a View, for example:
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()}}}Copy the code
summary
For the syntax features of SwiftUI, ViewBuilder and generics are a good use, and you can learn more from the statements, starting with the official tutorial of SwiftUI.
Refer to the article
-
UI = f(State), a little thought in Swift
-
ViewBuilder
-
resultBuilder