Core Data in Swift Tutorial (Part 3)

 

This post compatible with Xcode 6.1, Updated on October 30, 2014

This is part three of a tutorial series covering the usage of Core Data in Swift to write iOS apps with persistence. If you haven’t read part one yet, read that first.

If you really want to get your feet wet, my Swift book which is now available for pre-order with early access.


In this (somewhat lengthy) section of the tutorial, we’ll implement deleting rows, adding rows, and persisting the data so it stays put even after app close. When we’re done we’ll have something that works like the video here:

Implementing swipe-to-delete

Before we get started, let’s go ahead and wipe out the filtering we were toying around with in fetchLog(). We’ll just simplify fetchLog to look like this:

func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    
    // Create a sort descriptor object that sorts on the "title"
    // property of the Core Data object
    let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
    
    // Set the list of sort descriptors in the fetch request,
    // so it includes the sort descriptor
    fetchRequest.sortDescriptors = [sortDescriptor]
    
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

First, we just need to implement the canEditRowAtIndexPath callback from the UITableViewDataSource protocol. In our case, we’ll be able to delete anything, so we can just return true all the time. But, if for example we had some entries locked, we could check the indexPath and return false instead.

func tableView(tableView: UITableView, canEditRowAtIndexPath indexPath: NSIndexPath) -> Bool {
    return true
}

Now, this tells the UITableView that we can edit these rows (deleting a row is a form of editing it.) But iOS will look for one more callback, tableView:commitEditingStyle:forRowAtIndexPath.

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
    if(editingStyle == .Delete ) {
        // Find the LogItem object the user is trying to delete
        let logItemToDelete = logItems[indexPath.row]
        
        // Delete it from the managedObjectContext
        managedObjectContext?.deleteObject(logItemToDelete)
        
        // Refresh the table view to indicate that it's deleted
        self.fetchLog()
        
        // Tell the table view to animate out that row
        [logTableView .deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)]
    }
}

This method gets called on the UITableViewDataSource as well, and it’s called upon performing the swipe-to-delete action on iOS. Without this method implemented swipe-to-delete will simply be disabled.

So, we add it in here and we take action based on the deleted row. When the editingStyle is .Delete, we know that the intention is the delete the row. We identify the row data from our logItems array by accessing the indexPath.row index (this is the integer row number of the deleted item in the table view)

Next, we delete the object in Core Data using the deleteObject method on the managedObjectContext we computed earlier.

Now, we can update the table view itself by calling self.fetchLog() again. We could stop here and called reloadData() on the tableview, but instead we can use the deleteRowsAtIndexPaths:withRowAnimation: method of the table view to get some free table view animations.

Try running the app now, and you can swipe left on a table view row to delete it. You’ll get some free animations, and this data is updated in our Core Data object graph.

Being able to delete objects is great, but we also need to be able to add items if this is going to be a real app. Let’s make an adjustment to our viewDidLoad() code.

We have a line in our code right now setting the height of the tableview:

// Reduce the total height by 20 points
viewFrame.size.height -= 20

If we’re going to add a button we’ll just to adjust this even further, by the amount of the button height, and create the button. So we’ll set that up now:

// Add in the "+" button at the bottom
let addButton = UIButton(frame: CGRectMake(0, UIScreen.mainScreen().bounds.size.height - 44, UIScreen.mainScreen().bounds.size.width, 44))
addButton.setTitle("+", forState: .Normal)
addButton.backgroundColor = UIColor(red: 0.5, green: 0.9, blue: 0.5, alpha: 1.0)
addButton.addTarget(self, action: "addNewItem", forControlEvents: .TouchUpInside)
self.view.addSubview(addButton)

// Reduce the total height by 20 points for the status bar, and 44 points for the bottom button
viewFrame.size.height -= (20 + addButton.frame.size.height)

First, we create a UIButton instance, and set it’s size to be equal to 44×44 points. The position will be at an x coordinate of 0, and a y coordinate of whatever the screen height is, minus the size of the button (44 points) This puts it at the bottom of the screen.
We set the title to simply “+” to indicate adding a new item, set the background color, and add a target.

