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))
}
}
}
構造体CategoryViewWrapperをUIViewRepresentableクラスとして定義します。
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を使ってみたい場合は、今回紹介した方法を試すのもアリだと思います。
機会があればどのくらい効率が上がるか、測定してみたいと思います。