既存プロジェクトで 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 を選択して作成。
ここでは例として TableViewCellPreview.swift を作成。
作成したファイルを開くとまず available in iOS 13 or newer
のビルドエラーが起きるのでまず Xcode の自動Fixに従って @available(iOS 13.0, *)
を設定をしてエラーを修正。
するとひとまずプレビューが表示される。
テンプレートで作成されている struct TableViewCellPreview: View
は SwiftUI によるView実装で、既存クラスのプレビューだけなら不要なので削除。
struct TableViewCellPreview_Previews: PreviewProvider
の名前もくどいのでstruct TableViewCellPreview: PreviewProvider
に変更(お好みで)
前者の TableViewCellPreview
を削除した事によりまたエラーが起きるので static var previews: some View
の返り値は一旦適当な SwiftUI View にしておく。(例では Text("Text")
)
んで、以下のセッションで紹介されている 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
より大きい)場合はセルのラベルを縦レイアウトにするという実装をしている。
Accessible TableViewCell like system UITableViewCe ...
TableViewCellPreview
によるプレビューをキャンバスに表示した結果は以下になる。
期待通りの結果!
実機 or Simulator での実行や Environment Overrides、デバイス設定の変更などせず複数の状況を一度にプレビューできるのが素晴らし過ぎる。
既存プロジェクトの Deployment Target はiOS 13より前を指定したまま実装内容は変更せず、プレビュー機能のみを追加させる事ができるので導入も容易。
注意
SwiftUI がリンクされた状態でiOS 13より前のiOSで動作させるためには SwiftUI.framework の Weak Link が必要なのでお忘れなく。
Tipsとリファクタ
Tips 1
SwiftUI はプレビュー用途で使っているので実装ファイル自体をリリース版に含めたくないと思うかも知れない。
ところが SwiftUI のプレビューが記述されたファイルをターゲットから外すと
Cannot preview in this file -- active scheme dones not build this file
とエラーが出てプレビュー表示できなくなった。
なので SwiftUI による実装自体をリリース版に含めたくなければ全体を #if DEBUG
#endif
で囲むと良いと思う。*2
Tips 2
SwiftUI のプレビューは最適化レベルが -Onone
でないといけないらしく、Scheme で Run の Buile Configuration が Debug に設定されていなかったり、Build Settings で最適化レベルを変更している場合は以下のエラーが出てプレビュー機能が使えない。
Cannot preview in this file -- not building -Onone
Tips 3
WWDC のビデオによればプレビューの表示の際に UIApplicationDelegate
の application(_:didFinishLaunchingWithOptions:)
を通るらしく、ここの処理は軽くした方が良いらしい。
処理の退避先は UISceneDelegate
の scene(_: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
リファクタ
プレビュー用の 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:最近他のセッションビデオを色々見てるけど、このセッションでしか触れられていない様な、、こんな便利な機能なのに