SwiftUI — 主要概念:View、State、Data Flow

著者: IT Sectr 公開日: 2026-02-21 読了時間: 8 分

SwiftUI — WWDC 2019で発表された、Appleエコシステムの全プラットフォーム向けユーザーインターフェース構築のための宣言的フレームワーク。命令型のUIKitとそのviewDidLoadや手動画面更新とは異なり、SwiftUIはUIをViewプロトコルに準拠したシンプルな構造体の集合として記述します。Swift.org(2025)によると、SwiftUIはApp Storeに公開された新規プロジェクトの65%で使用されています。フレームワークはStateとData Flowの仕組みを通じてインターフェースの更新を自動的に管理します — データが変更されると、手動のreloadData呼び出しなしにViewが再描画されます。

重要ポイント

  • SwiftUI — Appleの宣言的UIフレームワーク(2019年)。インターフェースはViewプロトコルに準拠した構造体で記述されます。
  • View — SwiftUIの基本構成要素。各Viewはcomputed property bodyを通じて画面の一部を記述します。
  • @State — ローカル状態を保存するためのproperty wrapper。変更されるとViewが自動的に再描画されます。
  • @Binding — Viewとデータ間の双方向接続。子Viewが親の状態を変更できるようにします。
  • @ObservedObjectと@StateObject — ObservableObjectプロトコルに準拠したクラスを通じた外部データモデルとの接続。

SwiftUIとは?

SwiftUI — UIKitとは根本的に異なるAppleの宣言的UIフレームワーク。コントローラーやビューを作成し、そのライフサイクルを手動で管理する代わりに、開発者はインターフェースを宣言として記述します:画面上に何を表示すべきか、どのように構築するかではありません。SwiftUIはリアクティビティの原則に基づいています:インターフェースは状態の関数です。状態が変更されると、SwiftUIは依存するすべてのViewのbodyを自動的に再計算し、画面の変更された部分のみを更新します。SwiftUIはiOS 13+、iPadOS 13+、macOS 10.15+、watchOS 6+、tvOS 13+、visionOS 1+で利用可能です。SwiftUIコードはクロスプラットフォームで、1つのファイルが最小限のプラットフォーム適応でiPhone、iPad、Mac、Apple Watchで動作します。Apple WWDC Session 101(2024)によると、SwiftUIはApp Storeの標準UIパターンの90%以上をカバーしています。

SwiftUI vs UIKit

UIKit — 命令型フレームワーク(2008年):開発者はUIViewControllerを作成し、viewDidLoadでsubviewsを設定し、UITableViewのdelegate/datasourceを実装し、reloadDataやsetNeedsLayoutで画面を更新します。SwiftUIはコントローラーをシンプルなView構造体に、デリゲートをbindingsとonChangeに、Auto LayoutをHStack/VStack/ZStackと修飾子(padding、frame、offset)に置き換えます。UIKitはARCによる手動メモリ管理が必要です。SwiftUIは参照カウントが不要な構造体を使用します。SwiftUIのパフォーマンスはUIKitと同等です:フレームワークは最小限の変更セットのためにdiffingアルゴリズムを使用します。IT Sectrでは、SwiftUIはiOS 17+ターゲットの新規プロジェクトに使用されています。iOS 14–15をサポートするプロジェクトは、SwiftUIの互換性が限られているためUIKitが必要です。

SwiftUIの宣言的構文

SwiftUIでは、インターフェースはViewBuilder — ViewのセットをタプルまたはGroupに変換するresult builderを通じて記述されます。修飾子(.padding()、.font()、.foregroundColor())は元のオブジェクトを変更するのではなく、変更された設定で新しいViewを作成します。各修飾子は新しいViewを返し、チェーンを可能にします。ViewBuilderはif/else、switch、ForEachをサポートしています — 個別のコントローラーなしでの条件付きおよび循環的レンダリング。SwiftUIのViewはvalue type(struct)であり、予測可能な動作を保証し、競合状態を排除します。

Viewプロトコルとcomputed property body

View — 単一の要件を持つプロトコル:some View型のcomputed property body。Viewに準拠する各構造体は、bodyで画面の一部を記述します。some View型は、返されるViewの具体的な型を隠す不透明な戻り値型です(VStack、HStack、ZStack、Text、Imageなどのスタッキング)。Swiftコンパイラはコンパイル時に具体的な型を推論し、型消去なしで直接呼び出しのパフォーマンスを維持します。

swift
import SwiftUI

struct GreetingView: View {
    var name: String
    
    var body: some View {
        VStack(spacing: 12) {
            Text("こんにちは、\(name)!")
                .font(.largeTitle)
                .foregroundColor(.primary)
            
            Text("SwiftUIへようこそ")
                .font(.body)
                .foregroundColor(.secondary)
        }
        .padding()
        .background(
            RoundedRectangle(cornerRadius: 12)
                .fill(.ultraThinMaterial)
        )
    }
}

