SwiftUI's expression syntax for ViewState

background

In SwiftUI, View can be understood as the result of State operation, View = f(State), in processing the mapping relationship, than: In an analysis article , the following ViewState types are defined, and they try to map to SwiftUI View by extension.


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)
            }
        }
    }
}

复制代码

Here, due to the setting of the opaque return value type of some View of the body by SwiftUi, the return value type required by different cases needs to be consistent, and the current extension cannot be compiled from the syntax level.

solution

Option 1: Use of AnyView

Using the AnyView type, this satisfies the needs of the ContainerView in that article, that is, every return place is wrapped with AnyView.


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

复制代码

Note: This method is not desirable. AnyView will erase its own View type, lose the clear structure of SwiftUI, which is not conducive to the refresh and animation of the view, and directly affects the performance of the view.

Option 2: Correctly understand and use ViewBuilder

The use of some View in SwiftUI, because of the characteristics of the resultBuilder of ViewBuilder, makes the construction of the body have full flexibility and combination ability. For example, adding a generic parameter of View to the declaration of BuilderWidget, and adding a parameter of View type to ViewState, the above body code can be compiled and passed. Note that SwiftUI's support requirements for switch are of the same type. ViewBuilder has better support for the form of if case used in the code below.


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()
        }
    }
}

复制代码

The actual type of body is not the view of a certain branch, but a combined type, which is obtained by printing as follows:


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

复制代码

However, since this way, VIewState contains the information of the View, which does not conform to the responsibility isolation in the architecture.


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()
        }
    }
}

复制代码

summary

For the syntax features of SwiftUI, it has a good application to ViewBuilder and generics. You can learn further from its declaration, starting with the complete official tutorial of SwiftUI .

Reference article

Guess you like

Origin juejin.im/post/7079223939129409567