iOS 26でSwiftUIのactionSheetが表示崩れする問題をconfirmationDialogで解決した

iOS 26でSwiftUIのactionSheetが表示崩れする問題をconfirmationDialogで解決した

2025.09.17

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(解決後)
before after

本記事では、iOS 26.0におけるactionSheetの表示問題と、その解決策としてconfirmationDialogへの移行について紹介する。同じ問題に悩んでいる方の参考になれば幸いである。

検証環境

今回の検証は以下の環境でおこなった。

  • macOS Sequoia 15.6.1
  • Xcode 26.0
  • iOS 26.0 Simulator
  • iOS 18.5 Simulator (比較用)

問題の詳細

問題を明確にするため、ミニマムプロジェクト(検証コード)を作成することにした。actionSheetconfirmationDialogの両方を実装し、動作を比較できるようにした。

検証用のサンプルコード全文
			
			//
//  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でも公開している。

https://gist.github.com/CH3COOH/fc6e4bc0de3004855d6253da5eecb370

サンプルコードを実行して挙動を比較する

サンプルコードを両方のiOSバージョンで実行した結果は以下の通りである。

Version confirmationDialog actionSheet
iOS 18.5 ios18_confirmDialog_320px ios18_actionSheet_320px
iOS 26 ios26_confirmDialog_320px ios26_actionSheet_320px

iOS 26.0における動作を詳しく観察したところ、以下のことがわかった。

  • actionSheetでは、ダイアログが不自然に圧縮される
  • confirmationDialogでは、これらの問題が解決される

前述の通り、これまでactionSheetconfirmationDialogは同等の機能として使えていたため、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に直接アタッチすることで、アンカーポイントがボタンから生えるようになる。

anchor_320px

複数のダイアログを扱う場合

複数のconfirmationDialogを使用する場合は、それぞれ独立した@State変数で制御する必要がある。共有すると予期しない動作となる。ひとつのViewに複数のダイアログを表示させたい場合には気をつける必要がある。

			
			@State private var showDeleteDialog = false
@State private var showShareDialog = false // 別々に管理

		

まとめ

iOS 26.0でのactionSheet表示問題を発見し、confirmationDialogへの移行を実施した。移行作業は想像以上に簡単だった。しかも、結果として以前よりも優れたユーザー体験を提供できるようになった。

同じ問題に悩んでいる開発者の方は、ぜひconfirmationDialogへの移行を検討してほしい。本記事で紹介した手順に従えば、スムーズに移行できるはずだ。

今回の経験から、deprecatedになったAPIを放置することのリスクを痛感した。今後はiOSの新しい機能や変更に積極的に対応し、ユーザーに最高の体験を提供していきたい。

本記事が、同じ問題に直面している開発者の参考になれば幸いである。

参考資料

この記事をシェアする

FacebookHatena blogX

関連記事