Open Source Swift – A Look At The Top Swift Repositories

Github, the most popular open source repository for open source software, offers a feature that let’s us view repositories by language. In this post, I want to dissect some of the most popular Swift repositories as of June 5th, 2015. So, let’s kick it off with the most starred Swift repository, Alamofire.

Alamofire

Alamofire is an “Elegant HTTP Networking in Swift”, written by Matt Thompson, a well known Objective-C developer responsible for the AFNetworking library. The library takes the built-in iOS networking features and abstracts them away in to a simpler, and more Swift-y API.

Take for example the case of a simple GET request that returns JSON:

Alamofire.request(.GET, "http://httpbin.org/get")
         .responseString { (_, _, string, _) in
                  println(string)
         }

It’s the most popular library for Swift to date, so you should probably be using it, right?

Well, maybe… The library makes some common tasks simpler and less verbose, but if you don’t know the basics of networking in Swift (or Objective-C), it’s probably best to get a good understanding of the existing APIs first. After understanding what’s going on under the hood, you can make a more informed decision about whether or not you need the extra layer of abstraction. Alamofire is a big framework, and networking is a huge topic, so I don’t want to get too far in to the details on this library. But, suffice to say if you are working with complex networking requests with lots of back and forth with a web server, and/or working with a complicated authentication process, using Alamofire might reduce some of the repetitive coding tasks. If you are new to iOS development, I would recommend just stick to the APIs provided by Apple for now.

SwiftyJSON

SwiftyJSON is “The better way to deal with JSON data in Swift” according to it’s Github entry. This framework is one of the first I saw when Swift was first released, that combined with the fact that JSON parsing is such a common problem is how it became a top repository, helping to deal with the messiness of Apple’s built-in JSON parser. In particular, the static typing of Swift and optional syntax led to a lot of verbose JSON parsing code, guessing and checking each step of the way for each key and checking every cast. The truth is though, using this library is VERY similar to just using optional chaining and Swift’s normal casting syntax. There is not much benefit here, and from what I’ve seen in production, SwiftyJSON has some performance problems, as a result I’m not sure I would recommend using it right now, except in prototype apps, or during the learning phase.

Take a look at the example they give as the standard approach to parsing JSON, which they describe as “not good”:

let JSONObject: AnyObject? = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil)

if let statusesArray = JSONObject as? [AnyObject],
   let status = statusesArray[0] as? [String: AnyObject],
   let user = status["user"] as? [String: AnyObject],
   let username = user["name"] as? String {
    // Finally we got the username
}

They then present their alternative version of the syntax:

let json = JSON(data: dataFromNetworking)
if let userName = json[0]["user"]["name"].string{
  //Now you got your value
}

There’s a few issues here, first of which is that the simplifications they are showing are partially just taking advantage of language features that would actually work with the regular parser. Second, it seems like their example actually would not work.

Based on the example code shown above, the example JSON they are parsing looks something like this:

{
    "statuses": [
        {
            "user": {
                "name": "Bob"
            }
        }
    ]
}

One issue with this sample right off the bat is that they are casting the initial value of the JSON to an array, which would suggest that the root element is an array, which is invalid JSON. The type of the root object in valid JSON is always going to be a key/value. Or equivalently in Swift, a Dictionary of type [String:AnyObject]. Additionally, it’s good practice to actually check if the JSON parsing succeeded or not.

Once we start going through and fixing all the issues with the sample code, assuming we want to explicitly cast everything as they have shown, we end up with something like this:

if let JSONObject = NSJSONSerialization.JSONObjectWithData(data, options: nil, error: nil) as? [String:AnyObject],
    statusesArray = JSONObject["statuses"] as? [[String:AnyObject]] {
        let status = statusesArray[0] as [String: AnyObject]
        if let user = status["user"] as? [String: AnyObject],
            let username = user["name"] as? String {
                println("username: \(username)")
        }
}
else {
    println("Failed to parse JSON, handle this problem")
}

Now, this is in fact pretty verbose, and could be reduced quite a bit, but let’s stop a think about what we’re doing here. In this example, we are trying to download a list of statuses which are owned by users, and they have names. In an actual Swift application, I would expect there to be a model representing these objects. Maybe something like this:

struct User {
    let name: String
}
struct Status {
    let user: User
}

