Using SwiftData

Just scroll down to the big Tutorial header if that’s what you’re looking for. It’s pretty bare-bones and just designed to get you started.

Any code provided in this article is licensed under the MIT License.


Core Data has been with us for ages. Just one problem - it’s really hard to use.

To start using Core Data, we have to use the model editor first, and then generate some Swift files from the model, which we can then do some things with environment variables… It’s a mess.

I’m new to Swift - and this kind of thing makes it really hard to get started. Thankfully at WWDC 2023, Apple announced SwiftData - a way of declaring models right in code, and just using the models to read/write data.

Problem is, in typical fashion, Apple has made it avaliable only for the latest OS versions (iOS 17, macOS 14 and above). This creates yet another trade-off you need to consider when building your app - do you want to make your life easier, or support more users?

For us at Kochoran, it’s simply not possible for us to make good products in any form of timescale without easier technologies such as SwiftData.

Tutorial

Using some compiler magic from the @Model macro, it’s as easy as:

@Model
class Attempt {
  var attemptLength: Int
  var bpm: Double
  var date: Date
  var errorPercent: Double

  init(attemptLength: Int, bpm: Double, date: Date, errorPercent: Double) {
    self.attemptLength = attemptLength
    self.bpm = bpm
    self.date = date
    self.errorPercent = errorPercent
  }
}

Can you guess which of our apps this code comes from?

Create

To create a new Attempt and save it:

// Ensure that your View has this line in it.
// We pass this off to a handler function, but don't worry about that.
@Environment(\.modelContext) private var context

// ...

let newAttempt = Attempt(/* initialise Attempt */)
context.insert(newAttempt) // it really is that simple.

we can then @Query for all saved Attempts.

Read

@Query(sort: \Attempt.date) var attempts: [Attempt]

To make sure that this actually works, we just attach modelContext to one of the parent views.

For example, I have this inside [app_name]App.swift:

var body: some Scene {
  WindowGroup {
    ContentView()
  }
  .modelContainer(for: Attempt.self)
}

Previews

Previews are a special case.

Since App.swift isn’t started with each preview, and each file is meant to be self-contained, we have to set up its own modelContainer. We can set this modelContainer to have its own properties, such as not saving to disk.

#Preview {
  let config = ModelConfiguration(isStoredInMemoryOnly: true)
  let container = try! ModelContainer(for: Attempt.self, configurations: config)

  return GameView()
    .modelContainer(container)
}

It’s shockingly simple. I really want to see this technology succeed.

Joel

3/2/2024