iOS UIViewControllerライフサイクル【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 のライフサイクル(概念的)
【初期化・表示】
- View 作成(body 計算)
- onAppear 実行
- ビュー表示
【状態変更】
- @State 更新
- body 再計算
- ビュー更新(自動)
【終了】
- onDisappear 実行
- 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
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
API.fetchData { data in
self.updateUI(data) // 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