Assume we are using SwiftyJSON for a moment, how would we add these records to the model of our app? Maybe with a bit of code like this…

struct User {
    let name: String
}
struct Status {
    let user: User
}

let parsedJson = JSON(data: data)
for (key, val) in parsedJson["statuses"] {
    if let username = val["user"]["name"].string {
        let owner = User(name: username)
        let newStatus = Status(user: owner)
    }
}

This works relatively well, assuming we are just creating objects from a JSON feed rather than synchronizing them. But what if there is a server error and the JSON comes back invalid? For example if there is a server error which changes the JSON to present an “error” key, and it no longer includes “statuses”, this loop simply would not be executed. Failing silently is better than crashing, but it would be nice to check for issues and try again, or adjust something in the app.

Since we need to check for the presence of statuses, and this for loop doesn’t actually do that, we need to check the count of statuses first, which means we need to cast it to an array, and *then* check the count…

if(parsedJson["statuses"].count<1) {
    println("Oops! An error occurred")
}

And that's that! Right?
Well, no...

If the key isn't defined, this count property evaluates to 0, which could just mean there is no new statuses to see. The count really should not be zero, it should be null.. but SwiftyJSON is telling us it's 0. This seems like the kind of thing I really *don't* want a JSON parser to be doing. They really seem to not like the optional syntax in Swift, and instead reinvented it with these type properties. Why not just stick with convention?
Our final code might look something like this:

struct User {
    let name: String
}
struct Status {
    let user: User
}

let parsedJson = JSON(data: data)
for (key, val) in parsedJson["statuses"] {
    if let username = val["user"]["name"].string {
        let owner = User(name: username)
        let newStatus = Status(user: owner)
    }
}
if(parsedJson["statuses"].count<1) {
    println("Oops! An error occurred")
}
if let err = parsedJson["error"].string {
    println(err)
}

Our code is starting to grow, and this doesn't cover a ton of things we would need in a real-world application, such as updating the model, including more properties, checking for equality, enforcing uniqueness, cascading relationship changes, and a host of other things. Core Data can handle much of this, and it's common practice to implement models as Core Data models, but that still creates a situation where we have to custom implement all kinds of methods for converting the entire model object (such as Status) *back* in to JSON to update the server.

In the Objective-C world there is Mantle, a great library for handling such things. Before that there was RestKit. RestKit however made some ...interesting... design decisions a few years ago in a big update, and haven't ever really recovered since then. Unfortunately I haven't found a good solution for Swift just yet, and trying to work with Mantle proves to be problematic in it's current form, unless you implement all your models in Obj-C, something I'm not sure we all want to do at this stage.

I know this isn't all problems with SwiftyJSON, but they ironically break a lot of Swift conventions in dealing with optional values. SwiftyJSON is really a terrible name, they are very much not Swifty at all. However, the syntax is a little easier on the eyes. Personally, I don't use the library in my projects.

Spring

Spring is "A library to simplify iOS animations in Swift." How does it do this? Let's take a look.

Trying out some sample code I threw together this quick demo UIViewController that adds a blue square to the screen and animates it in, give it a try yourself, it's pretty nifty:

import UIKit
import Spring

class ViewController: UIViewController {
    var square = SpringView(frame: CGRectMake(0, 0, 200, 200))
    override func viewDidLoad() {
        super.viewDidLoad()
        
        square.center = self.view.center
        square.backgroundColor = UIColor.blueColor()
        square.animation = "squeezeDown"
        square.animate()

        self.view.addSubview(square)
    }
}

The SpringView seems to basically just be a UIView subclass with the animations added in. I don't know if I really like the idea of having to use their UIView, but I suppose most of the time I just use the basic UIView, and even if I didn't, I could just subclass SpringView instead.

Spring sports quite a few animation types, set as a string. The square.animation = "squeezeDown" here is what's determining the animation to play. The library goes beyond this, and in fact allows simple animations to be created in storyboards. So in theory you could put Spring in your Xcode project, and then pass it off to a designer to set up some nifty animations using this library. Very interesting idea, I would like to hear from someone who has tried to do exactly this.

Quick

Quick is "The Swift (and Objective-C) testing framework."

Really? It's THE testing framework? Let's take a look at how Quick works as opposed to XCTest, or expecta.

