Designing Animations with UIViewPropertyAnimator in iOS 10 and Swift 3

Designing Animations with UIViewPropertyAnimator in iOS 10 and Swift 3

This is part of a series of tutorials introducing new features in iOS 10, the Swift programming language, and the new XCode 8 beta, which were just announced at WWDC 16

UIKit in iOS 10 now has “new object-based, fully interactive and interruptible animation support that lets you retain control of your animations and link them with gesture-based interactions” through a family of new objects and protocols.

In short, the purpose is to give developers extensive, high-level control over timing functions (or easing), making it simple to scrub or reverse, to pause and restart animations, and change the timing and duration on the fly with smoothly interpolated results. These animation capabilities can also be applied to view controller transitions.

I hope to concisely introduce some of the basic usage and mention some sticking points that are not covered by the talk or documentation.

Building the Base App

We’re going to try some of the features of UIViewPropertyAnimator in a moment, but first, we need something to animate.

Create a single-view application and add the following to ViewController.swift.

import UIKit

class ViewController: UIViewController {
    // this records our circle's center for use as an offset while dragging
    var circleCenter: CGPoint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add a draggable view
        let circle = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
        circle.center = self.view.center
        circle.layer.cornerRadius = 50.0
        circle.backgroundColor = UIColor.green()
        
        // add pan gesture recognizer to
        circle.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragCircle)))
        
        self.view.addSubview(circle)
    }
    
    func dragCircle(gesture: UIPanGestureRecognizer) {
        let target = gesture.view!
        
        switch gesture.state {
        case .began, .ended:
            circleCenter = target.center
        case .changed:
            let translation = gesture.translation(in: self.view)
            target.center = CGPoint(x: circleCenter!.x + translation.x, y: circleCenter!.y + translation.y)
        default: break
        }
    }
}

There’s nothing too complicated happening here. In viewDidLoad, we created a green circle and positioned in the center of the screen. Then we attached a UIPanGestureRecognizer instance to it so that we can respond to pan events in dragCircle: by moving that circle across the screen. As you may have guessed, the result is a draggable circle:

Enjoy

About UIViewPropertyAnimator

UIViewPropertyAnimator is the main class we’ll be using to animate view properties, interrupt those animations, and change the course of animations mid-stream. Instances of UIViewPropertyAnimator maintain a collection of animations, and new animations can be attached at (almost) any time.

Note: “UIViewPropertyAnimator instance” is a bit of a mouthful, so I’ll be using the term animator for the rest of this tutorial.

If two or more animations change the same property on a view, the last animation to be added or started* “wins”. The interesting thing is than rather than an jarring shift, the new animation is combined with the old one. It’s faded in as the old animation is faded out.

* Animations which are added later to a UIViewPropertyAnimator instance, or are added with a start delay override earlier animations. Last-to-start wins.

Interruptable, Reversible Animations

Let’s dive in and add a simple animation to build upon later. With this animation, the circle will slowly expand to twice it’s original size when dragged. When released, it will shrink back to normal size.

First, let’s add properties to our class for the animator and an duration:

// ...
class ViewController: UIViewController {
    // We will attach various animations to this in response to drag events
    var circleAnimator: UIViewPropertyAnimator!
    let animationDuration = 4.0
// ...

Now we need to initialize the animator in viewDidLoad::

// ...
// animations argument is optional
circleAnimator = UIViewPropertyAnimator(duration: animationDuration, curve: .easeInOut, animations: { 
[unowned circle] in
    // 2x scale
    circle.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
})
// self.view.addSubview(circle) here
// ...

When we initialized circleAnimator, we passed in arguments for the duration and curveproperties. The curve can be set to one of four simple, predefined timing curves. In our example we choose easeInOut. The other options are easeIn, easeOut, and linear. We also passed an animations closure which doubles the size of our circle.

Now we need a way to trigger the animation. Swap in this implementation of dragCircle:. This version starts the animation when the user begins dragging the circle, and manages the animation’s direction by setting the value of circleAnimator.isReversed.

func dragCircle(gesture: UIPanGestureRecognizer) {
    let target = gesture.view!
    
    switch gesture.state {
    case .began, .ended:
        circleCenter = target.center
        
        if (circleAnimator.isRunning) {
            circleAnimator.pauseAnimation()
            circleAnimator.isReversed = gesture.state == .ended
        }
        circleAnimator.startAnimation()
        
        // Three important properties on an animator:
        print("Animator isRunning, isReversed, state: \(circleAnimator.isRunning), \(circleAnimator.isReversed), \(circleAnimator.state)")
    case .changed:
        let translation = gesture.translation(in: self.view)
        target.center = CGPoint(x: circleCenter!.x + translation.x, y: circleCenter!.y + translation.y)
    default: break
    }
}

Run this version. Try to make the circle “breathe”. Hold it down for a second..

A Sticking Point

Take a look at this video of our circle after it has made it all the way to the end of the animation:

It's not moving.

It’s not moving. It’s stuck in at the expanded size.

Ok, so what’s happening here? The short answer is that the animation threw away the reference it had to the animation when it finished.

Animators can be in one of three states:

  • inactive: the initial state, and the state the animator returns to after the animations reach an end point (transitions to active)
  • active: the state while animations are running (transitions to stopped or inactive)
  • stopped: a state the animator enters when you call the stopAnimation: method (returns to inactive)

Here it is, represented visually:

State Transitions

(source: UIViewAnimating protocol reference)

Any transition to the inactive state will cause all animations to be purged from the animator (along with the animator’s completion block, if it exists).

We’ve already seen the startAnimation method, and we’ll delve into the other two shortly.

Let’s get our circle unstuck. We need to change up the initialization of circleAnimator:

expansionAnimator = UIViewPropertyAnimator(duration: expansionDuration, curve: .easeInOut)

…and modify dragCircle::

// ...
// dragCircle:
case .began, .ended:
    circleCenter = target.center
        
    if circleAnimator.state == .active {
        // reset animator to inactive state
        circleAnimator.stopAnimation(true)
    }
    
    if (gesture.state == .began) {
        circleAnimator.addAnimations({
            target.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
        })
    } else {
        circleAnimator.addAnimations({
            target.transform = CGAffineTransform.identity
        })
    }

case .changed:
// ...

Now, whenever the user starts or stops dragging, we stop and finalize the animator (if it’s active). The animator purges the attached animation and returns to the inactive state. From there, we attach a new animation that will send our circle towards the desired end state.

A nice benefit of using transforms to change a view’s appearence is that you can reset the view’s appearance easily by setting its transform property to CGAffineTransform.identity. No need to keep track of old values.

Note that circleAnimator.stopAnimation(true) is equivalent to:

circleAnimator.stopAnimation(false) // don't finish (stay in stopped state)
circleAnimator.finishAnimation(at: .current) // set view's actual properties to animated values at this moment

The finishAnimationAt: method takes a UIViewAnimatingPosition value. If we pass start or end, the circle will instantly transform to the scale it should have at the beginning or end of the animation, respectively.

About Durations

There’s a subtle bug in this version. The problem is, every time we stop an animation and start a new one, the new animation will take 4.0 seconds to complete, no matter how close the view is to reaching the end goal.

Here’s how we can fix it:

// dragCircle:
// ...
case .began, .ended:
    circleCenter = target.center
    
