はじめに (対象読者・この記事でわかること)

iOSアプリ開発初心者から中級者を対象に、画面遷移が意図した通りに動かないというよくある悩みを解決します。この記事を読むと、Storyboard とコード両方で設定した segue の問題点を特定し、prepare(for:sender:) の正しい使い方や、遷移先の ViewController が期待通りに初期化されないケースの対処法が分かります。実際に起きたバグ例とデバッグ手順を交えて解説するので、同様のエラーで時間を浪費することがなくなるでしょう。

前提知識

この記事を読み進める上で、以下の知識があるとスムーズです。
- Swift の基本文法と Xcode の基本操作
- UIKit と ViewController のライフサイクルの概念
- Storyboard 上での segue 作成方法

画面遷移が期待と違う背景と主な原因

iOS アプリで「画面遷移が意図した通りに動かない」ケースは、実装のどこかに小さなミスが潜んでいることが多いです。代表的な原因は次の通りです。

  1. Storyboard の segue 設定ミス
    - 同じ identifier が複数の segue に付与されている
    - 手動で遷移させる performSegue(withIdentifier:sender:) と自動遷移が競合している

  2. prepare(for:sender:) の実装不備
    - 目的の segue かどうかの判定が曖昧で、間違ったタイミングでデータを渡している
    - 渡すべきプロパティが未初期化のままアクセスされ、 nil が入ってしまう

  3. 遷移先 ViewController の初期化順序
    - viewDidLoad で依存データを使用しようとして、prepare がまだ呼ばれていない
    - カスタムイニシャライザを使用した場合、Storyboard が期待する init(coder:) が呼び出されない

  4. Navigation Controller のスタック操作ミス
    - pushViewController(_:animated:)present(_:animated:) を混在させ、期待しない遷移が起きる
    - popToRootViewController 等でスタックが意図せずリセットされる

  5. 非同期処理と UI 更新のタイミング
    - API 呼び出しのコールバック内で遷移処理を走らせ、メインスレッドでの UI 更新が遅延する

これらの問題は、デバッグを通じて「どの段階で期待したオブジェクトが存在しないか」を特定することが鍵です。

具体的な手順と実装例

以下では、典型的なバグシナリオを取り上げ、原因の特定から解決までのフローを詳細に示します。コード例は Swift 5.8、Storyboard 使用前提です。

ステップ 1: バグの再現とログ出力

まずは不具合を最小限の手順で再現し、コンソールに有用な情報を出力します。

Swift
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { print("prepare called for segue identifier: \(segue.identifier ?? "nil")") if segue.identifier == "showDetail" { guard let destVC = segue.destination as? DetailViewController else { print("Destination is not DetailViewController") return } // デバッグ用に渡すデータを表示 print("Passing data: \(self.selectedItem?.title ?? "nil")") destVC.item = self.selectedItem } }
  • ポイントidentifier が正しいか、型キャストが成功しているかを必ず確認します。
  • 結果の例identifiernil や別名になっていると、prepare が期待通りに走りません。

ステップ 2: Storyboard 上の segue 設定をチェック

  1. Identifier が一意か
    - Interface Builder の Attributes Inspector で、対象 segue の Identifier を showDetail に統一します。
  2. 自動遷移 vs 手動遷移
    - ボタンやセルに showDetail の segue が直接結びついている場合、performSegue を呼び出すコードは削除します。
  3. Navigation Controller の有無
    - push が必要な場合は、遷移先が Navigation Stack に入るように設定し、present にしないよう注意します。

ステップ 3: prepare のタイミングとプロパティ初期化

prepare が呼ばれた直後にデータを扱うと、遷移先の viewDidLoad でまだ UI が構築されていないことがあります。安全にデータを反映させるためのパターンは次の通りです。

Swift
class DetailViewController: UIViewController { var item: Item? { didSet { // view がロード済みなら UI に反映 if isViewLoaded { configureUI() } } } override func viewDidLoad() { super.viewDidLoad() configureUI() } private func configureUI() { guard let item = item else { return } titleLabel.text = item.title descriptionLabel.text = item.description } }
  • 解説item がセットされたタイミングで didSet が走り、isViewLoaded が true の場合は即座に UI を更新します。viewDidLoad でも同様に configureUI を呼び出すので、どちらの順序でも正しく表示できます。

ステップ 4: Navigation Stack の正しい操作

push 系の遷移を行う場合、UINavigationController が必ず存在しているか確認します。

Swift
if let nav = self.navigationController { let detailVC = storyboard?.instantiateViewController(withIdentifier: "DetailViewController") as! DetailViewController detailVC.item = selectedItem nav.pushViewController(detailVC, animated: true) } else { // NavigationController が無いケースは present で代替 self.present(detailVC, animated: true, completion: nil) }
  • ポイントnavigationController が nil になると push が無効になるため、代替手段を用意しておくとクラッシュ防止になります。

ハマった点やエラー解決

発生した問題 原因 解決策
遷移後の画面が真っ白になる prepareitem が nil のまま渡された prepare でのキャストチェックと、selectedItem が正しく設定されているかデバッグ
同じ segue が二重に実行される ボタンに直接 segue が設定されている + performSegue を呼び出した どちらか一方に統一(ボタンの segue を削除しコードで制御)
viewDidLoad でクラッシュ item が未設定のまま UI ラベルにアクセス itemdidSetisViewLoaded チェックで安全に UI 更新
NavigationStack が期待と異なる presentpush を混在させた 遷移方式を統一し、navigationController?.pushViewController のみ使用

完全版サンプルコード

Swift
// MasterViewController.swift class MasterViewController: UITableViewController { var data: [Item] = [...] var selectedItem: Item? override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { selectedItem = data[indexPath.row] // 手動で segue を実行(Storyboard には segue を結ばない) performSegue(withIdentifier: "showDetail", sender: self) } override func prepare(for segue: UIStoryboardSegue, sender: Any?) { guard segue.identifier == "showDetail", let dest = segue.destination as? DetailViewController else { return } dest.item = selectedItem } } // DetailViewController.swift class DetailViewController: UIViewController { var item: Item? { didSet { if isViewLoaded { configureUI() } } } @IBOutlet weak var titleLabel: UILabel! @IBOutlet weak var descriptionLabel: UILabel! override func viewDidLoad() { super.viewDidLoad() configureUI() } private func configureUI() { guard let item = item else { return } titleLabel.text = item.title descriptionLabel.text = item.description } }

この構成で、Storyboard の segue とコードベースのデータ受け渡しを明確に分離し、遷移先の UI が常に正しいデータを持つことが保証されます。

まとめ

本記事では、Swift で画面遷移が期待通りに動かない典型的な原因と、Storyboard 設定の見直し、prepare(for:sender:) の安全な実装、Navigation Stack の正しい利用という三つの視点から対策手順を解説しました。

  • 原因分析:segue identifier 重複や手動・自動遷移の競合、データ渡しのタイミングミス
  • デバッグ手法:コンソールログで identifier と型を確認、didSetisViewLoaded で UI 更新を統一
  • 実装例:安全なデータ受け渡しと Navigation 操作のベストプラクティスをコードで提示

これにより、読者は画面遷移エラーの再現と瞬時の解決ができ、開発効率が大幅に向上します。次回は、SwiftUI での画面遷移と同様の落とし穴についても取り上げる予定です。

参考資料