In XCTest, you might define an assertion that you're testing against like this:

class JSONSwiftTests: XCTestCase {
    
    override func setUp() {
        super.setUp()
        // Put setup code here. This method is called before the invocation of each test method in the class.
    }
    
    override func tearDown() {
        // Put teardown code here. This method is called after the invocation of each test method in the class.
        super.tearDown()
    }
    
    func testContrivedExample() {
        let name = "jameson"
        XCTAssertEqual(name, "jameson", "Name should be \"jameson\"")
    }
}

This is okay, it makes the test basically confirm that name is equal to "jameson". It's simple enough, but there is a common trend/desire among developers to instead express test cases in terms of desired behavior, rather than specifically implementing what the desired behavior causes. Those may sound like the same thing, but take a look at how Quick (due to it's usage of the library Nimble) expresses the same thing like this:

import Quick
import Nimble

class AQuickTest: QuickSpec {
    override func spec() {
        describe("the user") {
            it("has the name 'Jameson'") {
                let name = "Jameson"
                expect(name).to(equal("Jameson"))
            }
        }
    }
}

More than anything else, this framework encourages behavioral tests, which is why this example includes more information about our expectations.

Quick also eases some of the pain of asynchronous testing. In XCTest I personally tend to use XCTestAsync, although Xcode 6 does introduce a way to do this using XCTestExpectation. The basic way that works is you can create an expectation object, and then fulfill it when the async operation is complete. It's not a bad approach.

import Quick
import Nimble

@objc class AsyncExample {
    var longTaskIsDone = false
    var timer: NSTimer?
    func startLongTask() {
        timer = NSTimer.scheduledTimerWithTimeInterval(2, target: self, selector: "taskIsDone", userInfo: nil, repeats: false)
    }
    func taskIsDone() {
        println("task done")
        longTaskIsDone = true
    }
}

class AQuickTest: QuickSpec {
    override func spec() {
        describe("the user") {
            it("has the name 'Jameson'") {
                let name = "Jameson"
                expect(name).to(equal("Jameson"))
            }
        }
        
        describe("Async Example") {
            describe("its long task") {
                it("should finish in 5 seconds") {
                    let asyncExample = AsyncExample()
                    asyncExample.startLongTask()
                    expect(asyncExample.longTaskIsDone).toEventually(beTruthy(), timeout: 3, pollInterval: 0.4)
                }
            }
        }
        
    }
}

In this example we just create an NSTimer that fires in 2 seconds, as a simulated async example. Then in the Async Example test, we can use the .toEventually() method to wait around and keep checking in to the asyncExample.longTaskIsDone property. This is slightly cleaner in that using expectations, because with this method we don't need to change our code to make sure the test is notified of this variable changing. Having an ongoing timer keep checking is great (just be careful not to have it calling methods with side effects!)

Overall Quick seems pretty interesting, the approach is sure to appeal to those in professional environments, or working with an Agile team where specs change fast.

That's it for this time, if you would like to see any of these libraries covered in greater detail be sure to let me know. You can find me on Twitter.


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



Subscribe via RSS

Core Data Migrations Swift Tutorial

In this post we’re going to discuss migrations in Core Data.

This is part three of a tutorial series covering the usage of Core Data in Swift to write iOS apps with persistence. While I think you can benefit strictly from reading this post, it may be easier to follow along if you go back and read Core Data in Swift Tutorial (Part 1) first.

This post compatible with Xcode 6.3 Beta, Updated on February 16, 2015

At this point in the tutorial, we’ve created a simple app that can create and delete log item’s backed by a Core Data store. We’ve learned about creating, deleting, and modifying Core Data objects. We looked at sorting lists of objects, filtering them out using predicates, and even combined predicates to make compound predicates. With this much information, we can do a ton of stuff with our apps.

Why Make Core Data Migrations?

However there is a problem. Core Data expects a consistent schema. Meaning that if we add or remove entities, attributes, or change their types, it will cause a conflict. This means that if you:
1) Store some data inside of an app using a set schema. For example a Model named LogItem with a title(String) attribute.
2) Change any of the schema. For example add a new full title attribute to become ‘fullTitle’.
3) Run the app again