The target is a selector that fires when the button is clicked. In our case, the selector is called “addNewItem”, which means we need to create this as a function…

let addItemAlertViewTag = 0
let addItemTextAlertViewTag = 1
func addNewItem() {
    var titlePrompt = UIAlertView(title: "Enter Title", message: "Enter Text", delegate: self, cancelButtonTitle: "Cancel", otherButtonTitles: "Done")
    titlePrompt.alertViewStyle = .PlainTextInput
    titlePrompt.tag = addItemAlertViewTag
    titlePrompt.show()
}

This function will create a UIAlertView object with a text field. We can use this to take input from the user without adding too much more view work. We are specifying here that the delegate of the UIAlertView is self, so we need to add the UIAlertViewDelegate protocol to our class…

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate, UIAlertViewDelegate {
...

We can implement the UIAlertViewDelegate method alertView:clickedButtonAtIndex: in order to determine which button was pressed, like so…

// MARK: UIAlertViewDelegate
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
    let cancelButtonIndex = 0
    let saveButtonIndex = 1

    switch (buttonIndex, alertView.tag) {
    case (saveButtonIndex, addItemAlertViewTag):
        println("Save new item")
    default:
        println("Default case, do nothing")
    }
}

The index of the cancel button is 0, and the done button is 1. How do you determine that? Well, you could memorize it… but what’s easier is just to say println(“\(buttonIndex)”) and look at what happens when you press the buttons yourself.

So, we just set up a switch that checks the tuple (buttonIndex, alertView.tag). Using Swift’s pattern matching we can check for the case where the user has pressed the saveButtonIndex in the alert view we’ve designated to have the tag addItemAlertViewTag. The purpose of checking the tag like this is so we can have multiple UIAlertView instances, and still identify them by using the tag element. This is a common pattern in iOS development.

Okay, so now that we can detect the save button, we can call a function to save the new item. We’ll call it saveNewItem:title

func saveNewItem(title : String) {
    // Create the new  log item
    var newLogItem = LogItem.createInManagedObjectContext(self.managedObjectContext!, title: title, text: "")
    
    // Update the array containing the table view row data
    self.fetchLog()
    
    // Animate in the new row
    // Use Swift's find() function to figure out the index of the newLogItem
    // after it's been added and sorted in our logItems array
    if let newItemIndex = find(logItems, newLogItem) {
        // Create an NSIndexPath from the newItemIndex
        let newLogItemIndexPath = NSIndexPath(forRow: newItemIndex, inSection: 0)
        // Animate in the insertion of this row
        logTableView.insertRowsAtIndexPaths([ newLogItemIndexPath ], withRowAnimation: .Automatic)
    }
}

Our method will take in the title as the only argument, and use the createInManagedObjectContext function we created earlier to add a new record with the title of title. For now, we can leave the text blank.

Similar to how we did before with the deletion, we need to call fetchLog() to update our table view’s backing array, logItems.

Finally, we can create our NSIndexPath for the new item, and called insertRowsAtIndexPaths to animate in the new row.

Now, we just need to call the new method from the UIAlertViewDelegate method clickedButtonAtIndex, when the user select the Done (save) button.

// MARK: UIAlertViewDelegate
func alertView(alertView: UIAlertView, clickedButtonAtIndex buttonIndex: Int) {
    let cancelButtonIndex = 0
    let saveButtonIndex = 1

    switch (buttonIndex, alertView.tag) {
    case (saveButtonIndex, addItemAlertViewTag):
        if let alertTextField = alertView.textFieldAtIndex(0) {
            println("Save new item \(alertTextField.text)")
            saveNewItem(alertTextField.text)
        }
    default:
        println("Default case, do nothing")
    }
}

Running the app you can now add and delete rows to our table, backed by Core Data. If you close the app and restart it, you may notice that the data is reset every time. This is happening because Core Data doesn’t save automatically, you must explicitly call it. So as a final step in this tutorial section, let’s create a save() method in our ViewController.swift file.

