プレビューとしてのSwiftUI
見出し画像

プレビューとしてのSwiftUI

堤さんのサロンでレイアウトを組むならIBがいいのかコードがいいのかという話が出ました。基本的に僕以外の方は皆さんIBを推奨していました。ただ僕はIBはデメリットが多いと思っています。この記事ではIBのデメリットを挙げつつ、対策としてのコードレイアウトのメリットとXcode 10までは苦手だったプレビューがXcode 11で改善できるのでその方法を紹介します。Interface Builderを利用するデメリットも長くなってしまったのでプレビューの方法だけ知りたい方は読み飛ばしてください。

Interface Builderを利用するデメリット

 先にざっとIBを利用すると発生するデメリットを挙げていきます。
ファイル数が増える
 単純に.storyboardや.xibとクラスのファイルが必要になるので管理すべきファイル数が増えます。管理する物は少ない方がいいです。
コンパイル時間が伸びる
 「ファイル数が増える」とほぼ同義ですがIBのファイルもコンパイルされるので単純にコンパイル時間が伸びます。1つの画面を作るのに2つ以上のファイルが必要になるのでコンパイル時間もそれに比例して伸びることになります。
明示的に設定したプロパティが分かり辛い
 
IBを開くとわかるのですが、たくさんのプロパティを編集できることが分かります。しかし、作っていく中で気づくのが、その中で実際に編集するプロパティってかなり少ないということです。特に他人が編集したIBファイルは追うのも大変ですよね...普段開かないペインに値が設定されていたりしてたった1つのことを探したいだけなのにすごい時間がかかったりします。XcodeのBuild Settingsのように変更したプロパティが分かると多少よくなるかもしれません。
プロパティの初期設定はコードで上書きする
 IBでUIを作る時にラベルやボタンに仮の値を設定しておくことは多いと思いますが、それらの値はインスタンスが生成される時には消えておいて欲しかったりします。大抵didSetとかでlabel.text = nilとか設定するんですが、その時にやってるんだろうと思うことは無いですか?僕はあります。
@IBと頭に付くアノテーションを意識する必要がある
 クラスを作る時にもInterface Builderで利用するためだけに必要なおまじないがたくさんあります。@IBDesignable, @IBInspectable, @IBAction, @IBOutletなど...コードで画面を作る時には不要なのになと思うことも少なくありません。
@IBOutletや@IBActionが切れてても気付きにくい
 これもあるあるだと思いますが、ちょっとした変更を加えたつもり(変数名やメソッド名を変更したなど)がInterface Builderの変更を忘れてしまい、そのまま実行するとクラッシュすることがあります。これはInterface Builderをよく見ないと気付き辛く、そのまま世に出てしまい、クラッシュレポートが上がることでようやく気付くということもあるかもしれません。(本当だったらビルド時点で落ちて欲しいですよね)
IB上でだけ要素を削除してコードだけ取り残されることがある
 これも@IBOutletや@IBActionが切れてても気付きにくい問題と似ていますが、IB側で要素を削除したりして、コードがそのままになっていることがあります。ちゃんとリファクタをしないと謎に残り続けるコードがあり、時間が経つと消していいのかよく分からない、いわゆる負債になってしまう可能性があります。
カスタムビューのプレビューに一癖ある
 独自のUIを作ってInterface Builder上に表示しようとするとうまく表示されないことがあります。また、Interface Builderのプレビューをするためにアプリ全体がビルドされることがあります。(Xcodeの設定で止めれますが...)カスタムビューをInterface Builderでプレビューしようとすると、クラス側のprepareForInterfaceBuilderメソッドでinstantiateしてaddSubviewしておく必要があります。この謎のおまじないを毎回するのも謎だなぁと思います。
PR辛い問題
 言わずもがなですね。

コードレイアウトでプレビューする方法

 ようやく本題です。僕はレイアウトはコードでAuto Layoutを組むことが好きです。理由はいくつかありますが、必要な要素を最小限指定するだけで良いということと、上記のようなInterface Builderを利用することによるデメリットに悩むことが無いということが大きいです。とはいえコードレイアウトにデメリットが無いのかというとそういうわけではなく、レイアウトが崩れている場合にどこが間違っているのか分かり辛い、組んでいるコードが正しいのか実行するまで分からない(プレビューが無いのが辛い)という問題はあると思います。この問題を解決するためにXcode 11から利用できるSwiftUIの技術とXcode Previewsという機能を利用して解決してみようと思います。
プレビュー用ターゲットの追加
 アプリとは別にプレビュー用のターゲットを新たに追加します。

一番肝なのはEmbed in ApplicationNoneにすることです。
必要なAssetとクラスをターゲット指定
 続いてプレビューに必要なアセットやコードを先ほど作成したPreviewターゲットに追加します。

プレビュー用のコードを書く
 プレビューに必要なコードを書き足していきます。UIKitに依存しているだけであればSwiftUIをimportして

import UIKit
import SwiftUI

下記のようなコードを書けば

@available (iOS 13.0, *)
struct CharacterButtonView: UIViewRepresentable {
   let toolbarItem: ToolbarItem
   typealias UIViewType = CharacterButton
   func makeUIView(context: Context) -> CharacterButton {
       return CharacterButton(toolbarItem: toolbarItem)
   }
   
   func updateUIView(_ uiView: CharacterButton, context: Context) {
       // nothing todo
   }
}
@available(iOS 13.0, *)
struct CharacterButtonView_Preview: PreviewProvider {
   static var previews: some View {
       ForEach(ToolbarItem.allCases[0..<15]) {
           CharacterButtonView(toolbarItem: $0)
               .previewLayout(.sizeThatFits)
       }
   }
}

このような感じでプレビューが表示されます。

 この方法でもInterface Bulderと同様にプレビューのためにはビルドを走らせる必要はありますが、ビルドするのがプレビュー用ターゲットのみということで不要なファイルまでビルドする必要が無くなります。また、アプリの中にはプレビュー用のターゲットを含めないことで、アプリ自体には影響を出さずにコードレイアウトのプレビューが可能です。ファイルを作成する時に一つチェックを多く入れるだけで多大なメリットを享受できるので良い方法だと思っています。(IBで作ったファイルもプレビューできますよ)
注意点
 今回はpreviewLayoutに.sizeThatFitsを利用しましたが、こちらを利用する場合は

setContentHuggingPriority(.defaultHigh, for: .horizontal)
setContentHuggingPriority(.defaultHigh, for: .vertical)

こちらの指定とintrinsicContentSizeで正しいサイズを返してあげる必要があります。

まとめ

 僕は2年以上前に下記のような発表をしたことがあるほど、当時からIBには疑問を持っていました。

また、近年の宣言的なUIやSwiftUIが登場したことでコードでレイアウトを組む頻度は上がっていくことと思っています。Interface Builderは見た目にすぐ反映されるので初心者向けと思われますが、Auto Layoutの知識が無いとうまくレイアウトが組めなかったり、謎のワーニングやエラーに悩まされることも少なくありません。個人的にはコードで小さい粒度でコンポーネントを作っていき、Auto Layoutに慣れながらそれらを組み合わせることで大きな画面になるような組んでいきかたを推奨したいと思っています。まずはここで紹介したようなプレビューの方法を利用してみてAuto Layoutに慣れていって欲しいなと思います。(そもそもうちはiOS 12を切ってSwiftUIだーという会社が出てきてもおかしく無いですが...)

参考

 元々はこちらの記事を参考にしていました。

sizeThatFitに関してはこちらの記事で知りました。

この記事が気に入ったら、サポートをしてみませんか?
気軽にクリエイターの支援と、記事のオススメができます!
👍🏻