LoginSignup
2

More than 1 year has passed since last update.

posted at

updated at

iOS13対応フルスクリーンモーダル

SwiftUIでiOS13含めて対応する場合、UIViewControllerのpresent(_:animated:completion:)で表示するフルスクリーンモーダルに相当する機能が提供されていません。
UIViewControllerRepresentableを利用してUIKit側にコントロールを渡して実現出来るように思えますが、modalPresentationStyleの設定が効いていないらしく結果に反映されません。
出来ました、勘違いですみません。。。

このため、フルスクリーンのモーダルを利用したい場合、それに似たようなViewを独自に定義する必要があります。
この投稿では、SwiftUIの機能だけで擬似的にモーダルを表示させていきます。

モーダルを表示させる領域

単純にモーダルのようなViewを表示させるなら、ZStackにViewが被るように表示させればよいだけですが、
この方法を採用した場合、そのモーダルは親Viewの配下にあるという認識をされるため、Viewのライフサイクルが親Viewに依存することになるので上手い方法ではありません。

親とは別のライフサイクルに乗せるためにはoverlayを利用することになります。

var body: some View {
    NavigationView {
    }
    .overlay(/* Modal */)
}

モーダル本体を作成する

表示領域が決まったので、今度はモーダルのようにアニメーションして表示されるViewを作成します。

struct ModalView<Content: View>: View {
    @Binding var isPresented: Bool

    let content: () -> Content

    var body: some View {
        if self.isPresented {
            self.content()
                // 画面を覆うためにスクリーンサイズをframeに設定
                .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
                // デフォルトの背景色を設定
                .background(Color.white.edgesIgnoringSafeArea(.all))
                // 大体0.25秒で本家モーダルのようなアニメーションになる
                .animation(.easeOut(duration: 0.25))
                // 下からせり上がるトランジションを設定
                .transition(.move(edge: .bottom))
        } else {
            EmptyView()
        }
    }
}

便利に使えるようにViewExtensionを作成する

overlayに先程作成したModalViewを直接指定しても良いのですが、もう少し使いやすくするためにViewExtensionを作ります。

extension View {
    func fullScreenModal<Content: View>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View {
        self.overlay(ModalView(isPresented: isPresented, content: content))
    }
}

完成

実際に使うには、NavigationBarの扱い方などで調整が必要になりますが、ひとまずはこれで完成です。

extension View {
    func fullScreenModal<Content: View>(isPresented: Binding<Bool>, content: @escaping () -> Content) -> some View {
        self.overlay(ModalView(isPresented: isPresented, content: content))
    }
}

struct ModalView<Content: View>: View {
    @Binding var isPresented: Bool

    let content: () -> Content

    var body: some View {
        if self.isPresented {
            self.content()
                .frame(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height)
                .background(Color.white.edgesIgnoringSafeArea(.all))
                .animation(.easeOut(duration: 0.25))
                .transition(.move(edge: .bottom))
        } else {
            EmptyView()
        }
    }
}

struct MyView: View {
    @State var isModalPresented = false

    var body: some View {
        NavigationView {
            VStack {
                Button("Show Modal") {
                    self.isModalPresented = true
                }
            }
            .fullScreenModal(isPresented: self.$isModalPresented) {
                Text("Modal")
            }
        }
    }
}

遷移先の画面からもモーダルが閉じれるようにEnvironmentValueなどを提供出来るようにするとより使いやすくなると思います。

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
2