
こんにちは、開発部第1グループの西岡です。
iOS 26 がリリースされて、いわゆる「Liquid Glass」と呼ばれる新しいデザインシステムの導入により、UI/UXが大きく変わりつつあります。中でもユーザーの意思決定を促す「アクションシート」は、従来のボトムアップ型から、発生源付近に浮かぶ『フローティング・バブル』形式へと変わりました。
本記事では、Xcode 26 を用いて、iOS 18 と iOS 26 での挙動の違いや実装方法、そしてデザイナーやエンジニアの関心事である「表示位置の制御」や「後方OSバージョンとの互換性」について検証してみました。
確認環境
今回の検証は以下の環境で行っています。
- Xcode: 26.1
- iOS: 18.2 (Before) / 26.1 (After)
- Swift: 6.1
アクションシートとは? 何が変わったのか?
アクションシートとは、特定の操作に対する複数の選択肢を提示したり、実行の最終確認を行ったりするためのモーダルビューです。 iOS 18 までは画面下部からスライドアップする形式が標準でした。
ところが、iOS 26 からはアクションシートが様変わりします。 (※UIDesignRequiresCompatibilityキーは未設定です)

実装方法
次に Liquid Glass 時代のアクションシートの実装をみていきましょう。
SwiftUI:confirmationDialog
アクションシートをSwiftUIで実装する場合、confirmationDialog がその役割を担います。