func save() {
    var error : NSError?
    if(managedObjectContext!.save(&error) ) {
        println(error?.localizedDescription)
    }
}

The API is pretty simple just call save() on the managedObjectContext. Optionally you can include an NSError pointer to capture errors. The save() function returns true on success, and false on failure. If the save succeeds, your data will now persist.

Let’s call our save() method after the user adds or removes an item.

func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
...
    // Tell the table view to animate out that row
     logTableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
     save()
...
func saveNewItem(title : String) {
...
    if let newItemIndex = find(logItems, newLogItem) {
        // Create an NSIndexPath from the newItemIndex
        let newLogItemIndexPath = NSIndexPath(forRow: newItemIndex, inSection: 0)
        // Animate in the insertion of this row
        logTableView.insertRowsAtIndexPaths([ newLogItemIndexPath ], withRowAnimation: .Automatic)
        save()
...
}

This concludes part 3 of the Core Data tutorial for now. If it is frequently requested I may revisit this some more. As always, be sure to subscribe to my newsletter for tutorial updates and more, and take a look at my upcoming book, which is now in early access.

Got a question or issue?

Join us on our new forums.

Sharing is caring :)

 
Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials. Available for pre-order today at a 50% discount.

Early Access Available Now Via PayPal


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

Core Data in Swift Tutorial (Part 2)

 

This post compatible with Xcode 6.1, Updated on October 30, 2014

This is part two of a tutorial series covering the usage of Core Data in Swift to write iOS apps with persistence. If you haven’t read part one yet, read that first.

If you really want to get your feet wet, my Swift book which is now available for pre-order with early access.

Creating more records

First off, if we’re going to do anything more interesting than just showing a single record pop up in an alert, we’ll need to generate some more records to play with. Let’s open up our LogItem.swift file and add a conveinence function for adding new records.

This will be a class method so instead of typing this:

let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
newItem.title = "Wrote Core Data Tutorial"
newItem.itemText = "Wrote and posted a tutorial on the basics of Core Data to blog."

We can type something more like this:

let newItem = LogItem.createInManagedObjectContext(managedObjectContext, "Item Title", "Item text")

The LogItem class is generic for any managedObjectContext, so we want to make sure we aren’t storing that anywhere in the model, it needs to be passed in when we want to create an object like this.

Okay so let’s implement the method…

class func createInManagedObjectContext(moc: NSManagedObjectContext, title: String, text: String) -> LogItem {
    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: moc) as LogItem
    newItem.title = title
    newItem.itemText = text
    
    return newItem
}

The first line is the function definition. It’s a class function called createInManagedObjectContext, which takes an object of type NSManagedObjectContext as the argument “moc”, a String called title, and a String called text. The function returns a LogItem object that’s been inserted in to the specified managed object context.

Then it executes nearly identical code as before to create a new LogItem object, except now it’s using the arguments passed in to the function to set up the new LogItem object.

We can replace our original code in ViewController.swift now to just use the new method. Let’s add a bunch of new items…

override func viewDidLoad() {
    super.viewDidLoad()
    
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "1st Item", text: "This is my first log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "2nd Item", text: "This is my second log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "3rd Item", text: "This is my third log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "4th Item", text: "This is my fourth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "5th Item", text: "This is my fifth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "6th Item", text: "This is my sixth log item")
    
    presentItemInfo()
}


If you already know how to set up a UITableView programmatically and want to skip ahead to the Core Data stuff, click here.

Now that we have a couple of records, let’s remove presentItemInfo and instead opt for a table view here. We’ll add this all right under viewDidLoad and programmatically create the UITableView. In my iTunes tutorial we do this using storyboards. If you are more interested in working with storyboards I recommend taking a pit stop there to read about how to get that set up.

We’ll set up the tableView by adding a logTableView to the ViewController class, and set it all up in viewDidLoad()

// Create the table view as soon as this class loads
var logTableView = UITableView(frame: CGRectZero, style: .Plain)