There will be a conflict, and the app will do a hard crash. This includes anyone who has downloaded your app from the app store! Not only that, if you change the text attribute, and you don’t perform any kind of migration, you will be risking losing user’s data. If you want people to uninstall your app, this is a *fantastic* way to make that happen. If not, keep reading 🙂

Causing a migration failure

To demonstrate the default behavior, it’s useful to see what happens if we don’t take care to perform migrations. If you’ve been following along you should have a Core Data model that contains one entity, LogItem with two String attributes, itemText and title, as shown in the figure below.

Core Data Model For Migrations

Core Data Model For Migrations

First off, we need to run our app and create some records. If you’ve been following along you can just add a few log items. This is enough to populate the Core Data store, and create a set schema.

Next, let’s add our “fullTitle” String attribute. Just click the “Add Attribute” button and punch in fullTitle as the name, and String as the type.

Now, run the app again, what do you find? Well, you get a rather massive looking confusing error in the console:

CoreData: error: -addPersistentStoreWithType:SQLite configuration:(null) URL:file:///Users/USERNAME/Library/Developer/CoreSimulator/Devices/70F453BF-0E8E-4916-8B1F-1D3FDAC9AAE3/data/Containers/Data/Application/90912396-CB66-4B4A-8502-BDE916F6243D/Documents/MyLog.sqlite options:(null) ... returned error Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x7ffd126842c0 {metadata={
    NSPersistenceFrameworkVersion = 519;
    NSStoreModelVersionHashes =     {
        LogItem = <3954a6a4 f606a77c d84981b4 c8ec4869 5eda2e90 4c7e0069 b090d4b8 825d8ee3>;
    };
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers =     (
        ""
    );
    NSStoreType = SQLite;
    NSStoreUUID = "";
    "_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary {
    metadata =     {
        NSPersistenceFrameworkVersion = 519;
        NSStoreModelVersionHashes =         {
            LogItem = <3954a6a4 f606a77c d84981b4 c8ec4869 5eda2e90 4c7e0069 b090d4b8 825d8ee3>;
        };
        NSStoreModelVersionHashesVersion = 3;
        NSStoreModelVersionIdentifiers =         (
            ""
        );
        NSStoreType = SQLite;
        NSStoreUUID = "";
        "_NSAutoVacuumLevel" = 2;
    };
    reason = "The model used to open the store is incompatible with the one used to create the store";
}
2014-12-15 14:00:36.135 MyLog[67495:21018487] Unresolved error Optional(Error Domain=YOUR_ERROR_DOMAIN Code=9999 "Failed to initialize the application's saved data" UserInfo=0x7ffd13b26970 {NSLocalizedDescription=Failed to initialize the application's saved data, NSLocalizedFailureReason=There was an error creating or loading the application's saved data., NSUnderlyingError=0x7ffd12684300 "The operation couldn’t be completed. (Cocoa error 134100.)"}), Optional([NSLocalizedDescription: Failed to initialize the application's saved data, NSLocalizedFailureReason: There was an error creating or loading the application's saved data., NSUnderlyingError: Error Domain=NSCocoaErrorDomain Code=134100 "The operation couldn’t be completed. (Cocoa error 134100.)" UserInfo=0x7ffd126842c0 {metadata={
    NSPersistenceFrameworkVersion = 519;
    NSStoreModelVersionHashes =     {
        LogItem = <3954a6a4 f606a77c d84981b4 c8ec4869 5eda2e90 4c7e0069 b090d4b8 825d8ee3>;
    };
    NSStoreModelVersionHashesVersion = 3;
    NSStoreModelVersionIdentifiers =     (
        ""
    );
    NSStoreType = SQLite;
    NSStoreUUID = "";
    "_NSAutoVacuumLevel" = 2;
}, reason=The model used to open the store is incompatible with the one used to create the store}])

This is a rather intimidating error message. There’s so much to read, and so little of it is familiar. I want you to focus on on particular key though, “reason”.
When you get a Core Data error, it comes back with a ton of information, and the relevant part is almost always the value of “reason”. In our error, we see a couple of reasons, but we’re most interested in the very first one.

reason=The model used to open the store is incompatible with the one used to create the store} with userInfo dictionary

