この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。
ポップアップで表示されるようなダイアログを作ってみたくなったので作ってみることにしました。
環境
- Xcode 13.3
- iOS 15.5
作ったもの
こんな感じで表示されるダイアログを作成してみました。
中身のContentは好きなものを渡せるようにしました。
コード
PopUpDialogView
struct PopUpDialogView<Content: View>: View {
@Binding var isPresented: Bool
let content: Content
let isEnabledToCloseByBackgroundTap: Bool
private let buttonSize: CGFloat = 24
var body: some View {
GeometryReader { proxy in
let dialogWidth = proxy.size.width * 0.75
ZStack {
BackgroundView(color: .gray.opacity(0.7))
.onTapGesture {
if isEnabledToCloseByBackgroundTap {
withAnimation {
isPresented = false
}
}
}
content
.frame(width: dialogWidth)
.padding()
.padding(.top, buttonSize)
.background(.white)
.cornerRadius(12)
.overlay(alignment: .topTrailing) {
CloseButton(fontSize: buttonSize,
weight: .bold,
color: .gray.opacity(0.7)) {
withAnimation {
isPresented = false
}
}
.padding(4)
}
}
}
}
}
init
extension PopUpDialogView {
init(isPresented: Binding<Bool>,
isEnabledToCloseByBackgroundTap: Bool = true,
@ViewBuilder _ content: () -> Content) {
_isPresented = isPresented
self.isEnabledToCloseByBackgroundTap = isEnabledToCloseByBackgroundTap
self.content = content()
}
}
- isPresented
- ポップアップダイアログが表示されているか
- isEnabledToCloseByBackgroundTap
- ダイアログの背景をタップしても画面を閉じることが出来るかどうか
- content
- ダイアログの中に埋め込むContent
ダイアログのwidth
今回はダイアログのwidth
は、GeometryReader
からwidth
を取得して、その0.75
倍にしています。特に理由はないので好きな値にしていただければと思います。
GeometryReader { proxy in
let dialogWidth = proxy.size.width * 0.75
Background
ポップアップダイアログのグレーがかった背景です。
BackgroundView(color: .gray.opacity(0.7))
.onTapGesture {
if isEnabledToCloseByBackgroundTap {
withAnimation {
isPresented = false
}
}
}
struct BackgroundView: View {
let color: Color
var body: some View {
Rectangle()
.fill(color)
.ignoresSafeArea()
}
}
onTapGesture
で、もし背景タップで画面を閉じるようにしている場合は、isPresented
をfalse
に切り替えて画面を閉じます。ポップアップっぽく見せる為にwithAnimation
内でフラグ切替処理を行っております。
content
content
.frame(width: dialogWidth)
.padding()
.padding(.top, buttonSize)
.background(.white)
.cornerRadius(12)
width
は、proxy.size.width
の0.75
倍の大きさにし、padding
を四方向に足した後に、上部に閉じるボタンのサイズ分だけさらにpadding
を追加しています。また、背景色を白にして、角丸に変更しています。
CloseButton
.overlay(alignment: .topTrailing) {
CloseButton(fontSize: buttonSize,
weight: .bold,
color: .gray.opacity(0.7)) {
withAnimation {
isPresented = false
}
}
.padding(4)
}
struct CloseButton: View {
let fontSize: CGFloat
let weight: Font.Weight
let color: Color
let action: () -> Void
var body: some View {
Button {
action()
} label: {
Image(systemName: "xmark.circle")
}
.font(.system(size: fontSize,
weight: weight,
design: .default))
.foregroundColor(color)
}
}
ダイアログの閉じるボタンは、.overlay(alignment: .topTrailing)
を使用して右上に配置して、CloseButton
を押した時のアクションは、withAnimation
の中でダイアログ表示のフラグをfalse
に切り替えています。
使用例
あとは、PopUpDialogViewに埋め込みたいViewを埋め込んで、任意のタイミングでダイアログ表示のフラグを切り替え表示するだけです。
struct ContentView: View {
@State private var shouldPresentPopUpDialog = false
var body: some View {
ZStack {
Button {
withAnimation {
shouldPresentPopUpDialog = true
}
} label: {
Text("Present Pop-up Dialog")
}
if shouldPresentPopUpDialog {
PopUpDialogView(isPresented: $shouldPresentPopUpDialog) {
Face()
}
}
}
}
}
おわりに
コードはGitHubに載せております。
とりあえず、やってみたかったことは出来ました!withAnimation
には他にもアニメーションがあるので、色々試してみて遊んでみるのも楽しそうですね!他にもハーフモーダルなど試してみたいことはあるので、機会があればやってみようと思います。