    let durationFactor = circleAnimator.fractionComplete // Multiplier for original duration
    // multiplier for original duration that will be used for new duration
    circleAnimator.stopAnimation(false)
    circleAnimator.finishAnimation(at: .current)
    
    if (gesture.state == .began) {
        circleAnimator.addAnimations({
            target.backgroundColor = UIColor.green()
            target.transform = CGAffineTransform(scaleX: 2.0, y: 2.0)
        })
    } else {
        circleAnimator.addAnimations({
            target.backgroundColor = UIColor.green()
            target.transform = CGAffineTransform.identity
        })
    }
    
    circleAnimator.startAnimation()
    circleAnimator.pauseAnimation()
    // set duration factor to change remaining time
    circleAnimator.continueAnimation(withTimingParameters: nil, durationFactor: durationFactor)
case .changed:
// ...

Now, we explicitly stop the animator, attach one of two animations depending on the direction, and restart the animator, using continueAnimationWithTimingParameters:durationFactor:to adjust the remaining duration. This is so that “deflating” from a short expansion does not take the full duration of the original animation. The method continueAnimationWithTimingParameters:durationFactor:can also be used to change an animator’s timing function on the fly*.

* When you pass in a new timing function, the transition from the old timing function is interpolated. If you go from a springy timing function to a linear one, for example, the animations may remain “bouncy” for a moment, before smoothing out.

Timing Functions

The new timing functions are much better than what we had before.

The old UIViewAnimationCurve options are still available (static curves like easeInOut, which I’ve used above), and there are two new timing objects available: UISpringTimingParameters and UICubicTimingParameters

UISpringTimingParameters

UISpringTimingParameters instances are configured with a damping ratio, and an optional mass, stiffness, and initial velocity. These are all fed into the proper equation to give you realistically bouncy animations. The initializer for your view property animator will still expect a duration argument when passed an instance of UISpringTimingParameters, but that argument is ignored. The equation doesn’t have a place for it. This addresses a complaint about some of the old spring animation functions.

Let’s do something different and use a spring animator to keep the circle tethered to the center of the screen:

ViewController.swift

import UIKit

class ViewController: UIViewController {
    // this records our circle's center for use as an offset while dragging
    var circleCenter: CGPoint!
    // We will attach various animations to this in response to drag events
    var circleAnimator: UIViewPropertyAnimator?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        // Add a draggable view
        let circle = UIView(frame: CGRect(x: 0.0, y: 0.0, width: 100.0, height: 100.0))
        circle.center = self.view.center
        circle.layer.cornerRadius = 50.0
        circle.backgroundColor = UIColor.green()
        
        // add pan gesture recognizer to circle
        circle.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.dragCircle)))
        
        self.view.addSubview(circle)
    }
    
    func dragCircle(gesture: UIPanGestureRecognizer) {
        let target = gesture.view!
        
        switch gesture.state {
        case .began:
             if circleAnimator != nil && circleAnimator!.isRunning {
                circleAnimator!.stopAnimation(false)
            }
            circleCenter = target.center
        case .changed:
            let translation = gesture.translation(in: self.view)
            target.center = CGPoint(x: circleCenter!.x + translation.x, y: circleCenter!.y + translation.y)
        case .ended:
            let v = gesture.velocity(in: target)
            // 500 is an arbitrary value that looked pretty good, you may want to base this on device resolution or view size.
            // The y component of the velocity is usually ignored, but is used when animating the center of a view
            let velocity = CGVector(dx: v.x / 500, dy: v.y / 500)
            let springParameters = UISpringTimingParameters(mass: 2.5, stiffness: 70, damping: 55, initialVelocity: velocity)
            circleAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters)
            
            circleAnimator!.addAnimations({
                target.center = self.view.center
            })
            circleAnimator!.startAnimation()
        default: break
        }
    }
}

Drag the circle and let it go. Not only will it bounce back to the starting point. it will even keep the momentum it had when you released, since we passed a velocity argument to initialVelocity: when we initialized the spring timing parameters:

// dragCircle: .ended:
// ...
let velocity = CGVector(dx: v.x / 500, dy: v.y / 500)
let springParameters = UISpringTimingParameters(mass: 2.5, stiffness: 70, damping: 55, initialVelocity: velocity)
circleAnimator = UIViewPropertyAnimator(duration: 0.0, timingParameters: springParameters)
// ...
Springy

At an interval, I drew a small circle at our main circle’s center point in order to trace the animation path for this screenshot. The “straight” lines curve a little, because some momentum was retained as the circle was released and pulled inward by the spring.

UICubicTimingParameters

UICubicTimingParameters allows you to set control points to define a custom cubic Bézier curve. Just note that coordinate points outside of 0.0 – 1.0 are trimmed to that range:

// Same as setting the y arguments to 0.0 and 1.0 respectively
let curveProvider = UICubicTimingParameters(controlPoint1: CGPoint(x: 0.2, y: -0.48), controlPoint2: CGPoint(x: 0.79, y: 1.41))
expansionAnimator = UIViewPropertyAnimator(duration: expansionDuration, timingParameters: curveProvider)

If you’re not happy with those timing curve providers, you can implement and use your own by conforming to the UITimingCurveProvider protocol.

Animation Scrubbing

You can manually set the progress of an paused animation by passing a value between 0.0 and 1.0* to your animator’s fractionComplete property. A value of 0.5, for example, will place the animatable properties halfway towards their final value, regardless of timing curve. Note that the position you set is mapped to the timing curve when you restart an animation, so a fractionComplete of 0.5 does not necessarily mean the remaining duration will be half of the original duration.

Let’s try out a different example. First, let’s initialize our animator at the bottom of viewDidLoad: and pass in two animations:

// viewDidLoad:
// ...
circleAnimator = UIViewPropertyAnimator(duration: 1.0, curve: .linear, animations: {
    circle.transform = CGAffineTransform(scaleX: 3.0, y: 3.0)
})
    
circleAnimator?.addAnimations({ 
    circle.backgroundColor = UIColor.blue()
}, delayFactor: 0.75)
// ...

We aren’t going to call startAnimation this time. The circle should get larger as the animation progresses and start turning blue at 75%.

We need a new dragCircle: implementation as well:

func dragCircle(gesture: UIPanGestureRecognizer) {
    let target = gesture.view!
    
    switch gesture.state {
    case .began:
        circleCenter = target.center
    case .changed:
        let translation = gesture.translation(in: self.view)
        target.center = CGPoint(x: circleCenter!.x + translation.x, y: circleCenter!.y + translation.y)
        
        circleAnimator?.fractionComplete = target.center.y / self.view.frame.height
    default: break
    }
}

Now we’re updating the animator’s fractionComplete to the circle’s vertical position on the view as it’s dragged:

Rev-3 Rev-4

I’ve used the linear timing curve, but this sample would be a good way to get a feel for other curves or a timing curve provider instance. The animation that changes the circle blue follows a compressed version of the animator’s timing curve.

* Custom animators can accept value outside of range 0.0 – 1.0, if they support animations going past their endpoints.

Extensibility

Finally, one of the most interesting things about this release was the apparent philosophy behind it. Don’t like something? Change it. Animation providers and timing curves providers can both be replaced with your own implementation, for example.

