💽

[WWDC2023] SwiftDataを使ってみた。

2023/06/14に公開
3

CoreDataの後継者であるSwiftDataがついにリリースされたので、簡単なアプリで使ってみた。

SwiftDataを導入するには、6つの簡単なステップを踏む。

  1. import SwiftData
  2. モデル作成
  3. AppmodelContainer作成
  4. @Environment(\.modelContext)を宣言
  5. @Queryを配列の変数につける
  6. モデルをcontextinsertしてsave()を行えば完成

それでは、ひとつずつ見ていこう。

1. SwiftData移入

まずはimportしよう

import SwiftData

2. モデル作成

CoreDataを使う場合モデル作成するには.xcdatamodelファイルを開いて、「Add Entity」でEntityを追加するように実装していたが、SwiftDataではローカルデータを使用しないようにclassを作り、その上に@Modelを記述するだけです。 @Modelを使用して、通常のSwiftの型でデータをモデル化することができる。

@Model
class Restaurant {
    let name: String
    var dateAdded: Date
    
    init(name: String, dateAdded: Date = .init()) {
        self.name = name
        self.dateAdded = dateAdded
    }
}

3. Appで.modelContainer()

CoreDataを実装するみたいにAppにcontainerを注入する。.modelContainer()という新しいメソッドで環境に注入できる。2.で宣言したRestaurantを指定する。

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

4. @Environment(\.modelContext)を宣言

次に、modelContextViewに宣言します。DBにデータを挿入する際には、contextが必要になる。

struct ContentView: View {
	@Environment(\.modelContext) var context
	// ...
}

5. @Queryを配列の変数につける

SwiftUIのビューで@Queryを使用してデータをフェッチすることができる。SwiftDataSwiftUIは連携して動作し、基になるデータが変更されると、ビューに自動的に最新の情報が反映される。結果を手動でリフレッシュする必要はない。

@Query private var favourites: [Restaurant]

CoreDataのようにSortDescriptorとPredicateが使える。
下記の場合、Restaurantが.reverse順番で表示する。

@Query(FetchDescriptor(sortBy: [SortDescriptor(\.dateAdded, order: .reverse)]),animation: .snappy) private var favourites: [Restaurant]

6. モデルをcontextにinsertとsaveしよう

contextにinsertとsaveしよう!
モデルをcontextに挿入した後は、保存することを忘れないでください。これは2つのステップからなるプロセスで、contextトに対して行われた変更は、保存することで確定される。

// ...
Button("Add Item") {
	insertIntoContext()
}
// ...
private func insertIntoContext() {
	let restaurant = Restaurant(name: "Hello User \(Date().formatted(date: .numeric, time: .shortened))")
        context.insert(restaurant)
        saveContext()
    }
    
private func saveContext() {
	do {
	    try context.save()
	} catch {
	    print(error.localizedDescription)
	}
    }

それで、 SwiftDataが使えてローカルデータで保存できた!

スクショ

使ったコード

ContentView.swift

import SwiftUI
import SwiftData

struct ContentView: View {
    @Environment(\.modelContext) var context
    
    @Query(FetchDescriptor(sortBy: [SortDescriptor(\.dateAdded, order: .reverse)]),
           animation: .snappy) private var restaurants: [Restaurant]
    
    var body: some View {
        NavigationStack {
            List(restaurants) { restaurant in
                HStack {
                    Text(restaurant.name)
                        .font(.caption)
                    
                    Spacer()
                }
            }
            .navigationTitle("SwiftData")
            .toolbar(content: {
                ToolbarItem(placement: .topBarTrailing) {
                    Button("Add Item") {
                        insertIntoContext()
                    }
                }
            })
        } 
    }
    
    private func insertIntoContext() {
        let restaurant = Restaurant(name: "\(Date().formatted(date: .numeric, time: .standard))にレストランで食べた")
        context.insert(restaurant)
        saveContext()
    }
    
    private func saveContext() {
        do {
            try context.save()
        } catch {
            print(error.localizedDescription)
        }
    }
}

Restaurant.swift

@Model
class Restaurant {
    let name: String
    var dateAdded: Date
    
    init(name: String, dateAdded: Date = .init()) {
        self.name = name
        self.dateAdded = dateAdded
    }
}

SwiftDataPracticeApp.swift

import SwiftUI

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

ここまで読んでくれてありがとうございました。

Spacely, Inc. App Div.
Dean Thompson

参考

https://developer.apple.com/xcode/swiftdata/

https://www.youtube.com/watch?v=4nRvm7EsB8s

Discussion

okuzawatsokuzawats

「3. Appで.modelContainer()」にて「.modelContainer()という新しいメソッドで環境に注入できる。2.で宣言したRestaurantを指定する。」という記述があります。サンプルコードとしては以下のコードが示されています。

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

このサンプルコードでは modelContainer にPerson.selfが渡されていますが、ここがRestaurant.selfになるのが正しいでしょうか?

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