実装例
上記のサンプルコードはこちらです。
@State var isShowingDialog: Bool = false // (1) アクションの発生源 Button("アクションシート(ConfirmationDialog)") { isShowingDialog = true } // (2) ボタンに対してconfirmationDialogを設定 .confirmationDialog( "タイトルです。", isPresented: $isShowingDialog, titleVisibility: .visible, actions: { if #available(iOS 26.0, *) { Button("確定", role: .confirm) { // Handle option 1 print("Confirm action selected") } Button("閉じる", role: .close) { // Handle option 1 print("Close action selected") } } // (3) 「キャンセル」ボタン // Liquid Glass(iOS26〜)ではシート上に現れない Button("キャンセル", role: .cancel) { // アクション:欄外タップでシートが消滅した際に、ここが呼ばれます。 print("Cancel action selected") } Button("破壊的な選択", role: .destructive) { // Handle destructive action print("Destructive action selected") } Button("デフォルト") { // Handle destructive action print("Default action selected") } }, message: { Text("メッセージ内容です。") } )
一番のポイントは、アクションの発生源(アンカー)の近くに配置することです。基本的には「modifierを付与したView」が自動的にアクションシートの発生源(アンカー)となるためです。
上記のソースコードでは、(1) アクションを発生させるUI要素(ボタン等)を配置したら、このUI要素(ボタン等)に (2) confirmationDialog を配置しています。こうすることでアクションシートは自動的にそのUI要素付近に浮遊し、吹き出し方向が発生源に向かいます。
今まではconfirmationDialogの配置先とシート表示はある意味で無関係でしたが、iOS 26 になるとアクションの発生源も意識する必要がありそうです。
また、 iOS 26 からは (3) ボタンロール.cancel の挙動も少し変わります。従来のアクションシートでは.cancel を設定するとシート上に「キャンセル/Cancel」ボタンが表示されましたが、iOS 26(Liquid Glass)になるとここが省略されます。「キャンセル」に相当する操作を行うには、シート欄外をタップするとアクションシート消滅し、その直後にButton内のコールバックが呼ばれます。
※ なおSwiftUI初期に提供されていた ActionSheet型 は既に非推奨になっており、iOS 26 では一部表示崩れが発生するという報告もあるようです。ご注意下さい!
参考: iOS 26でSwiftUIのactionSheetが表示崩れする問題をconfirmationDialogで解決した | DevelopersIO
吹き出しの位置はどこまで指定できるのか?
SwiftUIでは、基本的に「modifierを付与したView」が自動的に発生源(アンカー)となるため、アクションシートの浮遊位置やアンカー位置の調整など、現時点の検証では「位置指定の自由度」がさほど高くないようです。ただし、いくつか工夫することは可能です。
その1. アンカー(Source)の動的制御: 特定のボタンではなく、リストの行や特定のアイコンからアクションシートを出したい場合などが挙げられます。ただし、.confirmationDialog を付与する場所を慎重に選ぶ必要はあります。
struct SourceSelectionView: View { @State private var selectedItem: String? var body: some View { List(["Item A", "Item B", "Item C"], id: \.self) { item in HStack { Text(item) Spacer() Button { selectedItem = item } label: { Image(systemName: "ellipsis.circle") } // 各行のボタンに直接付与することで、そのボタンがアンカーになる .confirmationDialog( "\(item)のアクション", isPresented: Binding( get: { selectedItem == item }, set: { if !$0 { selectedItem = nil } } ), titleVisibility: .visible ) { Button("編集") { print("Default action selected") } Button("削除", role: .destructive) { print("Destructive action selected") } } } } } }
その2. .popover を使って厳密な指定: .confirmationDialog の代わりに.popoverで実装して、シートレイアウトを含めて厳密な指定や調整を行う代替案です。
・アンカー指定: attachmentAnchor 引数に対して .rect(.bounds) や .point(UnitPoint.bottom) を指定することで、ビューのどの位置から吹き出しを出すかを調整します。
・矢印の向き: arrowEdge 引数を指定することで、ソースの上下左右どちらに展開するかを制御します。
その3. UIKitの UIAlertController で実装する。(詳しくは後述へ)
UIKitでの実装 - UIAlertController
アクションシートの挙動も変わった!?
さて、UIKitの元祖アクションシートも未だ健在ですが、実はUIKitのアクションシートを制御する UIAlertController も、iOS 26でその挙動が大きく変わりました。
従来のアクションシートは、こんな感じの実装でした。
// (1) let alert = UIAlertController( title: "タイトルです。", message: "メッセージ内容です。", preferredStyle: .actionSheet ) // (2) alert.addAction(UIAlertAction(title: "破壊的な選択", style: .destructive)) alert.addAction(UIAlertAction(title: "デフォルト選択", style: .default)) alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel)) // (3) present(alert, animated: true)
一連の実装では、まず (1) アクションシート(.actionSheet)を指定して UIAlertController インスタンスを生成し、(2) そのインスタンスに複数の選択ボタン(UIAlertAction) を追加します。最後に (3) present(:) メソッドを呼び出す、という流れでした。
iOS 18.2 で動かした時のアクションシートはこちらです。

これを Xcode 26.1 でビルドし、iOS 26.1 で実行するとこんな挙動となります。


なんと… アラートが表示されてしまいました。実際のアラート(右図)と見比べてもアラートです。
先ほどのSwiftUI と同じような 新時代のアクションシート を期待していたのですが…。
UIKitの UIAlertController はどうなっているのでしょうか?
iOS 26 からの追加実装
実はAppleのドキュメントにヒントがあります。
Getting the user’s attention with alerts and action sheets | Apple Developer Documentation
実は preferredStyle: .actionSheet を指定した場合、 popoverPresentationController によるアンカー指定が実質的に必要となりました。
// (1) let alert = UIAlertController( title: "タイトルです。", message: "メッセージ内容です。", preferredStyle: .actionSheet ) // (2-1) alert.addAction(UIAlertAction(title: "破壊的な選択", style: .destructive)) alert.addAction(UIAlertAction(title: "デフォルト選択", style: .default)) alert.addAction(UIAlertAction(title: "キャンセル", style: .cancel)) // (2-2) iOS 26ではこの設定が不可欠 if let popover = alert.popoverPresentationController { // 発生源となるUIButtonなどを指定 popover.sourceView = sender // sender.frameではなく、sender.boundsを指定することで // ボタン自体の領域を基準とした適切な位置に吹き出しが出る popover.sourceRect = sender.bounds // 吹き出しが出る方向を上下に限定したい場合 popover.permittedArrowDirections = [.up, .down, .left, .right] } // (3) present(alert, animated: true)
このアンカー指定 (2-2) を怠ると、画面中央に不自然に浮いてしまい、結果的に意図に反したアラート表示となってしまうようです。
ちなみに popoverPresentationController 自体は iOS 26 リリース以前(iOS 8 〜)から存在しているインターフェースです。OSバージョンごとに分岐処理が必要か懸念されますが、iOS 18 以下で検証してみたところ、iOS 26 未満では alert.popoverPresentationController は nil を返します。そのため、既存のコードに対しても後方互換性が保たれていることが確認できました。
アンカーを指定して再度実行した結果がこちらです。

popoverPresentationControllerで何が指定できるのか?
| API | 仕様 |
|---|---|
| sourceView | sourceRect で指定する座標の「親」となるビューを指定します。通常、ユーザーがタップしたボタンや、詳細を表示したい対象のセルなどを指定します。 |
| sourceRect | sourceView の座標系に基づいた矩形を指定します。 |
| permittedArrowDirections | UIPopoverArrowDirection 型(OptionSet)で指定します。・ .any: システムが最適な方向を自動選択します(デフォルト)。・ .up, .down, .left, .right: 特定の方向を固定または組み合わせて指定します。 |
まとめ
iOS 26の変更点を整理すると、以下のようになります。
- iOS 18 から iOS 26 にかけて、アクションシートのデザインやUI/UXが大きく変わる。
- SwiftUI でアクションシートを実装する場合、アクションの発生源となるUI要素(ボタン)に
confirmationDialogを配置する必要がある。 - SwiftUI のアクションシートの浮遊位置やアンカーは、システムが自動調整してくれる。
- UIKit でアクションシートを実装する場合、
popoverPresentationControllerに対してアンカー指定の追加が必須となる。もし未設定のままだとアラート形式で表示される恐れがある。 - UIKit のアクションシートでは、アンカー属性(
sourceView,sourceRect,permittedArrowDirections)を調整することができる。
さいごに
iOS 26では、これまで以上に「どのUI要素から発生したのか」をコード上で明示する必要性を感じました。特に既存アプリのアップデートの時には、アクションシートが意図しない位置に表示されないよう、アンカー設定も再点検が必要になりそうです。アクションシート一つの変化をとっても、より直感的で物理的な実体に近いUIを目指すAppleの姿勢が色濃く出ている気がしました。
References
- https://developer.apple.com/documentation/swiftui/actionsheet
- https://developer.apple.com/documentation/uikit/getting-the-user-s-attention-with-alerts-and-action-sheets#Present-an-action-sheet
- https://developer.apple.com/jp/design/human-interface-guidelines/action-sheets
- https://dev.classmethod.jp/articles/ios26-actionsheet-display-issue-confirmationdialog-migration/