read

Using SwiftUI’s List is a refreshing change as we no longer use UITableView and NSFetchedResultsController.

The Core Data APIs are still the same (I covered the CRUD previously). The new additions are 2 helpers in the form of property wrapper.

1. Environment managedObjectContext

The managedObjectContext is passed via environment, and there is a provided system key. If you create a new project, the boilerplate will pass in in the SceneDelegate to the root view.

ContentView()
    .environment(\.managedObjectContext, context)

In child views, declare the environment to use.

@Environment(\.managedObjectContext) var context

Just like that, every children has the context, unlike some view decided to use otherwise.

2. @FetchRequest

This is a helper to create fetch requests in a view.

@FetchRequest(entity: Language.entity(),
              sortDescriptors: [NSSortDescriptor(...)],
              predicate: NSPredicate(...),
              animation: .spring())
var languages: FetchedResults<Language>

Much of it is the same as a NSFetchRequest, with an addition of animation! Indeed it is for a view.

You could have manually used NSFetchRequest and using the context to execute a fetch. But using @FetchRequest does give the view a very orderly look – you know exactly where fetch requests are declared.

Displaying in a list

Init ForEach with the variable wrapped by @FetchRequest. As easy as that, the list will refresh when the models are updated.

List {
    ForEach(languages) { language  in
        Text(language.name!)
    }
}

Oh, one thing about the Core Data model. In our example, the model Language needs to implement Identifiable.

extension Language: Identifiable {}

Delete and Move

These are really powered by List, which I covered here. For example, delete:

ForEach(languages) { language  in
    ...
}
.onDelete {
    let language = self.languages[indexSet.first!]
    self.context.delete(language)
    try! self.context.save()
}

Move will be similar using .onMove.

Binding optionals

Core Data models have optional properties, while SwiftUI control bindings (eg. TextField) require non-optional binding.

AQBlog provided nice extensions to binding with Core Data.

NOTE: To emphasize, it is the model’s properties here that are being bind, NOT the model itself. You can’t bind the model (read next section).

Observing NSManagedObject

NSManagedObject is a class type. Since binding only works for struct, you cannot bind NSManagedObject.

But you can observe NSManagedObject with @ObservedObject, because the class is already conveniently conformed to ObservableObject.

// OK, and you can bind the property with eg. $model.name
@ObservedObject var model: Language

// NOT ok
@Binding var model: Language

When you observe the model, SwiftUI will update when the context is saved.

Core Data is a framework that does not play too well with SwiftUI (yet). As suggested, it would be wiser to separate the persistent domain model and the view model.


Image

@samwize

¯\_(ツ)_/¯

Back to Home