In fact, this is almost more of a release of protocols than classes. The underlying protocols got about as much fanfare as everything else, which is great. I love this direction of making more and more built-in functionality accessible to the developer. I’m hoping we see more in the future, and I’m looking forward to seeing what people do with this.


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



Subscribe via RSS

Being Swifty with Collection View and Table View Cells

Here’s a common scenario: You have a table view or collection view that has a variety of different types of content. You want to display varying cells based on these types of content, and they’re all mixed within a single section. Pardon the stand-in art, but it looks roughly like this:

In the Objective-C world, it was typical to just use an NSArray to hold whatever records your collection view was going to be using as a data source, and then for each element check what class it is before picking a cell. This case seems particularly not Swifty these days.

- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath {
    UICollectionViewCell *cell = [collectionView dequeueReusableCellWithReuseIdentifier:@"identifier" forIndexPath:indexPath];
    id record = self.records[indexPath.row];
    if([record isKindOfClass:[PlaythroughItem class]]) {
        // ...
    }
    else if([record isKindOfClass:[ReviewItem class]]) {
        // ...
    }
    else if([record isKindOfClass:[TrailerItem class]]) {
        // ...
    }
    return cell;
}

shudder

Okay so that’s not the most type-safe approach, although it’s surprisingly common in Obj-C iOS code. As a better alternative in Swift, we can use enum cases for the different item types, and perform lookups of the items themeselves as needed. Let’s look at an example.

Example

In an entertainment app I’m working on I’ve got a few types of cells for the types of news items that come in:

enum NewsItem {
  case Trailer(index: Int)
  case Review(index: Int)
  case Playthrough(index: Int)
}

The index is just a way to keep track of which item this it supposed to represent in the database. We take this approach to keep the amount of data needed to produce the collection view down. We don’t need all the associated data with every video to be present when putting together a collection view, we just need the info on what cell is tapped, after it is tapped.

Let’s say we have a simple collection view that shows one of these three and picks a custom cell for each. In a Swift file NewsFeed.swift I have that acts as the dataSource of my collection view for the main news view. Of particular interest is the cellForItemAtIndexPath method, which runs the NewsItem record through a switch and produces the correct type of cell, with the relevant information populated:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let record = records[indexPath.row]
    switch(record) {
    case .Playthrough(let index):
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("PlaythroughCell", forIndexPath: indexPath) as! PlaythroughCollectionViewCell
        let playthrough = MediaDB.playthroughAtIndex(index)
        cell.titleLabel.text = playthrough.title
        cell.lengthLabel.text = playthrough.length.prettyTime
        return cell
    case .Review(let index):
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("ReviewCell", forIndexPath: indexPath) as! ReviewCollectionViewCell
        let review = MediaDB.reviewAtIndex(index)
        cell.ratingLabel.text = "\(review.rating) out of 10"
        cell.titleLabel.text = review.title
        return cell
    case .Trailer(let index):
        let cell = collectionView.dequeueReusableCellWithReuseIdentifier("TrailerCell", forIndexPath: indexPath) as! TrailerCollectionViewCell
        let trailer = MediaDB.trailerAtIndex(index)
        cell.titleLabel.text = trailer.title
        cell.lengthLabel.text = trailer.length.prettyTime
        return cell
    }
}

This code works well enough, record is of type NewsItem which can be one of three cases for the different news items we support:

enum NewsItem {
  case Trailer(index: Int)
  case Review(index: Int)
  case Playthrough(index: Int)
}

The associated index value is so that we can find the individual item in the DB when the collection view wants to display a cell.

Something about this code didn’t sit right with me though. I felt that too much of the code was boilerplate; in particular the switch felt bulky and like it had too much work being done inside each case.

But what if I created a protocol for any data source that could be presented as a collection view cell? It would change on a view-by-view basis so I don’t actually want this in my model.. but I do like having it on these particular CollectionViewCell subclasses.

So, I created a protocol called NewsCellPresentable, which I can adhere to in extensions with my custom collection view cells:

protocol NewsCellPresentable {
    func configureForIndex(index: Int)
}
extension PlaythroughCollectionViewCell: NewsCellPresentable {
    func configureForIndex(index: Int) {
        let playthrough = MediaDB.playthroughAtIndex(index)
        self.titleLabel.text = playthrough.title
        self.lengthLabel.text = playthrough.length.prettyTime
    }
}
extension ReviewCollectionViewCell: NewsCellPresentable {
    func configureForIndex(index: Int) {
        let review = MediaDB.reviewAtIndex(index)
        self.titleLabel.text = review.title
        self.ratingLabel.text = "\(review.rating) out of 10"
    }
}
extension TrailerCollectionViewCell: NewsCellPresentable {
    func configureForIndex(index: Int) {
        let trailer = MediaDB.trailerAtIndex(index)
        self.titleLabel.text = trailer.title
        self.lengthLabel.text = trailer.length.prettyTime
    }
}

This feels much cleaner already. Now I can go back to my cellForItemAtIndexPath method and trim it down to just the following:

func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
    let record = records[indexPath.row]
    var cell: NewsCellPresentable
    switch(record) {
    case .Playthrough(let index):
        cell = collectionView.dequeueReusableCellWithReuseIdentifier("PlaythroughCell", forIndexPath: indexPath) as! PlaythroughCollectionViewCell
        cell.configureForIndex(index)
    case .Review(let index):
        cell = collectionView.dequeueReusableCellWithReuseIdentifier("ReviewCell", forIndexPath: indexPath) as! ReviewCollectionViewCell
        cell.configureForIndex(index)
    case .Trailer(let index):
        cell = collectionView.dequeueReusableCellWithReuseIdentifier("TrailerCell", forIndexPath: indexPath) as! TrailerCollectionViewCell
        cell.configureForIndex(index)
    }
    return (cell as! MediaCollectionViewCell)
}

What do you think? Is this a cleaner approach? Let me know if you have a different method in the comment, or let me know on Twitter. My username is @jquave.

P.S.

If you want to try this out yourself and don’t have the same DB layer as me… guess what? Neither do I! You can easily stub this out like this:

class MediaDB {
    class func titleForRecord(index: Int) -> String {
        return "Title!!"
    }
    class func trailerAtIndex(index: Int) -> Trailer {
        return Trailer()
    }
    class func reviewAtIndex(index: Int) -> Review {
        return Review()
    }
    class func playthroughAtIndex(index: Int) -> Playthrough {
        return Playthrough()
    }
}
struct Trailer {
    let title = "Trailer Title"
    let length = 190
}
struct Review {
    let title = "Review Title"
    let rating = 4
}
struct Playthrough {
    let title = "Playthrough Title"
    let length = 9365
}
enum NewsItem {
    case Trailer(index: Int)
    case Review(index: Int)
    case Playthrough(index: Int)
}

Personally I always stub things out with static values before I do the work of integrating with a backend service or whatever data provider is needed. This makes it much easier to iterate.


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



Subscribe via RSS

Swift 2 – What’s new

Monday Apple announced Swift 2, and just about everything announced is an extremely welcome change. This post is a summary of Chris Lattner’s discussion in the WWDC video “What’s new in Swift”. Now, let’s run through them…

Fundamentals

enums can now be printed to the console and show the actually value instead of just (enum value). Additionally, println is now just print. If you have ever used log debugging to figure out a problem involving enum values, rejoice!

I tested this and saw that not only is the enum value is printed, but so is the entire context of when and where the print() function is called.

