【SwiftUI】Extra argument in call エラーが出てViewを追加出来ない時の対処法

2022.02.14

この記事は公開されてから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が付与されているクロージャーパラメーターの箇所がVStackViewを記述している箇所になります。

この@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同様に VStackHStackZStackを使うことで10個までしかViewを追加出来ない問題を解決出来ます。

あとは、カスタムコンポーネントを作成して、複数のViewをまとめたり、ForEachメソッドを使用するのも効果的です。FormListでリストを作るのも良いでしょう。

おまけ Section

Groupと同様にSectionを使用することでもコンテンツを収集できますが、SectionFormListと一緒に使用すると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!

参考