
iOS 26でSwiftUIのactionSheetが表示崩れする問題をconfirmationDialogで解決した
9月のApple Eventで新作iPhoneが発表され、iOS 26.0への対応が本格化してきた。私が個人開発しているアプリでも、iOS 26.0対応を進めている。
これまでSwiftUIのactionSheet
モディファイアを使い続けていた。アクション選択画面では定番のUIコンポーネントで、実装も簡単なためBaseViewModelに組み込んでいた。iOS 15.0でdeprecatedになったが、そのまま利用を続けていた。
ところが、iOS 26.0で実行してみると表示崩れが発生してしまった。具体的には、ダイアログが潰れたような表示になってしまうという問題である。
アップデート版のリリース準備中にこの問題を発見できたのは幸いだったが、早急な対応が必要となった。この「潰れる」という現象は言葉で説明するのが難しいため、実際の動作をGIF動画で確認していただきたい。左が問題のあるactionSheet
、右が解決策のconfirmationDialog
である。
actionSheet(問題あり) | confirmationDialog(解決後) |
---|---|
![]() |
![]() |
本記事では、iOS 26.0におけるactionSheet
の表示問題と、その解決策としてconfirmationDialog
への移行について紹介する。同じ問題に悩んでいる方の参考になれば幸いである。
検証環境
今回の検証は以下の環境でおこなった。
- macOS Sequoia 15.6.1
- Xcode 26.0
- iOS 26.0 Simulator
- iOS 18.5 Simulator (比較用)
問題の詳細
問題を明確にするため、ミニマムプロジェクト(検証コード)を作成することにした。actionSheet
とconfirmationDialog
の両方を実装し、動作を比較できるようにした。
検証用のサンプルコード全文
//
// ContentView.swift
// SampleConfirmationDialog
//
// Created by KENJIWADA on 2025/09/11.
//
import SwiftUI
struct ContentView: View {
@State private var showActionSheet = false
@State private var showConfirmationDialog = false
var body: some View {
NavigationView {
VStack(spacing: 30) {
// confirmationDialogを表示するボタン
Button(action: {
showConfirmationDialog = true
}) {
Label("confirmationDialogを表示", systemImage: "text.bubble")
.frame(maxWidth: .infinity)
.padding()
.background(Color.green)
.foregroundColor(.white)
.cornerRadius(8)
}
.confirmationDialog("オプション選択", isPresented: $showConfirmationDialog) {
Button("オプション1") {
}
Button("オプション2") {
}
Button("削除", role: .destructive) {
}
// キャンセルボタンは自動的に追加される
} message: {
Text("以下から選択してください")
}
// actionSheetを表示するボタン
Button(action: {
showActionSheet = true
}) {
Label("actionSheetを表示", systemImage: "square.stack.3d.up")
.frame(maxWidth: .infinity)
.padding()
.background(Color.blue)
.foregroundColor(.white)
.cornerRadius(8)
}
Spacer()
}
.padding()
.navigationTitle("Dialog比較デモ")
.actionSheet(isPresented: $showActionSheet) {
ActionSheet(
title: Text("オプション選択"),
message: Text("以下から選択してください"),
buttons: [
.default(Text("オプション1")) {
},
.default(Text("オプション2")) {
},
.destructive(Text("削除")) {
},
.cancel(Text("キャンセル"))
]
)
}
}
}
}
#Preview {
ContentView()
}
サンプルコードは以下のGistでも公開している。
サンプルコードを実行して挙動を比較する
サンプルコードを両方のiOSバージョンで実行した結果は以下の通りである。
Version | confirmationDialog | actionSheet |
---|---|---|
iOS 18.5 | ![]() |
![]() |
iOS 26 | ![]() |
![]() |
iOS 26.0における動作を詳しく観察したところ、以下のことがわかった。
actionSheet
では、ダイアログが不自然に圧縮されるconfirmationDialog
では、これらの問題が解決される
前述の通り、これまでactionSheet
とconfirmationDialog
は同等の機能として使えていたため、deprecatedになってもそのままにしていた。iOS 26.0でも見た目こそ似ているが挙動に差が生まれてしまった。これは実質的にactionSheet
を使うのをやめなさいということなのだと考え、confirmationDialog
への移行を決断した。
ただし、単純に置き換えるだけでは問題が残ることも判明した。スクリーンショットを見てもわかる通り、従来のactionSheet
を踏襲した実装方法では、アンカーポイントが適切に設定されない問題が発生する。この問題の解決方法については、後述の「トラブルシューティング」セクションの「ポップオーバー表示での問題」を参照されたい。
confirmationDialogについて
移行先となるconfirmationDialog
について確認していく。
confirmationDialog
に移行しなければならないのか
なぜactionSheet
ではなぜダメで、なぜconfirmationDialog
を使うべきなのか。Appleがこの変更を推進する理由を調査したところ、以下のようなメリットがあることがわかった。
- クロスプラットフォーム対応されており、macOS、watchOS、tvOSでも動作する
- 現代的なAPI設計で、SwiftUIのデザイン原則により適合している
- 継続的なサポートによって、新機能への対応が保証される
- 柔軟な表示制御で、タイトル表示などを細かく制御できる
3番目のメリットについてはまさに体感しているところで、confirmationDialog
を使っていなかったために苦労している最中だ。
基本的な構文
まず、confirmationDialog
の最も基本的な使い方を調べる。
.confirmationDialog(
_ titleKey: LocalizedStringKey,
isPresented: Binding<Bool>,
titleVisibility: Visibility = .automatic,
@ViewBuilder actions: () -> View
) -> some View
メッセージを追加する場合は、以下のように記述する。
.confirmationDialog(
_ titleKey: LocalizedStringKey,
isPresented: Binding<Bool>,
titleVisibility: Visibility = .automatic,
@ViewBuilder actions: () -> View,
@ViewBuilder message: () -> View
) -> some View
パラメータ
各パラメータの役割は以下の通りである。
- titleKey: ダイアログのタイトル
- isPresented: 表示状態を制御するBinding
- titleVisibility: タイトルの表示制御
.automatic
: システムが自動判断(デフォルト).visible
: 常に表示.hidden
: 非表示(アクセシビリティには残る)
- actions: ボタンを定義するクロージャ
- message: 追加メッセージ(オプション)
具体的な移行手順
後継APIだけあって、実際の移行作業は簡単だった。ここでは、典型的な移行パターンを具体例で示してみよう。
Before(actionSheet)
いままでアクションはActionSheet.Button
を定義していた。
.actionSheet(isPresented: $showSheet) {
ActionSheet(
title: Text("画像の選択"),
message: Text("画像の取得方法を選択してください"),
buttons: [
.default(Text("カメラで撮影")) {
openCamera()
},
.default(Text("ライブラリから選択")) {
openLibrary()
},
.destructive(Text("画像を削除")) {
deleteImage()
},
.cancel()
]
)
}
After(confirmationDialog)
.confirmationDialog(
"画像の選択",
isPresented: $showSheet,
titleVisibility: .visible
) {
Button("カメラで撮影") {
openCamera()
}
Button("ライブラリから選択") {
openLibrary()
}
Button("画像を削除", role: .destructive) {
deleteImage()
}
} message: {
Text("画像の取得方法を選択してください")
}
confirmationDialog
ではActionSheet.Button
を使用する必要がなく、汎用的なButton
を利用することができる。削除アクションなど注意しなければならないボタンについてはrole
に.destructive
を指定する。また、キャンセルボタンを明示的に定義する必要がなく、自動的に追加される。
注意点としては、iOS 26で新しく導入されたLiquid Glassエフェクトによって、トリガービューからアニメーションするため、不適切な場所にアタッチすると予期しない動作となる。トリガーとなるビューに直接アタッチしよう。
トラブルシューティング
ポップオーバー表示での問題
ポップオーバーとして表示される際、アンカーポイントが適切に設定されない問題に遭遇した。この問題は、モディファイアをボタン自体に直接アタッチすることで解決できた。
// NG: VStackにアタッチ
VStack {
Button("Show Dialog") {
showDialog = true
}
}
.confirmationDialog(...) // 位置が不適切
// OK: Buttonに直接アタッチ
Button("Show Dialog") {
showDialog = true
}
.confirmationDialog(...) // 正しい位置
Buttonに直接アタッチすることで、アンカーポイントがボタンから生えるようになる。
複数のダイアログを扱う場合
複数のconfirmationDialog
を使用する場合は、それぞれ独立した@State
変数で制御する必要がある。共有すると予期しない動作となる。ひとつのViewに複数のダイアログを表示させたい場合には気をつける必要がある。
@State private var showDeleteDialog = false
@State private var showShareDialog = false // 別々に管理
まとめ
iOS 26.0でのactionSheet
表示問題を発見し、confirmationDialog
への移行を実施した。移行作業は想像以上に簡単だった。しかも、結果として以前よりも優れたユーザー体験を提供できるようになった。
同じ問題に悩んでいる開発者の方は、ぜひconfirmationDialog
への移行を検討してほしい。本記事で紹介した手順に従えば、スムーズに移行できるはずだ。
今回の経験から、deprecatedになったAPIを放置することのリスクを痛感した。今後はiOSの新しい機能や変更に積極的に対応し、ユーザーに最高の体験を提供していきたい。
本記事が、同じ問題に直面している開発者の参考になれば幸いである。