UIAlertControllerは、UIKitでアラート(中央のダイアログ)とアクションシート(操作に続く選択肢)を出すためのクラスです。iOS 8.0で追加され、それ以前のUIAlertViewとUIActionSheetを1つにまとめました。この記事では、表示までの3手順をSwiftとObjective-Cで示したうえで、UIAlertActionのスタイル、テキストフィールドの入力検証、iPadでアクションシートが落ちる原因、表示されないときの切り分け、async/awaitで結果を待つ書き方、SwiftUIでの代替までを順に扱います。仕様はApple Developer DocumentationのUIAlertControllerとWWDC25のセッション「Build a UIKit app with the new design」の2026年9月時点の記述に合わせています。
まとめ
- 表示は「UIAlertControllerを作る→UIAlertActionを追加する→present(_:animated:completion:)で出す」の3手順です。
- preferredStyleが
.alertなら中央のダイアログ、.actionSheetなら選択肢のシートになります。テキストフィールドと preferredAction は.alertでしか使えません。 - UIAlertActionのスタイルは
.default・.cancel・.destructiveの3種類で、.cancelは1つまでです。2つ目を足すと例外で落ちます。 - アクションシートには端末を問わず sourceView+sourceRect か sourceItem を指定します。iOS 18以前のiPadでは未指定だと落ち、iOS 26以降はiPhoneでも提示元の上に表示されます。barButtonItem は iOS 27.0 で非推奨です。
- 見た目の変更は公開APIの範囲に留めます。Appleはサブクラス化とビュー階層の変更を認めていないため、画像や独自レイアウトが要るなら自作の画面に切り替えます。
- SwiftUIでは
.alertと.confirmationDialogを使い、UIAlertControllerを包む必要はありません。
UIAlertControllerでアラートを出す基本の3手順
最小構成は、初期化、アクションの追加、表示の3行です。アクションのボタンは addAction(_:) で追加した順に並びます(.cancel だけはシステムが位置を決めます)。ハンドラは省略でき、省略したボタンは押すとアラートを閉じるだけになります。
Swiftのコード
let alert = UIAlertController(
title: "変更を保存しますか?",
message: "保存しないで閉じると、編集内容は失われます。",
preferredStyle: .alert
)
alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel))
alert.addAction(UIAlertAction(title: "保存", style: .default) { [weak self] _ in
self?.saveDocument()
})
present(alert, animated: true)
ハンドラ内で self を使うときは [weak self] で捕まえます。self がアラートやアクションをプロパティで保持していると、self→アクション→ハンドラ→self の循環参照になるためです。表示のたびに作り直すだけなら循環しませんが、あとで保持する設計に変えたときの漏れを防げます。
Objective-Cのコード
UIAlertController *alert =
[UIAlertController alertControllerWithTitle:@"変更を保存しますか?"
message:@"保存しないで閉じると、編集内容は失われます。"
preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"キャンセル"
style:UIAlertActionStyleCancel
handler:nil]];
__weak typeof(self) weakSelf = self;
[alert addAction:[UIAlertAction actionWithTitle:@"保存"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
[weakSelf saveDocument];
}]];
[self presentViewController:alert animated:YES completion:nil];
Objective-Cではスタイル定数が UIAlertControllerStyleAlert/UIAlertControllerStyleActionSheet、アクション側が UIAlertActionStyleDefault/Cancel/Destructive という名前になります。
アラートとアクションシートの選び分け
判断の軸は「ユーザーが始めた操作の続きか」です。削除ボタンを押したあとの「削除/キャンセル」や、共有先の選択のように、直前の操作から分岐する選択肢はアクションシートにします。通信エラーや保存確認のように、アプリ側から知らせて判断を求める内容はアラートにします。機能面の違いは次のとおりです。
| 項目 | .alert | .actionSheet |
|---|---|---|
| 表示位置(iOS 18以前・iPhone) | 画面中央 | 画面下部 |
| 表示位置(iOS 18以前・iPad) | 画面中央 | ポップオーバー |
| 表示位置(iOS 26以降) | 画面中央 | 提示元の上 |
| テキストフィールド | 追加できる | 追加できない |
| preferredAction | 有効 | 使われない |
| 提示元の指定 | 不要 | iOS 18以前のiPadは必須 |
アクションシートに addTextField を呼ぶと例外で落ちます。公式ドキュメントも「テキストフィールドを追加できるのは preferredStyle が alert のときだけ」と明記しています。
UIAlertActionのスタイルとボタン配置の規則
UIAlertAction.Style は3種類です。見た目の違いだけでなく、置ける数と位置にも規則があります。
| スタイル | 用途(公式の定義) | 数の制限 |
|---|---|---|
| .default | 通常のボタン | なし |
| .cancel | 操作を取り消し何も変えない | 1つまで |
| .destructive | データを変更・削除しうる | なし |
.cancel を2つ追加すると、NSInternalInconsistencyException(UIAlertController can only have one action with a style of UIAlertActionStyleCancel)で落ちます。「閉じる」と「あとで」のように取り消し系を2つ置きたいときは、片方を .default にします。
強調するボタンは preferredAction(iOS 9.0以降)で指定します。指定したアクションは太字になり、キャンセルボタンがあってもそちらより優先して強調されます。物理キーボードをつないだ端末ではReturnキーでも選べます。代入するアクションは先に addAction(_:) で追加しておく必要があり、順番を逆にするとプログラマーエラー扱いです。アクションシートでは使われません。
ボタンを一時的に押せなくするには isEnabled を false にします。淡色表示になり、タップしても反応しません。次の入力検証で使います。
テキストフィールド付きアラートの入力取得と検証
addTextField(configurationHandler:) を呼んだ回数だけ入力欄が縦に積まれ、textFields 配列から値を読めます。実務で困るのは空欄のまま「作成」を押されるケースで、入力が条件を満たすまでボタンを無効にしておくのが確実です。iOS 14以降は UIControl.addAction(_:for:) でクロージャを登録できるので、@objc メソッドやNotificationCenterを使わずに書けます。
func askProjectName() {
let alert = UIAlertController(
title: "プロジェクト名",
message: "1〜20文字で入力してください。",
preferredStyle: .alert
)
let create = UIAlertAction(title: "作成", style: .default) { [weak self, weak alert] _ in
guard let name = alert?.textFields?.first?.text else { return }
self?.createProject(named: name)
}
create.isEnabled = false
alert.addTextField { field in
field.placeholder = "例:社内ポータル"
field.clearButtonMode = .whileEditing
field.addAction(UIAction { [weak field] _ in
let count = field?.text?.count ?? 0
create.isEnabled = (1...20).contains(count)
}, for: .editingChanged)
}
alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel))
alert.addAction(create)
alert.preferredAction = create
present(alert, animated: true)
}
ハンドラ内で alert を強参照すると、アラート→アクション→ハンドラ→アラートの循環になるため [weak alert] にしています。パスワード欄は field.isSecureTextEntry = true、数字を入力しやすくするには field.keyboardType = .numberPad を設定します。数値だけに制限するには、貼り付けなども含めて入力値を別途検証します。入力欄が3つを超えるフォームは、アラートではなく専用の画面にしたほうが入力ミスを直しやすくなります。
iPadでアクションシートが落ちる原因とsourceItemの指定
iPhoneで動いていたアクションシートがiPadだけで落ちる場合、まず例外メッセージに提示元の位置情報不足が示されていないか確認します。iOS 18以前のiPadではアクションシートがポップオーバーとして表示されるため、どこから吹き出すかを渡さないと次の例外になります。
Your application has presented a UIAlertController (...) of style UIAlertControllerStyleActionSheet
from UINavigationController (...). The modalPresentationStyle of a UIAlertController with this style
is UIModalPresentationPopover. You must provide location information for this popover through the
alert controller's popoverPresentationController. You must provide either a sourceView and sourceRect
or a barButtonItem.
ボタンなどのビューから出すなら sourceView と sourceRect、ナビゲーションバーやツールバーの項目から出すなら sourceItem(iOS 16.0以降)を指定します。popoverPresentationController への代入をオプショナルチェーンで書いておけば、iPhoneとiPadで分岐させる必要はありません。
// UIButtonから出す場合
@objc func deleteButtonTapped(_ sender: UIButton) {
let sheet = UIAlertController(title: nil, message: "この写真を削除しますか?", preferredStyle: .actionSheet)
sheet.addAction(UIAlertAction(title: "写真を削除", style: .destructive) { [weak self] _ in
self?.deletePhoto()
})
sheet.addAction(UIAlertAction(title: "キャンセル", style: .cancel))
sheet.popoverPresentationController?.sourceView = sender
sheet.popoverPresentationController?.sourceRect = sender.bounds
present(sheet, animated: true)
}
// ナビゲーションバーのUIBarButtonItemから出す場合(iOS 16以降)
@objc func shareItemTapped(_ sender: UIBarButtonItem) {
let sheet = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
sheet.addAction(UIAlertAction(title: "リンクをコピー", style: .default))
sheet.addAction(UIAlertAction(title: "キャンセル", style: .cancel))
sheet.popoverPresentationController?.sourceItem = sender
present(sheet, animated: true)
}
sourceRect には sender.frame ではなく sender.bounds を渡します。sourceRect は sourceView の座標系で解釈されるため、frame(親ビューの座標系)を渡すと吹き出しの位置がずれます。
iOS 26で変わったアクションシートの表示位置
iOS 26では、アクションシートがiPhoneでもiPadでも提示元のビューの上に直接表示されるようになりました。WWDC25のセッション「Build a UIKit app with the new design」は、端末を問わず sourceItem か sourceView を指定するよう求めています。提示元の上に出たアクションシートにはキャンセルボタンが表示されず、外側をタップすると閉じます。Appleのガイドによれば、ポップオーバー表示では .cancel のボタンが取り除かれ、外側のタップはキャンセルボタンと同じ扱いになってそのハンドラも呼ばれます。キャンセル時の後始末を .cancel のハンドラに書いておけば、表示形式が変わっても漏れません。提示元を指定しない場合は画面中央に表示され、キャンセルボタンが付きます。iOS 26以降は未指定でも落ちませんが、新しい表示にはならず、iOS 18以前をサポートするアプリではiPadで落ちたままです。iPhone向けのコードでも毎回指定してください。
バー項目の指定に長く使われてきた barButtonItem は、iOS 27.0で非推奨になり、ドキュメントは sourceItem への移行を案内しています。sourceItem を使うと、iOS 26以降ではポップオーバーが指定した項目から現れて項目を置き換え、選択か閉じる操作まで表示されます(iOS 18以前は矢印が項目を指す表示)。これから書くコードは sourceItem に揃えておくと、非推奨警告の対応が要りません。
アラートが表示されない・閉じないときの切り分け
present を呼んだのに何も出ないとき、Xcodeのコンソールには警告が1行だけ出て、アプリは落ちません。警告の文言で原因を切り分けます。
viewDidLoadからの提示と画面階層への追加タイミング
コンソールに whose view is not in the window hierarchy と出る場合、提示元の画面がまだウィンドウに載っていません。viewDidLoad の時点ではビューが画面階層に入っていないため、起動直後にアラートを出したいなら viewDidAppear(_:) へ移します。通信の完了ハンドラなどバックグラウンドスレッドから呼んでいる場合も表示が不安定になるので、UIの操作はメインスレッド(@MainActor または DispatchQueue.main)で行います。
モーダル表示中の提示元の選択
which is already presenting と出る場合、提示元がすでにモーダルやアラートを出しています。1つの画面が同時に提示できるのは1つだけなので、いちばん上に出ている画面から present します。
extension UIViewController {
var topmostPresented: UIViewController {
var current = self
while let next = current.presentedViewController {
current = next
}
return current
}
}
// 呼び出し側
topmostPresented.present(alert, animated: true)
ただしアラートの上にアラートを重ねると、最初のアラートの判断が済む前に次の判断を求めることになります。エラーが連続して起きる処理では、1件目を閉じてから次を出すか、複数のエラーを1つのメッセージにまとめるほうが読み手に親切です。
dismissによるアラートの終了
ボタン操作以外で閉じるには dismiss(animated:completion:) を呼びます。公式ドキュメントのとおり、閉じる責務は提示した側にあり、アラート自身に対して呼んでも提示元に処理が委ねられます。数秒後に自動で閉じる通知にするなら次のように書きます。
let notice = UIAlertController(title: "保存しました", message: nil, preferredStyle: .alert)
notice.addAction(UIAlertAction(title: "閉じる", style: .cancel))
present(notice, animated: true)
DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) { [weak notice] in
guard let notice, notice.presentingViewController != nil else { return }
notice.dismiss(animated: true)
}
Appleのガイドは、表示前に少なくとも1つのアクションを追加するよう求めています。ボタンが無いと、ユーザーは自分で閉じられません。ユーザーが先に「閉じる」を押した場合は presentingViewController が nil になるので、遅延処理は何もしません。[weak notice] で分かるのはオブジェクトが解放されていないことだけで、表示中かどうかは分からない点に注意してください。保存完了のように判断を求めない知らせなら、アラートより操作を遮らない画面内のメッセージが向いています。
async/awaitで選択結果を待つ書き方
UIAlertControllerの結果はハンドラのクロージャで受け取るため、「確認してから通信する」処理を書くと入れ子が深くなります。withCheckedContinuation で包むと、選ばれたボタンを Bool として await で待てます。非同期処理の基本は同期処理と非同期処理の違いを実装方式から解説した記事で整理しています。
@MainActor
extension UIViewController {
func confirm(title: String, message: String? = nil,
confirmTitle: String, isDestructive: Bool = false) async -> Bool {
await withCheckedContinuation { continuation in
let alert = UIAlertController(title: title, message: message, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel) { _ in
continuation.resume(returning: false)
})
alert.addAction(UIAlertAction(title: confirmTitle,
style: isDestructive ? .destructive : .default) { _ in
continuation.resume(returning: true)
})
present(alert, animated: true)
}
}
}
// 呼び出し側
Task {
guard await confirm(title: "ログアウトしますか?", confirmTitle: "ログアウト") else { return }
await session.signOut()
}
この書き方には守るべき条件が1つあります。continuation はちょうど1回だけ再開しなければなりません。どのボタンも押されないままアラートを dismiss で閉じると再開されず、Task は永久に待ち続けます。前の章の「自動で閉じる」処理や、画面遷移時の一括dismissと組み合わせる画面では、この関数を使わないか、閉じる直前に false で再開する経路を別に用意してください。
見た目のカスタマイズの限界と自作への切り替え基準
UIAlertControllerの公式ドキュメントには「このクラスはそのまま使う前提で、サブクラス化をサポートしない。ビュー階層はプライベートで、変更してはならない」とあります。ネット上で見かける setValue(_:forKey:) で attributedTitle や contentViewController を差し込む方法は、公開されていないキーに依存するため、OSの更新後も同じ表示や動作が維持される保証はありません。業務アプリで採用すべきではありません。
公開APIで調整できるのは、タイトルとメッセージの文言、アクションのスタイル、preferredAction による強調、ボタンの有効・無効までです。severity(iOS 16.0以降)に .critical を指定しても、公式ドキュメントのとおりiOSでは無視され、警告アイコンが付くのはMac Catalystのアプリだけです。
次のどれかが要件にあるなら、UIAlertControllerで粘らずに自作のビューコントローラへ切り替えます。
- 画像、チェックボックス、スイッチなど、テキストフィールド以外の部品を入れたい
- フォントや背景色をブランドカラーに合わせたい
- 4つ以上の入力欄や、入力中のエラー表示が要る
自作する場合は、modalPresentationStyle = .overFullScreen と modalTransitionStyle = .crossDissolve を設定した画面を present すると、下の画面を透かしたままダイアログ風に表示できます。画面の責務の分け方はMVCとMVVM・MVPの違いを同じ機能のコードで比較した記事が参考になります。
UIAlertView・UIActionSheetからの移行対応表
UIAlertViewはiOS 9.0、UIActionSheetはiOS 8.3で非推奨になりました。2026年9月時点のドキュメントでもSDKから削除はされていませんが、新しいコードで使う理由はありません。古いコードを置き換えるときの対応は次のとおりです。
| 旧API | UIAlertControllerでの書き方 |
|---|---|
| UIAlertView | preferredStyle: .alert |
| UIActionSheet | preferredStyle: .actionSheet |
| show() | present(_:animated:completion:) |
| delegateのclickedButtonAt | UIAlertActionのhandler |
| cancelButtonTitle | style: .cancel のアクション |
| destructiveButtonTitle | style: .destructive のアクション |
| alertViewStyle(入力欄) | addTextField(configurationHandler:) |
移行で最も手間がかかるのは、delegateの1メソッドでボタンのインデックスを見て分岐していた箇所です。UIAlertControllerではボタンごとにハンドラを持たせるため、インデックスの分岐はそのまま各ハンドラへ分解します。UIAlertViewは画面を持たずに show() で出せましたが、UIAlertControllerは提示元のビューコントローラが要る点も変わります。Swiftの版とXcodeの対応はSwiftの最新バージョンとXcode対応表の記事で確認できます。
SwiftUIでの代替:.alertと.confirmationDialog
SwiftUIの画面では、UIAlertControllerをUIViewControllerRepresentableで包む必要はありません。iOS 15以降は .alert(_:isPresented:actions:message:) がアラート、.confirmationDialog がアクションシートに当たります。
struct PhotoDetailView: View {
@State private var isConfirmingDelete = false
var body: some View {
Button("写真を削除", role: .destructive) {
isConfirmingDelete = true
}
.confirmationDialog("この写真を削除しますか?",
isPresented: $isConfirmingDelete,
titleVisibility: .visible) {
Button("写真を削除", role: .destructive) { deletePhoto() }
Button("キャンセル", role: .cancel) {}
}
}
func deletePhoto() { /* 削除処理 */ }
}
UIKitとの違いは2点です。ボタンは role(.cancel/.destructive)で役割を示し、システムが役割と強調に応じて並び順を変えることがあります。また .confirmationDialog はiPadなどの regular サイズクラスで自動的にポップオーバーになり、外側のタップで閉じるため、UIKitのような位置指定は不要です。SwiftUIとUIKitの使い分けはSwiftUIとUIKitの違いと2026年の使い分けの記事にまとめています。
よくある質問
アラートの外側をタップして閉じられますか?
.alert スタイルは外側のタップでは閉じず、いずれかのボタンを押す必要があります。アクションシートは、iPadのポップオーバー表示と、iOS 26以降で提示元の上に出る表示のときに外側のタップで閉じます。アラートで外側タップを受けたいなら、自作の画面でタップを検知して dismiss します。
表示したアラートを数秒後に自動で閉じるには?
DispatchQueue.main.asyncAfter で遅延させて dismiss(animated:completion:) を呼びます。ユーザーが閉じられるアクションも設け、遅延処理では presentingViewController が nil でない(まだ表示中である)ことを確かめてから閉じます。
severityを.criticalにするとiOSで赤い警告になりますか?
なりません。severity はMac Catalystのアプリで警告アイコンを付けるための設定で、iOSでは無視されます。iOSで危険な操作を目立たせるには、そのボタンを .destructive にします。
UIAlertControllerはtvOSやvisionOSでも使えますか?
使えます。公式ドキュメントの対応プラットフォームはiOS 8.0以降、iPadOS 8.0以降、Mac Catalyst 13.1以降、tvOS、visionOS 1.0以降です。
SwiftUIのアプリでUIAlertControllerを使う場面はありますか?
UIKitで書かれた既存画面をSwiftUIに組み込んでいる場合は、その画面の中でUIAlertControllerを出し続けて問題ありません。SwiftUIで新しく書く画面では .alert と .confirmationDialog を使います。