The error pretty much says what I described earlier. We changed the schema, and Core Data doesn’t like it. Let’s change the schema back by removing fullTitle, and set up a migration instead. (Run your app again to make sure it still works!)
If you find you have a conflicted Core Data model, but you don’t want to create a migration yet, you can also just delete the app from your simulator or device. This will create the new schema instead. This is generally my solution when my model is going through a lot of migrations, but you NEVER want to do this with a deployed model version. You’ll just have people on the app store with your app crashing. They aren’t going to know to delete the app and reinstall either, they’ll just delete it and leave you a 1-star review.

Create A Migration

To create a migration, we need to first make some changes to our persistent store. This is the code that Xcode generates for us when we create a project using an Xcode template. By default, Xcode does not include any kind of migration options at all.

In the code that Xcode provides there is a lazily computed variable called persistentStoreCoordinator, and it’s responsible for making migrations happens. It looks like this, and we’re most interested in line 8.

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added 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.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("MyLog.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: nil, error: &error) == nil {
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() 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.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }
    
    return coordinator
}()

Now, the persistent store coordinator is being added using the addPersistentStoreWithType method. This method looks like it is maybe not updated for Swift, but that’s okay, we just need to pay attention to what’s optional and what’s not. Let’s take a look at the documentation, click here to open in a new window.

Opening up these docs you can see some details on what these parameters are. In particular it describes “options”.

Apple Documentation For Core Data

Apple Documentation For Core Data

You’ll notice in the parameters table, the “options” parameter is a dictionary. It also has a link to a particular set of parameters you could use in the dictionary, “Migrations Options”. Clicking on that will bring you to the constants the iOS SDK has prepared for us already:

Migration options, specified in the dictionary of options when adding a persistent store using addPersistentStoreWithType:configuration:URL:options:error:.

Declaration
SWIFT
let NSIgnorePersistentStoreVersioningOption: NSString!
let NSMigratePersistentStoresAutomaticallyOption: NSString!
let NSInferMappingModelAutomaticallyOption: NSString!
Constants
NSIgnorePersistentStoreVersioningOption

Source: Apple.com

NSMigratePersistentStoresAutomaticallyOption sounds pretty nice. Automatic migration? Sign me up! NSInferMappingModelAutomaticallyOption will create the mapping model, while NSMigratePersistentStoresAutomaticallyOption will perform the migration.

So, to set this up, we just have to create a dictionary with these keys set, and pass that in as the options parameter of addPersistentStoreWithType

So let’s create the dictionary:

let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true]

And pass it in to the options parameter:

lazy var persistentStoreCoordinator: NSPersistentStoreCoordinator? = {
    // The persistent store coordinator for the application. This implementation creates and return a coordinator, having added 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.
    // Create the coordinator and store
    var coordinator: NSPersistentStoreCoordinator? = NSPersistentStoreCoordinator(managedObjectModel: self.managedObjectModel)
    let url = self.applicationDocumentsDirectory.URLByAppendingPathComponent("MyLog.sqlite")
    var error: NSError? = nil
    var failureReason = "There was an error creating or loading the application's saved data."
    let mOptions = [NSMigratePersistentStoresAutomaticallyOption: true,
        NSInferMappingModelAutomaticallyOption: true]
    if coordinator!.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: url, options: mOptions, error: &error) == nil {
        coordinator = nil
        // Report any error we got.
        var dict = [String: AnyObject]()
        dict[NSLocalizedDescriptionKey] = "Failed to initialize the application's saved data"
        dict[NSLocalizedFailureReasonErrorKey] = failureReason
        dict[NSUnderlyingErrorKey] = error
        error = NSError(domain: "YOUR_ERROR_DOMAIN", code: 9999, userInfo: dict)
        // Replace this with code to handle the error appropriately.
        // abort() 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.
        NSLog("Unresolved error \(error), \(error!.userInfo)")
        abort()
    }
    
    return coordinator
}()

So basically what we just did, is we specified we would like Core Data to try and automatically merge and map our models between versions.

If you think about what we actually changed in our model, it’s strange that it causes a hard crash. We weren’t using the fullTitle attribute anywhere, it’s just a new field to add really. It could just be automatically added to every model with a null value. Or, if we specified a default value for the attribute, it could just apply that to all existing records. That would be much better than crashing, but Xcode doesn’t want to just make the assumption that we want to do that. So this is how we specify we want Xcode to try anyway.