enums can now support multiple types, so long to Box!

enum<T1, T2> {
  case First(T1)
  case Second(T2)
}

The do keyword:

do {
  //...
} while(someVar<5)

// Can now be represented as:
repeat {
  //...
}

Option Sets can now use a standard Set type in Swift 2.0

viewOptions = .Repeat | .CurveEaseIn
viewOptions = nil

// Can now be represented as a set:

viewOptions = [.Repeat, .CurveEaseIn]
viewOptions = []

We can also create our own set types

(Default implementations in protocols)

struct MyStyle : OptionSetType {
  let rawValue: Int
  static let Bold     = MyStyle(rawValue: 1)
  static let Italic   = MyStyle(rawValue: 2)
}

iStyle.style = []
iStyle.style = [.Bold, .Italic]

if iStyle.style.contains(.Bold) {
  //...
}

Function arguments now behave the same way, regardless of if they are global functions, or methods on a data structure.

So now, you provide arguments labels on functions by default:

func myFunc(name: String, age: Int) { ... }

myFunc("John", age: 35)

The # syntax is now gone, previously used to make the external argument name the same as the internal argument name.

In tests, public and internal are visible, via running a special run mode.

Pattern Matching
Added the guard statement which exits the scope in the else statement.

guard let name = json["name"] as? String else {
  return .Second("missing name")
}

// You can also combine this is to complex guards
guard let name = json["name"] as? String,
      let year = json["year"] as? Int else
    return .Second("bad input")
}
// name and year are now String and Int types, not optionals!
return .First(name, year)

Switch/case can now be used with inline if statements

switch bar() {
case .SomeCase(let value) where value != 42:
  doThing(value)

default: break
}

// Can now be represented as
if case .SomeCase(let value) = bar() where value != 42 {
  doSomething(value)
}

for in loops now support boolean filters and full pattern matching

for value in mySequence where value != "" {
  doThing(value)
}

for case .MyEnumCase(let value) in enumValues {
  doThing(value)
}

BONUS: Example of try-catch for JSON vs invalid JSON

let jsonString = "{\"name\":\"Fred\", \"age\":40}"
let badJsonString = "This really isn't valid JSON at all"

let jsonData = jsonString.dataUsingEncoding(NSUTF8StringEncoding)!
let badJsonData = badJsonString.dataUsingEncoding(NSUTF8StringEncoding)!

do {
    // Try parsing some valid JSON
    let parsed = try NSJSONSerialization.JSONObjectWithData(jsonData, options: NSJSONReadingOptions.AllowFragments)
    print(parsed)
    
    // Try parsing some invalid JSON
    let otherParsed = try NSJSONSerialization.JSONObjectWithData(badJsonData, options: NSJSONReadingOptions.AllowFragments)
    print(otherParsed)
}
catch let error as NSError {
    // Catch fires here, with an NSErrro being thrown from the JSONObjectWithData method
    print("A JSON parsing error occurred, here are the details:\n \(error)")
}


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



Subscribe via RSS

Functional Programming in Swift

Thoughts on Functional Programming in Swift

Like most of you, I have to use Objective-C at my day job. I could only craft my Swift skills at night. Swift is not a purely functional language. It can be use imperatively because all frameworks from Apple are written in Objective-C at the time of writing. However, it is also functional, learning from modern languages like Haskell, F#, etc. At the beginning when reading Swift blogs and tutorials, many people talked about terms like functors and monads, those sounded alien-like to me. I started to learn Haskell to understand what they were talking about. I’m not an expert, and still learning functional programming and Swift, but I wanted to share what I’ve learned so far. Hopefully it will save you some troubles to get into the functional programming world.

Key Concepts

Higher-order functions

One key concept of functional programming is higher-order functions. According to Wikipedia, a higher-order function:

  • takes one or more functions as an input
  • outputs a function

In Swift, functions are first-class citizens, like structs and classes, as we can see in the following example:

let addFive = { $0 + 5 }
addFive(7) // Output: 12

We define a function as an inline closure, and then assign it to an inline closure. Swift provides shorthand argument to it. Instead of by name, we can refer to the arguments by number, $0, $1 and so on.

func addThreeAfter(f: Int -> Int) -> Int -> Int {
    return { f($0) + 3 }
}
let addEight = addThreeAfter(addFive)
addEight(7) // Output: 15

The argument type Int -> Int means it is a function that takes an Int as an argument, and returns an Int. If a function requires more than one arguments, for example, it has two Int argument, we can define the type as (Int, Int) -> Int.

The return type of addThreeAfter is also a function. It equivalents to func addThreeAfter(f: Int -> Int) -> (Int -> Int).

[addFive, addEight].map { $0(7) } // Output: [12 15]

map is a special function for container type, such as Array, Optional. It unwraps values from the container type, then applies a transform for each, and wraps them again. We use map here to pass 7 as an argument to each function in the array, and wraps the results into a new array.

In summary, we can assign functions to variables, store them in data structures, pass them as arguments to other functions, and even return them as the values from other functions.

Pure functions

A function is called a pure function if it has no observable side effects. But what the heck is side effect?

A function or expression is said to have a side effect if, in addition to returning a value, it also modifies some state or has an observable interaction with calling functions or the outside world.
— from Wikipedia

The two functions addFive and addEight are pure functions. They don’t modify the input value, or change any global state. In the example below, addFive2 modifies the input, so it is not a pure function.

func addFive2(inout a: Int) -> Int {
    a += 5
    return a
}
var a = 7
addFive2(&a)
a // Output: 12

Functions that access or modify a database, the local file system, or printing strings to the screen are also considered impure. Because they modify the state of the database records, file system, and display buffers respectively. Sometimes side effects are desirable. Without side effects, we could not interact with the program.

Haskell introduces types like IO to separate pure and impure layers. But in Swift, we don’t need to worry too much about this separation. We could still use Cocoa APIs as usual. But, I strongly encourage the use of pure functions whenever possible. With pure functions, the code is more predictable. A pure function will always return the same value if given the same input. It’s easy to test, too. We don’t need to mock a data structure to satisfy its internal state requirements to test a function.

Imperative & Functional Programming

All above are very theoretical. You may want to know how functional programming with help us solve problems in a better, clearer, or less error-prone way. What are the benefits we could gain from it?

First, you need to understand that we could do almost anything in imperative programming, and functional programming does not extend the possibilities of what we could do.

Second, if you come from the imperative programming world, some functional code is harder to understand at first, especially those with custom operators. It is not because functional programming is hard and obscure, but it’s because our mindsets are trained from years of imperative programming practices.

Take a look at this example of imperative code, which we can rewrite as functional code. These two do exactly the same things.

// Imperative
var source = [1, 3, 5, 7, 9]
var result = [Int]()
for i in source {
    let timesTwo = i * 2
    if timesTwo > 5 && timesTwo < 15 {
        result.append(timesTwo)
    }
}
result // Output: [6, 10, 14]
// Functional
let result2 = source.map { $0 * 2 }
                    .filter { $0 > 5 && $0 < 15 }
result2 // Output: [6, 10, 14]

It is arguable which one is clearer. But from this simple example you can see the main difference between imperative and functional programming. In imperative programming, we instruct the computer how to do something:

  1. Iterate through source
  2. Get the result from the element, and multiply by 2
  3. Compare it with 5 and 15
  4. If it is bigger than 5 and less than 15, put it into result

