Swift – Core Data

By | 30/11/2022

In this post, we will see how to use Core Data in an iOS project.
But first if all, what is Core Data?
From Apple web site:
“Core Data is a framework that you use to manage the model layer objects in your application. It provides generalized and automated solutions to common tasks associated with object life cycle and object graph management, including persistence.
Core Data typically decreases by 50 to 70 percent the amount of code you write to support the model layer. This is primarily due to the following built-in features that you do not have to implement, test, or optimize”


For understanding better Core Data, we will create an iOS application called ManageUsers where we will run all CRUD operations for the entity User, using Core Data.

We start creating a project, checking the option “Use Core Data”:

In the project, we can see a .xcdatamodeld file that is the file where we will manage the entities:

We can delete the default Item entity and we create a new one called User, so defined:

Then, we have to modify in the file Persistence.swift the property “preview” (it allows us to use the CoreData functionality inside preview simulators):

static var preview: PersistenceController = {
        let result = PersistenceController(inMemory: true)
        let viewContext = result.container.viewContext
        for _ in 0..<10 {
            // code created by Xcode ->  newItem = Item(context: viewContext)
            // code created by Xcode ->  newItem.timestamp = Date()
            let newItem = User(context: viewContext)
            newItem.id = UUID()
            newItem.surname = "Surname"
            newItem.name = "Name"
        }
        do {
            try viewContext.save()
        } catch {
            // Replace this implementation with code to handle the error appropriately.
            // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
            let nsError = error as NSError
            fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
        }
        return result
    }()



Now, we come back in the .xcdatamodeld file in order to generate the code of the User entity, that we will use for managing it.
We open the model file, we open the Editor item and then we select “Create NSManagedObject Subclass..”:

Now, in the project, we have other two new files called User+CoreDataClass.swift and User+CoreDataProperties.swift:

[User+CoreDataClass.swift]

import Foundation
import CoreData

@objc(User)
public class User: NSManagedObject {

}



[User+CoreDataProperties.swif]

import Foundation
import CoreData


extension User {

    @nonobjc public class func fetchRequest() -> NSFetchRequest<User> {
        return NSFetchRequest<User>(entityName: "User")
    }

    @NSManaged public var id: UUID?
    @NSManaged public var name: String?
    @NSManaged public var surname: String?

}

extension User : Identifiable {

}



Finally, we need to tell Xcode that the data model is no longer defined by “.xcdatamodeld” file only
but, manually defined by the corresponding subclass we just created.
We open the model file, we select the entity and then, in the Class properties (on the right), we choose “Manual Definition” in the Codegen property:


We have finished to setup up the Data layer and now, we can prepare the UI for managing data.


INSERT NEW USER
First of all, we add a SwiftUI file called NewUser.swift that it will be the form data for a new User:

[NEWUSER.SWIFT]

import SwiftUI

struct NewUser: View {
    @Environment (\.presentationMode) var presentationMode
    
    @State var name: String = ""
    @State var surname: String = ""
    
    func SaveData() {
        presentationMode.wrappedValue.dismiss()
    }
    
    var body: some View {
        NavigationView {
            Form {
                // Definition of User Info section
                Section{
                    TextField("Name", text: $name)
                    TextField("Surname", text: $surname)
                }
                Button(action: SaveData) {
                    Text("ADD USER")
                }
                .navigationBarTitle(Text("New User"))
            }
        }
    }
}

struct NewUser_Previews: PreviewProvider {
    static var previews: some View {
        NewUser()
    }
}



Then, we modify the file ContentView.swift, in order to use the file above:

[CONTENTVIEW.SWIFT]

import SwiftUI
import CoreData

struct ContentView: View {
    // variable used to show the Form for creating a new User
    @State var showNewUser = false
    var body: some View {
        NavigationView{
            List{
                Text("Mario Bianchi")
            }
            .navigationTitle("Users")
            .navigationBarItems(trailing: Button(action: {
                    showNewUser = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                }))
            
            .sheet(isPresented: $showNewUser) {
                    NewUser()
                }
        }
    }
}



If we run the application, this will be the result:

