ViewStateのSwiftUIの式構文

バックグラウンド

SwiftUIでは、Viewは、マッピング関係の処理における状態操作View = f(State)の結果として理解できます。分析記事では、次のViewStateタイプが定義されており、SwiftUIViewにマップしようとします。拡大。


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による本文の一部のビューの不透明な戻り値タイプの設定により、さまざまなケースで必要な戻り値タイプは一貫している必要があり、現在の拡張機能を構文レベルからコンパイルすることはできません。

解決

オプション1:AnyViewの使用

その記事のContainerViewのニーズを満たすAnyViewタイプを使用します。つまり、すべての戻り場所はAnyViewでラップされます。


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

复制代码

注:この方法は望ましくありません。AnyViewは独自のビュータイプを消去し、SwiftUIの明確な構造を失います。これは、ビューの更新とアニメーションに役立たず、ビューのパフォーマンスに直接影響します。

オプション2:ViewBuilderを正しく理解して使用する

ViewBuilderのresultBuilderの特性により、SwiftUIの一部のViewを使用すると、ボディの構築に完全な柔軟性と組み合わせ機能が備わっています。たとえば、ViewのジェネリックパラメーターをBuilderWidgetの宣言に追加し、ViewタイプのパラメーターをViewStateに追加すると、上記の本文コードをコンパイルして渡すことができます。SwiftUIのスイッチのサポート要件は同じタイプであることに注意してください。ViewBuilderは、以下のコードで使用されているifケースの形式でより適切にサポートされています。


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

复制代码

ボディの実際のタイプは、特定のブランチのビューではなく、次のように印刷することによって取得される複合タイプです。


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

复制代码

ただし、このようにすると、VIewStateにはビューの情報が含まれますが、これはアーキテクチャーの責任分離に準拠していません。


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の完全な公式チュートリアルから始めて、宣言からさらに学ぶことができます。

参考記事

おすすめ

転載: juejin.im/post/7079223939129409567