Pro SwiftUI

 

Preface

よく使うコマンド

Chapter 1. Layout and Identity

Parents and children

SwiftUIのレイアウトプロセスは3ステップ
  1. 親ビューが子にサイズを提案する
  1. その情報に基づいて子はサイズを選択する。親はそのサイズを尊重する
  1. 親ビューが子を配置する
 
下記のコードのTextビューの親は何?と尋ねたら「VStack」と答えたくなるが、正解であり間違いでもある
 
.frame()などのmodifierは、実際は親ビューに当たる を生成している
  • ビューは自身の位置を決定することができない
    • Text()は文字の領域に合わせたビューのみが生成されていて、中央寄せなどの位置調整は親の が決定している
  • 全ての修飾子が新しいビューを作成するわけではない

Fixing view sizes

レイアウトを決定する6つの値
  • Minimum width and minimum height
    • ビューが許容する最小スペース
    • これより小さい値は無視され、ビューが想定されたスペースからはみ出す
  • Maximum width and maximum height
    • ビューが許容する最大スペース
    • これより大きい値は無視され、親ビューは残りのスペース内にビューを配置する
  • Ideal width and ideal height
    • このビューに必要なスペース
    • 最大値から最小値までの範囲内で指定できる
 
はビューの理想的なサイズを最小サイズと最大サイズに昇格させる
は理想的なサイズを変更しない

Layout neutrality

他のビューとどう組み合わせて使用するかに応じて自動的にサイズ調整することを「Layout neutrality」という
6つのサイズ設定値はoptionalで、Layout neutralityと固定値を切り替えることができる

Multiple frames

単一のビューは、固定のサイズor可変サイズを持つことができるが、両方を持つことはできない
 
Q: frame(width,height)は6つの値を持ってない?特殊なビュー?

Inside TupleView

アンダースコアがついていない定義はpublic API
TupleViewはビューを10個までしか受け入れられない(自分で定義すれば拡張できる)

Understanding identity

idは2種類ある
  • An explicit identity:明示的に指定するid
  • A structural identity:暗黙的に生成されるid
 
明示的なidが必要な場面は2つ
  1. 配列のループ処理など動的なデータを処理している場面
  1. 特定の場所までスクロールするなど、特定のビューを参照する場面
 
Q: Tree diffingが分からない
 
Swiftコンパイラでは、使用しているすべてのサブビュー、すべての修飾子、すべての条件、すべてのループなどを、ビューの型に直接エンコードしている
e.g.
if文だけじゃなく、switchも同様に の組み合わせでできてる
 
@ViewBuilderがあるとレイアウトを型に変換する
bodyには元々ついている
 
ビューのidがビューの有効期間を決定する
idが変更されるとビューは破棄されて再生成される
  • パフォーマンス観点でかなり悪いこと
  • ビューが保持している値も破棄される
  • アニメーションもできないのでトランジション(フェード)になる
 
SwiftUI はビューのbodyを評価するたびに、その内部にあるすべてのビューの新しいインスタンスを作成する
 
プロパティを使うとビュー全体のインスタンス更新を防ぐことができる
三項条件演算子を使うといい
修飾子の中には、カスタマイズパラメータの受け取りを拒否するものがある。hidden()とか
self.opacity(hidden ? 0 : 1)で回避できる

Intentionally discarding identity

animationさせたくないようなケースでは、ランダムなidを明示することで別の要素として扱ってもらうことができる
当然再構築が発生するのでパフォーマンスには気を付けて

Optional views, gestures, and more

オプションは便利

Chapter 2. Animations and Transitions

Animating the unanimatable

明示的なアニメーション
暗黙的には を使う
 
通常アニメーション化できないものでも、を継承したクラスを定義して
modifier化すると実現できる
 
この種の制御は、iOS 16未満のバージョンをサポートする必要がある場合に特に重要です。iOS 15.6以下では多くの要素がアニメーション化できないため

Avoiding pain in iOS 15.6 and below

はiOS 15.6以下でアニメーション化できないが、 で近いことができる

Creating animated views

は間の値を補完するだけのシンプルな機能なので、アニメーション以外にも使える
VoiceOverを多用している人や、アプリにアニメーションを減らすよう指示している人もいるので、オプションを用意しておくと良い

Custom timing curves

カスタムタイミングカーブは自分で作成できる
こういうサイトでいい感じのカーブが作れる

Overriding animations

でユーザーの設定を取得できる
 
こういうグローバル関数を定義しておくと、 を直接呼んだ時と を呼んだ時で期待挙動を制御できる(後者は設定次第でアニメーションしない場合がある)
 
版も

を使うことで上書きもできる を使うと、そのビューに関連するTransactionだけを制御できる

Advanced transitions

シミュレータの [デバッグ] → [スロー アニメーション] をオンにするとアニメーションの調整がしやすい
0.0 にスケーリングしようとするとエラーになるため、0.00001 のような小さな値にしておくとよい

Want to go further?

こういうmodifierを定義するとグラデーションを実現できる
 

Chapter 3. Environment and Preferences

The environment

などは新しいビューを作成せずに要素に変更を加えられる
VStackなどにmodifierをつけても子のTextに伝搬される
これは Enviroment機能を使っている
 
こんな感じで定義できる
 
拡張機能にしておける
 
描画側
 
CirclesViewを呼び出すコード
 
って命名が予約語っぽくて紛らわしい
 
参考

@Environment vs @EnvironmentObject

 
EmvironmentKeyはビューの更新はやらない
 
可能な限りはEnviromentKeyを使った方がいい
  1. デフォルト値が提供されるから
  1. ObservableObjectは利用するすべてのビューを更新するから
    1. @Enviromentで紐づけておけばビューは更新されないけど環境キーは更新される
    2. 参考している値が変わってない場合でも ObservableObjectが変わるたびに更新されてしまい、無駄に負荷が高くなる
 
ObservableObjectとEnviromentKeyを併用して、全体にテーマを適用しつつ、キー自体はEnvriomentKeyで必要な場合のみ更新させる、みたいなこともできる
 
また、キーパスを指定して監視することもできる

Overriding the environment

でenvironmentを上書きできる

Preferences

はデータが子ビューから上位ビューに流れている
複数の値が流れてくる場合は、どの値を使うかはmodifierによって異なる( を使う)
複雑になるので使い方には注意が必要
を使うことで自分でも実装できる

Anchor preferences

でアンカーを取り出して指定した に設定できる

Chapter 4. Custom Layouts

    Chapter 5. Drawing and Effects

      Chapter 6. Performance