ViewBuilderは10個のViewまでコンパイル可能
VStack
や HStack
は ViewBuilder
によって複数のViewを受け付けることができます
ViewBuilderはFunction buildersの実装です。(ここでは割愛)
VStack {
Text("")
Text("")
}
2つのTextは実際には TupleView
によって包まれます。
TupleViewの特徴は要素の数と要素の型がコンパイル時に分かることです。
ですが TupleViewには個数の制限が存在します。
ViewBuilderの実装を見ると
@available(iOS 13.0, OSX 10.15, tvOS 13.0, watchOS 6.0, *)
extension ViewBuilder {
public static func buildBlock<C0, C1, C2, C3, C4, C5, C6, C7, C8, C9>(_ c0: C0, _ c1: C1, _ c2: C2, _ c3: C3, _ c4: C4, _ c5: C5, _ c6: C6, _ c7: C7, _ c8: C8, _ c9: C9) -> TupleView<(C0, C1, C2, C3, C4, C5, C6, C7, C8, C9)> where C0 : View, C1 : View, C2 : View, C3 : View, C4 : View, C5 : View, C6 : View, C7 : View, C8 : View, C9 : View
}
C0 から C9までなので、VStackの中には10個までViewが書けるということになりそうです。
実際にVStackに10個のTextを入れてみると、コンパイルは通ります。
そして、11個のTextを書いてみるとエラーになります。 ViewBuilderの実装が11個のViewをサポートしていないということを意味します。
ForEachを使って対応
無限に書ければ嬉しいのですが、今のところそのサポートは無いようです。 現実問題としてもTupleViewで無限の表現は無理で、Tupleの定義を10個から100個にしても限界は来るし、定義もものすごいことになってしまいます。
そこでリストとしてレンダリングを行う ForEach
というViewを使うことを検討してみます。
ForEach
はデータソースに Identifiable
protocolの実装を求めるので、適当に作ります。
struct IdentifiedView: View, Identifiable {
let id = UUID() // this is just for example
private let content: AnyView
init<Content>(@ViewBuilder _ content: () -> Content) where Content: View {
self.content = AnyView(content())
}
var body: some View {
content
}
}
使い方
IdentifiedView { Text("Hello") }
極端ですが、これでいくらでも書けます。
その代わりTupleViewと違い、Contentの型は不明瞭になります。
こんなことを調べていたのは、UIScrollViewを使ってカスタムUIなFormを作りたかったから。
import SwiftUI
struct DevelopmentMenuView: View {
var body: some View {
ScrollView(.vertical, showsIndicators: true) {
ZStack {
Spacer() // In order to expand view to full-screen
VStack(spacing: CGFloat(0.0)) {
ForEach([
section(title: "Options"),
separator(),
row(title: "A"),
separator(),
row(title: "B"),
separator(),
row(title: "C"),
separator(),
row(title: "D"),
separator(),
row(title: "E"),
separator(),
row(title: "F"),
], content: { $0 })
}
.padding(.init(top: 0, leading: 20, bottom: 0, trailing: 20))
}
}
}
}
struct IdentifiedView: View, Identifiable {
let id = UUID()
private let content: AnyView
init<Content>(@ViewBuilder _ content: () -> Content) where Content: View {
self.content = AnyView(content())
}
var body: some View {
content
}
}
private func section(title: String) -> IdentifiedView {
IdentifiedView {
HStack() {
Text(title)
.multilineTextAlignment(.leading)
.font(.system(.largeTitle, design: .default))
.foregroundColor(.black)
Spacer()
}
.padding(.init(top: 16, leading: 0, bottom: 16, trailing: 0))
}
}
private func row(title: String) -> IdentifiedView {
IdentifiedView {
HStack() {
Text(title)
.multilineTextAlignment(.leading)
.font(.system(.body, design: .default))
.foregroundColor(.black)
Spacer()
}
.padding(.init(top: 16, leading: 0, bottom: 16, trailing: 0))
}
}
private func separator() -> IdentifiedView {
IdentifiedView {
Color(white: 0.95)
.frame(height: 1)
}
}