Now, let’s actually add the new attribute. Before, we just opened up our model and added it as a new String attribute, but that’s not going to create a migration model. We need two versions of the model so that Core Data can understand the differences in the two models.

Add a Model Version

In the Project Navigator, select your Core Data model, for example MyLog.xcdatamodeld. Now in your menu bar, select Editor > Add Model Version.. as shown in the figure below.

Adding a Model Version in Xcode

Adding a Model Version in Xcode

You can call it whatever you want, I’ll be calling it MyLogV2, and press Finish.

You’ll now find you can expand your xcdatamodel file to see multiple versions. Select your second version as shown in the figure below.

Multiple Versions of a Core Data Model

Multiple Versions of a Core Data Model

Now, in our version 2 of the model, let’s add our fullTitle(String) attribute.

We’ll also want to update LogItem.swift, the NSManagedObject subclass we created to represent this model.

If you followed along in the earlier tutorials you should have something like this:

import Foundation
import CoreData

class LogItem: NSManagedObject {

    @NSManaged var itemText: String
    @NSManaged var title: String
    
    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
    }

}

We’re missing our new fullTitle attribute, both as a property and in the createInManagedObjectContext initializer. Let’s add it…

import Foundation
import CoreData

class LogItem: NSManagedObject {

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

}

Finally, we need to actually make version 2 of our model active.

Click on the MyLog.xcdatamodeld parent object in the Project Navigator. Then open the Utilities panel on the right-hand side of Xcode, and select the first tab, the File Inspector.

Here under “Model Version” there is a dropdown where you can select the new version 2 of the model, as shown in the figure below.

Changing the Core Data Model Version in Xcode

Changing the Core Data Model Version in Xcode

Now, if you’ve been following along, there are some errors you’ll need to fix where we are initializing the LogItem objects without specifying the fullTitle. We added the attribute to the initializer, so now we need to go one by one and specify the fullTitle in each of these cases.

In particular we need to add these in viewDidLoad where we create some starter objects.

// Loop through, creating items
for (itemTitle, itemText) in items {
    // Create an individual item
    LogItem.createInManagedObjectContext(moc, title: itemTitle, fullTitle: "\(itemTitle) \(itemText)", text: itemText)
}

In earlier tutorials, we also created a saveNewItem function that uses the initializer inside of ViewController.swift. So we need to specify a full title for that as well. For now, we’ll just use a static string “My Full Title” as the fullTitle value.

func saveNewItem(title : String) {
    // Create the new  log item
    var newLogItem = LogItem.createInManagedObjectContext(self.managedObjectContext!,
        title: title,
        fullTitle: "My Full 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)
        save()
    }
}

Now, for the moment of truth… run the app. If you get an error here, let us know on the forums. I know this is a difficult topic, and I know the tutorial is never going to be easy to follow. But, I know if you’ve come this far you can and will succeed in learning this material. We just need to make sure you don’t get stuck.

Otherwise, congratulations, you’ve created your first auto-migration! This is known as a light-weight migration. There is more to migrations than this, as you can specify custom mappings, and there are caveats once you have more than 2 versions of a model. But knowing what we discussed in this tutorial is good enough to get your app to v2.0, and published.

Full source code for this tutorial here: Core Data In Swift Tutorial Code.

Liked this tutorial? It is a modified version of a draft chapter in my Swift Book.

P.S. Wanna write for this site? I need help writing quality content. Learn More Here.


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 3)

This post compatible with Xcode 6.3 Beta, Updated on February 16, 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 = UIAlertController(title: "Enter Title",
        message: "Enter Text",
        preferredStyle: .Alert)
    
    var titleTextField: UITextField?
    titlePrompt.addTextFieldWithConfigurationHandler {
        (textField) -> Void in
        titleTextField = textField
        textField.placeholder = "Title"
    }
    
    titlePrompt.addAction(UIAlertAction(title: "Ok",
        style: .Default,
        handler: { (action) -> Void in
        if let textField = titleTextField {
            println(textField.text)
        }
    }))
    
    self.presentViewController(titlePrompt,
        animated: true,
        completion: nil)
}

