SwiftUIで@ViewBuilderを使用してビューの配列を作成/抽出する方法はありますか?質問する

SwiftUIで@ViewBuilderを使用してビューの配列を作成/抽出する方法はありますか?質問する

struct私は、ビューの配列を受け入れ、VStackそれらのビューがすべて斜めに積み重ねられていることを除いてそれらのビューを含む通常のものを返すシンプルなものを作成しようとしています。

コード:

struct LeaningTower<Content: View>: View {
    var views: [Content]
    var body: some View {
        VStack {
            ForEach(0..<views.count) { index in
                self.views[index]
                    .offset(x: CGFloat(index * 30))
            }
        }
    }
}

これはうまく機能しますが、これを呼び出す必要があるたびにいつもイライラします。

LeaningTower(views: [Text("Something 1"), Text("Something 2"), Text("Something 3")])

このように配列にビューをリストすることは非常に奇妙に思えるので、次のように構造体@ViewBuilderを呼び出す方法があるかどうか疑問に思いました。LeaningTower

LeaningTower {  // Same way how you would create a regular VStack
    Text("Something 1")
    Text("Something 2")
    Text("Something 3")
    // And then have all of these Text's in my 'views' array
}

ビューの配列を作成/抽出する方法があれば@ViewBuilder教えてください。

(たとえそれが不可能であっても、@ViewBuilder見た目をきれいにするあらゆる手段を使うと大いに役立ちます)

ベストアンサー1

ビューを抽出する必要があることはまれです配列@ViewBuilderコンテンツをビューに渡すだけの場合は、次の操作を実行するだけです。

struct ContentView: View {
    var body: some View {
        VStackReplica {
            Text("1st")
            Text("2nd")
            Text("3rd")
        }
    }
}

struct VStackReplica<Content: View>: View {
    @ViewBuilder let content: () -> Content

    var body: some View {
        VStack(content: content)
    }
}

これがあなたのユースケースに十分でない場合は、以下を参照してください。


推奨: 私のビュー抽出このパッケージは、ここでのコードよりもはるかに堅牢です。

汎用バージョンが動作しているので、異なる長さのタプルに対して複数の初期化子を作成する必要はありません。さらに、ビューは任意のものにすることができます (すべてがView同じ型である必要はありません)。

私がこのために作ったSwiftパッケージは、ジョージ・エルシャム/ViewExtractorこの回答は簡略化された基本バージョンに過ぎないため、この回答の内容よりも多くの内容が含まれています。コードはこの回答と若干異なるため、README.md例として最初の回答を読んでください。

答えに戻って、使用例:

struct ContentView: View {
    
    var body: some View {
        LeaningTower {
            Text("Something 1")
            Text("Something 2")
            Text("Something 3")
            Image(systemName: "circle")
        }
    }
}

ビューの定義:

struct LeaningTower: View {
    private let views: [AnyView]
    
    init<Views>(@ViewBuilder content: @escaping () -> TupleView<Views>) {
        views = content().getViews
    }
    
    var body: some View {
        VStack {
            ForEach(views.indices) { index in
                views[index]
                    .offset(x: CGFloat(index * 30))
            }
        }
    }
}

TupleView拡張機能(つまり、すべての魔法が起こる場所):

extension TupleView {
    var getViews: [AnyView] {
        makeArray(from: value)
    }
    
    private struct GenericView {
        let body: Any
        
        var anyView: AnyView? {
            AnyView(_fromValue: body)
        }
    }
    
    private func makeArray<Tuple>(from tuple: Tuple) -> [AnyView] {
        func convert(child: Mirror.Child) -> AnyView? {
            withUnsafeBytes(of: child.value) { ptr -> AnyView? in
                let binded = ptr.bindMemory(to: GenericView.self)
                return binded.first?.anyView
            }
        }
        
        let tupleMirror = Mirror(reflecting: tuple)
        return tupleMirror.children.compactMap(convert)
    }
}

結果:

結果

おすすめ記事