However, in functional programming, we describe what to do:

  1. Transform each element in source to itself multiplied by 2
  2. Only select the ones with value bigger than 5 and less than 15

I’m not going to persuade you functional programming is better. It’s your personal preference. After all, good code is all about writing code that:

  1. Works as intended
  2. Is clear to you and your team

An Example: Reverse Polish Notation Calculator

I like Swift and functional programming, because it enables me to solve a problem in a different perspective. There is usually more than one way to solve a problem. Exploring a better solution helps us grow to become good developers.

Let me show you a functional example. It is a calculator for algebraic expressions of reverse polish notation, or RPN in short. (It is a Swift implementation of the Haskell example in Learn You a Haskell for Great Good.)

A RPN expression of (3 + 5) 2 is 3 5 + 2 . You may think of this as a stack of numbers. We go through the RPN expression from left to right. When encountering a number, we push it onto the stack. When we encounter an operator, we pop two numbers from the stack, use the operator with those two numbers, and then push the result back onto the stack. When reaching the end of the expression, the only one number left on the stack is the result (assuming the RPN expression is valid). For more explanation about RPN, please check on Wikipedia.

We want a function that returns the result for an RPN expression.

func solveRPN(expression: String) -> Double {
    // Process the expression and return the result
}

Given a RPN expression String “3 5 + 2 *”, first we need to transform it into an array of elements that we can process. There are two kind of elements, operand and operator. The Enum data type in Swift comes in handy for defining the element. We name it RPNElement.

enum RPNElement {
    case Operand(Double)
    case Operator(String, (Double, Double) -> Double)
}

Next, we split the expression into an array of Strings, then map it into an RPNElement array.

extension String {
    func toRPNElement() -> RPNElement {
        switch self {
        case "*": return .Operator(self, { $0 * $1 })
        case "+": return .Operator(self, { $0 + $1 })
        case "-": return .Operator(self, { $0 - $1 })
        default: return .Operand(Double(self.toInt()!))
        }
    }
}
func stringToRPNElement(s: String) -> RPNElement {
    return s.toRPNElement()
}
func solveRPN(expression: String) -> Double {
    let elements = expression.componentsSeparatedByString(" ").map(stringToRPNElement)
        // Further process
}

Next, we will go through the array and process it according to how RPN works, as I described earlier. We reduce the array into a Double array. Assuming the expression is valid, the Double array should only contain one element. It will be the result we want.

func solveRPN(expression: String) -> Double {
    let elements = expression.componentsSeparatedByString(" ").map(stringToRPNElement)
    let results = elements.reduce([]) { (acc: [Double], e: RPNElement) -> [Doublein
        switch e {
        case .Operand(let operand):
            return [operand] + acc
        case .Operator(let op, let f):
            let r = f(acc[0], acc[1])
            return [r] + acc[2..<acc.count]
        }
    }
    return results.first ?? 0
}
solveRPN("3 5 + 2 *") // Output: 16

Where to Go From Here?

If you are interested in learning more about functional programming, I highly recommend the following two books:

Even though the second book is written for Haskell, but the concepts also apply to Optional in Swift as well. Besides, it explains Functor, Applicative, Monad in details.


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



Subscribe via RSS

Local Notifications in iOS 9+ with Swift (Part 1)

This post has been updated for compatibility with XCode 8 and iOS 10

Local notifications are a simple way to display information to your user even when your app is in the background. They allow you to display an alert, play a sound or badge your app’s icon. Local notifications can be triggered at a scheduled time or when your user enters or leaves a geographical area. In this tutorial, we’ll create a simple to-do list application and delve into a number of UILocalNotification features and quirks.

First, let’s create a new single view application in Xcode and call it LocalNotificationsTutorial. Remember to choose Swift as the language.

Screen Shot 2015-01-30 at 10.15.42 PM

Before we delve into writing any code, let’s get our view controllers and views set up. This is a necessary step, and I’ll be covering some of the basics of using Interface Builder, but if want to skip it and jump right into dealing with scheduling notifications you can follow along by getting the configured application from here.

Configuring the Views

Our finished application will have two views in a navigation controller: a root view that displays a chronologically ordered list of to-do items with deadlines, and a view for creating to-do list items.

Looking for something more in-depth than a tutorial? Try my book & video courses
Get The Swift Book
Learn About My Book & Video Packages »

 

iOS Simulator Screen Shot Feb 1, 2015, 11.43.36 PM iOS Simulator Screen Shot Feb 4, 2015, 10.26.58 PM

Creating the View Controllers

Before opening Interface Builder, we should generate view controllers to back each of our views. Ctrl or right-click on the project group in the project navigator and select “New File”.

Screen Shot 2015-01-30 at 10.29.29 PM

Select “Cocoa Touch Class” from the “iOS -> Source” menu and create a subclass of UITableViewController named “TodoTableViewController”. Don’t create a XIB file, and, of course, the language should be Swift. This will be our root view controller and it will be in charge of displaying the to-do list.

We need a separate view for creating our to-do items. Repeat the process, this time create a UIViewController subclass and name it “TodoSchedulingViewController”.

Setting up Navigation

Now that our view controllers are created, let’s hook them into our project’s storyboard. Click “Main.storyboard” and delete the root view controller. Go ahead and delete “ViewController.swift” as well. We won’t be using it.

Drag a new navigation controller from the object library onto the now blank storyboard. We’ve deleted our root view, so drag a Storyboard Entry Point onto the navigation controller so our application will have a root view.

Screen Shot 2015-01-30 at 11.11.26 PM

The object library

Select the navigation controller’s root view (a table view) and set its custom class to “TodoTableViewController” in the identity inspector.

Screen Shot 2015-01-30 at 11.22.17 PM

The identity inspector

Since we’re going to display deadlines for each to-do item, we need to select the table view’s first (and only) prototype cell, switch to the attributes inspector, and set the cell’s style to “Subtitle”.  It needs a reuse identifier as well, so we can refer to it in our code. We’ll use “todoCell”.

Screen Shot 2015-01-30 at 11.39.00 PM

The attributes inspector

Keep the attributes inspector selected. Drag a navigation item onto the table view and give it the title “Todo List”, then drag a bar button item onto that and set the identifier to “Add”.

Now we’ll set up the view on which users can schedule and title their to-do items. Drag a view controller into the storyboard. Its custom class should be set to “TodoSchedulingViewController”.

Ctrl or right-click on the “Add” button, drag from the “action” to the new view, and select “show”. Now all our navigation is linked up.

Screen Shot 2015-01-31 at 12.03.33 AM

We need to drag three controls onto this view: a text field (with “Title” as the placeholder text), a date picker and a button (titled “Save”). Just center and widen all three, then “add missing constraints” to all views in the view controller (Under “Resolve Auto Layout Issues”, the triangle icon towards the bottom right of Xcode). Adding constraints ensures that the view is laid out predictably across various screen sizes (instead of having portions of controls cut off or misaligned).

Screen Shot 2015-01-31 at 12.25.08 AM

Connecting Controls to Code

Now that our views and navigation are laid out, we have to link our text field and date picker controls to an IBOutlet in TodoSchedulingViewController.swift. This will allow us to access these controls (and their values) in our code. There are a few ways to do this, but the simplest is to enable the Assistant editor by clicking the interlocking circles in the top right of XCode, Ctrl or right-click the control, and drag the “New Referencing Outlet” circle into the TodoSchedulingViewController class body.

Screen Shot 2015-01-31 at 12.45.14 AM

Do this for both the text field and the date picker, naming them “titleField” and “deadlinePicker” respectively.

@IBOutlet weak var titleField: UITextField!
@IBOutlet weak var deadlinePicker: UIDatePicker!

The final step is to connect the save button to an IBAction (an event handler function). Just Ctrl or right-click the button, and drag from the “Touch Up Inside” circle to the code window. Name the action “savePressed” and optionally set the sender type to UIButton (no other controls will be firing this action, so we can be more specific).

@IBAction func savePressed(_ sender: UIButton) {
}

The views and navigation for this project are all set up. Go ahead and run the app in the simulator. Try it for a few different devices. You’ll notice that, because of the constraints we added, your views stretch or squeeze to fit the various screen sizes.

Now, let’s get out of Interface Builder and write some code.

Registering Notification Settings

We need to register our intent to use notifications with the application. Otherwise, the notifications we schedule simply won’t fire. Switch over to your Application Delegate (AppDelegate.swift) and add the following line to application:didFinishLaunchingWithOptions:

application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil))