This function will create a UIAlertController object with a text field by using the addTextFieldWithConfigurationHandler method. I’ll probably go over this as a separate blog post at some point, but for now it’s enough to know that when the handler on the “Ok” action gets called, the textField’s text value is inside of textField.text. We can use this to take input from the user without adding too much more view work.

Okay, so now that we can detect the text entered, 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 handler closure in the titlePrompt’s “Ok” button:

titlePrompt.addAction(UIAlertAction(title: "Ok",
    style: .Default,
    handler: { (action) -> Void in
    if let textField = titleTextField {
        self.saveNewItem(textField.text)
    }
}))

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 persist automatically, you must explicitly call save for that behavior. 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()
...
}

Running the app we now have a super nifty app where we can add or remove items from a list with neat animations!

This concludes part 3 of the Core Data tutorial for now. The full source code can be found here.

In the next section, we’ll set up migrations so you can make changes to the schema of your live apps. Core Data Migrations Tutorial in Swift ».

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.


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.3, Updated on February 16, 2015

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 helper function for adding new records.

Basically, we want to be able to simply call a method on the LogItem class, pass in some parameters, and get a new LogItem back.

For example, when we’re done the actual function call might look like this:

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

The LogItem class is not tied to any specific NSManagedObjectContext, so we want to make sure we aren’t storing the reference to the managed object context 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 in LogItem.swift:

class LogItem: NSManagedObject {

    @NSManaged var itemText: String
    @NSManaged var title: String

    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()
    // Do any additional setup after loading the view, typically from a nib.
    
    // Use optional binding to confirm the managedObjectContext
    if let moc = self.managedObjectContext {
        
        // Create some dummy data to work with
        var items = [
            ("Best Animal", "Dog"),
            ("Best Language","Swift"),
            ("Worst Animal","Cthulu"),
            ("Worst Language","LOLCODE")
        ]
        
        // Loop through, creating items
        for (itemTitle, itemText) in items {
            // Create an individual item
            LogItem.createInManagedObjectContext(moc,
                title: itemTitle, text: itemText)
        }
    }
}

To keep the code simple, we’re using some shortcuts in order to seed our data. What you’re seeing when I’m setting items is an array (using square brackets []), then each element is a tuple of two String values, [(String, String)].

Next, I’m decomposing them back in to two variables, itemTitle and itemText for each of the tuples in the array.

Finally, I call the createInManagedObjectContext method, which we created earlier, passing in the new itemTitle and itemText.


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()
    // Do any additional setup after loading the view, typically from a nib.
    
    // Use optional binding to confirm the managedObjectContext
    if let moc = self.managedObjectContext {
        
        // Create some dummy data to work with
        var items = [
            ("Best Animal", "Dog"),
            ("Best Language","Swift"),
            ("Worst Animal","Cthulu"),
            ("Worst Language","LOLCODE")
        ]
        
        // Loop through, creating items
        for (itemTitle, itemText) in items {
            // Create an individual item
            LogItem.createInManagedObjectContext(moc,
                title: itemTitle, text: itemText)
        }
        
        
        // 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. Note that we also removed the viewDidAppear function since we were only using that for testing out some things earlier.

import UIKit
import CoreData

class ViewController: UIViewController, UITableViewDataSource {
    
    // Retreive the managedObjectContext from AppDelegate
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

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

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // Use optional binding to confirm the managedObjectContext
        if let moc = self.managedObjectContext {
            
            // Create some dummy data to work with
            var items = [
                ("Best Animal", "Dog"),
                ("Best Language","Swift"),
                ("Worst Animal","Cthulu"),
                ("Worst Language","LOLCODE")
            ]
            
            // Loop through, creating items
            for (itemTitle, itemText) in items {
                // Create an individual item
                LogItem.createInManagedObjectContext(moc,
                    title: itemTitle, text: itemText)
            }
            
            
            // 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
            
        }
    }
    
    // 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
    }

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

}

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

If you ever get an error that says something like, The model used to open the store is incompatible with the one used to create the store
This means that the model Core Data thinks it’s working with doesn’t quite match what’s been stored in the database. Resolving this with a live app means migrating between versions of your model. But for the purpose of learning and doing the initial development, usually the quickest way to just keep moving is to delete the app from the simulator; wiping out the data, and therefore resolving the conflict.

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]
    println(logItem.itemText)
}

