LoginSignup
5
5

More than 3 years have passed since last update.

SwiftUIでフルスクリーンのモーダルを表示する(iOS13)

Last updated at Posted at 2021-03-20

SwiftUI(iOS13)でフルスクリーンモーダルを表示する

久しぶりの記事になります。おはこんばんにちは和尚です!
今日は一昨年Appleから発表されて話題となっているSwiftUIについての記事を書いていきたいと思います:v:

SwiftUI 2はiOS13では使用できない件

現在(2021.3月時点)ではSwiftUIはver.2までリリースされておりますが、残念なことにSwiftUI 2でリリースされた機能はiOS13で使用することができません。SwiftUI 1では一般的なアプリに必要な機能が全然揃っておらず、2以上にライブラリやUIKitに頼ることになります。

必須級ライブラリ
SwiftUIX
↑ SwiftUIにまだ実装されていないものを補ってくれます。説明書がないものがチラホラあるのが残念...

さて、本題であるフルスクリーンのモーダルですが、公式からは「.fullScreenCover」というものが用意されていますがこちらはSwiftUI 2から登場したものとなっており残念ながらiOS13では使用することができません。
ので、今回iOS13でも使えるフルスクリーンのモーダルを作っていきましょう!

※ちなみに筆者、初めてのiOSアプリ実装がSwiftUIとなっておりUIKitも現在絶賛勉強中ですのでお手柔らかに(笑)

フルスクリーンのモーダル実装

1. UIApplicationの拡張

UIApplicationを拡張して一番上のコントローラーを取得するメソッドと、フルスクリーンのモーダルを閉じるメソッドを作成していきます。

UIApplication+Extension.swift
extension UIApplication {
    /// 一番上にあるコントローラーを取得する
    public func getTopViewController() -> UIViewController? {
        guard let window = UIApplication.shared
                .connectedScenes
                .filter({$0.activationState == .foregroundActive})
                .map({$0 as? UIWindowScene})
                .compactMap({$0})
                .first?.windows.first else {
            return nil
        }

        window.makeKeyAndVisible()

        guard let rootViewController = window.rootViewController else {
            return nil
        }

        var topController = rootViewController
        while let newTopController = topController.presentedViewController {
            topController = newTopController
        }

        return topController
  }

    /// フルスクリーンのモーダルを閉じる
    public func closeModalView() {
        UIApplication.shared.getTopViewController()?.dismiss(animated: true, completion: nil)
    }
}

2. Viewの拡張

次はSwiftUIのViewでSheetViewやFullCreenCoverのようにフルスクリーンのモーダルが使用できるようにViewを拡張していきます。

View+Extension.swift
extension View {
    public func fullScreenView<Content>(
        isPresented: Binding<Bool>,
        @ViewBuilder content: @escaping () -> Content
    ) -> some View where Content: View {
        if isPresented.wrappedValue {
            let window = UIApplication.shared.windows.last
            window?.isHidden = true

            let view = content()
            let viewController = UIHostingController(rootView: view)
            viewController.modalPresentationStyle = .fullScreen

            DispatchQueue.main.async {
                guard let tvc = UIApplication.shared.getTopViewController() else {
                    return
                }

                tvc.present(viewController, animated: true, completion: nil)
                isPresented.wrappedValue = false
            }
        }

        return self
    }
}

実際に使ってみよう!

実際にViewで使用してみましょう!今回適当なサンプルViewを用意してみました!
こちら参考にみなさん是非使ってみてください:thumbsup:

ContentView.swift
struct ContentView: View {
    @State private var showModal: Bool = false

    var body: some View {
        VStack {
            Spacer()
            Button(action: {
                showModal.toggle()
            }, label: {
                Text("フルスクリーンのモーダルを表示する")
            })
            Spacer()
        }
        .fullScreenView(isPresented: $showModal) {
            ModalView()
        }
    }
}

struct ModalView: View {
    var body: some View {
        ZStack {
            Color.green.edgesIgnoringSafeArea(.all)
            Button(action: {
                UIApplication.shared.closeModalView()
            }, label: {
                Text("閉じる")
            })
        }
    }
}

ezgif-1-6f84b46cee07.gif

最後に

iOS13でSwiftUIとCombineを使用する際は、特にiOS13.0からiOS13.2までは気をつけましょう。バグがかなり多いです...
しかも、たちが悪いことにシュミレーションでは再現せず実機のみで再現するバグもいくつかあります。

実機がない場合は、SwiftUIをiOS13で使用することをおすすめしません:frowning2:
それでもiOS13を含めたい場合は最低でもiOS13.3以上にしましょう。

Let's Enjoy SwiftUI!!

5
5
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
5
5