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

# SwiftUI(iOS13)でフルスクリーンモーダルを表示する
久しぶりの記事になります。おはこんばんにちは和尚です！
今日は一昨年Appleから発表されて話題となっているSwiftUIについての記事を書いていきたいと思います:v:

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

_必須級ライブラリ_
[SwiftUIX](https://github.com/SwiftUIX/SwiftUIX)
↑ SwiftUIにまだ実装されていないものを補ってくれます。説明書がないものがチラホラあるのが残念...

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

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

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

```swift: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を拡張していきます。

```swift: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:

```swift: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](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/243225/bae61229-a2b3-6d7b-0513-76bdcc4c069f.gif)


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

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

Let's Enjoy SwiftUI！!