Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

SwiftUIの基本的なデータハンドリング方法

公式ドキュメントを読んでSwiftUIにおけるViewとデータのハンドリングの知識が深まったので、理解の再確認のためにも情報をまとめました。基本的に公式ドキュメントの要約です。

なお、過去にこちらの記事でView間のデータ受け渡し方法の紹介として、EnvironmentやPreferencesを紹介しましたが、基本はState、Binding、ObservableObject(EnvironmentObject)だと考えています。

指摘がありましたら、是非コメントください。

基本のStateとBinding

State

  • Viewの表示内容を制御するための変数
  • publicで宣言した場合は親Viewからコンストラクタ引数として値を受け取ることが可能(初期値は不要)
  • privateで宣言した場合は初期値が必須
  • 自分自身で利用するか子Viewへの値渡しとして利用可能
  • 親Viewから引数で受け取ったStateは子Viewで変更したとしてもその変更は親には伝わらない

Binding

  • publicで宣言したStateと同じく、親から値を受け取る
  • 親Viewから受け取ったStateの値変更を親に伝えたいときに利用する

StateとBindingは近い親子関係や自分自身で完結する場合にのみ使うべきです。
図で書くとこんな感じです。

スクリーンショット 2020-11-14 23.15.35.png

複数Viewを考慮すると必要になるObservableObject(EnvironmentObject)

先ほどのStateとBindingを使えばView間のデータのやり取りは可能です。
しかし、親子関係を持たないView間(図2)や階層の深いViewへのデータ渡し(図3)はどうすれば良いでしょうか?
そのような場合はStateのバケツリレーではなくObservableObjectを使います。

スクリーンショット 2020-11-15 23.12.13.png

スクリーンショット 2020-11-15 23.12.22.png

ObservableObject

class Book: ObservableObject {
    @Published var title = "Great Expectations"
}
  • イメージとしてはState変数を複数持てる構造体
  • 利用側がObservedObjectとして定義した場合、Published宣言された変数の変更を検知することができる
  • Stateのようにコンストラクタ引数にObservedObjectを渡すことで子Viewで値の参照&変更をすることができる
  • 子View側でPublished宣言された変数を変更すると、親View側で変更を検知することができる

EnvironmentObject

コンストラクタ引数で渡さないと行けないのであれば、先に挙げた課題は解決しないとのではと思った方は鋭いです。
いちいち、バケツリレーするのは面倒ですし、コピーミスが発生する恐れがあります。
そのような場合は EnvironmentObject を使います。親ViewでEnvironmentObjectとして、ObservableObjectインスタンスの指定があれば、そのViewの全ての子孫ViewがObservableObjectの値を参照することができます。
また、図2の画面AとBは直接の親子関係はありませんが、SwiftUIは全ての画面はSceneDelegateで指定されるRootViewの子孫関係にあり、元を辿れば必ず同じViewを見つけることができます。

struct BookReader: App {
    @StateObject var library = Library()

    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}
  • 上の例で言うと、LibraryがObservableObject
  • @StateObjectはObservableObjectの大元、Stateと同じく値を保持するため@StateObjectとして宣言する
  • LibraryView以下の子孫にLibraryを参照変更させたい場合はenvironmentObject()でLibraryのインスタンスを指定する
struct LibraryView: View {
    @EnvironmentObject var library: Library

    // ...
}
  • 利用側はEnvironmentObjectとしてメンバ定義を行う
  • 親のenvironmentObject()で指定されたインスタンスがここに入る
  • library内の@Published宣言された値を変更すると、LibraryをEnvironmentObjectとして保持している親と子孫にその変更が伝わる

EnvironmentObjectを使った時のイメージ

スクリーンショット 2020-11-15 23.45.33.png

スクリーンショット 2020-11-15 23.45.48.png

まとめ

  • 親と子のような関係が浅いView間のデータやり取りはState/Bindingで十分
  • 親子関係の無いView間でのデータやり取りはObservableObject(EnvironmentObject)を使う
  • 親、孫、ひ孫といった深いView間のデータやり取りはObservableObject(EnvironmentObject)を使う
noby111
最近はSwiftUIが好き。
https://portfolio.nsunrise.work
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away