Note from Ray: This is an abbreviated version of a chapter from Core Data by Tutorials released as part of the iOS 8 Feast to give you a sneak peek of what’s inside the book. We hope you enjoy!
In this tutorial, you’ll write your very first Core Data app using Swift. You’ll see how easy it is to get started with all the resources provided in Xcode, from the starter code templates to the data model editor. By the end of the tutorial you’ll know how to:
- model data you want to store in Core Data using Xcode’s model editor;
- add new records to Core Data;
- fetch a set of records from Core Data;
- display the fetched results to the user in a table view.
You’ll also get a sense of what Core Data is doing behind the scenes, and how you can interact with the various moving pieces there. We’re getting ahead of ourselves though – it’s time to build an app!
Getting Started
Open Xcode and create a new iPhone project based on the Single View Application template. Call the app HitList and make sure Use Core Data is checked:
Checking the Use Core Data box will cause Xcode to generate boilerplate code for what’s known as a Core Data stack in AppDelegate.swift.
The Core Data stack consists of a set of objects that facilitate saving and retrieving information from Core Data. There’s an object to manage the Core Data state as a whole, an object representing the data model, and so on.
Note: Not all Xcode templates have the option to start with Core Data. In Xcode 6, only the Master-Detail Application and the Single View Application templates have the Use Core Data checkbox.
The idea for this sample app is simple. There will be a table view with a list of names for your very own “hit list”. You’ll be able to add names to this list and eventually, you’ll use Core Data to make sure the data is stored between sessions. We don’t condone violence in this tutorial so you can think of this app as a “favorites list” to keep track of your friends too, of course! ;]
Click on Main.storyboard to open it in Interface Builder. Select its only view controller and change its size class to Regular Height and Compact Width to match an iPhone in portrait mode:
Next, embed the view controller in a navigation controller. From Xcode’s Editor menu, select Embed In…\ Navigation Controller.
Back in Interface Builder, drag a Table View from the object library into the view controller so that it covers the entire view.
Then, drag a Bar Button Item and place it on the view controller’s newly added navigation bar. Finally, double-click the bar button item to change its text to Add. Your canvas should now look like the following screenshot:
Every time you tap Add on the top-right, an alert containing a text field will appear on the screen. From there you’ll be able to type someone’s name into the text field. Dismissing the alert will save the name and refresh the table view with all the names you’ve saved up to that point.
Before you can do that, you need to make the view controller the table view’s data source. Ctrl-drag from the table view to the yellow view controller icon above the navigation bar, as shown below, and click on dataSource:
In case you were wondering, you don’t need to set up the table view’s delegate since tapping on the cells won’t trigger any action. It doesn’t get simpler than this!
Open the Assistant Editor by hitting Command-Option-Enter or by selecting the middle button on the Editor toolset on the Xcode bar. Ctrl-drag from the table view onto ViewController.swift, inside the class definition to insert an outlet:
Name the new IBOutlet
property tableView
, resulting in the following line:
@IBOutlet weak var tableView: UITableView! |
Ctrl-drag from the Add bar button item onto ViewController.swift, but this time, create an action instead of an outlet and name the method addName
:
@IBAction func addName(sender: AnyObject) { } |
You can now refer to the table view and the bar button item’s action in code. Next, set up the model for the table view. Add the following property to ViewController.swift:
//Insert below the tableView IBOutlet var names = [String]() |
names
is a mutable Array
to hold the strings for the table view to display.
Replace the implementation of viewDidLoad
with the following:
override func viewDidLoad() { super.viewDidLoad() title = "\"The List\"" tableView.registerClass(UITableViewCell.self, forCellReuseIdentifier: "Cell") } |
This will set a title and register the UITableViewCell
class with the table view. You do this so that when you dequeue a cell, the table view will return a cell of the correct type.
Still in ViewController.swift, declare that ViewController
will conform to the UITableViewDataSource
protocol by editing the class declaration:
//Add UITableViewDataSource to class declaration class ViewController: UIViewController, UITableViewDataSource { |
Immediately, Xcode will complain about ViewController not conforming to the protocol.
Below viewDidLoad
, implement the following data source methods to fix the error:
// MARK: UITableViewDataSource func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return names.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell cell.textLabel!.text = names[indexPath.row] return cell } |
If you’ve ever worked with UITableView
, this code should look very familiar. The first method says that the table view will have as many rows as the names
array has strings.
The second method, tableView(_:cellForRowAtIndexPath:)
, dequeues table view cells and populates them with the corresponding string in the names
array.
Don’t run the app just yet. First, you need a way to input names so the table view can display them.
Implement the addName IBAction
method you Ctrl-dragged into your code earlier:
//Implement the addName IBAction @IBAction func addName(sender: AnyObject) { var alert = UIAlertController(title: "New name", message: "Add a new name", preferredStyle: .Alert) let saveAction = UIAlertAction(title: "Save", style: .Default) { (action: UIAlertAction!) -> Void in let textField = alert.textFields![0] as UITextField self.names.append(textField.text) self.tableView.reloadData() } let cancelAction = UIAlertAction(title: "Cancel", style: .Default) { (action: UIAlertAction!) -> Void in } alert.addTextFieldWithConfigurationHandler { (textField: UITextField!) -> Void in } alert.addAction(saveAction) alert.addAction(cancelAction) presentViewController(alert, animated: true, completion: nil) } |
Every time you tap the Add bar button item, this method presents an UIAlertController
with a text field and two buttons, Save and Cancel.
Save takes whatever text is currently in the text field, inserts it into the name
array and reloads the table view. Since the names array is the model backing the table view, whatever you typed into the text field will appear in the table view.
Finally it’s time to build and run your app for the first time. Tap the Add bar button item. The alert controller will look like this:
Add four or five names to the list. You should wind up with something like this:
Your table view will display the data and your array will store the names, but the big thing missing here is persistence. The array is in memory but if you force quit the app or reboot your device, your hit list will be wiped out.
Core Data provides persistence, meaning it can store data in a more durable state so that it can outlive an app re-launch or a device reboot.
You haven’t added any Core Data yet, so nothing should persist after you navigate away from the app. Let’s test this out. Press the Home button if you’re using a physical device or the equivalent (Shift+⌘+H) if you’re on the Simulator. This will take you back to the familiar app grid on the home screen:
From the home screen, tap the HitList icon to bring the app back to the foreground. The names are still on the screen. What happened?
When you tap the Home button, the app that’s currently in the foreground goes to the background. When this happens, the operating system flash-freezes everything currently in memory, including the strings in the names
array. Similarly, when it’s time to wake up and return to the foreground, the operating system restores what used to be in memory as if you’d never left.
Apple introduced these advances in multitasking back in iOS 4. They create a seamless experience for iOS users but add a wrinkle to the definition of persistence for iOS developers. Are the names really persisted?
No, not really. If you had completely killed the app in the fast app switcher or turned off your phone, those names would be gone. You can verify this, as well. With the app in the foreground, double tap the Home button to enter the fast app switcher, like so:
From here, flick the HitList app snapshot upwards to terminate the app. There should be no trace of HitList in living memory (no pun intended). Verify that the names are gone by returning to the home screen and tapping on the HitList icon to trigger a fresh launch.
This difference between flash-freezing and persistence may be obvious if you’ve been working with iOS for some time and are familiar with the way multitasking works. In a user’s mind, however, there is no difference. The user doesn’t care if the names are “still there” because the app went into the background and came back, or because the app saved and reloaded them.
All that matters is that the names are still there when she comes back!
So the real test of persistence, the one you will use in this tutorial, is whether your data is still there after a fresh app launch.
Modeling Your Data
Now that you know how to check for persistence, let’s get started with Core Data. Your goal for the HitList app is simple: to persist the names you enter so they’re available for viewing after a fresh app launch.
Up to this point, you’ve been using plain old Swift strings to store the names in memory. In this section, you’ll replace these strings with Core Data objects.
The first step is to create a managed object model, which spells out the way Core Data represents data on disk. By default, Core Data uses an SQLite database as the persistent store (more on this later), so you can think of the data model as the database schema.
Note: You’ll come across the word “managed” quite a bit in this tutorial. If you see “managed” in the name of a class, such as in NSManagedObjectContext
, chances are you are dealing with a Core Data class. “Managed” refers to Core Data’s management of the life cycle of Core Data objects.
However, don’t assume that all Core Data classes contain the word “managed”—actually, most don’t. For a comprehensive list of Core Data classes, check out the Objective-C umbrella header CoreData/CoreData.h
.
Since you elected to use Core Data when you created the HitList project, Xcode automatically created a data model file for you and named it HitList.xcdatamodeld.
Click on HitList.xcdatamodeld to open it. As you can see, Xcode has a powerful data model editor that looks like this:
The data model editor has a lot of features. For now, let’s focus on creating a single Core Data entity.
Click on Add Entity on the lower-left to create a new entity. Double-click on the new entity and change its name to Person, like so:
You may be wondering why the model editor uses the term “Entity.” Weren’t you simply defining a new class? As you’ll see shortly, Core Data comes with its own vocabulary. Here’s a quick rundown of some of the terms you’ll commonly encounter:
- An entity is a class definition in Core Data. The classic example is an
Employee
or aCompany
. In a relational database, an entity corresponds to a table. - An attribute is a piece of information attached to a particular entity. For example, an
Employee
entity could have attributes for the employee’s name, position and salary. In a database, an attribute corresponds to a particular field in a table. - A relationship is a link between multiple entities. In Core Data, relationships between two entities are called to-one relationships, while those between one and many entities are called to-many relationships. For example, a
Manager
can have a to-many relationship with a set of employees, whereas an individualEmployee
will have a to-one relationship with his manager.
Note: As you’ve probably noticed, entities sound a lot like a classes. Likewise, attributes/relationships sound a lot like properties. What’s the difference? You can think of a Core Data entity as a class “definition” and the managed object as an instance of that class.
Now that you know what an attribute is, go back to the model editor and add an attribute to Person
. Select Person
on the left-hand side and click the plus sign (+) under Attributes.
Set the new attribute’s name to, well, name and change its type to String:
In Core Data, an attribute can be of one of several data types — one of them is the string type.
Saving to Core Data
Import the Core Data module at the top of ViewController.swift:
//Add below "import UIKit" import CoreData |
You may have had to link frameworks manually in your project’s Build Phases if you’ve worked with Objective-C frameworks. In Swift, a simple import
statement is all you need to start using Core Data APIs in your code.
Next, replace the table view’s model with the following:
//Change [String] to [NSManagedObject] var people = [NSManagedObject]() |
You’ll be storing Person
entities rather than just names, so you rename the Array
that serves as the table view’s data model to people
. It now holds instances of NSManagedObject
rather than simple Swift strings.
NSManagedObject
represents a single object stored in Core Data—you must use it to create, edit, save and delete from your Core Data persistent store. As you’ll see shortly, NSManagedObject
is a shape-shifter. It can take the form of any entity in your data model, appropriating whatever attributes and relationships you defined.
Since you’re changing the table view’s model, you must also replace both data source methods you implemented earlier with the following to reflect these changes:
//Replace both UITableViewDataSource methods func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return people.count } func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("Cell") as UITableViewCell let person = people[indexPath.row] cell.textLabel!.text = person.valueForKey("name") as String? return cell } |
The most significant change to these methods occurs in cellForRowAtIndexPath
. Instead of matching cells with the corresponding string in the model array, you now match cells with the corresponding NSManagedObject
.
Note how you grab the name attribute from the NSManagedObject
. It happens here:
cell.textLabel.text = person.valueForKey("name") as String |
Why do you have to do this? As it turns out, NSManagedObject
doesn’t know about the name
attribute you defined in your data model, so there’s no way of accessing it directly with a property. The only way Core Data provides to read the value is key-value coding, commonly referred to as KVC.
Note: If you’re new to iOS development, you may not be familiar with key-value coding or KVC.
KVC is a mechanism in Cocoa and Cocoa Touch for accessing an object’s properties indirectly using strings to identify properties. In this case, KVC makes NSMangedObject
behave more or less like a dictionary.
Key-value coding is available to all classes that descend from NSObject
, including NSManagedObject
. You wouldn’t be able to access properties using KVC on a Swift object that doesn’t descend from NSObject
.
Next, replace the save action in the addName @IBAction
method with the following:
let saveAction = UIAlertAction(title: "Save", style: .Default) { (action: UIAlertAction!) -> Void in let textField = alert.textFields![0] as UITextField self.saveName(textField.text) self.tableView.reloadData() } |
This takes the text in the text field and passes it over to a new method called saveName
. Add saveName
to ViewController.swift, as shown below:
func saveName(name: String) { //1 let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate let managedContext = appDelegate.managedObjectContext! //2 let entity = NSEntityDescription.entityForName("Person", inManagedObjectContext: managedContext) let person = NSManagedObject(entity: entity!, insertIntoManagedObjectContext:managedContext) //3 person.setValue(name, forKey: "name") //4 var error: NSError? if !managedContext.save(&error) { println("Could not save \(error), \(error?.userInfo)") } //5 people.append(person) } |
This is where Core Data kicks in! Here’s what the code does:
-
Before you can save or retrieve anything from your Core Data store, you first need to get your hands on an
NSManagedObjectContext
. You can think of a managed object context as an in-memory “scratchpad” for working with managed objects.Think of saving a new managed object to Core Data as a two-step process: first, you insert a new managed object into a managed object context; then, after you’re happy with your shiny new managed object, you “commit” the changes in your managed object context to save it to disk.
Xcode has already generated a managed object context as part of the new project’s template – remember, this only happens if you check the Use Core Data checkbox at the beginning. This default managed object context lives as a property of the application delegate. To access it, you first get a reference to the app delegate.
-
You create a new managed object and insert it into the managed object context. You can do this in one step with
NSManagedObject
’s designated initializer:init(entity:insertIntoManagedObjectContext:)
.You may be wondering what an
NSEntityDescription
is all about. Recall that earlier, I calledNSManagedObject
a “shape-shifter” class because it can represent any entity. An entity description is the piece that links the entity definition from your data model with an instance ofNSManagedObject
at runtime. - With an
NSManagedObject
in hand, you set thename
attribute using key-value coding. You have to spell the KVC key (“name” in this case) exactly as it appears on your data model, otherwise your app will crash at runtime. - You commit your changes to
person
and save to disk by callingsave
on the managed object context. Note thatsave
takes one parameter, which is a pointer to anNSError
; if there is ever an error with the save operation, you can inspect the error and alert the user if necessary. - Congratulations! Your new managed object is now safely ensconced in your Core Data persistent store. Insert the new managed object into the
people
array so that it shows up in the table view when it reloads.
That’s a little more complicated than an array of strings, but not too bad. Some of the code here—getting the managed object context and entity¬—could be done just once in your own init
or viewDidLoad
and then reused later. For simplicity, you’re doing it all at once in one method.
Build and run the app, and add a few names to the table view:
If the names are actually stored in Core Data, the HitList app should pass the persistence test. Double-tap the Home button to bring up the fast app switcher. Terminate the HitList app by flicking it upwards.
From Springboard, tap the HitList app to trigger a fresh launch. Wait, what happened? The table view is empty:
You saved to Core Data, but after a fresh app launch, the people
array is empty! The data is actually sitting there waiting, but you haven’t fetched it yet.
Fetching from Core Data
To get data from your persistent store and into the managed object context, you have to fetch it. Add the following method to ViewController.swift:
override func viewWillAppear(animated: Bool) { super.viewWillAppear(animated) //1 let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate let managedContext = appDelegate.managedObjectContext! //2 let fetchRequest = NSFetchRequest(entityName:"Person") //3 var error: NSError? let fetchedResults = managedContext.executeFetchRequest(fetchRequest, error: &error) as [NSManagedObject]? if let results = fetchedResults { people = results } else { println("Could not fetch \(error), \(error!.userInfo)") } } |
Step by step, this is what the code does:
- As mentioned in the previous section, before you can do anything with Core Data, you need a managed object context. Fetching is no different! You pull up the application delegate and grab a reference to its managed object context.
- As the name suggests,
NSFetchRequest
is the class responsible for fetching from Core Data. Fetch requests are both powerful and flexible. You can use requests to fetch a set of objects that meet particular criteria (e.g., “give me all employees that live in Wisconsin and have been with the company at least three years”), individual values (e.g., “give me the longest name in the database”) and more.
Fetch requests have several qualifiers that refine the set of results they return. For now, you should know thatNSEntityDescription
is one of these qualifiers (one that is required!).Setting a fetch request’s
entity
property, or alternatively initializing it withinit(entityName:)
, fetches all objects of a particular entity. This is what you do here to fetch allPerson
entities. - You hand the fetch request over to the managed object context to do the heavy lifting.
executeFetchRequest(_:error:)
returns an optional array of managed objects that meets the criteria specified by the fetch request.
Note: If there are no objects that match the fetch request’s criteria, the method returns an optional value containing an empty array.
If an error occurred during the fetch, the method returns an optional value that contains nil. If this happens, you can inspect the NSError
and respond appropriately.
Build and run the application once again. Immediately, you should see the list of names you added earlier:
Great! They’re back from the dead. Add a few more names to the list and restart the app to verify that saving and fetching are working properly. Short of deleting the app, resetting the Simulator or throwing your phone off a tall building, the names will appear in the table view no matter what.
Where To Go From Here?
In this tutorial you experienced several fundamental Core Data concepts: data models, entities, attributes, managed objects, managed object contexts and fetch requests. Here is the completed HitList project, fully integrated with Core Data.
There were a few rough edges in the HitList app: you had to get the managed object context from the app delegate each time, and you used KVC to access the attributes rather than a more natural object-style person.name. As you can see, Core Data is a large and extensive topic. There’s a lot more to learn!
If you’d like to learn more about creating Core Data applications using Swift, check out our book Core Data by Tutorials where you can go deeper into more advanced Core Data topics such as iCloud/Core Data integration, versioning and migration.
If you have any questions or comments on this tutorial, please join the forum discussion below!
Your First Core Data App Using Swift is a post from: Ray Wenderlich
The post Your First Core Data App Using Swift appeared first on Ray Wenderlich.