🚨

【Swift】「Extra argument in call」エラーを解消する

2023/12/30に公開

初めに

今回は SwiftUI で VisionOS を触っている途中で発生した 「Extra argument in call」 というエラーを解消する方法を共有したいと思います。

記事の対象者

  • Swift, SwiftUI 学習者
  • 上記エラーを解決したい方

目的

今回は以下のように実装中に発生した「Extra argument in call」エラーを解決します。

環境

  • Xcode 15.1 beta
  • MacBook Air M1, 2020
  • visionOS Simulator

エラー原因

「Extra argument in call」 は 「呼び出しの引数が余分です」の意味のようです。

エラーが発生したコードは以下のとおりです。

import SwiftUI

@main
struct InstructionManualApp: App {
    @ObservedObject private var model = AreaViewModel()
    var body: some Scene {
        WindowGroup(id: model.contentAreaId) {
            ContentView()
        }
        
        WindowGroup(id: model.shelfContentAreaId) {
            ShelfContentView()
        }
        
        WindowGroup(id: model.shelfRealityAreaId) {
            ShelfRealityArea()
        }
        .defaultSize(CGSize(width: 800, height: 1000))
        
        WindowGroup(id: model.equipmentRealityAreaId) {
            EquipmentRealityArea()
        }
        .defaultSize(CGSize(width: 700, height: 700))
        
        WindowGroup(id: model.videoAreaId) {
            VideoArea(videoSourcePath: "ScrewDetail")
        }
        .defaultSize(CGSize(width: 700, height: 700))
        
        WindowGroup(id: model.completedAreaId) {
            CompletedRealityArea()
        }
        .defaultSize(CGSize(width: 800, height: 900))
        
        WindowGroup(id: model.robotCleanerContentAreaId) {
            RobotCleanerContentView()
        }
        
        WindowGroup(id: model.robotCleanerNumId) {
            RobotCleanerNumRealityArea()
        }
        
        WindowGroup(id: model.robotCleanerMaintenanceId) {
            RobotCleanerMaintenanceArea()
        }
        
        WindowGroup(id: model.robotCleanerMaintenanceTermTableAreaId) {
            RobotCleanerMaintenanceTermTableArea()
        }
        
        WindowGroup(id: model.robotCleanerMaintenanceDustBoxRealityAreaId) {
            RobotCleanerMaintenanceDustBoxRealityArea()
        }
    }
}

この場合において「呼び出しの引数が余分」とは、「Scene で呼び出している引数の数が多すぎますよ」ということかと思います。

上記のコードでは、Scene の引数になっている WindowGroup は10個あります。
試しに WindowGroup を1つ削除してみると、エラーが解消されました。
したがって、このエラーは記述の通り、1つの Scene の引数が多すぎることが原因であるとわかります。

エラーを調べているうちにこちらの記事に辿り着きました。
この記事にもある通り、Scene を遡って調べてみました。

以下のように Scene には body があり、bodySceneBuilder として定義されていることがわかります。
今回は Scene に中括弧で WindowGroup は全て body に含まれていることがわかります。

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
public protocol Scene {

    /// The type of scene that represents the body of this scene.
    ///
    /// When you create a custom scene, Swift infers this type from your
    /// implementation of the required ``SwiftUI/Scene/body-swift.property``
    /// property.
    associatedtype Body : Scene

    /// The content and behavior of the scene.
    ///
    /// For any scene that you create, provide a computed `body` property that
    /// defines the scene as a composition of other scenes. You can assemble a
    /// scene from built-in scenes that SwiftUI provides, as well as other
    /// scenes that you've defined.
    ///
    /// Swift infers the scene's ``SwiftUI/Scene/Body-swift.associatedtype``
    /// associated type based on the contents of the `body` property.
    @SceneBuilder @MainActor var body: Self.Body { get }
}

body に含まれている SceneBuilder をさらに深ぼってみます。
SceneBuilder の中には以下の記述があります。
SceneBuilder では C0 ~ C9 まで Scene を10個のみ受け付けています。しかし、今回は10以上を渡してしまったため、「引数が余分である」という判定になったようです。

@available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *)
extension SceneBuilder {

    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) -> some Scene where C0 : Scene, C1 : Scene, 
    C2 : Scene, C3 : Scene, C4 : Scene, C5 : Scene, C6 : Scene, C7 : Scene, 
    C8 : Scene, C9 : Scene

}

解決策

改善後のコードは以下のようになります。

import SwiftUI

@main
struct InstructionManualApp: App {
    @ObservedObject private var model = AreaViewModel()
    var body: some Scene {
        WindowGroup(id: model.contentAreaId) {
            ContentView()
        }
        
        Group {    // Group でまとめる
            WindowGroup(id: model.shelfContentAreaId) {
                ShelfContentView()
            }
            
            WindowGroup(id: model.shelfRealityAreaId) {
                ShelfRealityArea()
            }
            .defaultSize(CGSize(width: 800, height: 1000))
            
            WindowGroup(id: model.equipmentRealityAreaId) {
                EquipmentRealityArea()
            }
            .defaultSize(CGSize(width: 700, height: 700))
            
            WindowGroup(id: model.videoAreaId) {
                VideoArea(videoSourcePath: "ScrewDetail")
            }
            .defaultSize(CGSize(width: 700, height: 700))
            
            WindowGroup(id: model.completedAreaId) {
                CompletedRealityArea()
            }
            .defaultSize(CGSize(width: 800, height: 900))
        }
        
        Group {    // Group でまとめる
            WindowGroup(id: model.robotCleanerContentAreaId) {
                RobotCleanerContentView()
            }
            
            WindowGroup(id: model.robotCleanerNumId) {
                RobotCleanerNumRealityArea()
            }
            
            WindowGroup(id: model.robotCleanerMaintenanceId) {
                RobotCleanerMaintenanceArea()
            }
            
            WindowGroup(id: model.robotCleanerMaintenanceTermTableAreaId) {
                RobotCleanerMaintenanceTermTableArea()
            }
            
            WindowGroup(id: model.robotCleanerMaintenanceDustBoxRealityAreaId) {
                RobotCleanerMaintenanceDustBoxRealityArea()
            }
        }
        
        ImmersiveSpace(id: model.immersiveAreaId) {
            ImmersiveView()
        }
        .immersionStyle(selection: .constant(.full), in: .full)
    }
}

Group でそれぞれ関連性の高い WindowGroup をまとめることで Scene に渡す引数を減らすことができ、エラーが解消されます。

今回調べてみたところ、先述の記事にあった VStackHStackView などは ViewBuilder として指定されており、ViewBuilder も引数に10以上の要素を指定できないようになっています。

今のところ、Flutterでは今回のエラーのような、パフォーマンスの低下を防ぐためのエラーは出たことがなかったかと思うので、よく考えられているなと感じました。
Group としてまとめることで、可読性も上がったかと思います。

まとめ

最後まで読んでいただいてありがとうございました。
誤っている点や他の実装方法等あればご指摘いただけると幸いです。

参考

https://developer.apple.com/documentation/swiftui/windowgroup

https://dev.classmethod.jp/articles/extra-argument-in-call-with-love/

Discussion