On the first launch of the app, users will now be prompted to allow your app to send notifications. If the user grants permission, we will be able to schedule notifications that display a banner, play a sound, and update our app icon’s badge number (which we’ll cover in part 2).

iOS Simulator Screen Shot Feb 3, 2015, 2.56.37 PM

Modeling the Application

For a simple app like this, it may be tempting to handle all of the logic in the view controllers that we just created, but we’ll have an easier time understanding and maintaining our code if we keep the management of the to-do list and the presentation logic separate.

I chose to model individual to-do items with a lightweight struct. Let’s create that now. Just click “File -> New -> File”, choose “Swift File” and name it “TodoItem”. Each to-do list item has a title and a deadline, so we’ll create properties for both.

struct TodoItem {
  var title: String
  var deadline: Date
}

Ultimately, each to-do list item needs to be backed by an on disk representation, so that the list will persist if the application is terminated. Instances of UILocalNotification have a userInfo property – a dictionary that we can use to store miscellaneous data like the title, but we can’t rely on that alone in this case. Local notifications are automatically unscheduled after they are fired, which means that we wouldn’t be able to retrieve past-due items. We’ll have to use another method to persist our items, and we need a way to associate an item we’ve retrieved from the disk with its corresponding local notification. For that, we’ll use a universally unique identifier (UUID).

struct TodoItem {
    var title: String
    var deadline: Date
    var UUID: String

    init(deadline: Date, title: String, UUID: String) {
        self.deadline = deadline
        self.title = title
        self.UUID = UUID
    }
}

Since we’re going to display overdue items in red, lets also add a convenience method that returns whether or not an item is overdue.

var isOverdue: Bool {
    // Optionally, you can omit "ComparisonResult" and it will be inferred.
    return (Date().compare(self.deadline) == ComparisonResult.orderedDescending) // deadline is earlier than current date
}

Saving To-Do Items (Scheduling Notifications)

We need a class to represent the list of items and handle persisting them. Create a new Swift file named “TodoList”.

Our application is only concerned with maintaining a single to-do list, so it makes sense to make a single shared instance available throughout the app.

class TodoList {
    class var sharedInstance : TodoList {
        struct Static {
            static let instance: TodoList = TodoList()
        }
        return Static.instance
    }
}

This method is the community-accepted way to implement the singleton pattern in Swift, which you can adapt to your own projects. If you’re curious, you can read the details about what it’s doing and why in this Stack Overflow answer.

UserDefaults provides a simple way to persist our to-do items to disk. The following snippet defines a method that adds a dictionary representation of a to-do item to standard user defaults (with UUID as the key), and then creates the associated local notification.

fileprivate let ITEMS_KEY = "todoItems"
func addItem(_ item: TodoItem) {
    // persist a representation of this todo item in UserDefaults
    var todoDictionary = UserDefaults.standard.dictionary(forKey: ITEMS_KEY) ?? Dictionary() // if todoItems hasn't been set in user defaults, initialize todoDictionary to an empty dictionary using nil-coalescing operator (??)
    todoDictionary[item.UUID] = ["deadline": item.deadline, "title": item.title, "UUID": item.UUID] // store NSData representation of todo item in dictionary with UUID as key
    UserDefaults.standard.set(todoDictionary, forKey: ITEMS_KEY) // save/overwrite todo item list
 
    // create a corresponding local notification
    let notification = UILocalNotification()
    notification.alertBody = "Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification 
    notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view" 
    notification.fireDate = item.deadline as Date // todo item due date (when notification will be fired) notification.soundName = UILocalNotificationDefaultSoundName // play default sound 
    notification.userInfo = ["title": item.title, "UUID": item.UUID] // assign a unique identifier to the notification so that we can retrieve it later
 
    UIApplication.shared.scheduleLocalNotification(notification)
}

Notice that we’re just playing the default sound when the notification fires. You can provide your own sound file, but audio files over 30 seconds in length are not supported. The default sound will play instead.

We’re almost to the point where users can create new list items. It’s time to implement savePressed: in TodoSchedulingViewController.

@IBAction func savePressed(_ sender: UIButton) {
    let todoItem = TodoItem(deadline: deadlinePicker.date, title: titleField.text!, UUID: UUID().uuidString)
    TodoList.sharedInstance.addItem(todoItem) // schedule a local notification to persist this item
    let _ = self.navigationController?.popToRootViewController(animated: true) // return to list view
}

Note that, since this is a new to-do list entry, we’re passing in a newly generated UUID.

Try out the app now. Launch it in the simulator, create a new item due a minute in the future, and return to the home or lock screen (Shift-CMD-H or CMD-L) to view the notification. The notification won’t necessarily fire right on the stroke of the minute (due to a hidden ‘seconds’ value on the time picker control), but you’ll see it within the minute.

iOS Simulator Screen Shot Feb 3, 2015, 4.29.05 PMiOS Simulator Screen Shot Feb 3, 2015, 4.33.13 PM

The 64 Notification Limit

It’s important to note that you’re limited to scheduling 64 local notifications. If you schedule more, the system will keep the 64 soonest firing notifications and automatically discard the rest.

We can avoid running into this issue by disallowing the creation of new items if 64 already exist.

In TodoTableViewController:

func refreshList() {
    todoItems = TodoList.sharedInstance.allItems()
    if (todoItems.count >= 64) {
        self.navigationItem.rightBarButtonItem!.enabled = false // disable 'add' button
    }
    tableView.reloadData()
}

Retrieving To-Do Items

The fact that to-do items are persisted as an array of dictionaries is an implementation detail that outside classes shouldn’t have to worry about. Our TodoList class needs a public facing function that the list view controller can query to retrieve a list of to-do items.

func allItems() -> [TodoItem] {
    let todoDictionary = UserDefaults.standard.dictionary(forKey: ITEMS_KEY) ?? [:]
    let items = Array(todoDictionary.values)
    return items.map({
        let item = $0 as! [String:AnyObject]
        return TodoItem(deadline: item["deadline"] as! Date, title: item["title"] as! String, UUID: item["UUID"] as! String!)
    }).sorted(by: {(left: TodoItem, right:TodoItem) -> Bool in
        (left.deadline.compare(right.deadline) == .orderedAscending)
    })
}