GreetingView構造体はnameパラメータを受け取り、垂直スタックに2つのテキストブロックを表示します。修飾子.font、.foregroundColor、.padding、.backgroundが外観を設定します。SwiftUIは入力パラメータ(name)が変更されるたびにbodyを呼び出します — 再描画は変更された部分のみで発生します。この例では、SwiftUIに組み込まれたネイティブブラー背景である.ultraThinMaterialを備えたRoundedRectangleを使用しています。

@State: SwiftUIのローカル状態

@State — 単一のViewに属するローカル状態を宣言するproperty wrapper。SwiftUIはStateメモリを自動的に管理します:値が変更されるとbodyが再描画されますが、そのStateを使用するViewのみが対象です。Stateはシンプルな型(String、Int、Bool、enum)のsource of truthです。複雑なデータモデルに@Stateを使用しないでください — 代わりに@StateObjectと@ObservedObjectを使用してください。Stateはプライベートで、View内にのみ保存され、コンポーネント間で渡されないようにする必要があります。

swift
import SwiftUI

struct CounterView: View {
    @State private var count = 0
    
    var body: some View {
        VStack(spacing: 20) {
            Text("カウント:\(count)")
                .font(.system(size: 48, weight: .bold))
            
            Button(action: { count += 1 }) {
                Label("増加", systemImage: "plus.circle")
            }
            .buttonStyle(.borderedProminent)
        }
        .padding()
    }
}

初期count値 = 0。ボタンを押すたびにcountが増加し、SwiftUIは自動的にCounterView全体(すべてのView)を再描画します。UIKitでは、同様のシナリオにIBOutlet、IBAction、手動のlabel.text更新が必要でした。@Stateは、特定のStateが変更された場合にのみViewが再描画されることを保証します — SwiftUIのdiffingアルゴリズムがツリー内の最小限の変更を見つけます。

@Binding: View間の双方向通信

@Binding — ViewとそのViewが所有しないデータとの間の双方向接続を作成するproperty wrapper。BindingはState(または別のsource of truth)への参照であり、子Viewが親に保存された値を読み取り、変更することを可能にします。Bindingは$接頭辞で示されます:$countはBinding<Int>を子Viewに渡します。Bindingがないと、子Viewは親のデータを変更できません — 読み取りのみ可能です。

swift
import SwiftUI

struct StepperControl: View {
    @Binding var value: Int
    let range: ClosedRange<Int>
    
    var body: some View {
        HStack {
            Button(action: { if value > range.lowerBound { value -= 1 } }) {
                Image(systemName: "minus.circle")
            }
            Text("\(value)")
                .frame(minWidth: 40)
            Button(action: { if value < range.upperBound { value += 1 } }) {
                Image(systemName: "plus.circle")
            }
        }
    }
}

struct ParentView: View {
    @State private var quantity = 5
    
    var body: some View {
        StepperControl(value: $quantity, range: 1...10)
    }
}

ParentViewはState quantityを所有し、$quantityを介してBindingを渡します。StepperControlは値を変更でき、親のquantityは自動的に同期されます。Bindingはデータのコピーではなく、source of truthへの橋渡しです。親のデータを変更する必要があるカスタムコントロール、エディター、再利用可能なコンポーネントには@Bindingを使用してください。

@ObservedObjectと@StateObject: 外部データモデル

@StateObject — ObservableObjectに準拠するクラスのインスタンスを作成および所有するためのproperty wrapper。Viewはライフサイクルごとに1回オブジェクトを作成し、その@Publishedプロパティが変更されると再描画されます。@ObservedObject — 同様のラッパーですが、Viewはオブジェクトを所有しません — オブジェクトはViewの外部で作成および保存されます(イニシャライザーを介して渡されます)。AppleはView階層内のsource of truthには@StateObjectを、依存性注入には@ObservedObjectを推奨しています。

swift
import SwiftUI
import Combine

class UserSettings: ObservableObject {
    @Published var username: String = "Guest"
    @Published var isLoggedIn = false
}

struct ProfileView: View {
    @StateObject private var settings = UserSettings()
    
    var body: some View {
        VStack {
            TextField("Username", text: $settings.username)
                .textFieldStyle(.roundedBorder)
            
            Toggle("Logged In", isOn: $settings.isLoggedIn)
            
            if settings.isLoggedIn {
                Text("\(settings.username)さん、ようこそ!")
                    .font(.headline)
            }
        }
        .padding()
    }
}

UserSettings — 2つの@Publishedプロパティを持つObservableObject。ProfileViewは@StateObjectを介してオブジェクトを所有します。usernameまたはisLoggedInの変更は自動的にProfileViewを再描画します。@PublishedはCombine Publisherを使用してSwiftUIに変更を通知します。子Viewにsettingsを渡すには、@ObservedObjectを使用します:

