Open17

SwiftUI

さしもんさしもん

SwiftUI in UIKit

案件のコードを見ていたりTwitterを見ているとSwiftUIにはいまだに問題があるみたいなことは見かける
そう考えるとこの記事は1年以上前のものだけどいまだにFull SwiftUIではなくてSwiftUI in UIkitになるのかな

記事内で書かれている「UIKit と比較しての機能不足」問題は同時期にスタディサプリさんも書かれていて
SwiftUIで対応しきれずUIKitを使ったコンポーネントとしてまとめてくれてる

さしもんさしもん
さしもんさしもん

https://qiita.com/yusuga/items/5ad13bf54952e1daca2e

SwiftUIはどうやってUIの再レンダリングをコントロールしているか

State と ObservedObject のみが値を変更したときに View の再レンダリングが行われる

State

Stateをプロパティの属性として定義すると、そのプロパティが変更されたらViewのbodyが再計算される

@Stateを宣言することでSwiftUIが管理しているメモリ領域に値を格納してくれる
https://qiita.com/akasasan454/items/b8a2d239bb118dbba449

@Stateを付与したプロパティはメモリ管理がSwiftUIフレームワークに委譲され、変更が可能となります。
プロパティは値の変更が監視され、変更時に宣言されたViewのbodyが再描画されます。
ただし、そのプロパティへのアクセスは宣言されたView内でのみとなります。
また、値が変更されるようなアクセスでは’$’を付与する必要があります。
https://www.isoroot.jp/blog/2381/

ObservableObjectとObservedObject

ObservableObjectプロトコルに準拠したクラス内の@￰Published属性があるプロパティが変更されると、そのクラスを@￰ObservedObject属性でプロパティを持つViewは変更のたびにbodyを再生成して画面を更新してくれるそのプロパティがバインディングされたSwiftUIのViewが更新される

自前実装でViewを再レンダリングをさせる方法

現状は完全に独自実装でViewを再レンダリングさせることは不可で、StateとObservedObjectのみが値を変更したときにViewの再レンダリングが行われるので、それらをラップする方法で実現できる

bodyは変更のたびに再生成されるがパフォーマンス上の問題はないか

SwiftUIのレンダリングシステムが賢く差分でレンダリングしてくれるので問題なし

Appleが推奨するアーキテクチャはMVCから変わった?

今まではMVCでしたがData Flow Through SwiftUIの資料にはFluxっぽい図が書かれていた


参考: https://developer.apple.com/videos/play/wwdc2019/226/

だからReduxアーキテクチャであるTCAが人気なのかな?

さしもんさしもん

https://rensbr.eu/blog/swiftui-diffing/

If one thing sets SwiftUI apart from other declarative UI frameworks like React, it is its use of a static type system.

もしSwiftUIをReactのような他の宣言的UIフレームワークと区別するなら、それは静的型システムを利用していることです

Diffing and declarative UI

Declarative UI frameworks and diffing go hand in hand

宣言型UIフレームワークと差分計算は手と手を取り合って進む

Diffing views

さしもんさしもん

@State/@ObservableObject/@ObservedObject/@EnvironmentObject

さしもんさしもん

@State

SwiftUIはdata drives UI
データが変わればそれに紐づくUIも更新される
つまり直接操作してUIを変更するのではなくてデータを変更することでUIを更新させる

SwiftUIを使うと、例えば@StateをつけることでそのプロパティのストレージをそのオブジェクトではなくてSwiftUIに持たせられるので
Stateを失うことなくViewのスクラップ&ビルドができる

参照: https://www.hackingwithswift.com/quick-start/swiftui/what-is-the-state-property-wrapper

This creates a property inside a view, but it uses the @State property wrapper to ask SwiftUI to manage the memory. This matters: all our views are structs, which means they can’t be changed, and if we weren’t even able to modify an integer in our apps then there wouldn’t be much we could do.
So, when we say @State to make a property, we hand control over it to SwiftUI so that it remains persistent in memory for as long as the view exists. When that state changes, SwiftUI knows to automatically reload the view with the latest changes so it can reflect its new information.
@State is great for simple properties that belong to a specific view and never get used outside that view, so as a result it’s important to mark those properties as being private to re-enforce the idea that such state is specifically designed never to escape its view.

参照: https://www.hackingwithswift.com/quick-start/swiftui/whats-the-difference-between-observedobject-state-and-environmentobject

@Stateをプロパティにつけるとそのメモリ管理をSwiftUIにお願いする。
従って@StateをプロパティにつけるとSwiftUIに制御を渡し、Viewが存在する限りメモリに永続的に保持されるようになる