This function retrieves the array of item representation from disk, converts it to an array of TodoItem instances using an unnamed closure we pass to map, and sorts that array chronologically. Describing the map and sort functions in detail is beyond the scope of this tutorial, but you can find more information in the Swift language guide’s section on closures.

Now we can hook up TodoTableViewController to display the list.

class TodoTableViewController: UITableViewController {
    var todoItems: [TodoItem] = []
    override func viewDidLoad() {
        super.viewDidLoad()
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        refreshList()
    }

    func refreshList() {
        todoItems = TodoList.sharedInstance.allItems()

        if (todoItems.count >= 64) {
            self.navigationItem.rightBarButtonItem!.enabled = false // disable 'add' button
        }
        tableView.reloadData()
    }

    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return todoItems.count
    }

    override func tableView(_ tableView: UITableView, cellForRowAtIndexPath indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "todoCell", for: indexPath) // retrieve the prototype cell (subtitle style)
        let todoItem = todoItems[(indexPath as NSIndexPath).row] as TodoItem
        cell.textLabel?.text = todoItem.title as String!
        if (todoItem.isOverdue) { // the current time is later than the to-do item's deadline
            cell.detailTextLabel?.textColor = UIColor.red
        } else {
            cell.detailTextLabel?.textColor = UIColor.black // we need to reset this because a cell with red subtitle may be returned by dequeueReusableCellWithIdentifier:indexPath:
        }

        let dateFormatter = DateFormatter()
        dateFormatter.dateFormat = "'Due' MMM dd 'at' h:mm a" // example: "Due Jan 01 at 12:00 PM"
        cell.detailTextLabel?.text = dateFormatter.string(from: todoItem.deadline as Date)
        return cell
    }
}

Our to-do list now shows each item in chronological order, with the date label in red if the item is overdue.

iOS Simulator Screen Shot Feb 4, 2015, 10.26.58 PM

There are two issues we need to deal with here. Users currently don’t receive any visual feedback that a notification has fired (and a to-do item is overdue) when the app is running in the foreground. Also, when the app resumes, the list won’t automatically be refreshed, meaning that missed deadlines may not appear in red. Lets solve both issues now.

In TodoTableViewController:

override func viewDidLoad() {
    super.viewDidLoad()
    NotificationCenter.default.addObserver(self, selector: #selector(TodoTableViewController.refreshList), name: NSNotification.Name(rawValue: "TodoListShouldRefresh"), object: nil)
}

In AppDelegate:

func application(_ application: UIApplication, didReceive notification: UILocalNotification) {
    NotificationCenter.default.post(name: Notification.Name(rawValue: "TodoListShouldRefresh"), object: self)
}
func applicationDidBecomeActive(_ application: UIApplication) {      
    NotificationCenter.default.post(name: Notification.Name(rawValue: "TodoListShouldRefresh"), object: self)
}

Please note that, despite the presence of the word “notification”, NotificationCenter is unrelated to UILocalNotification. NotificationCenter’s purpose is to provide a simple way to implement the observer pattern in your apps.

Here we register TodoTableViewController as an observer to the “TodoListShouldRefresh” notification. Whenever a notification with that name is posted, the reloadData method will be called.

I’ve omitted this step, but it is generally better to define notification names as static constants to avoid repeating yourself.

Completing To-Do Items (Canceling Notifications)

Our to-do list application isn’t very useful without a way to clear out completed items, and the simplest way to do that is to delete them. We need to add some functionality to TodoList.

func removeItem(_ item: TodoItem) {
    let scheduledNotifications: [UILocalNotification]? = UIApplication.shared.scheduledLocalNotifications
    guard scheduledNotifications != nil else {return} // Nothing to remove, so return
 
    for notification in scheduledNotifications! { // loop through notifications...    
        if (notification.userInfo!["UUID"] as! String == item.UUID) { // ...and cancel the notification that corresponds to this TodoItem instance (matched
            UIApplication.shared.cancelLocalNotification(notification) // there should be a maximum of one match on UUID
            break
        }
    }

    if var todoItems = UserDefaults.standard.dictionaryForKey(ITEMS_KEY) {
        todoItems.removeValue(forKey: item.UUID)
        UserDefaults.standard.set(todoItems, forKey: ITEMS_KEY) // save/overwrite todo item list
    }
}

Note that passing an existing notification to scheduleLocalNotification: will cause a duplicate to be created. If you want to give users the ability to edit existing local notifications, you’ll need to retrieve the old one and cancel it before scheduling the new one.

Now we just need to allow users to remove items by swiping the item’s cell and pressing “Complete”.

In TodoTableViewController:

override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
    return true // all cells are editable
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
    if editingStyle == .delete { // the only editing style we'll support
        // delete the row from the data source
        let item = todoItems.remove(at: (indexPath as NSIndexPath).row) // remove TodoItem from notifications array, assign removed item to 'item'
        tableView.deleteRows(at: [indexPath], with: .fade) 
        TodoList.sharedInstance.removeItem(item) // delete backing property list entry and unschedule local notification (if it still exists) 
        self.navigationItem.rightBarButtonItem!.isEnabled = true // we definitely have under 64 notifications scheduled now, make sure 'add' button is enabled
    }
}

iOS Simulator Screen Shot Feb 4, 2015, 10.26.58 PM

Conclusion

We now have a working to-do list application that lets our users schedule and cancel local notifications with sound and custom alert messages. The source code can be downloaded here.

In part 2 of this series, which builds on this project, we’ll add support for an application icon badge and learn about notification actions, a new feature that allows us to trigger code from a notification without ever opening the app.

Go to part 2 now »


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



Subscribe via RSS

Local Notifications in iOS 9+ with Swift (Part 2)

This post has been updated for compatibility with XCode 8 and iOS 10

In part 1 of this series, we created a simple to-do list application that used local notifications to alert users when to-do items were overdue. This time, we’re going to build on that the project by enabling application icon badges to display the number of overdue items and add support for notification actions, allowing our users to complete and edit to-do items without even opening the app.

You can download the source code for part 1 here.

Badging the App Icon

It bears mentioning that we can badge the app icon without using local notifications. The applicationWillResignActive: method in AppDelegate is a good place to do so, since it will be fired just before the user returns to the home screen where they can see the app icon.

func applicationWillResignActive(_ application: UIApplication) { // fired when user quits the application
    let todoItems: [TodoItem] = TodoList.sharedInstance.allItems() // retrieve list of all to-do items
    let overdueItems = todoItems.filter({ (todoItem) -> Bool in
        return todoItem.deadline.compare(Date()) != .orderedDescending
    })
    UIApplication.shared.applicationIconBadgeNumber = overdueItems.count // set our badge number to number of overdue items
}

iOS Simulator Screen Shot Feb 4, 2015, 10.31.14 PM iOS Simulator Screen Shot Feb 4, 2015, 11.51.25 PM

This is a good start, but we need the badge number to automatically update when to-do items become overdue. Unfortunately, we can’t instruct the app to simply increment the badge value when our notifications fire, but we can pre-compute and provide a value for the “applicationIconBadgeNumber” property of our local notifications. Lets provide a method in TodoList to set an associated badge number for each notification.

