見出し画像

SwiftUIのTabViewでUIKitと連携する時の罠

SwiftUIのTabViewでUIKitと連携する時にハマってしまった部分があったので、その内容と回避策を共有したい。

事象

今回はTabViewで既存のUIViewControllerを表示したかったので、UIViewControllerRepresentableに準拠するViewを定義してTabViewで表示した。
なお、わかりやすいようにUIViewController.view.backgroundColorをRGBで設定しており、UIViewControllerWrapperにも.background(.gray)を設定している。

struct ContentView: View {
    let redViewController = UIViewController()
    let greenViewController = UIViewController()
    let blueViewController = UIViewController()
    
    var body: some View {
        TabView {
            UIViewControllerWrapper(viewController: redViewController)
                .background(.gray)
                .ignoresSafeArea()
            UIViewControllerWrapper(viewController: greenViewController)
                .background(.gray)
                .ignoresSafeArea()
            UIViewControllerWrapper(viewController: blueViewController)
                .background(.gray)
                .ignoresSafeArea()
        }
        .tabViewStyle(.page)
        .onAppear {
            redViewController.view.backgroundColor = .red
            greenViewController.view.backgroundColor = .green
            blueViewController.view.backgroundColor = .blue
        }
        .ignoresSafeArea()
    }
}

struct UIViewControllerWrapper: UIViewControllerRepresentable {
    private let viewController: UIViewController
    
    init(viewController: UIViewController) {
        self.viewController = viewController
    }
    
    func makeUIViewController(context: Context) -> some UIViewController {
        return viewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewControllerType, context: Context) {}
}

これを実行すると、冒頭のGIFにあるように、縦画面では問題なくページングできるが、2枚目の画面で横回転して最初の画面に戻ってさらにページングすると、UIViewController.viewの背景色を.redに設定しているにも関わらず、それが表示されずUIViewControllerWrapperの.grayしか表示されていないことがわかる。

View Hierarchyで確認しても、正常に表示されている時(右側)と違って、UIViewController自体が消えている(左側)ことがわかる。

この事象の原因は不明だが、SwiftUIでUIViewControllerRepresentableの使用時のバグだと思われる。ちなみに、TabViewで表示するUIViewControllerWrapperを2つにすると問題がなく、4つにすると3枚目の画面から2枚目の画面に戻る時でも発生した。

回避策

上記の通り原因は不明だが、UIViewController自体が消えていたので、UIViewControllerRepresentableをUIViewRepresentableに変更して、makeUIView(context:)でUIViewController.viewを返却するようにしたところ、この事象は発生しなくなった。

struct UIViewWrapper: UIViewRepresentable {
    private let viewController: UIViewController
    
    init(viewController: UIViewController) {
        self.viewController = viewController
    }
    
    func makeUIView(context: Context) -> some UIView {
        return viewController.view
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {}
}

同じような事象に遭遇された場合は、この方法を試してくみるといいかもしれません。

サンプルコード

この記事が気に入ったらサポートをしてみませんか?