@State should be used for value types (structs and enums).
@ObservedObject should be used for reference types (classes), since they trigger refreshing a view whenever any @Published property of the ObservableObject changes.

参照: https://stackoverflow.com/questions/61361788/state-vs-observableobject-which-and-when

さしもんさしもん

@EnvironmentObject

@ObserverdObjectと同様に、ObservableObjectプロトコルに準拠したデータクラス(classのインスタンス)とViewを紐付ける仕組みです。
@ObserverdObjectとの違いは、バケツリレーのように子Viewに引き渡す必要がありません。インスタンスを階層トップのViewに紐付けると、アプリケーション全体からアクセス可能になります。
アプリケーション全体で共有するようなデータを扱う用途で使用します。
参照: https://capibara1969.com/3119/

  • ObservableObjectプロトコルに準拠したデータクラス(classのインスタンス)とViewを紐付ける仕組み
    紐付けにはenvironmentObject modifierを使う
  • @ObserverdObjectとの違い: バケツリレーのように子Viewに引き渡す必要がない(インスタンスを階層トップのViewに紐付けると、アプリケーション全体からアクセス可能になる)
  • 用途: アプリケーション全体で共有するようなデータを扱う場合
さしもんさしもん

ライフサイクル

  • SwiftUIを使った新しいライフサイクル
  • 従来のライフサイクル

いい記事な気がするライフサイクル重要だし簡単なものを作りながら理解してみる
https://qiita.com/snoozelag/items/fe95df7e748ad5fc3205#シーンベースでのコールスタック

さしもんさしもん

SwiftUIを使った新しいライフサイクル

@main属性を持つApp構造体がアプリケーションのエントリーポイントとして動作する
そのため、従来のSceneDelegateやAppDelegateのメソッドが自動的には期待通りに呼び出されない場合がある

Then use the UIApplicationDelegateAdaptor property wrapper inside your App declaration to tell SwiftUI about the delegate type:

なのでAppDelegateを利用するなら@mainが付与されている構造体で @UIApplicationDelegateAdaptorを使う必要がある

このケースにおいてSceneDelegateを使うなら以下を参照

You can provide this kind of delegate to a SwiftUI app by returning the scene delegate’s type from the application(_:configurationForConnecting:options:) method inside your app delegate:

参照: https://developer.apple.com/documentation/swiftui/uiapplicationdelegateadaptor

ここに書かれてる通りにやってできたから
この場合においては特にinfo.plistは触らなくても良さそう

従来のライフサイクル

従来通りなら@UIApplicationMainをAppDelegateにつければいいだろうから
その場合はApp構造体から@mainを取り除く必要がありそう

さしもんさしもん

AppDelegateだけからAppDelegate+ SceneDelegateへ移行

AppDelegateだけでになっていたところから一部をSceneDelegateへ移動しAppDelegateとSceneDelegateで担うようになった

従来

アプリケーションのプロセスは1つで、それに対するUIインスタンスも1つでした。「プロセスのライフサイクル(起動や終了)」「UIの状態のライフサイクル」すべてをシステムはAppDelegateに通知していました。
実際のアプリのAppDelegateでは、ワンタイムの非UIの処理(データベースへの接続やデータ構造の初期化など)を行ったあと、UIのセットアップの処理を行う、全てが行われていました。

iOS13以降

アプリケーションのプロセスは1つで、それに対するUIインスタンスは複数です。プロセスは複数のシーンセッションに共有されます。
それに伴いAppDelegateの責任は変わります。「プロセスのライフサイクル(起動や終了)」のみになり、新しいSceneDelegateが「UIの状態のライフサイクル」の責任を担います。(そしてAppDelegateには新しく「シーンの作成・破棄」の責任が加わります。)
UIのセットアップや、不要になったUIの取り外しの処理はSceneDelegateで行うようにします。
iOS13での、シーンライフサイクルを採用すると、「UIの状態のライフサイクル」に関するAppDelegateのデリゲートメソッドは呼びだしはされません。バックグラウンドやフォアグラウンドといったUIの状態はSceneDelegateへと通知されるようになります。移行するメソッドの内容はたいてい1対1となるためシンプルです。これらのデリゲートメソッドは移行が必要です。
iOS13で、マルチウインドウを採用しても、iOS12以前のサポートは可能です。単に両方のメソッドを保持して、実行時にUIKitが適切な方を呼び出します。
(iOS12以下にターゲットを変更、コンパイラのエラーFixサジェストが参考になります。)

さしもんさしもん

設定方法

これの設定方法は2つ

  • Info.plistにシーン構成を定義
  • UIApplicationのデリゲートメソッドを実装してシーン構成
    → application(_:configurationForConnecting:options:)を使う

このどちらか