見出し画像

UIKitベースの開発環境で Xcode Previews を使う方法

はじめに


UIKit環境での実装では、シュミレータでUIを確認したりと面倒ですが、SwiftUI環境での実装では、Xcode PreviewsでリアルタイムでUIを確認できるので、開発効率が高いと言えます。

では、「SwiftUI環境に移行すれば良いのでは?」と思いますが、そう簡単に開発環境の移行は出来ません。

そのためUIKit環境でもXcode Previewsを導入できないか調査したところ、メルカリの技術ブログの方に文献を見つけましたので、それを参考に今回実装してみました。

下記、文献になります。

使用環境


● OS:macOS Big Sur 11.3.1
● Xcode:13.2.1
● Swift:5.4

Xcode Previewsの概要


Xcode Previewsとは、SwiftUIの開発環境で搭載されているUIをリアルタイムで表示するツールです。

つまり、シュミレータを使用しなくてもUIの挙動や、APIのレスポンス値をリアルタイムで確認できるツールになります。

Xcode Previewsを使うには、Deployment TargetをiOS 13以上でないといけません。開発環境によっては、Deployment TargetをiOS 12 などありますので、開発環境、リリース環境、プレビュー環境とSchemeを管理する必要があります。

下記、実際にXcode Previewsを使用しているgifになります。

Xcode PreviewsをUIKit環境に導入


今回はプレビュー用にビルド環境を切り替えしていません。
開発環境によってはビルド環境の切り替えが必要になります。その場合は、新規Schemeを追加し、Deployment Target をiOS13以上に設定して下さい。

Xcode Previewsを適用させたいViewの実装

UIViewクラスでセットした値を継承し、Xcode Previews側に値を反映させたいので、プロトコル(protocol)を使用します。

プロトコルの概要に関しては、下記の文献に記載されています。

下記、InputAppliableというprotocolを作成します。

// InputAppliable.swift

import Foundation

protocol InputAppliable {
    associatedtype Input
    func apply(input: Input)
}

associatedtypeは、簡単に言うと「準拠時に決める型」になります。
詳しい概要は、下記の文献に記載されています。

UIViewクラスには、下記のコードを追加します。

// CategoryView.swift

import UIKit

class CategoryView: UIView, InputAppliable {
    
    @IBOutlet private weak var label: UILabel!
    
    enum Input {
        case updateTextColor(textColor: UIColor)
        case updateLabelText(labelText: String)
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        loadNib()
    }
    
    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)!
        loadNib()
    }
    
    /// CategoryViewを読み込む
    private func loadNib() {
        let view = Bundle.main.loadNibNamed("CategoryView", owner: self, options: nil)?.first as! UIView
        view.frame = self.bounds
        
        self.addSubview(view)
    }
    
    /// UIを更新する
    /// - Parameter input: UI更新用の型Input
    func apply(input: Input) {
        switch input {
        case .updateTextColor(textColor: let textColor):
            label.textColor = textColor
        case .updateLabelText(let labelText):
            label.text = labelText
        }
    }
    
}

InputAppliableにセットしたInputをCategoryViewクラス配下に、列挙型として定義します。

    enum Input {
        case updateTextColor(textColor: UIColor)
        case updateLabelText(labelText: String)
    }

InputAppliableにセットしたapplyメソッドを定義して、列挙型Inputで定義した列挙子の具体的な処理内容を追加します。

    /// UIを更新する
    /// - Parameter input: UI更新用の型Input
    func apply(input: Input) {
        switch input {
        case .updateTextColor(textColor: let textColor):
            label.textColor = textColor
        case .updateLabelText(let labelText):
            label.text = labelText
        }
    }

Xcode Previews自体の機能を実装

下記のコードを実装します。

// CategoryViewPreviews.swift

import SwiftUI

struct CategoryViewWrapper: UIViewRepresentable {
    
    typealias UIViewType = CategoryView
    
    let inputs: [CategoryView.Input]
    
    init(inputs: [CategoryView.Input]) {
        self.inputs = inputs
    }
    
    func makeUIView(context: Context) -> CategoryView {
        CategoryView()
    }
    
    func updateUIView(_ uiView: CategoryView, context: Context) {
        inputs.forEach {
            uiView.apply(input: $0)
        }
    }
    
}

struct CategoryViewPreviews: PreviewProvider {
    static var previews: some View {
        Group {
            CategoryViewWrapper(inputs: [.updateLabelText(labelText: "カテゴリ1"), .updateTextColor(textColor: .orange)])
                .previewDisplayName("CategoryView_type_1")
                .previewLayout(.fixed(width: 414.0, height: 100.0))
            CategoryViewWrapper(inputs: [.updateLabelText(labelText: "カテゴリ2"), .updateTextColor(textColor: .cyan)])
                .previewDisplayName("CategoryView_type_2")
                .previewLayout(.fixed(width: 414.0, height: 100.0))
            CategoryViewWrapper(inputs: [.updateLabelText(labelText: "カテゴリ3"), .updateTextColor(textColor: .lightGray)])
                .previewDisplayName("CategoryView_type_3")
                .previewLayout(.fixed(width: 414.0, height: 100.0))
        }
    }
}

構造体CategoryViewWrapperUIViewRepresentableクラスとして定義します。

struct CategoryViewWrapper: UIViewRepresentable {...}

typealias UIViewTypeに表示させたいUIViewクラスをセットします。

typealias UIViewType = CategoryView

UIViewクラスから値を継承させたいので、下記のコードを追加します。

    let inputs: [CategoryView.Input]
    
    init(inputs: [CategoryView.Input]) {
        self.inputs = inputs
    }

applyメソッドを更新させたいので、下記のコードを追加します。

    func makeUIView(context: Context) -> CategoryView {
        CategoryView()
    }
    
    func updateUIView(_ uiView: CategoryView, context: Context) {
        inputs.forEach {
            uiView.apply(input: $0)
        }
    }

構造体CategoryViewPreviews配下に、CategoryViewWrapperを呼び出し値をセットします。

CategoryViewWrapper(inputs: [.updateLabelText(labelText: "カテゴリ1"), .updateTextColor(textColor: .orange)])
                .previewDisplayName("CategoryView_type_1")
                .previewLayout(.fixed(width: 414.0, height: 100.0))

Xcode Previewsで表示

表示させたいViewとXcode Previewsの機能の実装が完了したら、プレビュー表示が出来ます。下記、今回実装したCategoryViewのプレビュー表示になります。

おわりに


今回はUIKitベースの開発環境で Xcode Previews を使う方法を紹介しました。

UIKitでもXcode Previewsが使えるとシュミレータでもUIの確認が減り、開発効率が上がると思います。

SwiftUIに完全移行する前に、Xcode Previewsを使ってみたい場合は、今回紹介した方法を試すのもアリだと思います。

機会があればどのくらい効率が上がるか、測定してみたいと思います。

参考文献



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