🍎

VoicyのSwiftUIを使った大型リニューアルで苦労したこと

2023/12/07に公開

こちらはVoicy Advent Calendar 2023の8日目の記事です。
https://qiita.com/advent-calendar/2023/voicy

こんにちは、VoicyでiOSエンジニアをしているムッチョです。記事執筆時点では入社から1ヶ月と7日の新人です。

Voicyでは最近ホーム画面をUIKitベースのものから、SwiftUIベースの新ホーム画面にリニューアルをしました。しかし、リリースへの道は決して滑らかではなく、SwiftUI特有の闇に苦しまされました。今回のアドベントカレンダーではVoicyのような大規模サービスでSwiftUIを扱う上で苦しんだ点の一部を話していこうと思います。

OS差分・端末差分が大きい

開発者の使う端末では完璧な挙動でも他の端末では問題があるというケースが何度も発生しました。原因はSwiftUIのOS間の差分や端末間の差分にあり、これがSwiftUIの中で最も苦労した点です。サービスが大きければ大きいほど、古いOSのサポートを切るハードルは高くなり、それに合わせてSwiftUIによって発生するOS間の差は広がり、品質を保証するために必要なコストは大きくなります。VoicyではiOS15までしかサポートしていないので他社と比べて比較的新しいとは思いますが、"iOS15でしか発生しない不具合"を何度か経験しました。

OSだけではなく、端末の種類に依存する不具合も経験しました。それも単純に小さい端末だから起きる、大きい端末だから起きるというわけではない時もあります。SwiftUIの開発を進める時は、UIKitで開発する時よりもより多様なOS・端末で常に品質を保証することを意識していく必要があります。

画面の更新回数を最低限に抑えよ

SwiftUIではStateが更新されると画面全体が再描画されます。不要なStateの更新はパフォーマンス低下させるだけではなく、一部の端末で不具合が発生させる原因になったりしました。更新された時にUIを再描画するべき変数とそうではないべき変数を切り分けて、必要な変数のみをStateとして扱い、画面描画の回数を抑えていきたいです。ちょっとしたことでStateとして扱っている変数を更新しているといつの間にか更新回数が膨れ上ります。。。

画面更新がどれだけされているかは触っているだけでは分かりにくいので、時々ログをとって確認してみるのがおすすめです。
https://zenn.dev/musa/articles/10d57ac8a21f92

VStack/HStackのSpacing

HStack {
  // view
}

VStack {
  // view
}

このようにHStackやVStackを使うことは良くあると思いますが、この時、spacingの値はデフォルトでとうなっているかご存知でしょうか? definitionを見てみましょう。

0じゃないんです。nilなんです。 Spacing nilって何だよ!
https://developer.apple.com/documentation/swiftui/vstack/init(alignment:spacing:content:)

spacing
The distance between adjacent subviews, or nil if you want the stack to choose a default distance for each pair of subviews.

Spacing nilは厄介です。Appleが決めたdefaultのspaceを用意してくれちゃいます。これ、OS差分があります。iOS17だとspaceないのに、iOS15で見るとあるみたいなことが起きて、UIが崩れちゃうので、spacingは常に指定しておくようにしましょう。

同じViewを使い回して複数の画面を実装する

似たような画面が複数ある時、同じViewにDataを入れ替える形で複数画面を実装すると、ScrollViewのScrollの位置やスピードだったりTabViewの現在のTabの位置などがデータを入れ替えた後も引き継がれて、思わぬバグに繋がります。そんな時は、

View().id("aaa")

こんな感じでidをつけて、SwiftUIにIDの違う画面は別のインスタンスとして扱ってもらうことができ、問題を解決できました。

Previewが動かない

SwiftUIのPreviewを動かすにはDependency Injectionを適切に行なって一つ一つのViewがなるべく独立した状態にし、Preview用の環境をセットアップできるようにしないと大規模プロジェクトでは動いてくれないことがあります。それだけでなく、ビルド時間が重いとPreviewの更新がもたついて開発しづらくなったり、遅すぎるととうとうtimeoutエラーを起こして全く見れなくなるので、Previewを使うにはビルド速度も最適化しておかないといけません。

画面を作る時だけ空っぽの別プロジェクトのPreviewが超高速に動作する環境で開発してから本プロジェクトに入れ込むという逃げ道もありです。

まとめ

SwiftUIの直感的で効率的なUI開発は魅力的ですが、まだまだ不完全で詰まる部分がありました。Voicyのアドベントカレンダーは今後もたくさん更新されるので、ぜひ見ていって下さい!
https://qiita.com/advent-calendar/2023/voicy

Discussion