この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
SwiftUIでVStack
のクロージャー内にView
を記述していると、Extra argument in callというエラーが発生して、View
を新規で追加出来なくなりました。
その発生理由と対処法を調べたので記載しておきます。
環境
- Xcode 13.2.1
発生原因
エラーメッセージの意味
Extra argument in call
を意訳すると、
呼び出しの中で引数が過剰です。
というエラーメッセージのようです。
原因調査
VStack
の定義を見てみました。
@frozen public struct VStack<Content> : View where Content : View {
/// Creates an instance with the given spacing and horizontal alignment.
///
/// - Parameters:
/// - alignment: このスタック内のサブビューを整列させるためのガイド
/// - spacing: 隣接するサブView間の距離。デフォルトの距離は「nil」
/// - content: このスタックのコンテンツを作成するViewBuilder
@inlinable public init(alignment: HorizontalAlignment = .center, spacing: CGFloat? = nil, @ViewBuilder content: () -> Content)
init
メソッドの第三引数の@ViewBuilder
が付与されているクロージャーパラメーターの箇所がVStack
でView
を記述している箇所になります。
この@ViewBuilder
のついて調べることにしました。
@ViewBuilder
Appleのドキュメント ViewBuliderの概要には、
ViewBulderは子Viewを生成するクロージャーパラメーターのパラメーター属性として使用します。これらのクロージャーは複数の子Viewを提供できるようにします。
つまりは、@ViewBulider
を付与したクロージャーは複数の子Viewを提供できる。
はずだが、この複数の子Viewの数が過剰になっている為、今回のエラーが出ていると考えられる。
ViewBuilder
の定義を見てみましょう。
@resultBuilder public struct ViewBuilder {
/// 命令文を含まないブロックから空のViewを返します
public static func buildBlock() -> EmptyView
/// 子Viewとして書き込まれた単一のViewを変更せずに渡します。
///
/// 例は以下の通りです
/// `{ Text("Hello") }`.
public static func buildBlock(_ content: Content) -> Content where Content : View
}
子Viewを生成するクロージャーの中では、このbuildBlock
メソッドが実行されており、上記のメソッドだと単一のViewを返すだけですが、extension
に10個までのViewを渡せるbuildBlock
メソッドが存在しています。
10個の子Viewを持つ1つのTupleView
を返しています。
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
}
Xcode 13.2.1時点では、最大10個までのViewを渡せる引数しかないので、今回のExtra argument in call
エラーが出た意味も分かりました。
発生理由
Content
を表示する為のbuildblack
メソッドの引数が最大10個までしかない為、それ以上View
を追加出来ないというエラーが発生していました。
今後の引数が増える対応があった場合には、追加できるView
の数も増えるかもしれませんね。
対処法
では、10個以上が追加出来ないのかというとそういうわけでもありません。
Groupを使う
Groupはコンテンツ内の複数のインスタンスをレイアウトの影響を受けることなく、収集出来ます。
@inlinable public init(@ViewBuilder content: () -> Content)
Groupのクロージャー引数のcontent
にも@ViewBuilder
がついているので、10個までのViewしか渡すことはできません。
ですが、Group
自体は1つのView
なので、10個の子Viewを持ったGroupを複数呼ぶことで、body内に10個以上のViewを呼ぶことが出来ました。
struct ContentView: View {
var body: some View {
VStack {
Group {
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
}
Text("愛に埋め尽くされたい")
Group {
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
}
}
}
}
無事に念願だった愛に埋め尽くされることが出来ました。
その他
Group
同様に VStack
、HStack
、 ZStack
を使うことで10個までしかView
を追加出来ない問題を解決出来ます。
あとは、カスタムコンポーネントを作成して、複数のView
をまとめたり、ForEach
メソッドを使用するのも効果的です。Form
やList
でリストを作るのも良いでしょう。
おまけ Section
Group
と同様にSection
を使用することでもコンテンツを収集できますが、Section
はForm
やList
と一緒に使用すると100%の力を発揮します。
struct ContentView: View {
var body: some View {
Form {
Section() {
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
}
Text("愛に埋め尽くされたい")
Section {
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
}
}
}
}
プレビューを見てみましょう。
こうも簡単にSwiftUIではstatic Table viewのようなものを実装できるのですね、、
またSection
にタイトルを追加することも出来ます。
Section("愛に言葉なんていらない") {
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
Text("❤️")
}
おわりに
愛に埋め尽くされたので素敵なバレンタインデーになりました。
Happy Valentine!