Skip to main content

SwiftDataP

概要

txt
Application <---> Model Context
|
Model Container <---> Database

Model

swift
@Model
class Book: Identifiable {
@Attribute(.unique) var id: UUID = UUID()
@Relationship(deleteRule: .nullify, inverse: \Author.books) var author: Author?
..
init() {
}
}
swift
@Model
class Author: Identifiable {
@Attribute(.unique) var id: UUID = UUID()
@Relationship(deleteRule: .nullify) var books: [Book]? = []
..
}
  • TODO: inverse つける意味は?

App

swift
@main
struct TestApp: App {
WindowGroup {
ContentView()
.environment(appData)
.modelContainer(for: [Book.self])
}
}

ContentView

固定のQuery

swift
struct ContentView: View {
@Environment(ApplicationData.self) private var appData
@Environment(\.modelContext) var modelContext
@Query var listBooks: [Book]
}

Dynamic Query

swift
struct ContentView: View {
@Environment(ApplicationData.self) private var appData
@Environment(\.modelContext) var modelContext
@Query var listBooks: [Book]

func init() { // TODO: init 以外でも行ける?かを確認
_listBooks = Query(sort: \Book.title, order: ..)
}
}

Preview での書き方

swift
#Preview {
ContentView()
.environment(ApplicationData())
.modelContainer(for: [Book.self], inMemory: true)

@Query : View から参照する場合はこれ

Sort

swift
// 1個の場合
@Query(sort: \Book.title, order: .forward) private var listBooks: [Book]

// 複数の場合
@Query(sort: [
SortDescriptor(\Book.author?.name, order: .forward),
SortDescriptor(\Book.year),
]) private var listBooks: [Book]

@Query(sort: [
SortDescriptor(\Book.title, comparator: .lexical, order: .forward)
]) private var listBooks: [Book]
  • .lexical : 文字通りの辞書順?
  • .localizedStandard : Finder で使われている

Filter

swift
@Query(filter: #Predicate<Book> {
$0.year == 1986
}) private var listBooks: [Book]

@Query(filter: #Predicate<Book> {
$0.author?.name.localicaseStandardContains("Stephen") == true
}) private var listBooks: [Book]

Fetch : View以外から参照する場合はこれ

でも、 @Query の場合と異なり、変更の監視はされないということだと思うので、使い分けが必要そう

swift
// Fetch
let predicate = #Predicate<Book> { book in
book.author?.name == nil
}
let sd = SortDescriptor<Book>(\.title, order: .forward)
let fd = FetchDescriptor<Book>(predicate: predicate, sortBy: [sd])

// List 取得の場合
if let list = try? modelContext.fetch(fd) {
for book in list {
print(book.title)
}
}

// count だけの場合
if let count = try? modelContext.fetchCount(descriptor) {
print(count)
}

IDで探す

これが最適かは不明だが

swift
@State var selectedBookID: PersistentIdentifier? = nil

テンプレ

swift
struct XxxApp: App {
// @StateObject var appData = ApplicationData()

var sharedModelContainer: ModelContainer = {
let schema = Schema([
Park.self,
])
let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)

do {
return try ModelContainer(for: schema, configurations: [modelConfiguration])
} catch {
fatalError("Could not create ModelContainer: \(error)")
}
}()

var body: some Scene {
WindowGroup {
ContentView()
}
// .environmentObject(appData)
.modelContainer(sharedModelContainer)
}
}

Group by ..

SQL 相当のものをそのまま叩けるわけでもなさそうなので、 全部取ってきてから Dictionary で group 化する。count をひたすらするより速い。

swift
let parksByPref = Dictionary(grouping: parks, by: { $0.pref })

その他

  • 配列は使用できないらしい

基本型以外のもの

Codable にすれば良いらしい

SwiftDataの@Modelを付けたクラスにStructやEnumを含めたい

iCloud 同期されないとき

  • cascade は対応してなさそうなので、nullify にしておく
  • 型は すべてのフィールド optional にする。初期値もセットする必要がある