bodyプロパティはSwiftUIのViewプロトコルの中心的な要素であり、画面に表示されるコンテンツを決定します。Apple Developer Documentation, 2024によると、bodyはViewプロトコルの唯一の必須要件であり、同じプロトコルに準拠する何らかの型を返します。SwiftUIは状態が変化するたびにbodyを呼び出し、新しい要素ツリーを構築して比較します。
重要なポイント
bodyはViewプロトコルの唯一の必須要件であるcomputed propertyです。Viewに準拠するすべての構造体はbodyを実装する必要があります。このプロパティはSwiftUIが画面に表示するコンテンツを返します — テキスト、画像、ボタン、ネストされた要素を持つコンテナ、またはViewプロトコルに準拠するその他の型です。
bodyのシグネチャは常に固定されています:var body: some View { get }。戻り値の型はsome View(不透明型)であり、具体的な型ではありません。これは、異なるViewsがbodyで異なる具体的な型を返すことができるが、Swiftコンパイラが各実装の具体的な型をコンパイル時に固定することを意味します。
WWDC 2022によると、bodyは宣言的インターフェース記述のエントリポイントです。UIViewを命令的に作成および構成するUIKitとは異なり、SwiftUIでは何を表示すべきかを宣言的に記述し、SwiftUI自身がそれを実装する方法を計算します。
bodyは純粋関数のように振る舞うべきです — 同じ入力(構造体のプロパティと状態)で同じViewツリーを返す必要があります。bodyが外部の可変状態(グローバル変数、@AppStorageラッパーなしのUserDefaults)に依存する場合、動作が予測不能になり、SwiftUIが画面を誤って再描画する可能性があります。
Computed property bodyは値を保持しません — アクセスされるたびに計算されます。SwiftUIが状態が変更されたと判断すると、View構造体を再作成し、表示用の現在の要素ツリーを取得するためにbodyの新しい値を読み取ります。
struct CounterView: View {
@State private var count = 0
var body: some View {
VStack {
Text("カウンター: \(count)")
.font(.largeTitle)
Button("インクリメント") {
count += 1
}
.padding()
.background(.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
}
}
この例では、bodyはTextと修飾子付きのボタンを含むVStackを返します。ボタンが押されると、@Stateプロパティのcountがインクリメントされ、SwiftUIはCounterView構造体を再作成し、新しいText値で更新されたツリーを取得するためにbodyを再度呼び出します。
修飾子(.font、.padding、.background、.foregroundColor、.cornerRadius)は元のViewを変更するのではなく、ModifiedContent — 修飾を追加する新しい型でラップします。各修飾子はネストの別のレベルを作成し、これはパフォーマンスにとって重要です。
bodyの戻り値の型におけるsome Viewは単なる慣習ではなく、コンパイラの要件です。Swiftでは、body内のすべての戻りパスが同じ具体的な型を持つ必要があります。@ViewBuilderがないと、ある分岐でTextを返し、別の分岐でButtonを返すことはできません — コンパイラがエラーを出力します。
struct ConditionalView: View {
var isReady: Bool
@ViewBuilder
var body: some View {
if isReady {
Text("準備完了")
.foregroundColor(.green)
} else {
ProgressView()
}
}
}
bodyの@ViewBuilderは、コンパイルエラーなしで条件付きロジック(if/else、switch)を使用できるようにします。ViewBuilderは自動的に異なる分岐をConditionalContent — 具象型の違いを隠す特別な型でラップします。これは動的インターフェースを構築するための重要な機能です。
@ViewBuilderがない場合、コンパイラはすべての戻りパスに単一の型を推論しようとします。型が異なる場合 — エラーが発生します。これが、SwiftUIがView宣言でbodyに暗黙的に@ViewBuilderを適用する理由ですが、ユーザーコードでは、複数のViewsを返すカスタムメソッドとプロパティに明示的にアノテーションを追加する必要があります。
具象型の代わりにsome Viewを使用してもパフォーマンスは低下しません — コンパイラはコンパイル時に正確な型を知っており、動的ディスパッチなしで直接コードを生成します。一方AnyViewは、存在コンテナでのラッピングのオーバーヘッドを伴う型消去を使用します。
bodyはSwiftUIによって3つの主要なシナリオで呼び出されます:Viewが最初に表示されるとき、@State/@Binding/@ObservedObject/@StateObjectが変更されたとき、親Viewがイニシャライザを通じて新しい値を渡したときです。SwiftUIは環境値(@Environment)が変更されたときにもbodyを呼び出すことがあります。
body呼び出しの頻度を心配する必要はありません — SwiftUIは識別子メカニズムを通じて再描画を最適化します。階層内の各Viewには一意の識別子があります。識別子と入力データが変更されていない場合 — 親Viewが再描画されてもbodyは呼び出されません。これはEquatable比較と構造的安定性によって達成されます。
struct ParentView: View {
var body: some View {
ChildView(name: "Alice") // 安定した識別子
}
}
struct ChildView: View {
let name: String
var body: some View {
Text("こんにちは、\(name)!")
}
}
この例では、ParentViewが再描画されても同じname値を渡す場合 — ChildView.bodyは呼び出されません。SwiftUIは構造体の入力データを比較し、変更されていない場合は子コンポーネントの再描画をスキップします。これがビュー差分メカニズムです。
bodyの予期しない呼び出しにつながるいくつかの落とし穴があります:ObservableObjectなしでのクラスの使用、body内で作成されたクロージャの受け渡し(各クロージャ作成は新しい識別子を生成します)、およびEquatableViewの誤った使用。bodyが頻繁に呼び出される場合 — すべての子コンポーネントの識別子の安定性を確認してください。
最初のルール:bodyは最小限にすべきです。複雑なロジックは、Viewを返す個別のcomputed propertyまたはメソッドに移動してください。これにより可読性が向上し、SwiftUIが階層のどの部分が変更されたかをより正確に判断できるようになります。大きなbodyは、明確な責任範囲を持つサブコンポーネントに分割してください。
2番目のルール:作業を実行するためにbodyを使用しないでください。データのロード、ネットワーク操作、データベースへの書き込み — これらはすべて、bodyの外部で、タスク、onChange修飾子、またはObservableObjectを通じて行う必要があります。bodyはインターフェース宣言のみを目的としています。
3番目のルール:標準の構造比較が不十分な場合、ViewsにはEquatableViewプロパティまたはカスタムEquatableプロトコルを使用してください。これにより、子Viewがいつ再描画を必要とするかをSwiftUIに明示的に伝え、不要なbody呼び出しを回避できます。
4番目のルール:bodyに複雑な計算(フォーマット、フィルタリング、ソート)が含まれる場合 — 結果をキャッシュするために@Stateを使用するか、計算をonChangeから呼び出される別のメソッドに移動してください。状態が更新されるたびにbodyで計算を繰り返すと、アニメーションが遅くなる一般的な原因です。
5番目のルール:リスト(List、ForEach)では、idパラメーターを使用して安定した識別子を確保してください。安定した識別子がないと、ForEachは変更があるたびにすべての要素を再作成し、各要素に対してbodyを呼び出します(1つの要素だけが変更された場合でも)。
よくある質問
bodyはViewプロトコルのcomputed propertyであり、表示するコンテンツを返します。これはプロトコルの唯一の必須要件です。戻り値の型はsome Viewであり、SwiftUIがコンパイル時に階層を最適化できるようにします。
はい、SwiftUIは状態(@State、@Binding、@ObservedObject)または入力データが変更されるたびにbodyを呼び出します。これは宣言型フレームワークの通常の動作です。SwiftUIは識別子メカニズムとEquatable比較を通じて呼び出し頻度を最適化します。
some Viewは具象実装を隠す不透明型です。コンパイラはコンパイル時に型を固定し、直接呼び出しのパフォーマンスを保証します。これにより柔軟性が得られます:シグネチャを変更せずに戻り値の型を変更できます。
いいえ、bodyをオプショナルにすることはできません — 戻り値の型some Viewはnilを許可しません。条件に応じて要素を非表示にする必要がある場合は、@ViewBuilder内で条件付きロジックを使用するか、階層内でスペースを取らないEmptyViewを返してください。
各修飾子は新しいModifiedContentレイヤーを作成し、階層の深さを増やします。ほとんどの画面(最大50個の修飾子)では影響は無視できます。過剰な修飾子(数百個)は差分計算を遅くする可能性があります。関連する修飾子をカスタム拡張機能でグループ化してください。
まとめ
ターンキー方式のモバイルアプリケーションを開発します
IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。