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