The Pragmatic Ball boy

iOSを中心にやってる万年球拾いの老害エンジニアメモ

UIViewRepresentableのCoordinatorはなぜ必要か

UIKitのViewをSwiftUIとして使うには、UIViewRepresentableを使います。

そしてUIKitのViewのdelegateやtarget actionをハンドリングしたい場合structではハンドリングできないので、Coordinatorという仕組みが用意されています。

makeCoordinator()がmakeUIViewより先に呼ばれるのでそこでCoordinatorを作成し、その後はmakeUIView()やupdateUIView()などで渡されるcontextにcoordinatorが含まれているので、それを用いればcoordinatorとやり取りができます。

struct WebView: UIViewRepresentable {
    var loadUrl:String

    func makeUIView(context: Context) -> WKWebView {
        let webview = WKWebView()
        webview.navigationDelegate = context.coordinator
        return webview
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        // ※ 再描画されるたびにURLが読み込まれます
        uiView.load(URLRequest(url: URL(string: loadUrl)!))
    }

    class Coordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("読み込み完了")
        }
    }

    func makeCoordinator() -> Coordinator {
        return Coordinator()
    }
}

しかし、このようにstruct内にclassを保持させてやればもっとシンプルにできちゃうのになぜこんな仕組みが用意されているのでしょう?

struct WebView: UIViewRepresentable {
    var loadUrl:String

    private let coordinator = OreoreCoordinator()

    func makeUIView(context: Context) -> WKWebView {
        let webview = WKWebView()
        webview.navigationDelegate = coordinator
        return webview
    }

    func updateUIView(_ uiView: WKWebView, context: Context) {
        uiView.load(URLRequest(url: URL(string: loadUrl)!))
    }

    class OreoreCoordinator: NSObject, WKUIDelegate, WKNavigationDelegate {
        func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
            print("読み込み完了")
        }
    }
}

それはSwiftUIがUIKitのようなclassベースではなくstructベースで、描画されるたびに破棄されて生成されるからです。

structにclassを保持させるパターンだと描画される度にcoordinatorも再生成されます。 makeUIViewの時点でdelegateに渡しているcoordinatorは再描画時には破棄されるので、このコードは再描画されるとdelegateは呼ばれなくなります。 makeUIViewではなくupdateUIViewでdelegateにセットすれば動きはしそうですが、毎回coordinatorを生成されてしまう分が無駄なのでやめたほうがいいかなと思います。

こういった問題を解決するためにCoordinatorというかContextが用意されていると思われます。

UIViewRepresentableではmakeCoordinatorで生成されたCoordinatorはstructが再描画されても同じCoordinatorが参照できるような仕組みが用意されているわけです。

@Stateなども似たような仕組みかなと思います。

UIKitで慣れ親しんだ人はこのSwiftUIはstructで再描画時には再生成されるといった点は大きな違いで考え方を改めないと間違った実装をしてしまいがちかなと思います。SwiftUIで用意された仕組み以外を使って状態や参照を保持しようとしないようにしましょう。