We explored the basics of Core Data in previous articles like this one, where the data was managed directly on the View. This is fine for managing simple data sets, but when it starts to get complicated it's recommended to manage that data from a ViewModel.
Data Model file and entity Person
will be created in the same way as in the previous Core Data articles. Then we can create a Core Data manager called CoreData
.
import CoreData
class CoreData {
let container = NSPersistentContainer(name: "Model")
static let shared = CoreData()
private init() {
container.loadPersistentStores { description, error in
if let error = error {
print("Core Data failed to load: \(error.localizedDescription)")
}
}
}
}
CoreData
as a Singleton in order to use always the same context
. This is because all the Core Data operations related with an object instance must be done in the same context
, so in order to simplify everything we just use a Singleton.
ViewModel
The next step is to create a ViewModel. From one side, this ViewModel will store the array of Person
and the inputText
that the View will use to show the results, and on the other side, fetch()
and addPersonTapped()
will update the data using moc
.
import CoreData
import SwiftUI
@Observable
class ViewModel {
let moc = CoreData.shared.container.viewContext
var persons: [Person] = []
var inputText: String = ""
func fetchData() {
// Create NSFetchRequest instance
let fetchRequest = NSFetchRequest(entityName: "Person")
// Add sort descriptors
let sortDescriptor = NSSortDescriptor(keyPath: \Person.name, ascending: true)
fetchRequest.sortDescriptors = [sortDescriptor]
// Add predicate (uncomment to apply a filter)
//let predicate = NSPredicate(format: "name == %@", "Pablo")
//fetchRequest.predicate = predicate
// Fetch data
do {
persons = try moc.fetch(fetchRequest)
} catch let error {
print("Error fetching items: \(error.localizedDescription)")
}
}
func addButtonTapped() {
guard !inputText.isEmpty else {
return
}
addPerson(name: inputText)
inputText = ""
}
func addPerson(name: String) {
var person = Person(context: moc)
person.id = UUID()
person.name = name
try? moc.save()
fetchData()
}
}
View
The View will show the data as usual. This View will also trigger the first data fetch through onAppear()
modifier.
import SwiftUI
struct ContentView: View {
@State var viewModel = ViewModel()
var body: some View {
VStack {
List(viewModel.persons) { person in
Text(person.name ?? "No name")
}
inputBar
}
.onAppear(perform: {
viewModel.fetchData()
})
}
var inputBar: some View {
HStack {
TextField("", text: $viewModel.inputText)
.textFieldStyle(.roundedBorder)
Button {
viewModel.addButtonTapped()
} label: {
Text("ADD")
}
}
.padding()
}
}
#Preview {
ContentView()
}
Result
You can find all this code together in our samples repository, here.
Be the first to comment