iOS UIViewController ライフサイクル完全ガイド【2026年UIKit + SwiftUI対応】

iOS UIViewControllerライフサイクル【2026年UIKit + SwiftUI 対応】

📌 関連記事: この記事は 【iOS】UIViewControllerのライフサイクル (Swift)(2019年)の2026年版アップデートです。UIKit に加えて SwiftUI 対応を追加しました。

2019年の記事では UIViewControlller のライフサイクルを紹介していた。2026年は状況が変わった。SwiftUI が標準になりつつあり、UIKit は「レガシー」扱いになりつつある。

ただ、既存プロジェクトや複雑な UI は UIKit が必須。両方理解すべき。


2019年 vs 2026年:フレームワークの立場

【2019年】

  • UIKit が主流
  • SwiftUI は登場したばかり(iOS 13)
  • ほぼ全員が UIKit を使っていた

【2026年】

  • SwiftUI が標準(iOS 16+対応アプリが主流)
  • UIKit は「既存プロジェクト対応」用途へ
  • 新規プロジェクト:SwiftUI 推奨
  • 既存プロジェクト:UIKit を理解する必要

UIKit:UIViewControllerのライフサイクル

全ライフサイクルメソッド(実行順序)

ビュー表示時

1. init(coder:) または init(nibName:bundle:)
   ↓
2. viewDidLoad()
   ↓
3. viewWillAppear(_:)
   ↓
4. viewWillLayoutSubviews()
   ↓
5. viewDidLayoutSubviews()
   ↓
6. viewDidAppear(_:)

ビュー非表示時

1. viewWillDisappear(_:)
   ↓
2. viewDidDisappear(_:)
   ↓
3. deinit (メモリから削除)

各メソッドの役割

class MyViewController: UIViewController {

    // ① 初期化時(1回のみ)
    override func viewDidLoad() {
        super.viewDidLoad()
        // UI の初期化、データの読み込み
        // 重い処理はここで実行 OK
    }

    // ② ビュー表示直前(毎回呼ばれる)
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // データの更新、画面リフレッシュ
        // 他の ViewController からの復帰時
    }

    // ③ レイアウト計算前
    override func viewWillLayoutSubviews() {
        super.viewWillLayoutSubviews()
        // Auto Layout の前処理
        // ビューのサイズが確定する前
    }

    // ④ レイアウト計算完了
    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()
        // サイズに依存した処理
        // フレーム値が確定した後
    }

    // ⑤ ビュー表示完了
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // アニメーション開始
        // ネットワーク通信開始
        // センサー (GPS など) の監視開始
    }

    // ⑥ ビュー非表示直前
    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)
        // タイマー停止
        // ネットワーク通信キャンセル
    }

    // ⑦ ビュー非表示完了
    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // センサーの監視停止
        // リソース解放
    }

    // ⑧ メモリから削除
    deinit {
        // 最終クリーンアップ
        print("MyViewController が削除されました")
    }
}

SwiftUI:View のライフサイクル

SwiftUI では全く異なる

SwiftUI は 宣言型 UI で、UIViewControlller のような「ライフサイクルメソッド」は存在しない。

代わりに onAppear / onDisappear を使う。

struct ContentView: View {
    @State var count = 0

    var body: some View {
        VStack {
            Text("Count: \(count)")
            Button("Increment") {
                count += 1
            }
        }
        .onAppear {
            // ビュー表示時
            print("View appeared")
            // データ読み込み、API 呼び出しはここ
        }
        .onDisappear {
            // ビュー非表示時
            print("View disappeared")
            // リソース解放
        }
    }
}

SwiftUI のライフサイクル(概念的)

【初期化・表示】

  1. View 作成(body 計算)
  2. onAppear 実行
  3. ビュー表示

【状態変更】

  1. @State 更新
  2. body 再計算
  3. ビュー更新(自動)

【終了】

  1. onDisappear 実行
  2. View 削除

UIKit vs SwiftUI:ライフサイクル比較表

フェーズ UIKit SwiftUI
初期化 init(coder:) View 作成
初回ロード viewDidLoad() onAppear
表示前 viewWillAppear() なし(自動)
レイアウト viewWillLayoutSubviews() 自動(Combine)
表示完了 viewDidAppear() onAppear
非表示前 viewWillDisappear() なし(自動)
非表示完了 viewDidDisappear() onDisappear
終了 deinit View 削除

