Tl;Dr
GitHub Discussions にて公開しています。
どなたもリアクション・コメントなどで、お気軽にディスカッションいただければ幸いです (もちろん読むだけでもOKです)
経緯(興味のある方のみどうぞ)
私はここ2〜3ヶ月ほど、SwiftUI にどっぷりと浸かっていました。
SwiftUI はコードの見た目からは想像できないほど 難しく、また Apple からは推奨される設計・実装パターンはおろか、必要十分といえるドキュメントすら提供されていないように感じました。
そのような状況の中で試行錯誤を繰り返しているうちに、これは多くの SwiftUI プロジェクトで使える 効果的(Effective) なパターン・慣習であると感じるものが見つかってきました。
最初はそれを思いついたタイミングで、雑にツイートしていました。
しかし、途中からはスレッドすら使わずに投稿し始めてしまい、自分であとから参照するのも不便になってきました。
そこで過去のツイートを遡り、すべてをZennのスクラップに移植したのですが、Zennのスクラップは目次が表示されない仕様であり、項目の一覧としてざっと見たいときに不便でした。
もう”このまま書き捨てる形でいいか”と思ったこともあったのですが、SwiftUI を利用した開発において、世界中の開発者の集合知 というものがいかに大切であるかはよく身にしみていた為、どこか自由にディスカッションできる場所に移そうと考えるようになりました。
そしてある日に決意を固め、これまでの項目を 全部見直して1から書き直し、GitHub Discussions に移行しました。
内容
現在は以下の30項目に分かれています。(これからも増減すると思います)
効率的な実装
- プレビュー用に空の SwiftUI プロジェクトを用意する。
- 新規 View / Modifier を作成する際はスニペットを利用する。
- body 関数のコンパイルエラーの内容が理解できない場合、小さな関数に移動して試す。
- 複雑な View を作成する時は「コードの折りたたみ」機能を活用する
ドキュメント
コーディングスタイル
- サブビューを生成する処理は Computed-property よりメソッドを好む。
- Button における
action
の指定方法について検討する。 - View の構造を把握しやすくするために目立つコメントを記述する。
API を理解して使用する
- 離れた View にデータを渡す際には
@Environment
や@EnvironmentObject
の利用を検討する。 - 本当に必要な場合を除き
AnyView
を避ける。 UIViewRepresentable
のプロトコル要件を正しく理解して使う。- シートを実装する際は
isPresented
とdismiss
を利用する。 @AppStorage
で不足な場合は Defaults の利用を検討する。- 標準以外の方法でローカライズを処理する際のデメリットを理解する。
- View を拡張したい場合は原則として extension を使用し、状態保持が必要な場合のみ
ViewModifier
を実装する。
トレードオフ
マルチプラットフォーム
テクニック・アイディア集
- 可読性のための Modifier を導入する。
.tag()
を enum などで指定する場合、型付けされた専用のメソッドを用意する。- 必要な場合は
Binding<T>
をBinding<T?>
に変換する。 - View の更新トリガーを調べるために
_printChanges()
を利用する。 - 上位互換 API を自前で実装し、OSアップデート時のコストを低減する。
未成熟な仮説(私の中で答えが出ていないもの)
- ViewModel で非同期通信が必要な場合、
@MainActor
で宣言する。 - NavigationView は常に利用する側の View で指定する。
@ObservedObject
への DI をonAppear
で決して行わない。- 初期化のために一度だけ実行する処理は
lazy var
で行う。
ご覧のようにドキュメントの検索・参照の仕方といった簡単なものから、APIについての正しい理解やコーディング時のテクニックまで、様々となっています。
初学者の方に向けて
これらの項目は ”効果的に使う(Effective Use)” を意図して書かれたもので、 ”効果的に学ぶ(Effective Learn)” を意図して書かれたものではありません。
しかし、初学者のうちから知っておくと便利であろうと思われる項目もいくつかあります。
例えば、『効率的な実装』と『ドキュメント』は、初学者のタイミングからでも目を通しておく価値があるかと思います。
効率的な実装
- プレビュー用に空の SwiftUI プロジェクトを用意する。
- 新規 View / Modifier を作成する際はスニペットを利用する。
- body 関数のコンパイルエラーの内容が理解できない場合、小さな関数に移動して試す。
- 複雑な View を作成する時は「コードの折りたたみ」機能を活用する
ドキュメント
これらは私が SwiftUI を書き始めた頃に知りたかった項目になります。どれも単純なものですが、これらが有用なテクニックであると気づくまでには多かれ少なかれ時間がかかりました。
また、『コーディングスタイル』についても、比較的初期のタイミングで書き方に迷うであろうものについて触れられているかも知れません。
コーディングスタイル
中級者以上の方に向けて
是非、お気軽にご意見(賛成・反論・感想)などをいただければと思います。
これらの項目を書く上で、自分なりに反論する視点をもって慎重に書き上げていますが、現時点では私の仮説に過ぎないものばかりです。
例えば、『本当に必要な場合を除きAnyView
を避ける。』では、AnyView
をいつ使うべきか、それを使った場合のパフォーマンスへの影響はどうなるのか、という点にも言及しています。
あるいは、『View の更新トリガーを調べるために _printChanges()
を利用する。』では、独自研究によっておそらく正しいだろうと思われる項目の意味についても記載しています。
これらは Apple の公式ドキュメントや実際の動作から注意深く推察したものですが、これらが 100%正しいという確信は持てていません。
もし、「それは違うよ!」と感じるものがありましたらご指摘頂けますと幸いです。
最後に
私が SwiftUI で開発を進めていく中で、もっとも衝撃的だったのが SwiftOnTap の存在でした。
これは完全に有志の手によって作られているにも関わらず、スクリーンショット(あるいは動画)付きのサンプルコードが驚くほど掲載されており、それは公式ドキュメントを遥かに凌駕するものでした。
これにより私の生産性はそれまでより大きく上がりました。
それ以外にも Developer Forums や StackOverflow、個人の解説・見解記事など、公式ドキュメント以外のたくさんのものに本当に助けられました。
それでは私に何が出来るだろうか、と思い書き始めたのが、この『Effective SwiftUI 候補(仮説)』でした。
そんな中、iOS界隈の更新情報を日本語で提供する『iOS Osushi🍣』にてご紹介いただきました。(もちろん私から頼んだわけではありません)
私はこういった自分で書いたOSS・記事の宣伝みたいなものはあまり書かないタイプなのですが、こうして掲載いただけたということは、”多くのiOSエンジニアにとって価値がある(可能性がある)”という意味だと理解し、このように Qiita 記事にて紹介させていただきました。
SwiftUI を利用されているiOSエンジニアの方々に、少しでも貢献できるものが含まれていれば幸いです。