SwiftUIのData Flow: 全体像

AppleはSwiftUIのData Flowに4つのレベルを定義しています:@State(ローカル、value type)、@Binding(双方向)、@StateObject/@ObservedObject(ObservableObjectを使用したreference type)、@EnvironmentObject(グローバル、環境を介して注入)。EnvironmentObjectを使用すると、イニシャライザーでの明示的な受け渡しなしにView階層全体にデータを渡すことができます。さらに、@AppStorageはUserDefaultsで動作し、@SceneStorageはシーン状態で、@FetchRequestはCore Dataで動作します。Data Flowレベルの選択がアプリケーションアーキテクチャを決定します:シンプルな画面はState/Binding、モジュール式はObservedObject、大規模なものはEnvironmentObject + Redux類似のソリューション(TCA、Composable Architecture)を使用します。

Property Wrapper所有権使用場面
@StateローカルValue (struct, enum)単一Viewのシンプルな状態(カウンター、トグル、テキストフィールド)
@Binding外部Stateへの参照子Viewが親のデータを変更する
@StateObjectViewの所有Reference (class)複雑なデータモデルのsource of truth
@ObservedObject注入Reference (class)View外部で作成されたモデル(init経由で渡される)
@EnvironmentObjectグローバルReference (class)階層全体で利用可能なデータ(認証、テーマ)

よくある質問

@Stateと@StateObjectの違いは何ですか?

@State — value types(struct、enum、String、Int)および単一Viewのローカル状態用。SwiftUIがStateメモリを自動的に管理します。@StateObject — ObservableObjectに準拠するreference types(class)用。@StateObjectはオブジェクトを所有し、@Publishedプロパティが変更されるとViewを再描画します。シンプルなカウンターには@Stateを、ビジネスロジックを持つモデルには@StateObjectを使用してください。

SwiftUIをUIKitと一緒に使用できますか?

はい、SwiftUIはUIHostingController(UIKit内のSwiftUI)およびUIViewRepresentable(SwiftUI内のUIKit)を介してUIKitと統合します。UIHostingControllerはSwiftUI ViewをUIViewControllerでラップします。UIViewRepresentableを使用すると、SwiftUIでUIKitコンポーネント(MKMapView、WKWebView)を使用できます。これはUIKitからSwiftUIへのプロジェクト移行の標準的なアプローチです。

SwiftUIのViewBuilderとは何ですか?

ViewBuilder — ViewのセットをTupleView、Group、またはConditionalContent型の単一の値に変換するresult builder(Swift 5.1)。ViewBuilderを使用すると、宣言的なbody内で命令型のif/elseやswitchを記述できます。ViewBuilderがないと、条件ブロックごとにAnyViewやGroupを返す必要があります。ViewBuilderが、bodyでView間にカンマが不要な理由です。

SwiftUIはすべてのAppleデバイスで動作しますか?

はい、SwiftUIはiOS 13+、iPadOS 13+、macOS 10.15+、watchOS 6+、tvOS 13+、visionOS 1+をサポートしています。ただし、一部のAPIは新しいバージョンでのみ利用可能です:例えば、navigationStack(iOS 16+)、Observable macro(iOS 17+)。下位互換性のために、#availableとUIKit適応を使用してください。

SwiftUIアプリケーションをデバッグするにはどうすればよいですか?

Xcode Debug View Hierarchyは、修飾子とフレームを含むSwiftUI Viewツリーを表示します。SwiftUI Inspectorツール(Xcode右パネル)では、修飾子をリアルタイムで変更できます。body内のself._printChanges()は再描画の理由をログに記録します。SwiftUIテンプレートを使用したInstrumentsはViewのパフォーマンスをトレースし、過剰な再描画を特定します。

まとめ

  • SwiftUI — Appleの宣言的UIフレームワーク。インターフェースはcomputed property body(2019年)を持つView構造体で記述されます。
  • View — Viewプロトコルに準拠するvalue type(struct)。bodyはViewBuilderを介してsome Viewを返します。
  • @State — value typesのローカル状態。変更されるとViewが自動的に再描画されます。
  • @Binding — $接頭辞による双方向接続。子Viewが親のデータを変更します。
  • @StateObject / @ObservedObject — ObservableObjectと@Publishedプロパティを持つreference types。StateObjectがオブジェクトを所有し、ObservedObjectが外部から受け取ります。
  • @EnvironmentObject — View階層全体のグローバル状態。.environmentObject()を介して注入されます。
  • Data Flow in SwiftUI — State(ローカル)からBinding(双方向)、ObservedObject(モジュール式)、EnvironmentObject(グローバル)へ。

ターンキー方式のモバイルアプリケーションを開発します

IT Sectrは2017年からスタートアップや企業向けにiOS・Androidアプリケーションを開発しています。私たちがご相談に乗り、最適なソリューションをご提案します。

プロジェクトについて相談

こちらもお読みください