The UI works fine and now, we will add the code in order to save data using Core Data.
First of all, we modify the file NewUser.swift for saving a new User:

[NEWUSER.SWIFT]

import SwiftUI

struct NewUser: View {
    @Environment (\.presentationMode) var presentationMode
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    @State var name: String = ""
    @State var surname: String = ""
    
    func SaveData() {
        // we create a new User and we insert values of Name and Surname
        let newUser = User(context: viewContext)
        newUser.name = name
        newUser.surname = surname
        // we create an ID
        newUser.id = UUID()
        
        do {
            // we try to save the new User
            try viewContext.save()
            // if it saved, system will print a message in the output view
            print("New User saved.")
        }
        catch {
            print(error.localizedDescription)
        }
        
        presentationMode.wrappedValue.dismiss()
    }
    
    var body: some View {
        NavigationView {
            Form {
                // Definition of User Info section
                Section{
                    TextField("Name", text: $name)
                    TextField("Surname", text: $surname)
                }
                Button(action: SaveData) {
                    Text("ADD USER")
                }
                .navigationBarTitle(Text("New User"))
            }
        }
    }
}

struct NewUser_Previews: PreviewProvider {
    static var previews: some View {
        NewUser()
    }
}



If we run the application, this will be the result:

and in the output view, we will have this message:



LIST OF ALL SAVED USERS
We can see that everything works fine and now, we have to modify the file ContentView.swift for showing the list of saved users:

[CONTENTVIEW.SWIFT]

import SwiftUI
import CoreData

struct ContentView: View {
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    // FetchRequest is used to extract data from the persistent storage
    // In this case the entity User without any sort and any predicate
    // In sql this would be: select * from user
    @FetchRequest(entity: User.entity(), sortDescriptors: [], predicate: nil)
    // We put the result of the query above in a variable called lstUser
    private var lstUser: FetchedResults<User>
    
    // variable used to show the Form for creating a new User
    @State var showNewUser = false
    var body: some View {
        NavigationView{
            List{
                // we show all users
                ForEach(lstUser) { user in
                    VStack(alignment: .leading){
                        Text("\(user.name!) - \(user.surname!)")
                            .font(.headline)
                    }
                }
            }
            .navigationTitle("Users")
            .navigationBarItems(trailing: Button(action: {
                    showNewUser = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                }))
            
            .sheet(isPresented: $showNewUser) {
                    NewUser()
                }
        }
    }
}



If we run the application, this will be the result:

It works fine and now, we will modify the code for adding Delete and Update operations.


DELETE USER
For Delete, we have just to modify the file ContentView.swift in this way:

[CONTENTVIEW.SWIFT]

import SwiftUI
import CoreData

struct ContentView: View {
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    // FetchRequest is used to extract data from the persistent storage
    // In this case the entity User without any sort and any predicate
    // In sql this would be: select * from user
    @FetchRequest(entity: User.entity(), sortDescriptors: [], predicate: nil)
    // We put the result of the query above in a variable called lstUser
    private var lstUser: FetchedResults<User>
    
