SwiftData's model objects are powered by the same observation system that makes @Observable
classes work, which means changes to your model objects are automatically picked up by SwiftUI so that our data and our user interface stay in sync.
This support extends to the @Bindable
property wrapper we looked at previously, which means we get delightfully straightforward object editing.
To demonstrate this, we could create a simple User
class with a handful of properties. Create a new file called User.swift, add an import at the top for SwiftData, then give it this code:
@Model
class User {
var name: String
var city: String
var joinDate: Date
init(name: String, city: String, joinDate: Date) {
self.name = name
self.city = city
self.joinDate = joinDate
}
}
Now we can create a model container and model context for that by adding another import SwiftData
in the App
struct file then using modelContainer()
like this:
WindowGroup {
ContentView()
}
.modelContainer(for: User.self)
When it comes to editing User
objects, we would create a new view called something like EditUserView
, then use the @Bindable
property wrapper to create bindings for it. So, something like this:
struct EditUserView: View {
@Bindable var user: User
var body: some View {
Form {
TextField("Name", text: $user.name)
TextField("City", text: $user.city)
DatePicker("Join Date", selection: $user.joinDate)
}
.navigationTitle("Edit User")
.navigationBarTitleDisplayMode(.inline)
}
}
That's identical to how we used a regular @Observable
class, and yet SwiftData still takes care of automatically writing out all our changes to permanent storage – it's completely transparent to us.
Important: If you want to use Xcode's previews with this, you need to pass a sample object in, which in turn means creating a custom configuration and container. First add an import for SwiftData, then change your preview to this:
#Preview {
do {
let config = ModelConfiguration(isStoredInMemoryOnly: true)
let container = try ModelContainer(for: User.self, configurations: config)
let user = User(name: "Taylor Swift", city: "Nashville", joinDate: .now)
return EditUserView(user: user)
.modelContainer(container)
} catch {
return Text("Failed to create container: \(error.localizedDescription)")
}
}
We could make a really simple user editing app out of this by adding a new user when a button is pressed, then using programmatic navigation to take the app straight to the new user for editing.
Let's build this step by step. First, open ContentView.swift and an import for SwiftData, then add properties to get access to the model context, load all our User
objects, then store a path we can bind to a NavigationStack
:
@Environment(\.modelContext) var modelContext
@Query(sort: \User.name) var users: [User]
@State private var path = [User]()
Replace the default body
property with this:
NavigationStack(path: $path) {
List(users) { user in
NavigationLink(value: user) {
Text(user.name)
}
}
.navigationTitle("Users")
.navigationDestination(for: User.self) { user in
EditUserView(user: user)
}
}
And now we just need a way to add users. If you think about it, adding and editing are very similar, so the easiest thing to do here is to create a new User
object with empty properties, insert it into the model context, then immediately navigate to that by adjusting the path
property.
Add this extra modifier below the two navigation modifiers:
.toolbar {
Button("Add User", systemImage: "plus") {
let user = User(name: "", city: "", joinDate: .now)
modelContext.insert(user)
path = [user]
}
}
And that works! In fact, it's pretty much the same approach Apple's own Notes app takes, although they add the extra step of automatically deleting the note if you exit the editing view without actually adding any text.
As you can see, editing with SwiftData objects is no different from editing regular @Observable
classes – just with the added bonus that all our data is loaded and saved neatly!
SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.