0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

SwiftUIAdvent Calendar 2020

Day 1

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

Last updated at Posted at 2020-12-14

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などを提供出来るようにするとより使いやすくなると思います。

0
2
0

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
  3. You can use dark theme
What you can do with signing up
0
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?