Movatterモバイル変換


[0]ホーム

URL:


Future Tech Blog
フューチャー技術ブログ
Mobileカテゴリ

SwiftUIのカスタムアラートダイアログについて考える

はじめに

こんにちは。HealthCare Innovation Group(HIG)1所属の清水です。

本記事は、夏の自由研究ブログ連載2023の5本目です。

SwiftUIにおけるアラートダイアログを自作で実装する場合、どんな方法があるか考えてみました。

Swiftを用いたiOSアプリ開発で、アラートを自作したいと考えたことはありませんか? Webアプリでもモバイルアプリでも、ユーザに問題を報告したり動作による影響を警告したりする上で、アラートは重要です。

SwiftUIでもalertが標準で用意されています。ただし、フォントサイズやダイアログのサイズが変更できないため、iPadで利用する場合画面に対してかなり小さく扱いづらいです。

SwiftUI標準のalertをシミュレータ iPad Pro (12.9-inch) で表示した際の図

画面に対してかなり小さい・・・。

そこで、「自由にカスタマイズできるアラートダイアログを自作する場合、どのような実装方法があるか」を自由研究の題材とすることにしました。

今回試してみた3種類のカスタムアラートを、実装方法と共に紹介したいと思います。
※もっと良い実装方法をご存知でしたら、教えていただきたいです!

