28
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

UIKitをSwiftUIみたいにリアルタイムプレビューしたい! - SwiftUIを知らない人のためのXcode Previewsの使い方

Last updated at Posted at 2020-08-28

UIKitでも使えるリアルタイムプレビュー

SwiftUIではXcode11から追加されたXcode Previewsという機能を使って、コードの変更結果をリアルタイムでプレビューすることができます。
実はこの機能は、UIKit(UIView/UIViewController)でも利用することができます。

この記事では、SwiftUIまだ触ってないよって人向けに、UIKitをリアルタイムプレビューする方法を解説します。
この記事で紹介しているコードはこちらにおいてあります。

リアルタイムプレビューのイメージ

ViewController上のTableViewの表示をデータを変更しながらチェック

カスタムしたTableViewCellの表示をいろんなパターンで同時にチェック

前提

Xcode Previewsを使うためにはデプロイメントターゲットをiOS13以降する必要があります。
iOS13より前をターゲットにしているプロジェクトに取り入れる方法については、Xcode Previewsを用いたUIKitベースのプロジェクトの開発効率化が参考になります。

環境

  • Xcode 11.6
  • Swift 5.2.4
  • デプロイメントターゲット 13.0

UIViewをプレビューする

ここでは例として、カスタムTableViewCellをプレビューできるようにしていきます。
データを受け取ってテキストと画像をセットするconfigureメソッドを追加したものです。

MyTableViewCell.swift
final class MyTableViewCell: UITableViewCell {
    static let reuseIdentifier = "\(MyTableViewCell.self)"
    
    func configure(with prop: CellProp) {
        textLabel?.text = prop.title
        imageView?.image = UIImage(systemName: prop.imageType.rawValue)
    }
}

SwiftUIのViewでUIViewをラップする

Xcode PreviewsはSwiftUIのView用に作られているので、MyTableViewCellをSwiftUIのViewでラップする必要があります。
UIViewをラップするためのプロトコルとして、UIViewRepresentableというものが用意されています。

MyTableViewCell.swift
import SwiftUI

// SwiftUIのViewはstructで定義する
struct MyTableViewCellWrapper: UIViewRepresentable {
    ...
}

UIViewRepresentableに準拠させるには、makeUIView/updateUIViewの二つのメソッドを定義します。
また、プレビュー時に使用するデータをセットするために、プロパティvar prop: CellPropを追加しています。

MyTableViewCell.swift
struct MyTableViewCellWrapper: UIViewRepresentable {
    var prop: CellProp
    
    // 初期化メソッド
    func makeUIView(context: Context) -> MyTableViewCell {
        // 単純に初期化して返す
        MyTableViewCell(style: .default, reuseIdentifier: MyTableViewCell.reuseIdentifier)
    }

    // SwiftUI側から更新がかかったときに呼ばれるメソッド
    func updateUIView(_ cell: MyTableViewCell, context: Context) {
        // 更新用のメソッドはupdateUIView内で呼ぶ
        cell.configure(with: prop)
    }
}

プレビュー用のstructを追加する

最後のステップとして、Xcode Previewsがプレビューを表示するときの設定を読み取れるように、PreviewProviderプロトコルに準拠したstructを定義します。

このプロトコルに準拠させるために、static var previews: some Viewを追加します。
ViewはSwiftUIにおける基本的なViewを表すstructです。someはSwiftUIのViewであれば、なんでもいいよという意味です。

MyTableViewCell.swift
struct MyTableViewCell_Previews: PreviewProvider {
    static var previews: some View {
        Group {
            MyTableViewCellWrapper(prop: ("Pencil", .pencil))
            MyTableViewCellWrapper(prop: ("Scribble", .scribble))
            MyTableViewCellWrapper(prop: ("Scissors", .scissors))
        }
        .previewLayout(.fixed(width: 320, height: 50))
    }
}

Groupは主にプレビューのために使われるSwiftUIのViewで、クロージャーに複数のViewを並べることができます。
previewLayoutメソッドでサイズを指定してやることで、次の画像の様にプレビュー時のサイズを調整することができます1

Option+Command+リターンを入力(またはGUIからCanvasをクリック)すると、プレビュー用の画面が開きます。編集中のファイルに、PreviewProviderプロトコルに準拠したstructがある場合は、そのプレビューが表示されます2


MyTableViewCellWrapperに渡すpropの値やプレビューサイズを編集すると、リアルタイムでプレビューが更新されます。
また、プレビューの右に表示されている再生ボタンを押すと、"Live Preview"がはじまり、タップイベントやスクロールなどのユーザーインタラクションをプレビュー画面上でテストすることができます3

UIViewControllerをプレビューする

UIViewControllerもUIViewとほとんど同じ手順でプレビュー可能です。
まずは、UIViewControllerRepresentableに準拠したstructでUIViewControllerをラップします。

ViewController.swift
struct ViewController_Wrapper: UIViewControllerRepresentable {
    // プレビュー用のデータを返すスタブ
    var presenter: PresenterStub
    
    // 初期化メソッド
    func makeUIViewController(context: Context) -> ViewController {
        let vc = ViewController()
        vc.presenter = presenter
        presenter.view = vc
        return vc
    }
    
    // SwiftUI側から更新がかかったときに呼ばれるメソッド
    func updateUIViewController(_ vc: ViewController, context: Context) {
        // 更新用のメソッド
        presenter.didPushFetchButton()
    }
}

今回はプレビュー用のデータを渡しやすいように、ViewControllerが表示するデータはPresenterInputというプロトコルで管理するようにしています。

Presenter.swift
protocol PresenterInput {
    var data: [CellProp] { get }
    func didPushFetchButton()
}
ViewController.swift
final class ViewController: UIViewController {
    var presenter: PresenterInput!
    ...
}

PreviewProviderに準拠したstruct内でViewを初期化する際に、プレビュー用のデータを返すスタブを注入します。
このように実装することで、プレビューしたいデータごとにスタブを用意して、複数のデータについての表示を同時に確認することも可能です。

ViewController.swift
struct ViewController_Wrapper: UIViewControllerRepresentable {
    // プレビュー用のデータを返すスタブ
    var presenter: PresenterStub
    ...    
}

struct ViewController_Previews: PreviewProvider {
    static var previews: some View {
        let presenter = PresenterStub()
        return ViewController_Wrapper(presenter: presenter)
    }
}

次の画像は、PesenterStub内のコードを変更したときに、リアルタイムで表示が変わっている様子です。

おわりに

Viewのレイアウトをコードでやっちゃう派の人は、Xcode Previewsの利点を最大限に活用できるのではないでしょうか。
ストーリーボード派の方も、来るSwiftUI時代に備えて、この機会に一度チャレンジしてみてはいかがでしょうか!

本記事で紹介したコードはこちらに置いてあります。参考にしてみてください。

参考

Xcode Previewsを用いたUIKitベースのプロジェクトの開発効率化
Introducing SwiftUI

  1. いろいろなサイズでプレビューしたい場合は、Group内のそれぞれのViewに対して呼ぶこともできます。

  2. 自動でプレビューが始まらない場合は、右上のOption+Command+Pを入力(または右上の"Resume"ボタンをクリック)します。

  3. ただし、print関数などを実行してもコンソールに表示する機能はまだないようです。

28
14
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
28
14

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?