CoreData and Swift 3

Lately I have been working on a simple task manager app. I have decided to write it in swift 3 and since I didn't want to use any pods I switched from my beloved Realm to CoreData and that is where our journey begins.

CoreData alongside with Swift 3 were quite new to me so I wanted to read the documentation but it was still in Swift 2.2. No problem there StackOverflow will save me for sure! Well it turned out it was not gonna be that simple. It took me couple of hours to find all the resourses and make it working. Therefore I am writing this tutorial now so that you can find in one place what I found scattered all over the internet.

  1. Adding CoreData to your Xcode project

A. Simple solution would be to just select CoreData while creating Xcode project. Xcode will add all the necessary code your AppDelegate file and it will also create .xcdatamodeld file for you. From here skip to bullet point 2.

B. Adding CoreData to an existing project

This is not gonna be that simple but still pretty easy to do. First thing is to open your AppDelegate file and import CoreData at the top. Next step is to add CoreData methods as follows:

// MARK: - Core Data stack
lazy var persistentContainer: NSPersistentContainer = {
/*
The persistent container for the application. This implementation
creates and returns a container, having loaded the store for the
application to it. This property is optional since there are legitimate
error conditions that could cause the creation of the store to fail.
*/
let container = NSPersistentContainer(name: "tets")
container.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
// 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.
/*
Typical reasons for an error here include:
* The parent directory does not exist, cannot be created, or disallows writing.
* The persistent store is not accessible, due to permissions or data protection when the device is locked.
* The device is out of space.
* The store could not be migrated to the current model version.
Check the error message to determine what the actual problem was.
*/
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
return container
}()
// MARK: - Core Data Saving support
func saveContext () {
let context = persistentContainer.viewContext
if context.hasChanges {
do {
try context.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)")
}
}
}

Once you have all that create .xcdatamodeld file. Call it in the context of you app's data model. I call mine TaskManager.xcdatamodeld. Now you're all set lets get to CoreData magic.

2. Defining your data model

Open your .xcdatamodeld file and create an Entity and define its attributes. I have created Task with many attributes e.g. finished: Bool, taskDescription: String and uuid: String etc. In case you need to create e.g. UIColor or another Type that is not listed simple choose Transformable and than once you gonna use it for an UI element just retrieve it as? UIColor.

Once you create your Entity go under Editor > Create NSManagedObject Subclass. Make sure to check your Entity and hit create.

Now that we have defined our entity all we want to do is to use it! Save some data to it, retrieve it, update it, delete it… you name it!

Now that we have defined our entity all we want to do is to use it! Save some data to it, retrieve it, update it, delete it… you name it!

3. Saving data

What I like to do and I think is well known good practice is to put all methods that are based on my Entity in one class and than just use it in view controllers.

In this case I have a class called Tasks and everything happens there. It is important to use it as a singleton and not to create multiple instances of it. In case you are not sure what I mean just google it and you can look into my sample project as well. https://github.com/Majkitos/taskManager

Saving data is pretty straight forward. First thing is to create our context.

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

Next step is simply creating a constant of type Task with our context. Than saving data to it and performing saveContext() method from AppDelegate. In my case it looks like this:

func addTask(description: String, date: Date, finished: Bool, category: Int, uuid: String) {
let task = Task(context:context)
task.category = Int32(category)
task.date = date
task.finished = finished
task.taskDescription = description
task.uuid = uuid
task.notificationEnabled = false
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}

4. Retrieving data

Now we have saved data persistently but to display them within our app we need to retrieve them from the storage.

I have an array of type Task in my Task class and I use it to save my data. To fetch data from storage use fetch method as follows. And again it is important to use your class as a singleton!

func tasksData() -> [Task] {
do {
tasks = try context.fetch(Task.fetchRequest())
}catch {
print("Error fetching data from CoreData")
}
return tasks
}

5. Deleting data

Saving data without the ability to delete them would be quite useless so here is how to delete data

First thing is to retrieve data and then select the right object to delete. I am using UUID as an unique identifier for every task I create. That way I can easily identifiy my task that needs to be deleted or edited. I am using for in loop to filter task with given uuid:

func deleteTask(withUUID: String) {
let request = NSFetchRequest<Task>(entityName: "Task")
do {
let searchResults = try context.fetch(request)
for task in searchResults {
if task.uuid == withUUID {
// delete task
context.delete(task)
}
}
} catch {
print("Error with request: \(error)")
}
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}

6. Updating data

And of course last thing is how to update data. I am often updating my task's finished property. So I have created a method to do this quickly. It is very similar to the deleteTask() method. You again retrieve all your Task data, filter through them with for in loop and once you find a task with a selected uuid you update all you want to update and you save all changes into your context. Easy-peasy-lemon-squeezy.

func updateTask(uuid: String, isFinished: Bool) {
let request = NSFetchRequest<Task>(entityName: "Task")
do {
let searchResults = try context.fetch(request)
for task in searchResults {
if task.uuid == uuid {
task.finished = isFinished
}
}
} catch {
print("Error with request: \(error)")
}
(UIApplication.shared.delegate as! AppDelegate).saveContext()
}

All right so there you have it! CoreData with Swift3. You can check my sample project here: https://github.com/Majkitos/taskManager. Just one note here. As I am writing this (31.10.16) this project is not 100% finished and I will commit all my changes once it is finished. I am currently finishing notifications and I need to tune a bit more my navigation because there are some memory leaks. But as for CoreData that is all finished and 100% working!

Leave your questions in comments;)

Michal

Show your support

Clapping shows how much you appreciated Michal Svěrák’s story.