本記事では触れていませんが、カスタムアラートダイアログに関するライブラリも公開されています。合わせて確認頂けると良いと思います(例:CustomAlert

検証内容

アラートは、通常の画面より手前(通常画面に重なる形)に表示します。
今回は、重ねて表示するためのコンポーネントの中から、以下の3種類の実装方法で比較していきたいと思います。

  1. fullScreenCoverを利用した方法
  2. overlayを利用した方法
  3. ZStackを利用した方法

事前準備

画面に重ねて表示するためのダイアログをViewとして用意しています。SwiftUI標準のalertに見た目を寄せたアラートViewを、iPad Pro (12.9-inch) 向けにサイズ調整したViewがこちらです。このViewをいくつかの方法で重ねていきます。

※実装は、記事後半に記載しています。

1.fullScreenCoverを利用した方法

1つ目は、fullScreenCoverを利用した方法です。

引数に渡したフラグがtrueの時、全画面を覆うViewをモーダル表示します。

他の方法と比較

  • メリット
    • 画面全体にモーダル表示するため、アラート表示中に他の動作ができないように制限しやすい。
  • デメリット
    • fullScreenCover は表示時に画面下から上まで覆うアニメーションを伴って表示するため、アニメーションのカスタマイズが難しい(調べた限りほぼできない)。
      アニメーションを非活性にすることはできる。2

2.overlayを利用した方法

2つ目は、overlayを利用した方法です。
名前の通り、親Viewより手前に指定したViewを重ねて表示します。アラートの使用用途を考えるとちょうど良さそうです。

他の方法と比較

  • メリット
    • overlay自体は重ねて表示するだけなので、アニメーションなどをカスタマイズしやすい。
  • デメリット
    • あくまでも元となるViewに重ねるため、重ねて表示する範囲は呼び出すViewに依存する。
      例えば、VStackに紐づけるとVStackの範囲が重ねるViewの表示範囲となり、表示範囲外は操作できてしまう(下のgif参照)

3.ZStackを利用した方法

3つ目は、ZStackを利用した方法です。
fullScreenCoveroverlayはViewのInstance Methodを利用していたのですが、こちらは内包するViewの表示順を制御するものなので、毛色が異なります。

他の方法と比較

  • メリット
    • アプリ大元のViewに重ねることで、画面全体を覆って表示制御できる。
  • デメリット
    • ダイアログ表示を制御するフラグやダイアログに表示する内容をApp階層まで伝える必要があるため、単一のView内で状態管理が完結しない。

コード実装例

今回利用したソースコードは、こちらです。

App

structIsShowAlert:EnvironmentKey {
staticlet defaultValue:Binding<Bool>= .constant(false)
}

extensionEnvironmentValues {
var isShowAlert:Binding<Bool> {
get {self[IsShowAlert.self] }
set {self[IsShowAlert.self]= newValue }
}
}

@main
structSampleApp:App {
@Stateprivatevar isShowAlert:Bool=false

var body:someScene {
WindowGroup {
ZStack {
ContentView()
.environment(\.isShowAlert,$isShowAlert)
/// 3. `ZStack`を利用した実装方法
if isShowAlert {
CustomAlertView(alertTitle:Text("タイトル")) {
Button("OK") {
$isShowAlert.wrappedValue.toggle()
}
} message: {
Text("メッセージ")
}
}
}
}
}
}

ContentView

structContentView:View {
/// 標準アラートの表示を管理するフラグ
@Statevar isShowDefaultAlert:Bool=false
/// `fullScreenCover`を利用したカスタムアラートの表示を管理するフラグ
@Statevar isShowFullScreenCoverAlert:Bool=false
/// `overlay`を利用したカスタムアラートの表示を管理するフラグ
@Statevar isShowOverlayAlert:Bool=false
/// `ZStack`を利用したカスタムアラートの表示を管理するフラグ
@Environment(\.isShowAlert)var isShowAlert

var body:someView {
ZStack {
VStack(spacing:50) {
Button(
action: {
isShowDefaultAlert=true
},
label: {
Text("標準アラートを表示する")
}
)
Button(
action: {
isShowFullScreenCoverAlert=true
},
label: {
Text("`fullScreenCover`のアラートを表示する")
}
)
Button(
action: {
isShowOverlayAlert=true
},
label: {
Text("`overlay`のアラートを表示する")
}
)
Button(
action: {
isShowAlert.wrappedValue=true
},
label: {
Text("`ZStack`のアラートを表示する")
}
)
}
}
.alert(Text("タイトル"), isPresented:$isShowDefaultAlert) {
Button("OK") {
isShowDefaultAlert.toggle()
}
} message: {
Text("メッセージ")
}
.alertFullScreenCover(Text("タイトル"), isPresented:$isShowFullScreenCoverAlert) {
Button("OK") {
isShowFullScreenCoverAlert.toggle()
}
} message: {
Text("メッセージ")
}
.alertOverlay(Text("タイトル"), isPresented:$isShowOverlayAlert) {
Button("OK") {
isShowOverlayAlert.toggle()
}
} message: {
Text("メッセージ")
}
}
}

Extensions

extensionView {
/// 1. `fullScreenCover`を利用した実装方法
funcalertFullScreenCover<A,M>(
_title:Text,
isPresented:Binding<Bool>,
@ViewBuilderactions:@escaping () ->A,
@ViewBuildermessage:@escaping () ->M
) ->someViewwhereA :View,M :View {
fullScreenCover(isPresented: isPresented) {
CustomAlertView(alertTitle: title, actions: actions, message: message)
}
}

/// 2. `overlay`を利用した実装方法
funcalertOverlay<A,M>(
_title:Text,
isPresented:Binding<Bool>,
@ViewBuilderactions:@escaping () ->A,
@ViewBuildermessage:@escaping () ->M
) ->someViewwhereA :View,M :View {
overlay {
if isPresented.wrappedValue {
CustomAlertView(alertTitle: title, actions: actions, message: message)
}
}
}
}

CustomAlertView

import SwiftUI

structCustomAlertView<A,M>:ViewwhereA :View,M :View {

var alertTitle:Text
@ViewBuildervar actions: () ->A
@ViewBuildervar message: () ->M

var body:someView {
ZStack {
// 背景部分
Color.black
.opacity(0.2)
.edgesIgnoringSafeArea(.all)
// ダイアログ部分
VStack {
alertTitle
.font(.largeTitle)
.bold()
.padding(.top,20)
.padding(.bottom,10)
message()
.font(.title)
Divider()
.padding(.top,20)
actions()
.font(.title)
.padding(.vertical,20)

}
.frame(width:UIScreen.main.bounds.width/2)
.background(.white)
.cornerRadius(15)
}
}
}

structCustomAlert_Previews:PreviewProvider {
staticvar previews:someView {
CustomAlertView(alertTitle:Text("タイトル"), actions: {
Button("OK") {}
}, message: {
Text("メッセージ")
})
}
}

さいごに

本記事では、自由研究としてアラートダイアログを自作する際の実装方法に関して、3種類を比較してみました。

アラートダイアログは性質上、表示中他の操作ができないことが求められると思います。今回試した中では、3つ目のZStackを利用した方法がアプリ大元のViewに重ねることでアラートを一番手前に表示する要件を満たしやすいと感じました。

また、今回試して分かったメリット・デメリットを以下の表にまとめました。

試した実装方法メリットデメリット
1.fullScreenCoverを利用した方法画面全体にモーダル表示するため、アラート表示中に他の動作ができないように制限しやすい。fullScreenCover は表示時に画面下から上まで覆うアニメーションを伴って表示するため、アニメーションのカスタマイズが難しい(調べた限りほぼできない)。
2.overlayを利用した方法overlay自体は重ねて表示するだけなので、アニメーションなどをカスタマイズしやすい。あくまでも元となるViewに重ねるため、重ねて表示する範囲は呼び出すViewに依存する。
3.ZStackを利用した方法アプリ大元のViewに重ねることで、画面全体を覆って表示制御できる。ダイアログ表示を制御するフラグやダイアログに表示する内容をApp階層まで伝える必要があるため、単一のView内で状態管理が完結しない。

何かしらの参考になれば幸いです。

参考リンク

https://developer.apple.com/documentation/SwiftUI/View/alert(_:isPresented:actions:)-1bkka

https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:)

https://developer.apple.com/documentation/swiftui/view/overlay(alignment:content:)


  1. 1.医療・ヘルスケア分野での案件や新規ビジネス創出を担う、2020年に誕生した事業部です。設立エピソードは未来報の記事をご覧ください。
  2. 2.SwiftUI: fullScreenCover with no animation?

目次

  1. はじめに
  2. 検証内容
    1. 事前準備
    2. 1. fullScreenCoverを利用した方法
    3. 2. overlayを利用した方法
    4. 3. ZStackを利用した方法
    5. コード実装例
      1. App
      2. ContentView
      3. Extensions
      4. CustomAlertView
  3. さいごに
  4. 参考リンク

カテゴリー


[8]ページ先頭

©2009-2025 Movatter.jp