    // variable used to show the Form for creating a new User
    @State var showNewUser = false
    var body: some View {
        NavigationView{
            List{
                // we show all users
                ForEach(lstUser) { user in
                    VStack(alignment: .leading){
                        Text("\(user.name!) - \(user.surname!)")
                            .font(.headline)
                    }
                }
                // delete item
                .onDelete {
                    indexSet in
                    for index in indexSet{
                        // we delete the user
                        viewContext.delete(lstUser[index])
                    }
                    do {
                        // save the operation
                        try viewContext.save()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
            }
            .navigationTitle("Users")
            .navigationBarItems(trailing: Button(action: {
                    showNewUser = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                }))
            
            .sheet(isPresented: $showNewUser) {
                    NewUser()
                }
        }
    }
}



If we run the application, this will be the result:



UPDATE USER
Finally for Update, we have to modify the view called ContentView and the view called NewUser.
From ContentView, we have to pass the object User to update in the view NewUser where, it will show the previous values and here, it will be possible to modify them:

[CONTENTVIEW.SWIFT]

import SwiftUI
import CoreData

struct ContentView: View {
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    // FetchRequest is used to extract data from the persistent storage
    // In this case the entity User without any sort and any predicate
    // In sql this would be: select * from user
    @FetchRequest(entity: User.entity(), sortDescriptors: [], predicate: nil)
    // We put the result of the query above in a variable called lstUser
    private var lstUser: FetchedResults<User>
    
    func UpdateUser(inputUser: User) -> Void{
        // for the update we pass the object User selected
        objUser = inputUser
        showNewUser = true
        isUpdate = true
    }
    
    init() {
        _showNewUser = State(initialValue: false)
        _isUpdate = State(initialValue: false)
    }
    
    // variable used to show the Form for creating a new User
    @State var showNewUser: Bool
    @State private var objUser: User = User()
    @State private var isUpdate: Bool
    
    var body: some View {
        NavigationView{
            List{
                // we show all users
                ForEach(lstUser) { user in
                    HStack{
                        VStack(alignment: .leading){
                            Text("\(user.name!) - \(user.surname!)")
                                .font(.headline)
                        }
                        Spacer()
                        Button(action: { self.UpdateUser(inputUser: user)}) {
                            Text("Update")
                                .foregroundColor(.blue)
                        }
                    }
                }
                // delete item
                .onDelete {
                    indexSet in
                    for index in indexSet{
                        // we delete the user
                        viewContext.delete(lstUser[index])
                    }
                    do {
                        // save the operation
                        try viewContext.save()
                    } catch {
                        print(error.localizedDescription)
                    }
                }
            }
            .navigationTitle("Users")
            .navigationBarItems(trailing: Button(action: {
                    isUpdate = false
                    objUser = User()
                    showNewUser = true
                }, label: {
                    Image(systemName: "plus.circle")
                        .imageScale(.large)
                }))
            
            .sheet(isPresented: $showNewUser) {
                    NewUser(inputUser:  objUser , isUpdate: isUpdate)
                }
        }
    }
}



[NEWUSER.SWIFT]

import SwiftUI

struct NewUser: View {
    
    @Environment (\.presentationMode) var presentationMode
    // with Environment we can access here to the viewContext
    @Environment(\.managedObjectContext) private var viewContext
    
    @State var name: String = ""
    @State var surname: String = ""
    @State var isUpdate: Bool
    private var objUser: User
    private var title: String
    
    init(inputUser: User, isUpdate: Bool) {
        _isUpdate = State(initialValue: isUpdate)
        objUser = inputUser
        
        // definition of the title based on the variable isUpdate
        if(isUpdate)
        {
            title = "Modify User"
            // we put in name and surname the previous values
            _name = State(initialValue: inputUser.name!)
            _surname = State(initialValue: inputUser.surname!)
        }
        else
        {
            title = "New User"
        }
    }
         
    
    func SaveData() {
        var newUser:User
        
        if(isUpdate)
        {
            // For the update we use the User passed in input
            newUser = objUser
        }
        else
        {
            // We create a new User object
            newUser = User(context: viewContext)
            newUser.id = UUID()
        }
        
        newUser.name = name
        newUser.surname = surname
        
        
        do {
            // we try to save the new User
            try viewContext.save()
            // if it saved, system will print a message in the output view
            print("New User saved.")
        }
        catch {
            print(error.localizedDescription)
        }
        
        presentationMode.wrappedValue.dismiss()
    }
    
    var body: some View {
        NavigationView {
            Form {
                // Definition of User Info section
                Section{
                    TextField("Name", text: $name)
                    TextField("Surname", text: $surname)
                }
                Button(action: SaveData) {
                    Text(!isUpdate ? "ADD USER" : "MODIFY USER")
                }
                .navigationBarTitle(Text(self.title))
            }
        }
    }
}

struct NewUser_Previews: PreviewProvider {
    static var previews: some View {
        NewUser(inputUser: User(), isUpdate: false)
    }
}



If we run the application, this will be the result:



Leave a Reply

Your email address will not be published. Required fields are marked *