override func viewDidLoad() {
    super.viewDidLoad()
    
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "1st Item", text: "This is my first log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "2nd Item", text: "This is my second log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "3rd Item", text: "This is my third log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "4th Item", text: "This is my fourth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "5th Item", text: "This is my fifth log item")
    LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "6th Item", text: "This is my sixth log item")
    
    
    // Now that the view loaded, we have a frame for the view, which will be (0,0,screen width, screen height)
    // This is a good size for the table view as well, so let's use that
    // The only adjust we'll make is to move it down by 20 pixels, and reduce the size by 20 pixels
    // in order to account for the status bar
    
    // Store the full frame in a temporary variable
    var viewFrame = self.view.frame
    
    // Adjust it down by 20 points
    viewFrame.origin.y += 20
    
    // Reduce the total height by 20 points
    viewFrame.size.height -= 20
    
    // Set the logTableview's frame to equal our temporary variable with the full size of the view
    // adjusted to account for the status bar height
    logTableView.frame = viewFrame
    
    // Add the table view to this view controller's view
    self.view.addSubview(logTableView)
    
    // Here, we tell the table view that we intend to use a cell we're going to call "LogCell"
    // This will be associated with the standard UITableViewCell class for now
    logTableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "LogCell")
    
    // This tells the table view that it should get it's data from this class, ViewController
    logTableView.dataSource = self
    
}

Since we set the dataSource to be our ViewController class, we also need to adhere to the UITableViewDataSource protocol, so add that to the ViewController’s class definition:

class ViewController: UIViewController, UITableViewDataSource {

…and add the actual dataSource methods…

// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 5
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as UITableViewCell
    cell.textLabel.text = "\(indexPath.row)"
    return cell
}

Still with me? Hope so… if not here’s the full ViewController.swift code up until this point:

import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDataSource {

    lazy var managedObjectContext : NSManagedObjectContext? = {
        let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        if let managedObjectContext = appDelegate.managedObjectContext {
            return managedObjectContext
        }
        else {
            return nil
        }
    }()
    
    // Create the table view as soon as this class loads
    var logTableView = UITableView(frame: CGRectZero, style: .Plain)
    
    override func viewDidLoad() {
        super.viewDidLoad()

        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "1st Item", text: "This is my first log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "2nd Item", text: "This is my second log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "3rd Item", text: "This is my third log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "4th Item", text: "This is my fourth log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "5th Item", text: "This is my fifth log item")
        LogItem.createInManagedObjectContext(self.managedObjectContext!, title: "6th Item", text: "This is my sixth log item")
        
        // Now that the view loaded, we have a frame for the view, which will be (0,0,screen width, screen height)
        // This is a good size for the table view as well, so let's use that
        // The only adjust we'll make is to move it down by 20 pixels, and reduce the size by 20 pixels
        // in order to account for the status bar
        
        // Store the full frame in a temporary variable
        var viewFrame = self.view.frame
        
        // Adjust it down by 20 points
        viewFrame.origin.y += 20
        
        // Reduce the total height by 20 points
        viewFrame.size.height -= 20
        
        // Set the logTableview's frame to equal our temporary variable with the full size of the view
        // adjusted to account for the status bar height
        logTableView.frame = viewFrame
        
        // Add the table view to this view controller's view
        self.view.addSubview(logTableView)
        
        // Here, we tell the table view that we intend to use a cell we're going to call "LogCell"
        // This will be associated with the standard UITableViewCell class for now
        logTableView.registerClass(UITableViewCell.classForCoder(), forCellReuseIdentifier: "LogCell")
        
        // This tells the table view that it should get it's data from this class, ViewController
        logTableView.dataSource = self
        
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
    
    // MARK: UITableViewDataSource
    func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 5
    }
    
    func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as UITableViewCell
        cell.textLabel.text = "\(indexPath.row)"
        return cell
    }

}

This will give us a numbered list of rows if we run the app. This just confirms the table view is set up correctly.

Now that we have this all set up and ready to go, we can start working on getting our log data showing up in the table view.

