Pro SwiftUI

PrefaceChapter 1. Layout and IdentityParents and childrenFixing view sizesLayout neutralityMultiple framesInside TupleViewUnderstanding identityIntentionally discarding identityOptional views, gestures, and moreChapter 2. Animations and TransitionsAnimating the unanimatableAvoiding pain in iOS 15.6 and belowCreating animated viewsCustom timing curvesOverriding animationsAdvanced transitionsWant to go further?Chapter 3. Environment and PreferencesThe environment@Environment vs @EnvironmentObjectOverriding the environmentPreferencesAnchor preferencesChapter 4. Custom LayoutsChapter 5. Drawing and EffectsChapter 6. Performance
Preface
よく使うコマンド
Chapter 1. Layout and Identity
Parents and children
SwiftUIのレイアウトプロセスは3ステップ
- 親ビューが子にサイズを提案する
- その情報に基づいて子はサイズを選択する。親はそのサイズを尊重する
- 親ビューが子を配置する
下記のコードの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つ
- 配列のループ処理など動的なデータを処理している場面
- 特定の場所までスクロールするなど、特定のビューを参照する場面
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を使った方がいい
- デフォルト値が提供されるから
- ObservableObjectは利用するすべてのビューを更新するから
- @Enviromentで紐づけておけばビューは更新されないけど環境キーは更新される
- 参考している値が変わってない場合でも ObservableObjectが変わるたびに更新されてしまい、無駄に負荷が高くなる
ObservableObjectとEnviromentKeyを併用して、全体にテーマを適用しつつ、キー自体はEnvriomentKeyで必要な場合のみ更新させる、みたいなこともできる
また、キーパスを指定して監視することもできる
Overriding the environment
でenvironmentを上書きできる
Preferences
はデータが子ビューから上位ビューに流れている
複数の値が流れてくる場合は、どの値を使うかはmodifierによって異なる( を使う)
複雑になるので使い方には注意が必要
を使うことで自分でも実装できる
Anchor preferences
でアンカーを取り出して指定した に設定できる