ObjecTips

Swift & Objective-C で iOS とか macOS とか

既存プロジェクトで SwiftUI のプレビュー機能を使う Using SwiftUI preview in existing project

WWDC 2019 Session 233 Mastering Xcode Previews
https://developer.apple.com/videos/play/wwdc2019/233/
を見ていて、あれ、これもしかして Deployement Targe iOS 13以降のプロジェクトじゃなくても SwiftUI のプレビュー表示は使えるんじゃない?と思って試してみたらできたので共有。

iOS 13より前をサポートしている既存のプロジェクトで Xcode の New > File... で SwiftUI View を選択して作成。

f:id:Koze:20200305225313p:plain

ここでは例として TableViewCellPreview.swift を作成。
作成したファイルを開くとまず available in iOS 13 or newer のビルドエラーが起きるのでまず Xcode の自動Fixに従って @available(iOS 13.0, *) を設定をしてエラーを修正。

f:id:Koze:20200305225359p:plain

f:id:Koze:20200305225513p:plain

するとひとまずプレビューが表示される。

f:id:Koze:20200305225952p:plain

テンプレートで作成されている struct TableViewCellPreview: View は SwiftUI によるView実装で、既存クラスのプレビューだけなら不要なので削除。
struct TableViewCellPreview_Previews: PreviewProvider の名前もくどいのでstruct TableViewCellPreview: PreviewProvider に変更(お好みで)
前者の TableViewCellPreview を削除した事によりまたエラーが起きるので static var previews: some View の返り値は一旦適当な SwiftUI View にしておく。(例では Text("Text")

f:id:Koze:20200305230605p:plain

んで、以下のセッションで紹介されている UIView UIViewController を SwiftUI で使用する実装方法に沿って実装する。
WWDC 2019 Session 233 Mastering Xcode Previews
https://developer.apple.com/videos/play/wwdc2019/233/
WWDC 2019 Session 231 Integrating SwiftUI
https://developer.apple.com/videos/play/wwdc2019/231

実装例は以下
今回プレビューしたい TableViewCell は xib でレイアウトを作成しているので makeUIView のところで UINib からインスタンス化している。updateUIView は今回は空でOK。

Using SwiftUI preview with UIView

既存プロジェクトの TableViewCell の実装内容は以下
UILabel にはそれぞれ Title1, Subhead, Body のフォントを設定して、Automatically Adjusts Font をオンにして Dynamic Type に対応させている。
そして標準の UITableViewCell と同じ様に Dynamic Type の文字が Accessibility 対応の文字の大きさになっている(ContentSizeCategory.extraExtraExtraLarge より大きい)場合はセルのラベルを縦レイアウトにするという実装をしている。

f:id:Koze:20200305234153p:plain

Accessible TableViewCell like system UITableViewCe ...

*1

TableViewCellPreview によるプレビューをキャンバスに表示した結果は以下になる。

f:id:Koze:20200306042326p:plain

期待通りの結果!
実機 or Simulator での実行や Environment Overrides、デバイス設定の変更などせず複数の状況を一度にプレビューできるのが素晴らし過ぎる。
既存プロジェクトの Deployment Target はiOS 13より前を指定したまま実装内容は変更せず、プレビュー機能のみを追加させる事ができるので導入も容易。

注意

SwiftUI がリンクされた状態でiOS 13より前のiOSで動作させるためには SwiftUI.framework の Weak Link が必要なのでお忘れなく。

f:id:Koze:20200306003253p:plain

Tipsとリファクタ

Tips 1

SwiftUI はプレビュー用途で使っているので実装ファイル自体をリリース版に含めたくないと思うかも知れない。
ところが SwiftUI のプレビューが記述されたファイルをターゲットから外すと

Cannot preview in this file -- active scheme dones not build this file

とエラーが出てプレビュー表示できなくなった。

f:id:Koze:20200306004542p:plain

なので SwiftUI による実装自体をリリース版に含めたくなければ全体を #if DEBUG #endif で囲むと良いと思う。*2

Tips 2

SwiftUI のプレビューは最適化レベルが -Onone でないといけないらしく、Scheme で Run の Buile Configuration が Debug に設定されていなかったり、Build Settings で最適化レベルを変更している場合は以下のエラーが出てプレビュー機能が使えない。

Cannot preview in this file -- not building -Onone

f:id:Koze:20200306004822p:plain

Tips 3

WWDC のビデオによればプレビューの表示の際に UIApplicationDelegateapplication(_:didFinishLaunchingWithOptions:) を通るらしく、ここの処理は軽くした方が良いらしい。
処理の退避先は UISceneDelegatescene(_:willConnectTo:options:) なのでiOS 13以降のみ対応のアプリでしか使えないリファクタ。

Tips4 - Development Assets

プレビューに使うデータをネットワークから引っ張ってくる実装になっていると、プレビューのリフレッシュの度にネットワークアクセスが発生してしまうので*3、プレビューに必要なJSONや画像をスタブを使ってローカルから読み込む様にする。
プロジェクトにスタブを含めつつリリース版には同梱しない様にする素晴らしい新機能 Development Assets が Session 233 で紹介されていた。 https://developer.apple.com/videos/play/wwdc2019/233/ 15分38秒あたりから

Target > General の一番下に追加されている Development Assets
ここで指定したアセットやフォルダは開発時のみにターゲットに含まれる様になる。 *4
1, 2分でさらっと紹介されているけど便利過ぎる。*5

f:id:Koze:20200306001947p:plain

リファクタ

プレビュー用の SwiftUI のファイルは別途作成しなくても良い。
もし View の実装が軽いのであれば既存の実装ファイルに直接 PreviewProvider を実装するのもアリかと。
各Tipsとリファクタを踏まえて、また UIViewRepresentable の要求する typealias UIViewType を設定して previews makeUIView updateUIView の各メソッドから型の直書きを削除したリファクタバージョンは以下。
これで既存プロジェクトへの簡単な SwiftUI Preview の追加が完成。

Adding SwiftUI preview to existing class

まとめ

  • Deployment Target iOS 13以降じゃなくても Swift UI の部分に @available(iOS 13.0, *) を指定して SwiftUI によるプレビュー機能を使って開発を行う事ができる
  • SwiftUI.framework の Weak Link を忘れず
  • スタブは Development Assets を使う

*1:for SwiftUI preview のコメント部分は別記事書くかも

*2:WWDC のビデオでは #if DEBUG を使っているのをよく見かけたけど現在の Xcode 11 では SwiftUI View を作成した際のテンプレートで #if DEBUG が使われていない。気になって古い Xcode を引っ張ってきて SwiftUI View のテンプレートを確認して見たところ Xcode 11 beta 5 までのテンプレートではプレビュー部分を #if DEBUG で囲っており Xcode 11 beta 6 のテンプレートから #if DEBUG の指定が無くなった模様

*3:既存アプリの ViewController でプレビュー機能を試してみたらプレビュー内でちゃんと広告バナーまで表示されてそれはそれでプレビュー機能の凄さに感動した

*4:クラスファイルは対象ではないっぽい

*5:最近他のセッションビデオを色々見てるけど、このセッションでしか触れられていない様な、、こんな便利な機能なのに