Please note that at this point in the tutorial, I’m intentionally avoiding the NSFetchedResultsController class. I’m doing this because I believe it will make more sense from the Core Data perspective the first time around if you see it done the old fashioned way. After this tutorial is over, I encourage you to look over the class and see how you might use it to implement some of these things instead. I think it is important to first learn how to implement a core data backed table view without using the helper class. The helper class is not applicable in all situations, and learning it first would be doing yourself a disservice. You’ll find it doesn’t work for all use cases (not even close), and in it’s magic it hides some of what’s going on.

First off, let’s fetch all the results from Core Data in viewDidLoad(), and store them in an array of LogItem’s, logItems.

First we’ll add the variable to the ViewController class:

// Create an empty array of LogItem's
var logItems = [LogItem]()

Next, we’ll populate it from viewDidLoad(), inside of a function called fetchLog().

override func viewDidLoad() {
    ...
    fetchLog()
}

func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Now we can modify the tableView dataSource methods to refer to this array instead of hard-coding values.

// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    // How many rows are there in this section?
    // There's only 1 section, and it has a number of rows
    // equal to the number of logItems, so return the count
    return logItems.count
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCellWithIdentifier("LogCell") as UITableViewCell
    
    // Get the LogItem for this index
    let logItem = logItems[indexPath.row]
    
    // Set the title of the cell to be the title of the logItem
    cell.textLabel.text = logItem.title
    return cell
}

This should set us up to see the items listed in the table view, but we want to show the text of an item when it’s clicked. So we need to set the table view to use this view controller as it’s delegate, so we can receive the callback method, didSelectRowAtIndexPath.

Similar to before, add the UITableViewDelegate protocol to the class…

class ViewController: UIViewController, UITableViewDataSource, UITableViewDelegate {

And set the delegate to be self, you can do this right under where you set the dataSource in viewDidLoad…

// This tells the table view that it should get it's data from this class, ViewController
logTableView.dataSource = self
logTableView.delegate = self

Finally we can implement the method, knowing the Table View will call it when a cell is clicked…

// MARK: UITableViewDelegate
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
    let logItem = logItems[indexPath.row]
    let alert = UIAlertView(title: logItem.title, message: logItem.itemText, delegate: nil, cancelButtonTitle: nil, otherButtonTitles: "OK")
    alert.show()
}

This will set us up so that when the button is clicked, a message will pop up showing the logItem.title message.

 

The purpose of this tutorial is not really to explain how to set up a table view manually, but it’s sort of necessary in order to get a good look at the data. For this reason, I’m providing the source code up until this point in the tutorial here. Feel free to simply check this out and work from this code base, from here we’ll be talking more about the Core Data side of things.
https://github.com/jquave/Core-Data-In-Swift-Tutorial/tree/Part2.5

You may see something kind of like this now:

Your ordering may be different, this is because it’s pretty much random. In some data stores you might get this data back in the same order as it was inserted, but with Core Data it ends up being fairly random. Let’s change this by adding a sort to the fetch request.

func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    
    // Create a sort descriptor object that sorts on the "title"
    // property of the Core Data object
    let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
    
    // Set the list of sort descriptors in the fetch request,
    // so it includes the sort descriptor
    fetchRequest.sortDescriptors = [sortDescriptor]
    
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

Now the fetch request has it’s sortDescriptors property set. Note that this is an array, which is why we need the brackets around the single sortDescriptor we created using the title key. Running the app you should now see the sorted (alphabetically) list of items, much better.

Let’s also look at filtering out certain elements. First, let’s just try and only get the “1st Item”. We’ll create an NSPredicate that uses a string to represent the requirement that any object must fulfill in order to pass through the query.

func fetchLog() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    
    // Create a sort descriptor object that sorts on the "title"
    // property of the Core Data object
    let sortDescriptor = NSSortDescriptor(key: "title", ascending: true)
    
    // Set the list of sort descriptors in the fetch request,
    // so it includes the sort descriptor
    fetchRequest.sortDescriptors = [sortDescriptor]
    
    // Create a new predicate that filters out any object that
    // doesn't have a title of "1st Item" exactly.
    let predicate = NSPredicate(format: "title == %@", "1st Item")
    
    // Set the predicate on the fetch request
    fetchRequest.predicate = predicate
    
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        logItems = fetchResults
    }
}