This will set us up so that when the button is clicked, a message will show in the console (in the Xcode window) showing the itemText for the clicked item.

 

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/Part1.5

You may see something kind of like this now, except with different data:

Your ordering may be different every time you run your app, this is because it contains no sorting when it performs the fetch request. 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. Note your data is expected to be different.

Let’s also look at filtering out certain elements. First, let’s just try and only get the item named “Best Language”. 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 "Best Language" exactly.
    let predicate = NSPredicate(format: "title == %@", "Best Language")
    
    // 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 == %@”, “Best Language”)

What the compiler sees is something like this:
(“title == ‘Best Language'”)

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

Running the app now we should see just one item.

We could also do a string comparison using the contains keyword, if we look for the substring “Worst” we’ll only get the items that contain that string…

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

// 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 “Worst” and any one with a title “Best Language”?

First, let’s create the two separate predicates:

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

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

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

Since we want both cases of the “Best Language” and any item containing “Worst”, 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 »

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.


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.3 Beta, Updated on February 16, 2015
Don’t have 6.3 yet? Make sure to download it here using your iOS Developer account.

Core Data is the de facto standard way to persist and manage data in both iPhone and Mac applications, and with Swift it’s a bit easier. 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 for something more in-depth than a tutorial? Try my book & video courses
Get The Swift Book
Learn About My Book & Video Packages »

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. The defaults are fine for now. The primary object that needs to be used to work with Core Data is the managedObjectContext defined here.

If you used the Core Data template as shown above, this code will already be present.

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
}()

All you really need to know about this, is that managedObjectContext is a lazy variable on AppDelegate that is at our disposable for use in performing Core Data calls. Knowing this we can access the managedObjectContext 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. (New lines are highlighted)

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.
    
    // Retreive the managedObjectContext from AppDelegate
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext
    
    // Print it to the console
    println(managedObjectContext)
}

We’ll be accessing the managedObjectContext pretty frequently, so we should pull this out of the viewDidLoad() method and move it somewhere we can access it easily. How about if we just store it as an instance variable on the ViewController?

import UIKit

class ViewController: UIViewController {
    
    // Retreive the managedObjectContext from AppDelegate
    let managedObjectContext = (UIApplication.sharedApplication().delegate as! AppDelegate).managedObjectContext

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        
        // Print it to the console
        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:

Optional(<NSManagedObjectContext: 0x7fe68b58c800>)

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 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.

IMPORTANT!

From this point on, any changes you make to your Core Data model, such as adding a new Entity or Attribute will lead to an inconsistency in the model of the app in the iPhone Simulator. If this happens to you, you’ll get a really scary looking crash in your app as soon as it starts. You’ll also see something like this show up at the very bottom of your console, “reason=The model used to open the store is incompatible with the one used to create the store”.

If this happens to you there is a very easy fix:
In the iPhone Simulator, or on your device, just delete the app, and then perform a new Build & Run command in Xcode. This will erase all out of date versions of the model, and allow you to do a fresh run.

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. It’s contents should be something very close to this:

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 instance variables.

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”, assuming your app is called “MyLog”.

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, it’s not going to prevent you from being able to use Core Data. 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()
    // Do any additional setup after loading the view, typically from a nib.
    
    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."
}

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. Let’s override the viewDidAppear() method and implement a way to look at our items info. We’ll perform a Core Data fetch (which is like a query if you have worked with SQL before), then we will present the contents of the row we retrieved in a new alert window.

override func viewDidAppear(animated: Bool) {
    super.viewDidAppear(animated)
    
    // Create a new fetch request using the LogItem entity
    let fetchRequest = NSFetchRequest(entityName: "LogItem")
    
    // Execute the fetch request, and cast the results to an array of LogItem objects
    if let fetchResults = managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [LogItem] {
        
        // Create an Alert, and set it's message to whatever the itemText is
        let alert = UIAlertController(title: fetchResults[0].title,
            message: fetchResults[0].itemText,
            preferredStyle: .Alert)
        
        // Display the alert
        self.presentViewController(alert,
            animated: true,
            completion: nil)
    }
}

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. If you are familiar with SQL, a fetch request with no predicate on the LogItem entity is something like “SELECT * FROM LogItem”.
Next we create a UIAlertController instance 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 objects).

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 storage.

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

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.


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



Subscribe via RSS