Core Data is a framework to help you manage data in an app. Core Data lets you define the type of data you want to save and the relationships between these different chunks of data.
Core Data stores data using entities and attributes. An attribute defines a single chunk of data to store such as a name, address, age, gender, e-mail address, and phone number. An entity represents all of these attributes used to define a single chunk of related data such as a person.
Think of a Core Data entity like a database record or table and a Core Data attribute like a database field.
Core Data requires objects to manage your data. These objects are called the Core Data stack:
Creating a Data Model File
A data model is a Core Data file that lets you define entities and attributes where an entity represents a single object such as a person and attributes represent details such as a name, phone number, and e-mail address. You can manually add a data model file to any project or let Xcode add a data model file when you create a new project.
To add a Core Data file manually to a project, follow these steps:
// MARK: - Core Data stack lazy var persistentContainer: NSPersistentContainer = { let container = NSPersistentContainer(name: "Core_Data") container.loadPersistentStores(completionHandler: { (storeDescription, error) in if let error = error as NSError? { 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 { let nserror = error as NSError fatalError("Unresolved error \(nserror), \(nserror.userInfo)") } } }
To create a new project that includes a Core Data file, follow these steps:
Customizing a Data Model File
Creating a Core Data file creates a file with the .xcdatamodeld file extension. First, you’ll need to create at least one entity and one or more attributes in each entity where an entity represents a group of related data such as a person that contains data such as a name, age, address, or phone number.
To create an entity in a Core Data file, follow these steps:
After you’ve created at least one entity, you’ll need to add one or more attributes to hold data. An attribute consists of descriptive name (typed in lowercase) and the type of data the attribute will hold such as a string, integer, or date.
To define an attribute in an entity, follow these steps:
If you are used to working with databases, you will notice that you did not add a primary key. A primary key is a field (usually a number) that is used to uniquely identify each record in a database. In Core Data databases, there is no need to create primary keys. The framework will manage all of that for you.
Designing the User Interface
For our CoreDataApp project, we’ll design a simple user interface that will consist of two text fields, two buttons, and a label. The two text fields will allow us to input data, the label will display all stored data, and the two buttons will let us add or delete data.
To design the user interface for the CoreDataApp project, follow these steps:
Under the import UIKit line, add the following:
import Foundation import CoreData
Add two outlets and connect them
@IBOutlet var myLabel: UILabel! @IBOutlet var myProductTextField: UITextField! @IBOutlet var myPriceTextField: UITextField!
Under the IBOutlets, add the following two lines:
var dataManager: NSManagedObjectContext! var listArray = [NSManagedObject]()
We need to write Swift code to add data from the two text fields when the user clicks the Add Data button.
Edit the viewDidLoad
method as follows:
override func viewDidLoad() { super.viewDidLoad() let appDelegate = UIApplication.shared.delegate as! AppDelegate dataManager = appDelegate.persistentContainer.viewContext myLabel.text?.removeAll() fetchData() } func fetchData() { let fetchRequest: NSFetchRequest<NSFetchRequestResult> = NSFetchRequest(entityName: "Item") do { let result = try dataManager.fetch(fetchRequest) listArray = result as! [NSManagedObject] for item in listArray { let product = item.value(forKey: "name") as! String let cost = item.value(forKey: "price") as! String myLabel.text! += product + " " + cost + ", " } } catch { print ("Error retrieving data") } } @IBAction func addDataButton(_ sender: UIButton) { let newEntity = NSEntityDescription.insertNewObject(forEntityName: "Item", into: dataManager) newEntity.setValue(myProductTextField.text!, forKey: "name") newEntity.setValue(myPriceTextField.text!, forKey: "price") do { try self.dataManager.save() listArray.append(newEntity) } catch { print ("Error saving data") } myLabel.text?.removeAll() myProductTextField.text?.removeAll() myPriceTextField.text?.removeAll() fetchData() }
Next we need more Swift code to delete data when the user clicks the Delete Data button.
@IBAction func deleteDataButton(_ sender: UIButton) { let deleteItem = myProductTextField.text! for item in listArray { if item.value(forKey: "name") as! String == deleteItem { dataManager.delete(item) } } do { try self.dataManager.save() } catch { print ("Error deleting data") } myLabel.text?.removeAll() myProductTextField.text?.removeAll() fetchData() }
Adding relationships
Vendor
.title
and phone
, both of which are strings.Once this is done, you should have two entities in your entity list. Now you need to add the relationships.
Item
entity, and then click and hold on the Add Attribue button that is located on the bottom right of the screen. Select Add Relationship. (You can also click the plus under the Relationships section of the Core Data model.)vendor
as the name and select Vendor from the Destination drop-down menu.Vendor
entity. Click and hold the Add Attribute button located at the bottom right of the screen and select Add Relationship. You will use the entity name that you are connecting to as the name of this relationship, so you will call it items
. Under Destination, select Item, and under Inverse, select the vendor relationship you made in the previous step. In the Utilities window on the right side of the screen, select the Data Model Inspector. Select To Many for the type of the relationship.By default, Xcode will now know and be able to use your new entities. If you need to customize your Core Data objects such as adding validation or custom calculated properties, you will need to tell Xcode to generate NSManagedObject
subclasses for you.
When Xcode generates the Class Definitions for your entities, it will then make subclasses of NSManagedObject
. NSManagedObject
is an object that handles all of the Core Data database interaction. It provides the methods and properties you will be using in this example.
Managed Object Context
Xcode will create a managed object class called Item
. In Core Data, every managed object should exist within a managed object context. The context is responsible for tracking changes to objects, carrying out undo operations, and writing the data to the database. This is helpful because you can now save a bunch of changes at once rather than saving each individual change. This speeds up the process of saving the records. As a developer, you do not need to track when an object has been changed. The managed object context will handle all of that for you.
The following steps will assist you in setting up your interface:
UIBarButtonItem
. Search for bar button in your Object Library and drag a Bar Button
item to the top right of your view on the navigation bardataSource
or the delegate
. You will need to assign both to the View Controller. The order in which you select the items does not matter, but you will have to Control-drag the Table View twice.addNew
. Click Connect to finish the connection.myTableView
. You will need this outlet later to tell your Table View to refresh. Click Connect to finish the connection.The interface is complete now, but you still need to add the code to make the interface do something. Go back to the Standard editor (click the list icon to the left of the two circles icon in the top right of the Xcode toolbar) and select the ViewController.swift
file from the file list on the left side. Because you now have a Table View that you have to worry about, you need to tell your class that it can handle a Table View. We'll add extenstion to your class:
extension ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
You added UITableViewDelegate
and UITableViewDataSource
to your declaration. This tells your controller that it can act as a table view delegate and data source. These are called protocols. Protocols tell an object that they must implement certain methods to interact with other objects. For example, to conform to the UITableViewDataSource
protocol, you need to implement the following method:
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
Without this method, the Table View will not know how many rows to draw. Before continuing, you need to tell your ViewController.swift file about Core Data. To do this, you add the following line to the top of the file just under import UIKit
:
import CoreData
You also need to add a managed object context to your ViewController
class. Add the following line right after the class ViewController
line:
var managedObjectContext: NSManagedObjectContext!
Now that you have a variable to hold your NSManagedObjectContext
, you need to instantiate it so you can add objects to it. To do this, you need to add the following lines to your override func viewDidLoad()
method:
let appDelegate: AppDelegate = UIApplication.shared.delegate as! AppDelegate managedObjectContext = appDelegate.persistentContainer.viewContext as NSManagedObjectContext
The first line creates a constant that points to your application delegate. The second line points your managedObjectContext
variable to the application delegate’s managedObjectContext
. It is usually a good idea to use the same managed object context throughout your app.
The first new method you are going to add is one to query your database records. Call this method loadItems
.
func loadItems() -> [Item] { let fetchRequest: NSFetchRequest<Item> = Item.fetchRequest() var result: [Item] = [] do { result = try managedObjectContext.fetch(fetchRequest) } catch { NSLog("My Error: %@", error as NSError) } return result }
You will now need to add the data source methods for your Table View. These methods tell your Table View how many sections there are, how many rows are in each section, and what each cell should look like. Add the following code to your ViewController.swift file:
extension ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { func tableView(_ tableView: UITableView, numberOfRowsInSection section:Int) -> Int { return loadItems().count } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: "Cell") else { return UITableViewCell() } let item: Item = loadItems()[indexPath.row] cell.textLabel?.text = item.title return cell } }
This is all you need to do to display the results of your loadItems
method in the Table view. You still have one problem. You do not have any items in your database yet.
To fix this issue, you will add code to the addNew
method that you created earlier. Add the following code inside the addNew
method you created:
@IBAction func addNew(_ sender: UIBarButtonItem) { let item: Item = NSEntityDescription.insertNewObject(forEntityName: "Item", into: managedObjectContext) as! Item item.title = "My Item" + String(loadItems().count) do { try managedObjectContext.save() } catch let error as NSError { NSLog("My Error: %@", error) } myTableView.reloadData() }
Example of create data
func createData(){ guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } let managedContext = appDelegate.persistentContainer.viewContext let item = NSEntityDescription.insertNewObject(forEntityName: "Item", into: managedContext) item.setValue("Item 1", forKey: "name") item.setValue(9.99, forKey: "price") do { try managedContext.save() } catch let error as NSError { print("Could not save. \(error), \(error.userInfo)") } }
Example of retrieve data
func retrieveData() { guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } let managedContext = appDelegate.persistentContainer.viewContext let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item") // fetchRequest.fetchLimit = 1 // fetchRequest.predicate = NSPredicate(format: "name = %@", "Item 1") // fetchRequest.sortDescriptors = [NSSortDescriptor.init(key: "price", ascending: false)] do { let result = try managedContext.fetch(fetchRequest) for data in result as! [NSManagedObject] { print(data.value(forKey: "name") as! String) } } catch { print("Error retrieving data: \(error)") } }
Example of update data
func updateData(){ guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } let managedContext = appDelegate.persistentContainer.viewContext let fetchRequest:NSFetchRequest<NSFetchRequestResult> = NSFetchRequest.init(entityName: "Item") fetchRequest.predicate = NSPredicate(format: "name = %@", "Item 1") do { let test = try managedContext.fetch(fetchRequest) let objectUpdate = test[0] as! NSManagedObject objectUpdate.setValue("New item!", forKey: "name") do { try managedContext.save() } catch { print("Error updating data: \(error)") } } catch { print("Error retrieving data: \(error)") } }
Example of delete data
func deleteData(){ guard let appDelegate = UIApplication.shared.delegate as? AppDelegate else { return } let managedContext = appDelegate.persistentContainer.viewContext let fetchRequest = NSFetchRequest<NSFetchRequestResult>(entityName: "Item") fetchRequest.predicate = NSPredicate(format: "name = %@", "New item!") do { let test = try managedContext.fetch(fetchRequest) let objectToDelete = test[0] as! NSManagedObject managedContext.delete(objectToDelete) do{ try managedContext.save() } catch { print("Error deleting data: \(error)") } } catch { print("Error retrieving data: \(error)") } }
How to get path to sqlite file in Simulator
You can get path to sqlite file in Simulator using following snippet
let container = NSPersistentContainer(name: "xx") var defaultURL: URL? if let storeDescription = container.persistentStoreDescriptions.first, let url = storeDescription.url { defaultURL = FileManager.default.fileExists(atPath: url.path) ? url : nil } print(defaultURL)
You can get path to sqlite file inside group identifier (for example, if you use Today Extension) in Simulator using following snippet
let storeURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "group.xx")!.appendingPathComponent("xx.sqlite") print(storeURL)