If you haven’t seen the format syntax yet, or haven’t seen it in a while, it’s simple enough to say that any time you see format as a named parameter, it comes from the Objective-C methods “predicateWithFormat”, or “stringWithFormat”, and so on. This replaces any instances of the %@ symbol with an object’s description (the value of a string, or an otherwise useful representation of other types of objects). For primitive types such as an Int, you’ll want to instead opt for %i, %f for Float, and so on.

So when you see
(format: “title == %@”, “1st Item”)

What the compiler sees is something like this:
(“title == ’1st Item’”)

So we’re just specifying we want title to equal that exact string.

Running the app now we should see just our first item.

We could also do a string comparison using the contains keyword, if we look for the substring “th” we’ll only get the 4th, 5th, and 6th items.

// Search for only items using the substring "th"
let predicate = NSPredicate(format: "title contains %@", "th")

// Set the predicate on the fetch request
fetchRequest.predicate = predicate

What if we wanted to combine the two though? We want both items containing the string “th” and any one with a title “1st Item”?

First, let’s create the two separate predicates:

// Create a new predicate that filters out any object that
// doesn't have a title of "1st Item" exactly.
let firstPredicate = NSPredicate(format: "title == %@", "1st Item")

// Search for only items using the substring "th"
let thPredicate = NSPredicate(format: "title contains %@", "th")

Then combine them using the NSCompoundPredicate constructor:

// Combine the two predicates above in to one compound predicate
let predicate = NSCompoundPredicate(type: NSCompoundPredicateType.OrPredicateType, subpredicates: [firstPredicate, thPredicate])

// Set the predicate on the fetch request
fetchRequest.predicate = predicate
ERROR: Cannot invoke 'init' with an argument list of type '(type: NSCompoundPredicateType, subpredicates: $T3)'

Whoops! What the heck does that mean?
Well, it is saying that we’re passing in the wrong types, and the reason why is because although the first argument is of the correct type, NSCompoundPredicateType, the second type is supposed to be [AnyObject]!, and instead we’re passing in [AnyObject]?

The reason this happens is because firstPredicate, and thPredicate are formed from the NSPredicate method, which returns an Optional.

It’s simple enough to unwrap the two variables to work with, and just assume they have values. But, that’s sort of sloppy and not really good form. So instead, we’ll use optional binding to check that these values are both available.

if let fp = firstPredicate {
    if let thp = thPredicate {
        // Combine the two predicates above in to one compound predicate
        let predicate = NSCompoundPredicate(type: NSCompoundPredicateType.OrPredicateType, subpredicates: [fp, thp])
        
        // Set the predicate on the fetch request
        fetchRequest.predicate = predicate
    }
}

Since we want both cases of the “1st Item” and the 4-6 items, we use a compound predicate type of NSCompoundPredicateType.OrPredicateType

All this is just a confusing way of saying, “give me any items where the firstPredicate or the thPredicate is true.”

What we did here is quite powerful in practice. We can use string comparison as predicates, and filter/order large lists of data through Core Data. The Core Data API will then connect to the backing store (SQLite) and produce an efficient query to quickly retrieve the needed information. This is a very common pattern in iOS development, and understanding it is very important. So, if you got stuck on this tutorial or have any questions, don’t hesitate to ask for help on the forums.

This concludes Part 2 for now, in the next section we’ll move in to a more applicable scenario by adding a way for user’s to create log items, edit, save, and delete them.

The complete source code to this section »

Go to part 3 ->

If you’re reading this scratching your head, or just want a deeper dive in to Core Data, be sure to look at my Swift book, which is now available for early access. And don’t forget to subscribe to get updates on this tutorial series via e-mail.

Got a question or issue?

Join us on our new forums.

