👋

SwiftUIにおけるモデルデータの管理について(公式ドキュメントの抜粋)

2021/07/24に公開

SwiftUIアプリにおけるモデルデータの管理について、以下の公式ドキュメントの内容を抜粋してみました。

https://developer.apple.com/documentation/swiftui/managing-model-data-in-your-app

モデルのデータを監視可能にする

モデルのデータをSwiftUIから監視可能にするためにObservableObjectプロトコルを使ってデータを宣言します。

class Book: ObservableObject { ... }

オブジェクトのプロパティが変化した時にSwiftUIへ同期させるためにはPublished属性をつけて定義します。

class Book: ObservableObject {
    @Published var title = "Great Expectations"
}

逆に、変更を監視する必要がないプロパティは監視することによる無駄なオーバーヘッドを回避するために属性を付けないようにします。

class Book: ObservableObject {
    @Published var title = "Great Expectations"

    let identifier = UUID() // A unique identifier that never changes.
}

SwiftUIのViewでオブジェクトの変更を監視する

BookクラスはObservableObjectとして宣言しているのでSwiftUIから監視できます。SwiftUIから監視対象とするにはObservedObject属性を追加して変数を宣言します。

struct BookView: View {
    @ObservedObject var book: Book

    var body: some View {
        Text(book.title)
    }
}

用語を整理します。

  • ObservableObject
    • オブジェクトの変更をSwiftUIから監視可能にするためのプロトコル
  • ObservedObject
    • オブジェクトのインスタンスをSwiftUIから監視対象とするために付与する属性
  • Published
    • プロパティの変更をSwiftUIへ同期させるために付与する属性

監視対象のオブジェクトは子Viewに渡すことができます。監視対象のデータが変更されると影響を受ける全てのViewが自動的に更新されます。個々のプロパティを監視対象のまま渡すことも可能です。

struct BookView: View {
    @ObservedObject var book: Book

    var body: some View {
        BookEditView(book: book)
    }
}

struct BookEditView: View {
    @ObservedObject var book: Book

    // ...
}

モデルオブジェクトのインスタンスをViewで生成する

SwiftUIは必要に応じてViewの再生成(破棄と生成)を行います。StateObject属性を付けて生成したインスタンスはViewが破棄されてもインスタンスが保持されます。

struct LibraryView: View {
    @StateObject var book = Book()

    var body: some View {
        BookView(book: book)
    }
}

注意点として、SwiftUIはViewのインスタンスごとにオブジェクトのインスタンスを作成します。次のコードではそれぞれのLibraryViewが異なる(別々の)Bookインスタンスを作成します。

VStack {
    LibraryView()
    LibraryView()
}

アプリ全体でオブジェクトを共有する

アプリ先頭のAppインスタンスやSceneインスタンスで監視対象のオブジェクトを定義することで、アプリ全体でオブジェクトを利用することができます。

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

    // ...
}

アプリ全体で使用するオブジェクトを深い階層の子Viewに渡したいときはenvironmentObjectメソッドを使用することができます。

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

    var body: some Scene {
        WindowGroup {
            LibraryView()
                .environmentObject(library)
        }
    }
}

オブジェクトを利用したい子ViewはEnvironmentObject属性のプロパティを宣言することでインスタンスにアクセスすることができます。

struct LibraryView: View {
    @EnvironmentObject var library: Library

    // ...
}

EnvironmentObjectを使用するときは使用するViewの上位にあるViewで宣言する必要があることに注意してください。PreviewProviderへの追加が必要であることも忘れないようにしましょう。

struct LibraryView_Previews: PreviewProvider {
    static var previews: some View {
        LibraryView()
            .environmentObject(Library())
    }
}

バインディングを使用する

オブジェクトの名前の前にドル記号($)を付けることでObservedObjecgtStateObjectEnvironmentObjectのプロパティへのバインディングを取得できます。バインディングを使用することでSwiftUIによるデータの変更がプロパティに反映されます。

struct BookEditView: View {
    @ObservedObject var book: Book

    var body: some View {
        TextField("Title", text: $book.title)
    }
}

Discussion