17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

[WWDC 2023] SwiftDataを試してみる

Last updated at Posted at 2023-06-15

WWDC23で発表されたSwiftDataについて、実際にプログラムを書いて試してみました。

SwiftDataとは

SwiftData

SwiftData makes it easy to persist data using declarative code. You can query and filter data using regular Swift code. And it’s designed to integrate seamlessly with SwiftUI.

データの永続化、SwiftUIとのシームレスな統合。いわばSwiftUIと親和性のあるCoreData, RealmSwiftみたいなものでしょうか。

データ定義

Pokemon.swift
@Model
class Pokemon {
    @Attribute(.unique) var name: String
    var iconURL: URL
    var types: [String]
    var abilities: [String]

    init(name: String, iconURL: URL, types: [String], abilities: [String]) {
        self.name = name
        self.iconURL = iconURL
        self.types = types
        self.abilities = abilities
    }
}

データを定義しているクラスに、@Modelをつけます。これだけでそのクラスはSwiftDataによって永続化が可能になります。

さらに、@Modelをつけることでそのデータ定義クラスはObservableプロトコルに準拠します。
Observableについて詳しくは以下のリンクやWWDCのセッションを見ると良さそうです。SwiftDataにおいてはデータ変化をUIに通知する、ObservableObjectの代わりに用いられるもの、と言えるかもしれません。

また、@Attribute(.unique)でユニーク制約を表現できたりします。

SwiftUIで使ってみる

定義したデータはView側でこのように使えたりします。

PokeListView.swift
import SwiftUI
import SwiftData

struct PokeListView: View {
    @Query private var pokeArray: [Pokemon]
    @State private var showingAdditionView = false

    var body: some View {
        NavigationView {
            List(pokeArray) { pokemon in
                HStack {
                    AsyncImage(url: pokemon.iconURL) {
                        $0.image?.resizable()
                    }
                    .frame(width: 50, height: 50)
                    .padding(.trailing)
                    VStack(alignment: .leading) {
                        Text("なまえ: \(pokemon.name)")
                        Text("タイプ: \(pokemon.types.joined(separator: "・"))")
                        Text("とくせい: \(pokemon.abilities.joined(separator: "・"))")
                    }
                }
            }
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button("ついか") {
                        self.showingAdditionView.toggle()
                    }
                }
            }
            .sheet(isPresented: $showingAdditionView) {
                PokeAdditionView()
            }
        }
    }
}

@Queryという新規のproperty wrapperを追加します。これによりSwiftUI ViewからSwiftDataで定義したモデルのデータを取得することができます。
PokemonクラスがObservableプロトコルに準拠している、そして@Queryを使用していることで@Stateのようにデータの変化に合わせてViewが更新されるようにもなっています。

さて、永続化する処理はどのようにして呼び出すのでしょう。
このプログラムでは、ポケモンの名前やタイプなどを入力すると保存ができるようにしたいです。

PokeAdditionView.swift
import SwiftUI
import SwiftData

struct PokeAdditionView: View {
    @Environment(\.presentationMode) private var presentationMode
    @Environment(\.modelContext) private var modelContext
    @ObservedObject private var viewItem: PokeAdditionViewItem = PokeAdditionViewItem()

    var body: some View {
        VStack {
            PokeSimpleTextFiled(text: $viewItem.name, title: "なまえ")
            PokeSimpleTextFiled(text: $viewItem.iconURLText, title: "アイコン")
            PokeMultipleTextFiled(textArray: $viewItem.typeArray, title: "タイプ")
            PokeMultipleTextFiled(textArray: $viewItem.abilityArray, title: "とくせい")

            Button(action: {
                let pokemon = Pokemon(
                    name: viewItem.name,
                    iconURL: URL(string: viewItem.iconURLText)!,
                    types: viewItem.typeArray,
                    abilities: viewItem.abilityArray
                )

                self.modelContext.insert(pokemon)
                self.presentationMode.wrappedValue.dismiss()
            }) {
                Text("ほぞん")
            }.disabled(!viewItem.saveButtonEnabled)

            Spacer()
        }
        .padding()
    }
}

モデルデータの追加は、self.modelContext.insert(pokemon)の呼び出しで行っています。

このmodelContextとはなんでしょうか。

ModelContext

An object that enables you to fetch, insert, and delete models, and save any changes to disk.

いわばデータソースとして扱うためのオブジェクトです。
データの追加には、insertメソッドを使うようです。> func insert<T>(object: T)
他にも、fetch、update、deleteなどのメソッドがあります。

ModelContextを使ってデータを操作していくのはなんとなく掴めました。
そしてもう一点、ModelContextを扱うにあたって必要な要素があります。ModelContainerです。

ModelContainer

An object that manages an app’s schema and model storage configuration.

「アプリのスキーマとモデルストレージの設定を管理するオブジェクト」とのこと。アプリ全体のストレージスタックを生成するようです。
おそらくこのオブジェクトの設定がないとデータを使用したいViewなどでデータ操作ができません。

そのため説明動画に沿って、ModelContainerをセットアップしてみます。

practice-swiftdataApp.swift
import SwiftUI

@main
struct practice_swiftdataApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        .modelContainer(for: Pokemon.self)
    }
}

modelContainer(for: Pokemon.self) ← このモディファイアが必要です。

メソッド定義を見る感じ、for引数に複数のモデル型を配列でセットできるイニシャライザもある模様。

動かしてみる

ポケモンの名前を登録して、リストに出すだけのプログラムを作成しました。
SwiftDataが正しく使えていれば、アプリを一度閉じても登録したポケモンの表示が出るはず。

何も登録してない状態から追加して、アプリを閉じる。そして再度開く。

ezgif.com-video-to-gif.gif

出ました。大したことをしていないので全てが参考になるわけでは無いですが、Realmなどよりも楽に使えそうなポテンシャルを感じます。


(TCAと使うときどうするんだろう。。)

17
13
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
13

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?