Sharing is caring :)

 
Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials. Available for pre-order today at a 50% discount.

Early Access Available Now Via PayPal


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS

Core Data in Swift Tutorial (Part 1)

 

This post compatible with Xcode 6.1, Updated on October 30, 2014

Core Data is the de facto standard way to persist and manage data in both iPhone and Mac applications. So it’s only natural that we should take some time to learn about it when building apps. Eager to see what we’ll have created by the end of this tutorial? Take a look at the video, we’ll be creating this table view, populating it with data, adding the ability to delete records, add records, and sort/search records all backed by Core Data. This data is persistent and lasts even through a complete shut down of your phone.

The first thing to know about Core Data before diving in is that it is not a relational database, and although it uses SQLite as a backing engine, is not an ORM to a relational database either. The SQLite backend is more of an implementation detail, and in fact binary files or plists can be used instead.

The official Apple documentation describes Core Data like this:

“The Core Data framework provides generalized and automated solutions to common tasks associated with object life-cycle and object graph management, including persistence.”
[developer.apple.com]

Before we get too technical about what Core Data is, I think it’s useful to dive in and start playing with the API a bit.

Create a new Xcode 6 project using a single-view template, Swift as the language, and with Core Data enabled. I’ll call the project MyLog.

Looking at the AppDelegate.swift file you’ll notice using this option has added quite a few functions. Most of these are setting up the Core Data stack and the defaults are fine for now. The primary object that needs to be used to work with Core Data is the managedObjectContext defined here.

lazy var managedObjectContext: NSManagedObjectContext? = {
    // Returns the managed object context for the application (which is already bound to the persistent store
    // coordinator for the application.) This property is optional since there are legitimate error
    // conditions that could cause the creation of the context to fail.
    let coordinator = self.persistentStoreCoordinator
    if coordinator == nil {
        return nil
    }
    var managedObjectContext = NSManagedObjectContext()
    managedObjectContext.persistentStoreCoordinator = coordinator
    return managedObjectContext
}()

For the most part, all you really need to know about this though, is that managedObjectContext is a lazily computed variable on AppDelegate. Knowing this we can find it from our ViewController.swift file. For example in viewDidLoad() of ViewController.swift, we can use this code to print the managedObjectContext’s description to the console.

First we need to set up a lazily loaded variable in the ViewController.swift file that can help us get the managedObjectContext from the app delegate using the UIApplication.sharedApplication().delegate property. Note that you’ll need to import CoreData in order to use the NSManagedObjectContext class name here.

import UIKit
import CoreData

class ViewController: UIViewController {

