【SwiftUI】ポップアップで表示されるダイアログを作ってみた

2022.08.25

ポップアップで表示されるようなダイアログを作ってみたくなったので作ってみることにしました。

環境

  • Xcode 13.3
  • iOS 15.5

作ったもの

こんな感じで表示されるダイアログを作成してみました。

popup-dialog-view

中身の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で、もし背景タップで画面を閉じるようにしている場合は、isPresentedfalseに切り替えて画面を閉じます。ポップアップっぽく見せる為にwithAnimation内でフラグ切替処理を行っております。

content

content
    .frame(width: dialogWidth)
    .padding()
    .padding(.top, buttonSize)
    .background(.white)
    .cornerRadius(12)

widthは、proxy.size.width0.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には他にもアニメーションがあるので、色々試してみて遊んでみるのも楽しそうですね!他にもハーフモーダルなど試してみたいことはあるので、機会があればやってみようと思います。

参考