UIKitでXcode Previews(SwiftUI Preview)を使う

SwiftUIで開発するとプレビュー機能が非常に便利ですよね。
簡単な変更ならビルドせずとも表示してくれますし、
Live Previewでシミュレータを起動せずに動作確認もできます。

私はSwiftUIから入ったので、少しUIKitを学ぼうと思った時に面倒だなと感じました。
そこでPreviewを表示することにしました。

Xcode Previewsは結局のところPreviewProviderを継承した構造体を表示してくれているだけです。
つまりこの構造体にプレビューしたいViewControllerやViewを正しく渡せればOKです。

PreviewProviderはSwiftUI側なのでUIKitをSwiftUIで表示する際のProtocolを使用します。
UIViewControllerRepresentableやUIViewRepresentableです。

import UIKit
import SwiftUI

struct UIViewControllerPreviewWrapper:UIViewControllerRepresentable{
    let previewController:UIViewController
    
    func makeUIViewController(context: Context) -> UIViewController {
        return self.previewController
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
}

struct UIViewPreviewWrapper:UIViewRepresentable {
    
    let previewView:UIView
    
    func makeUIView(context: Context) -> some UIView {
        return self.previewView
    }
    
    func updateUIView(_ uiView: UIViewType, context: Context) {
    }
}
class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

struct ViewController_Preview: PreviewProvider {
    
    static var previews: some View {
        UIViewControllerPreviewWrapper(previewController: ViewController())
    }
}

View単体やStoryboardを使用していないViewControllerやViewは単純にUIViewControllerRepresentableやUIViewRepresentableを介して渡せばOKです。

Storyboardを使用している場合はUIStoryboardを介してViewControllerのインスタンスを生成する必要があります。

import UIKit
import SwiftUI

struct StoryboardPreviewWrapper:UIViewControllerRepresentable {
    
    let previewStoryboard:String = "Main"
    let previewId:String
    
    func makeUIViewController(context: Context) -> UIViewController {
        let storyboard = UIStoryboard(name: self.previewStoryboard, bundle: Bundle.main)
        let controller = storyboard.instantiateViewController(identifier: self.previewId)
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
    
}

特別難しいことをするわけではなくStoryboardを複数に分割している人からすれば馴染み深いコードだと思います。

初期化時に引数にStoryboardファイル名とStoryboard IDを渡す形にしてみました。
ViewControllerを初期値のまま表示するならこれでOKですね。

値を渡す必要がある場合はmakeUIViewControllerで渡す必要があります。
単純に対応するならViewController毎にWrapperを用意してmakeUIViewControllerで渡せばいいのですが、
面倒なので汎用性を持たせる為にクロージャを渡してそこでセットします。

struct StoryboardPreviewWrapper:UIViewControllerRepresentable {
    
    var previewStoryboard:String = "Main"
    let previewId:String
    var setValue:(UIViewController)->Void = {_ in }
    
    func makeUIViewController(context: Context) -> UIViewController {
        let storyboard = UIStoryboard(name: self.previewStoryboard, bundle: Bundle.main)
        let controller = storyboard.instantiateViewController(identifier: self.previewId)
        self.setValue(controller)
        return controller
    }
    
    func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
    }
    
}
import UIKit
import SwiftUI

class ViewController: UIViewController {

    @IBOutlet var label: UILabel!
    
    var labelText = "Hello World"
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        
        self.label.text = self.labelText
    }


}

struct ViewController_Preview:PreviewProvider {
    static var previews: some View{
        StoryboardPreviewWrapper(previewId: "idMain", setValue: { controller in
            let viewController = controller as! ViewController
            viewController.labelText = "Preview"
        })
    }
}

これでクロージャの中で好きなようにできますね。
今回は適当にラベルの値を変数でセットするようにしました。

実際にSwiftUIにUIKitのクラスをViewを使用する時はSwiftUIとUIKit間で値をやりとりする為にCoordinatorを使用しますが、今回は表示のみなので単純なコードで済みました。

Xcode Previewsは多少面倒な点もあるのですが、有効に活用出来ると非常に強力な機能ですので是非試して見てください。

コメント

  1. […] UIKitでXcode Previews(SwiftUI Preview)を使うSwiftUIで開発するとプレビュー機能が非… […]

  2. […] UIKitでXcode Previews(SwiftUI Preview)を使うSwiftUIで開発するとプレビュー機能が非… […]

タイトルとURLをコピーしました