    lazy var managedObjectContext : NSManagedObjectContext? = {
        let appDelegate = UIApplication.sharedApplication().delegate as AppDelegate
        if let managedObjectContext = appDelegate.managedObjectContext {
            return managedObjectContext
        }
        else {
            return nil
        }
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        println(managedObjectContext!)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

The managedObjectContext variable is computed using the existing managedObjectContext in the application’s delegate. In viewdidLoad() we cause this variable to be computed by printing it to the console. If your application is set up right you should see something like this:

<NSManagedObjectContext: 0x7fff39c3b790>

You’ll notice the Xcode template produced an additional file, MyLog.xcdatamodeld.

Opening up this file you can see the Core Data model editor.

Let’s add a new Core Data entity called LogItem. Our log app will show a list of LogItems, which have a bit of text in them.

Click the “Add Entity” button and then in the right-hand panel select the “Data Model Inspector”. From here we can rename the default name, Entity, to by LogItem.

Next, at the bottom we can add our first attribute by pressing the “+ Atrribute” button at the bottom.

Name this attribute title, and give it a type of String. We’ll also add a second attribute of type String called itemText.

Now that we have our first Entity created, we want to also be able to directly access this entity as a class in our code. Xcode provides an automated tool to do this. In the menubar select Editor->Create NSManagedObject Subclass…

In the first prompt, check the MyLog model and press next. Then, check the LogItem entity, and press next again.
A file save window should appear with an option to specify the language as Swift, select this. Finally hit Create, and you should now see a LogItem.swift file added.

import Foundation
import CoreData

class LogItem: NSManagedObject {
    @NSManaged var title: String
    @NSManaged var itemText: String
}

This class is generated from the xcdatamodeld file. The entity we created is represented by the similarly named class LogItem, and the attributes are turned in to variables using the @NSManaged identifier, which gives the variables special treatment allowing them to operate with Core Data. For most intents and purposes though, you can just think of these as your typical properties.

Because of the way Swift modules work, we need to make one modification to the core data model. In the field “Class” under the data model inspector for our entity, LogItem, we need to specify the project name as a prefix to the class name. So instead of just specifying “LogItem” as the class, it needs to say “MyLog.LogItem”.

In our ViewController.swift file in the viewDidLoad method, let’s instantiate some instances of LogItem. There are many ways to do this, but the least verbose is to use the insertNewObjectForEntityForName method of NSEntityDescription.

override func viewDidLoad() {
    super.viewDidLoad()

    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
}

Here we insert a new object in to the core data stack through the managedObjectContext that the template function added to AppDelegate for us. This method returns an NSManagedObject which is a generic type of Core Data object that responds to methods like valueForKey. If you don’t quite understand what that means, don’t worry too much about it, let’s keep moving.

With an NSManagedObject version of newItem, we could say newItem.valueForKey(“title”) to get the title. But this is not the best approach because it leaves too much opportunity to mis-type an attribute name, or get the wrong object type unexpectedly and have hard crashes trying to access these attributes.

So, in our case, we instead cast the NSManagedObject that insertNewObjectForEntityForName returns, to our generated class LogItem.

What this means, simply put, is that we can set the title and itemText like this:

override func viewDidLoad() {
    super.viewDidLoad()
    
    let newItem = NSEntityDescription.insertNewObjectForEntityForName("LogItem", inManagedObjectContext: self.managedObjectContext!) as LogItem
    
    newItem.title = "Wrote Core Data Tutorial"
    newItem.itemText = "Wrote and post a tutorial on the basics of Core Data to blog."
    presentItemInfo()
}

If we had not generated our LogItem.swift file earlier, the type LogItem would not be defined and we would be limited to working only with NSManagedObject types. This is a really nasty way to work with the Core Data API as it relies heavily on determining object classes, entities, state, and more at runtime based on string comparisons, yuck!

Now that we’ve created a new item, and set both it’s title and text, we can query it elsewhere in our app and get the object back. At the end of viewDidLoad() call the method, presentItemInfo, we’ll implement it now:

func presentItemInfo() {
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        
        let alert = UIAlertView()
        alert.title = fetchResults[0].title
        alert.message = fetchResults[0].itemText
        alert.show()
    }
}

First, we create a new NSFetchRequest instance using the entity LogItem.
Next, we create a fetchResults variable by using the executeFetchRequest method of managedObjectContext. The only thing we have specified in our fetchRequest is the entity, so this particular fetch just returns every record.
Next we create an empty UIAlertView object to present a message to the screen, and set it’s title and message properties to the title and itemText properties of the first LogItem in our fetch results (which is an array of LogItem’s).

Run the app and you should see the item presented to the screen. You’ve now stored and retrieved data from Core Data. This is the first step in to building apps with persistent data.

In part 2, we’ll talk about working with multiple records and using NSPredicate to perform filtered requests.

Go To Part 2 Now »

The full source code to this part is available here on Github.

If you found this post useful, check out my other tutorials, and take a look at my Swift eBook, which is now available for early access to anyone who pre-orders via PayPal.

Got a question or issue?

Join us on our new forums.

Sharing is caring :)

 
Developing iOS 8 Apps in Swift
An upcoming ebook detailing everything you need to know to produce marketable apps for iOS 8 using swift.
Learn to produce real world applications through tutorials. Available for pre-order today at a 50% discount.

Early Access Available Now Via PayPal


Sign up now and get a set of FREE video tutorials on writing iOS apps coming soon.



Subscribe via RSS