WWDC24 SALE: Save 50% on all my Swift books and bundles! >>

Editing SwiftData model objects

Paul Hudson    @twostraws   

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!

Save 50% in my WWDC sale.

SAVE 50% To celebrate WWDC24, all our books and bundles are half price, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.

Save 50% on all our books and bundles!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

Average rating: 4.6/5

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.