func setBadgeNumbers() {
    let scheduledNotifications: [UILocalNotification]? = UIApplication.shared.scheduledLocalNotifications // all scheduled notifications
    guard scheduledNotifications != nil else {return} // nothing to remove, so return
 
    let todoItems: [TodoItem] = self.allItems()
 
    // we can't modify scheduled notifications, so we'll loop through the scheduled notifications and
    // unschedule/reschedule items which need to be updated.
    var notifications: [UILocalNotification] = []

    for notification in scheduledNotifications! {
        print(UIApplication.shared.scheduledLocalNotifications!.count)
        let overdueItems = todoItems.filter({ (todoItem) -> Bool in // array of to-do items in which item deadline is on or before notification fire date
            return (todoItem.deadline.compare(notification.fireDate!) != .orderedDescending)
        })

        // set new badge number
        notification.applicationIconBadgeNumber = overdueItems.count 
        notifications.append(notification)
    }
 
    // don't modify a collection while you're iterating through it
    UIApplication.shared.cancelAllLocalNotifications() // cancel all notifications
 
    for note in notifications {
        UIApplication.shared.scheduleLocalNotification(note) // reschedule the new versions
    }
}

There’s no way to update a scheduled notification, but you can achieve the same effect by simply canceling the notification, making your changes and rescheduling it.

The applicationIconBadgeNumber property can accept values up to 2,147,483,647 (NSIntegerMax), though anything over five digits will be truncated in the icon badge. Setting it to zero or a negative number will result in no change.

Screen Shot 2015-02-04 at 11.36.36 PMScreen Shot 2015-02-04 at 11.38.14 PM

Now we just need to call this method when our to-do list changes. Add the following line to the bottom of addItem: and removeItem: in TodoList

self.setBadgeNumbers()

Now, when a notification fires, the badge number will be automatically updated.

Repeating Notifications

UILocalNotificaiton instances have a repeatInterval property that we can use to, unsurprisingly, repeat a notification at a regular interval. This is a good way to get around the 64 notification limit in some cases; a repeating notification is only counted against it once.

Unfortunately, we have to choose between using repeatInterval and applicationIconBadgeNumber in this app. Badge numbers are set on the application icon each time a notification is fired. Older notifications could end up “out of phase” with newer notifications and actually lower the badge number when repeated. We could get around this by scheduling two notifications for each to-do item, a repeating notification that displays the alert and plays the sound, and a non-repeating notification that updates the badge count, but this would cut the number of to-do items we could schedule in half.

The biggest limitation with repeating notifications is that the repeatInterval doesn’t accept a custom time interval. You have to provide an NSCalendarUnit value like “HourCalendarUnit” or “DayCalendarUnit”. If you want a notification to fire every 30 minutes, for example, you’ll have to schedule two notifications (offset by 30 minutes) and set them both to repeat hourly. If you want it to fire every 31 minutes, then you’re out of luck.

Performing Actions in the Background

iOS 8 introduced a really useful new feature, notification actions, which let our users trigger code execution without even having to open the app. Lets give our users the ability to complete and schedule reminders for to-do items directly from the notification banner.

iOS Simulator Screen Shot Feb 6, 2015, 12.43.34 AM iOS Simulator Screen Shot Feb 6, 2015, 12.43.37 AM

In AppDelegate:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey : Any]? = nil) -> Bool {
    let completeAction = UIMutableUserNotificationAction()
    completeAction.identifier = "COMPLETE_TODO" // the unique identifier for this action
    completeAction.title = "Complete" // title for the action button
    completeAction.activationMode = .background // UIUserNotificationActivationMode.Background - don't bring app to foreground
    completeAction.isAuthenticationRequired = false // don't require unlocking before performing action
    completeAction.isDestructive = true // display action in red
 
    let remindAction = UIMutableUserNotificationAction()
    remindAction.identifier = "REMIND"
    remindAction.title = "Remind in 30 minutes"
    remindAction.activationMode = .background
    remindAction.isDestructive = false
 
    let todoCategory = UIMutableUserNotificationCategory() // notification categories allow us to create groups of actions that we can associate with a notification
    todoCategory.identifier = "TODO_CATEGORY"
    todoCategory.setActions([remindAction, completeAction], for: .default) // UIUserNotificationActionContext.Default (4 actions max)
    todoCategory.setActions([completeAction, remindAction], for: .minimal) // UIUserNotificationActionContext.Minimal - for when space is limited (2 actions max)

    // we're now providing a set containing our category as an argument
    application.registerUserNotificationSettings(UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: Set([todoCategory])))
    return true
}

Notice that we’re calling todoCategory.setActions() twice, once for each of the two available action contexts. If your users are displaying notifications from your app as banners, then the actions in the minimal context will be displayed. If notifications are displayed as alerts (the “default” context), up to four actions will be displayed.

iOS Simulator Screen Shot Feb 6, 2015, 12.41.20 AM iOS Simulator Screen Shot Feb 6, 2015, 12.06.33 AM

The order of the actions in the array we pass to setActions: is the order that the actions will be displayed in the UI, though, oddly, the items are ordered right-to-left in the minimal context.

Lets make sure to set this category for the notification we’re scheduling in TodoList’s addItem: method.

notification.category = "TODO_CATEGORY"

We already have a method for “completing” to-do items, removeItem:, but we need to implement one for scheduling a reminder in TodoList.

func scheduleReminder(forItem item: TodoItem) {
    let notification = UILocalNotification() // create a new reminder notification
    notification.alertBody = "Reminder: Todo Item \"\(item.title)\" Is Overdue" // text that will be displayed in the notification
    notification.alertAction = "open" // text that is displayed after "slide to..." on the lock screen - defaults to "slide to view"
    notification.fireDate = Date(timeIntervalSinceNow: 30 * 60) // 30 minutes from current time
    notification.soundName = UILocalNotificationDefaultSoundName // play default sound
    notification.userInfo = ["title": item.title, "UUID": item.UUID] // assign a unique identifier to the notification that we can use to retrieve it later
    notification.category = "TODO_CATEGORY"
 
    UIApplication.shared.scheduleLocalNotification(notification)
}

Note that we aren’t changing the due date on the to-do item (or trying to cancel the original notification – it’s been automatically removed). Now we just have to jump back to AppDelegate and implement a handler for the actions:

func application(_ application: UIApplication, handleActionWithIdentifier identifier: String?, for notification: UILocalNotification, completionHandler: @escaping () -> Void) {
    let item = TodoItem(deadline: notification.fireDate!, title: notification.userInfo!["title"] as! String, UUID: notification.userInfo!["UUID"] as! String!)
    switch (identifier!) {
    case "COMPLETE_TODO":
        TodoList.sharedInstance.remove(item)
    case "REMIND":
        TodoList.sharedInstance.scheduleReminder(forItem: item)
    default: // switch statements must be exhaustive - this condition should never be met
        print("Error: unexpected notification action identifier!")
    }
    completionHandler() // per developer documentation, app will terminate if we fail to call this
}

Go ahead and try it out now (you may want to pass a lower value to dateByAddingTimeInterval: for testing purposes).

iOS Simulator Screen Shot Feb 6, 2015, 1.25.36 AM

We’ve covered all of the non-geographic functionality of UILocalNotification and now have a pretty full-featured to-do list app, so this concludes our series. You can download the full source code for this project from here.


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