実装パターン:よくある用途別

UIKit版パターン 1:データの初期化

class UserProfileViewController: UIViewController {
    var user: User?

    override func viewDidLoad() {
        super.viewDidLoad()
        // ❌ ここで API 呼び出しは避ける
        // loadUser()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        // ✅ ここで最新データを取得(毎回)
        loadUser()
    }

    func loadUser() {
        API.fetchUser { [weak self] user in
            self?.user = user
            self?.updateUI()
        }
    }
}

SwiftUI版パターン 1:データの初期化

struct UserProfileView: View {
    @State var user: User?
    @State var isLoading = false

    var body: some View {
        VStack {
            if let user = user {
                Text(user.name)
            }
        }
        .onAppear {
            loadUser()
        }
    }

    func loadUser() {
        isLoading = true
        API.fetchUser { user in
            self.user = user
            isLoading = false
        }
    }
}

UIKit版パターン 2:センサーの監視開始・停止

class MapViewController: UIViewController {
    var locationManager: CLLocationManager?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        // ✅ ビュー表示時に監視開始
        locationManager = CLLocationManager()
        locationManager?.startUpdatingLocation()
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        // ✅ ビュー非表示時に監視停止
        locationManager?.stopUpdatingLocation()
    }
}

SwiftUI版パターン 2:センサーの監視開始・停止

struct MapView: View {
    @StateObject var locationManager = LocationManager()

    var body: some View {
        VStack {
            Text("Latitude: \(locationManager.latitude)")
        }
        .onAppear {
            locationManager.startUpdating()
        }
        .onDisappear {
            locationManager.stopUpdating()
        }
    }
}

class LocationManager: NSObject, ObservableObject {
    @Published var latitude = 0.0
    let manager = CLLocationManager()

    func startUpdating() {
        manager.startUpdatingLocation()
    }

    func stopUpdating() {
        manager.stopUpdatingLocation()
    }
}

UIKit版パターン 3:タイマー

class CountdownViewController: UIViewController {
    var timer: Timer?

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
            self.updateCountdown()
        }
    }

    override func viewDidDisappear(_ animated: Bool) {
        super.viewDidDisappear(animated)
        timer?.invalidate()  // ❌ これを忘れるとメモリリーク
        timer = nil
    }
}

SwiftUI版パターン 3:タイマー

struct CountdownView: View {
    @State var count = 10
    @State var timer: Timer?

    var body: some View {
        Text("\(count)")
            .onAppear {
                timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in
                    count -= 1
                }
            }
            .onDisappear {
                timer?.invalidate()  // 自動クリーンアップ
            }
    }
}

メモリリークを避けるためのベストプラクティス

UIKit

❌ メモリリーク:self をキャプチャしすぎ
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    API.fetchData { data in
        self.updateUI(data)  // self が保持され続ける
    }
}
✅ weak self で回避
override func viewDidAppear(_ animated: Bool) {
    super.viewDidAppear(animated)
    API.fetchData { [weak self] data in
        self?.updateUI(data)  // self が解放される
    }
}

SwiftUI

// SwiftUI は自動的に管理してくれる場合が多い
struct MyView: View {
    @StateObject var viewModel = MyViewModel()

    var body: some View {
        Text(viewModel.data)
            .onAppear {
                viewModel.load()  // 自動的に適切にハンドル
            }
    }
}

2019年 vs 2026年:推奨される実装方針

状況 2019年の推奨 2026年の推奨
新規プロジェクト UIKit SwiftUI
既存 UIKit プロジェクト UIKit 継続 UIKit 継続(段階的に SwiftUI 導入)
複雑な UI UIKit UIKit(SwiftUI の限界回避)
学習用 UIKit SwiftUI

まとめ

【2019年】

UIViewController のライフサイクルを理解 = iOS 開発の基本

【2026年】

  • 新規プロジェクト:SwiftUI を使い、onAppear/onDisappear で対応
  • 既存プロジェクト:UIViewController ライフサイクルは依然重要
  • 両方の理解が必須(業界の過渡期)

実装判断:

  • iOS 16+ のみ対応 → SwiftUI
  • iOS 15 以下対応が必要 → UIKit
  • iOS 14 以下対応が必要 → 必ず UIKit

参考資料