📱

VIPERアーキテクチャの資産を活かしながらSwiftUIを導入する - はじめに

2023/03/06に公開

この記事は?

VIPERアーキテクチャでその資産を活かしながらSwiftUIを導入する話とそれを基に得た知見などをまとめていきます。

SwiftUIの発表から約3年が経ちそろそろSwiftUIを導入しなきゃな〜とお考えの皆さんの参考になったら幸いです!

VIPERアーキテクチャとは?

VIPERアーキテクチャとは、クリーンアーキテクチャを参考に作られたiOSの構成です
View , Interactor, Presenter , Entity , Routerの役割に分かれていて、責務を分担することでテストしやすい構成で開発するような設計です

本記事では詳しいVIPERアーキテクチャの言及は本筋と離れると考え避けますが下記の記事が参考になると思います。

https://cheesecakelabs.com/blog/ios-project-architecture-using-viper/
https://qiita.com/hicka04/items/09534b5daffec33b2bec

VIPERアーキテクチャの資産を活かすとは?

実際iOS開発に携われてる皆さんはなるべく既存のコードを変えずにSwiftUIを導入していきたいと思うでしょう
僕もそうです。
そこでなるべくVIPERアーキテクチャの構成を変えずに導入をしていきます。

これはCookpadさんで公開されている下記の記事を 存分に参考にさせていただきました 徹底的にパクりました。
https://techlife.cookpad.com/entry/2021/01/18/kaimono-swift-ui

この記事と同じように
   【方針】View 層のみで SwiftUI を部分的に導入する
という方針で導入していきたいと思います。

View 層のみで SwiftUI を部分的に導入するとは?

ここで具体的にView 層のみで SwiftUI を部分的に導入する方法を見ていきます
これにはUIHostingControllerを使用します。

https://developer.apple.com/documentation/swiftui/uihostingcontroller

公式のページはありますが、とりあえずUIHostingControllerを利用したサンプルコードを書いたのでみてください!
書いた想定としては setupUIkit() にやむを得ず残さなきゃいけないUIKit群があり、それを無理矢理SwiftUIで上書きするというコードです。

import UIKit
import SwiftUI
import Combine
import PlaygroundSupport

class MyViewController : UIViewController {
    override func loadView() {
        setupUIkit()

        let host = UIHostingController(rootView: DebugView())
        setupUI(hostingController: host)
    }
    
    // 何らかの事情で残す必要があるUIKitのコードこれを残さなきゃいけない前提でUIHostingControllerを入れる
    func setupUIkit() -> Void {
        let view = UIView()
        view.backgroundColor = .white

        let label = UILabel()
        label.frame = CGRect(x: 200, y: 200, width: 200, height: 20)
        label.text = "1枚目だよ"
        label.textColor = .black
        view.addSubview(label)
        
        self.view = view
    }
}

struct DebugView: View {
    var body: some View {
        Text("2枚目だよ")
        .foregroundColor(Color.blue)
    }
}


extension UIViewController {
    func setupUI(hostingController: UIHostingController<some View>) {
        addChild(hostingController)
        view.addSubview(hostingController.view)
	
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate(
            [
                hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
                hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
            ]
        )
    }
}

PlaygroundPage.current.liveView = MyViewController()

UIHostingControllerでDebugViewを設定します。
UIHostingControllerで宣言することでUIKit上でSwiftUIを利用することができます。

    let host = UIHostingController(rootView: DebugView())

UIHostingControllerをUIViewController上で呼び出します。
大体のUIKitを利用している場面でUIViewControllerが使われると思うのでextensionで表示しています。
UIHostingControllerは既存のUIViewControllerの子供に追加し、制約を加えています。
こうしないとレイアウトが崩れたりSafeAreaが適用されない可能性があるので全体的なレイアウトはUIViewControllerに乗っかります。

extension UIViewController {
    func setupUI(hostingController: UIHostingController<some View>) {
        addChild(hostingController)
        view.addSubview(hostingController.view)
        
        hostingController.view.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate(
            [
                hostingController.view.topAnchor.constraint(equalTo: view.topAnchor),
                hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor),
                hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor),
                hostingController.view.trailingAnchor.constraint(equalTo: view.trailingAnchor)
            ]
        )
    }
}

では実際にビルドしてみましょう
できました!いいですね!

このような形でSwiftUIに関する特にVIPERやUIKitの連携Tipsを今後も上げていきますのでフォローお願いします😊コメントでも嬉しいです😊

Voicyテックブログ

Discussion