Category Archives: iOS9

Understand and Use Closures in Swift

Have you ever seen some factory method in UIKit and see this strange parameter called completion or completionHandler? Usually you put nil there and leave it alone. These strange things like (Double,Double) -> Bool things are closures. Closures are functions you use as types, often in parameters of other functions. In this lesson we’ll explore closures and get under the hood to explain what they are and what they are really doing.

Set Up a Playground

Set up a playground in Xcode, the iPad or on the web at the IBM Swift Sandbox. IF using the IBM Swift Sandbox, make sure you have Foundation imported.

import Foundation

The Basics of Closures

Suppose I want to calculate the volume of a deep dish pizza. Add these functions to the playground:

 //area of a round pizza
    func roundArea(radius:Double) -> Double{
        return M_PI * radius * radius
    }
    //volume of a round pizza
    func roundVolume(height:Double, radius:Double ) -> Double{
        return roundArea(radius: radius) * height
    }

Add a variable to compute the volume of a pizza of radius 6 and height 2, then print it

var volume = roundVolume(height:2,radius:6)
print( String(format:"Round volume %4.2f",volume))

Run this and get the volume from the area on the console. Which is fine if I had only round pizzas. What if I had rectangle, oval or triangle pizzas? I’d have to write two new functions each: one for the area and one for the volume. However in each case I’m only changing the area calculation. What if I could pass just the calculation to the volume function? That’s what a closure is: a way of writing something like a function but assigning it as a type. Add the function to the class.

func volume(
    height:Double, 
    dim1:Double,
    dim2:Double, 
    area:(Double,Double) -> Double) -> Double
    {
        return area(dim1,dim2) * height
    }

Look at the parameter area. I’ve declared it (Double,Double) -> Double. That looks a lot like a function that returns Double, and two Double parameters. That’s what a closure does as a type. A closure gives a skeletal structure like a function you can pass as a parameter. Below the volume function add the following line of code.

let rectangleArea = { (length:Double,width:Double) -> Double in
    return length * width
}

When I declare the contents of the identifier, I flesh out something like a function inside of curly braces. Instead of func before the parameters, I put in after the return type. There are parameters (length:Double,width:Double). I have the return type as a Double. For closures, there must always be a return value. If you don’t have one mark it Void or ().

Since I declared the type in the closure declaration, I often can remove it from the declaration

        
let  rightTriangleArea = {(length,width) -> Double in
    return length * width / 2.0
}  
let ovalArea = {(length,width)->Double in
    return length/2.0 * width/2.0 * M_PI
}

It doesn’t work in all cases though. This give an error if you do not specify the type

let roundSliceArea = { (radius:Double, slices:Double) ->Double in
    return M_PI * radius * radius / slices
}

I can use the same volume function and change how I compute the area.

print (volume(height: 2,dim1: 10, dim2: 12, area:rectangleArea))
print (volume(height: 2,dim1: 10,dim2: 12, area:roundSliceArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:rightTriangleArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:ovalArea))

I do have a problem with a circle. I could define it like this in my storyboard:

 let circleArea = {(radius)->Double in
            return radius * radius * M_PI
 }
print (self.volume(height: 2,dim1: 10, dim2: 12, area:circleArea))        

This doesn’t work for the circle, because the number of parameters are different. You’d get this error message
Cannot convert value of type '(Double) -> Double' to expected argument type '(Double, Double) -> Double'

You have choices: Use the oval with the same length and width or a placemarker.  I tend to choose the place marker. Change circleArea to add the extra parameter. Fix the above code to this:

let circleArea = {(radius:Double, placeMarker:Double) -> Double in
    return M_PI * radius * radius 
}
print (volume(height: 2,dim1: 10, dim2: 12, area:circleArea))

Literal Closure in Parameters

You don’t have to assign the closure to a constant. You can write it explictly in the function. For a rectangle area, you can write this in the playground.

 
volume = volume(
    height: 2,
    dim1: 10,
    dim2: 12,
    area: {(width,length)->Double in
        return width * length
    }
)
        print(volume)

You’ll find this a very common way of specifying the closure. When the closure is the trailing parameter, you can place it after the function like this:

volume =  volume(
    height: 2,
    dim1: 10,
    dim2: 12) 
    {(radius,placemarker)->Double in
        return M_PI * radius * radius
    }
print(volume)

Closures as Completion Handlers

One very common use of closures is completion handlers. Make a new function like this:

func volume(
    height:Double,
    dim1:Double,
    dim2:Double,
    completionHandler:(Double)->()
){
    let result = dim1 * dim2 * height
    completionHandler(result) 
}

Neither the completionHandler closure nor the volume function returns anything. Instead the result is the parameter of the completion handler.
Why would anyone do this? The reason is asynchronous processing. There are methods which Apple or the developer don’t want to run in the main thread. If you are querying a database, or opening a file any results appear in a closure. The database query might take some time, and you don’t want the rest of your application to freeze while you wait for the result. Similarly, while loading a file you don’t want to freeze the system waiting to learn if the load was successful.
Instead of return in these functions, the method runs on another thread at its own pace and lets the main thread go on its merry way. When done, the method calls something like completionHandler, handler, or completion with the results it returns as the completion parameter.

The developer uses the result their implementation of the completion handler. This is why most often closures happen completely within a function call. Usually they are specific to the code around it. For example, the volume function with the completionHandler would code like this to save the result to the volume property and output the result.

volume(height: 2,dim1: 10,dim2: 12)
{(result)->() in
    print(result)
    volume = result  //in a class use self
    resultLabel.text = String(format:"Completion %5.2f")
}

Completion handlers often pass off the value to a property in a class, like the code above assigns the value of  result to volume. If you run the code above within a class, be sure to include the class identifier or self. volume = result should be self.volume = result. The identifiers within a closure have no scope to the class. They must be explicitly stated.

A Real Example of a Completion Handler

As a general rule, you’ll find completion handlers when you don’t know when you will complete a task. One good example of this is presenting alert views.

Set up a new single view project named ClosureDemo, with Swift as the language. Go to the storyboard and drag a label and button.

Set the font on both to Title 1.  Select the button. Title the button Alert. Click the alignment iconalignment icon in the auto layout menu bar. Check Horizontally in Container and Vertically in container.  Change Update from to Items of new constraints like this:

center alignment

Click Add 2 constraints.  Select the Label. Title it Results.  Click the pin icon pinMenuButton in the auto layout toolbar.  In the dialog box that appears type 10 then tab, 10 then tab and 10 then tab. Click down to the bottom and Update frames with Items of New Constraints.

2016-09-02_07-07-01

Click Add 3 constraints.

Open the assistant editor and control-drag the label to the code. Name the outlet resultsLabel. Control-Drag from the button to the code. Change to an action. Name the action presentAlert.

Close the Assistant editor.  Open the Viewcontroller.swift code.

In the presentAlert action, add the following code:

 @IBAction func presentAlert(_ sender: UIButton) {
     volume = 240.0
 //example of a real completion handler.
     let alert = UIAlertController(
         title: "Alert",
         message: "Volume is \(volume)",
         preferredStyle: .alert)
   

Alert actions only activate after the alert shows and the action’s button gets tapped by the user. The UIAlertAction initializer uses handlers to describe the action when pressed. Add this to the compute action:

   
let clearAction = UIAlertAction(
    title: "Clear", style: .destructive,
    handler: {(action)->() in
        self.volume = 0
        self.resultLabel.text = "Cleared"
    }
)
 

In clearAction, the user sets the volume property to 0 and reflects that in the Label.  Again, the closure is independent of the scope of its class. You must specify self in order to use the ViewController classes’ properties and methods.

Add two more examples of actions:

       
let doubleAction = UIAlertAction(
    title: "Double",
    style: .default,
    handler: {(action)->() in
        self.volume *= 2
        self.resultLabel.text = "\(self.volume)"
    })
let cancelAction = UIAlertAction(
    title: "Cancel",
    style: .cancel,
    handler:{(action)->() in
       self.resultLabel.text = "\(self.volume)"
    })
   

Add the actions to the alert

alert.addAction(clearAction)
alert.addAction(doubleAction)
alert.addAction(cancelAction)

Finally present the alert controller. present has a completion handler. Add a print statement to it to see it in action, then a print after the present.

 
present(alert, animated: true, completion: 
{()->Void in
    print ("present Completed")
 })
 print ("Outside Handler")

Build and run. Tap the Alert button. You get the alert. The console printed this:

Outside Handler
present Completed

After calling present, The system executed the next step, and did not wait for it to finish printing  Outside Handler. Once the presentation completed, the system printed present Completed. The alert shows:

2016-09-12_06-03-57

Tap the double button. The label reads 240. Try the other buttons.

2016-09-12_06-04-38

You’ll find closures in most often in these handlers. Anything that takes and unpredictable amount of time will use a handler in a closure.

The Whole Code

You can find the code for this tutorial on the IBM Swift Sandbox. For cutting and pasting into an Apple Playground on iPad or Xcode, Use the code below.

//
// Closure Demo file 
// For MakeAppPie.com  closure tutorial 
// Sep 2016 by Steven Lipton 
// 
//
import Foundation

 //area of a round pizza
    func roundArea(radius:Double) -> Double{
        return M_PI * radius * radius
    }
    //volume of a round pizza
    func roundVolume(height:Double, radius:Double ) -> Double{
        return roundArea(radius: radius) * height
    }
    var volume = roundVolume(height:2,radius:6)
print( String(format:"Round volume %4.2f",volume))

//Closure to change the area formula
func volume(
    height:Double, 
    dim1:Double,
    dim2:Double, 
    area:(Double,Double) -> Double) -> Double
    {
        return area(dim1,dim2) * height
    }
    
    //Assigning type (Double,Double) -> Double to Classes
    
    let rectangleArea = { (length:Double,width:Double) -> Double in
    return length * width
}
let  rightTriangleArea = {(length,width) -> Double in
    return length * width / 2.0
}  
let ovalArea = {(length,width)->Double in
    return length/2.0 * width/2.0 * M_PI
}
let roundSliceArea = { (radius:Double, slices:Double) ->Double in
    return M_PI * radius * radius / slices
}
//Trying out the volume function

print (volume(height: 2,dim1: 10, dim2: 12, area:rectangleArea))
print (volume(height: 2,dim1: 10,dim2: 12, area:roundSliceArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:rightTriangleArea))
print (volume(height: 2,dim1: 10, dim2: 12, area:ovalArea))

//Fitting a single parameter formula with a placemarker
let circleArea = {(radius:Double, placeMarker:Double) -> Double in return M_PI * radius * radius }
print (volume(height: 2,dim1: 10, dim2: 12, area:circleArea))
// closure within parameters
        volume = volume(height: 2, dim1: 10, dim2: 12, area: {(width,length)->Double in return width * length})
        print(volume)
// Trailing closure outside parentheses
volume =  volume(height: 2, dim1: 10, dim2: 12) {
     (radius,placemarker)->Double in
          return M_PI * radius * radius
}

//Closures as Completion Handlers
func volume(
    height:Double,dim1:Double,dim2:Double,
    completionHandler:(Double)->()
){
    let result = dim1 * dim2 * height
    completionHandler(result) 
}

// For asynchronous processing, use the closure instead of returning the result. 
volume(height: 2,dim1: 10,dim2: 12)
{(result)->() in
         print(result)
         volume = result
         
}

The ClosureDemo Alert project

This is the code for the ClosureDemo Alert project. Add the label and button as described above than connect to this code in ViewController.

//
//  ViewController.swift
//  ClosurePlay
//
//  Created by Steven Lipton on 9/9/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    
    var volume = 0.0
    
    @IBOutlet weak var resultLabel: UILabel!
    @IBAction func compute(_ sender: UIButton) {
        volume = 240.0
        //example of a real completion handler.
        let alert = UIAlertController(
            title: "Alert",
            message: "Volume is \(volume)",
            preferredStyle: .alert)
        
        let clearAction = UIAlertAction(
            title: "Clear", style: .destructive,
            handler: {(action)->() in
                self.volume = 0
                self.resultLabel.text = "\(self.volume)"
        })
        
        let doubleAction = UIAlertAction(
            title: "Double",
            style: .default,
            handler: {(action)->() in
                self.volume *= 2
                self.resultLabel.text = "\(self.volume)"
        })
        let cancelAction = UIAlertAction(
            title: "Cancel",
            style: .cancel,
            handler:{(action)->() in
                self.resultLabel.text = "\(self.volume)"
        })
        
        
        alert.addAction(clearAction)
        alert.addAction(doubleAction)
        alert.addAction(cancelAction)
        present(alert, animated: true, completion: {()->Void in
            print ("In present Handler")
        })
        print ("Outside Handler")
        
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    
}

How to Read CSV files from the Web in Swift

There’s a lot of data out there for the taking, if you only know how to get it.  There is a treasure trove of  government-based data sets available to the public. There’s a lot of information free for the taking you might want to know, or add to your app.

There’s also many different ways to get that data. Three data formats CSV, XML,and JSON all provide great ways to getting that data. Some data portals are one type, others you have the option of all of them.  In our last lesson, we began to look at the CSV file format with a data file in the app bundle. In this lesson we’ll dig deeper into CSV files, and learn to load them from an external source.

The CSV Data Format

We don’t often have just text in a text file. Instead, we use a variety of arrangements to store other types of data than text. One of the oldest of these arrangements is the CSV, or comma separated value file. Here’s an example.

Title,data1,data2
Hello Pizza!,24,42
Ice Cream,16,32

As the name implies, we separated out three values per line by a comma. The CSV file represents this table:

Title data1 data2
Hello Pizza! 24 42
Ice Cream 16 32

While standards exist, you’ll rarely find anyone following them.  There is no real standard for CSV that everyone follows. You’ll find several variations. Fortunately there are a few rules that tend to be true. A row of data usually ends with a newline character \n. The elements, which I’ll often call fields of the row have some character to delimit them. The most popular is the comma, but tab characters and the pipe character | show up too. Once again, it’s important to know what you are dealing with. Notice the first line of our CSV file has a description, often referred to as the header, of the column of data. That is helpful, and you’ll find it often in data sets, but it isn’t always there. When writing my own, I tend to include it. Sometimes it is the only documentation about this file.

Now consider this file:

Title,data1,data2
Hello, Pizza!,24,42
Ice Cream,16,32

Line 2 of this file has a problem. The text Hello, Pizza!uses a comma. In CSV,  this means we have four fields in our first row of data, and that messes up our table.

Title data1 data2
Hello Pizza! 24 42
Ice Cream 16 32

There are two approaches in CSV to handle this. One is to change the delimiter from a comma to something not often used as a character. Two common ones are the tab character (Often referred to a Tab-Demlimited) or a pipe | character.

Title|data1|data2
Hello, Pizza!|24|42
Ice Cream|16|32

The most common way to handle the problem is to use quotes. Quotes in a CSV file mean this is a string and to include everything in between the quotes as a single field.

"Title","data1","data2"
"Hello, Pizza!",24,42
"Ice Cream",16,32

Some implementations of code are smart enough to know quotes are strings, numbers without decimal points are Int, and with decimal points are Double. For reasons I’ll discuss later, I usually don’t make things this smart.

Many web data portals such as the Socrata.com platforms listed in the Open Data Network get paranoid about the whole thing and put everything in quotes.

"Title","data1","data2"
"Hello, Pizza!","24","42"
"Ice Cream","16","32"

We’ll be working with files of this last kind. However, any one of these are possible. One of may many rules about using data is always check the specs and context of your data set. Look at that data and code appropriately. Which leads to a disclaimer here. This demo will lead to one way of handling data. There are as many ways of handling data in CSV as there are datasets. For any project, you’ll have to do some work in getting it to work with your data set.

This by the way is why I’m not a big fan of 3rd party libraries. I simply don’t trust them. A lot of talented people did a lot of good work, but I can’t tell that without so much testing and reading of code that I’d write my own a lot faster and keep it up to date a lot easier and faster.

Biking to the Center of the Universe

The Fremont neighborhood of Seattle has proclaimed itself the Center of the Known Universe. This quirky neighborhood has its own troll, a rocket, and has a statue of Vladimir Lenin.  Adobe and Google both have offices here.  Fremont also has one of the few bridges between the northwest neighborhoods of Seattle and downtown. In October 2012,  Seattle installed bike sensors on the sidewalks of the bridge to count the number of bicycles that cross the bridge.  That data is a rather simple data set where we can see patterns on bicycle traffic over this bridge. In another post, I explained how to download data to a file, which you could then put into the app bundle. That  does not give us updated information. To get updated  information,  we poll the website directly, which is what we’ll do in this lesson.

Setting up the Storyboard

For this project we’ll use a very simple storyboard. I could do this in the playgrounds, but I find them very slow in compiling.  We’ll display to a text view. Open a new project in Xcode called CSVWebDemo. Set the language to Swift and a Universal device. Save the file and go to the storyboard.   Change the background color of the view to Light gray in the attributes inspector. Find the Text View  in the object library

2016-05-27_07-13-41

Drag the text view to the center of the storyboard.

2016-05-27_07-18-25

On the auto layout toolbar in the lower right of the  storyboard, click the pin button pinMenuButton.  Pin the view 10 Points up, 0 points right, 0 points left, and 20 points down. Be sure to press Tab after each field to make sure the entry takes. Set the Update Frames to Items of New Constraints.

2016-05-27_07-22-09

Click the Add 4 Constraints button. We get this:

2016-05-27_07-26-49

Open the assistant editor. Control drag from the text view to the ViewController class code and make an outlet named textView.

Close the assistant editor.

The CSVDemo Class

We’ll make a new class for our data model.  Press Command-N and make a new Cocoa Touch class CSVDemo subclassing NSObject. Add three properties to the class

var data:[[String:String]] = []
var columnTitles:[String] = []
var columnType:[String] = ["NSDate","Int","Int"]

This class will read the CSV as a text file and store it in a string. We’ll convert it to array containing a data dictionary stored as strings. To help us access that data easily, we have two additional arrays. columnTitles is the header from the first row of the array. These are the keys for the dictionaries in data. When we need to convert this to the actual data types for the columns, we have a second array for the type of the corresponding column. Looking at the data portal we are getting this data from, we determine those types.

2016-05-27_07-41-53

If there’s  API documentation, it will tell us the type:

2016-05-27_07-45-42

However, you still might need to do some thinking. From this, I know my first field is an NSDate. Since number is a count of bikes, Int is the best choice for a type for the other two fields.

Reading a Web File

Our first task is reading a text file as a string into the app. Add this code:

func readStringFromURL(stringURL:String)-> String!{
        if let url = NSURL(string: stringURL) {
            do {
                return try String(contentsOfURL: url, usedEncoding: nil) 
            } catch {
                print("Cannot load contents")
                return nil
            }
        } else {
            print("String was not a URL")
            return nil
        }
    }

We return an optional String value from this method. This code could be two lines:

let url = NSURL(string: stringURL)
return String(contentsOfURL: url, usedEncoding: nil) 

The first line makes a NSURL from the string we pass to it. The second line reads a text file and stores it in a string,  returned in the function.  It would be noce if this was all we needed, but it isn’t. Don’t trust anything when reading and writing data. Neither does iOS, and the String initializer that reads data from an external source throws errors. We can have dozens of things go wrong between server, transmission, and client when reading data from the web. Swift forces us to use the do-catch clause with this String initializer. If the function throws an error, we return nil and send a message to the console. We also check that our URL is well formed, THe NSURL initilizaer first checks that the string we give it is really a URL If it isn’t, the method returns nil.  If it is properly formed, we enter the do-catch clause to read the data with the String:contentsOfURL:usedEncoding initializer.

Go to viewDidLoad in the ViewController class. Change viewDidLoad to this. To make sure the URL is correct, I suggest copying and pasting this code.

override func viewDidLoad() {
    super.viewDidLoad()
    let csvDemo = CSVDemo()
    let stringURL = "https://data.seattle.gov/resource/4xy5-26gy.csv?$where=date%20between%20%272016-04-01T00:00:00%27%20and%20%272016-04-30T23:00:00%27&$order=date"
        textView.text = csvDemo.readStringFromURL(stringURL)
    }

We make an instance of our model and set a constant to the url we want. While I’ll go into the full anatomy of this URL in a separate lesson, there are a two important points to note. I could have written this

let stringURL = "https://data.seattle.gov/resource/4xy5-26gy.csv

which would have given me the full database of 31,000 records unsorted. That is a huge memory investment. Try to load a little as possible and try to avoid heavy processing on the device. Instead leave that heavy lifting to the server, which is what the rest of this URL is about. The API for the Socrata platform allows a subset of SQL call SoQL for retrieving data, so the filtered data is April 2016 and sorted it by date.

We used a https in this url. Pay attention if this is a http or https. iOS doesn’t trust http. Since it is a less secure protocol, iOS  and Foundation will reject your request if you use a site that loads from http. I discuss this in more details in my post about web views and security if you want to know how to bypass the iOS security.

Build and run. You’ll get this

2016-05-27_08-19-09

I happen to like this view this in landscape. If you want to do that press Command-Right Arrow.

2016-05-28_08-13-32

You can scroll down by swiping up. Scroll to  see the last row of the data

2016-05-28_08-20-12

Converting the Data to CSV

The data we have is one long string.  We’d like it to be accessible data. There are many data structures use can use to do that. You can make a struct or class for the data in each row. For this file I could do this.

class DataRow:NSObject{
    var date:NSDate
    var northCount:count
    var southCount:count
}

Then keep the rows together in an array.

var dataTable = [DataRow]

If the data will be used in user interfaces, you want to use an array to arrange the rows. Most of the views that have large sets of data like UITableView use arrays.

In our case we’ll use dictionaries. I prefer dictionaries since it’s easy to do repetitive tasks with them. In a class or struct I’d have to directly assign everything. In a dictionary, as long as I have an array of keys, I can make a loop to do that work for me. You could, of course, use an array instead of a dictionary, but Apple uses Dictionaries in some of the other data format conversions so it’s good to get used to it.

As I discussed in the last lesson, there are several steps to converting the string to a CSV data file.

  1. Clean the row delimiter to be \n
  2. Break the string into an array of rows
  3. Loop through the array of rows
    1. Break the row into an array
    2. Place that array into a structure with an element in a new array.
  4. Return the new array

We’ll do the same here, with two changes: Our data has quotes around all the fields. We need to remove the quotes. We want our fields to be of type NSDate and Int instead of strings. We’ll add to 3.1 and 3.2 some code to accomplish these tasks.

Clean the Row Delimiter

First task is to clean the rows and return the separated rows. Add the following method to CSVDemo.

func cleanRows(stringData:String)->[String]{
    var cleanFile = stringData
    cleanFile = cleanFile.stringByReplacingOccurrencesOfString("\r", withString: "\n")
    cleanFile = cleanFile.stringByReplacingOccurrencesOfString("\n\n", withString: "\n")
     return cleanFile.componentsSeparatedByString("\n")
}

Or row delimiter is \n. We might have some people or systems  who put \r\n, \r, or \n\n instead. To make sure we have the correct delimiter, we replace the string using the String function stringByReplacingOccurrencesOfString:withString: so they are all \n

Once they are \n, separate into an array of rows using the componentsSeparatedByString string function and return the result

Clean the Rows of Quotes

Next we’ll clean individual rows and make a string array of them. This works the same way as the previous method. Add this method:

 func getQuoteFieldsForRow(oldString:String) -> [String]{
    let delimiter = "\t"
    var newString = oldString.stringByReplacingOccurrencesOfString("\",\"", withString: delimiter)
    newString = newString.stringByReplacingOccurrencesOfString(",\"", withString: delimiter)
    newString = newString.stringByReplacingOccurrencesOfString("\",", withString: delimiter)
    newString = newString.stringByReplacingOccurrencesOfString("\"", withString: "")
    return newString.componentsSeparatedByString(delimiter)
}

Remember there’s a reason for the quotes. We want to properly reflect commas in the data. So we take the strategy we mentioned earlier of converting quotes to a delimiter we can’t use in the string. I use tabs that delimiter. I find every combination of quotes and change them to  tabs or delete them.

If you are paying attention, you’ll might be wondering something. I said earlier that you should pay attention to the context of your data. Here I have no string types in my data and the comma is not going to show up. I could cut three lines of code from this and just delete the quotes. However, I’m also not sure that data won’t change and integers start using commas. We play it safe by setting things up this way.

Putting It All Together

We can make nested loops to build our data structure. Add this method to CSVDemo

func convertCSV(stringData:String) -> [[String:AnyObject]] {
    
        let rows = cleanRows(stringData)
        if rows.count > 0 {
            data = []
            columnTitles = cleanFields(rows.first!)
            for row in rows{
                let fields = cleanFields(row)
            }
        } else {
            print("No data in file")
        }
        return data
    }

This is the basic structure of the rows loop. As long as there is one row in the file for each row, we break the row into an array of fields. If there isn’t any rows, we send an error message. We also do something special with the first row, adding its contents into columnTitles, the dictionary keys for the other rows.
After the cleanFields statement add this:

if fields.count != columnTitles.count {continue}

This is a check that we have a valid row. The header had the names of each row. The number of elements in the fields array should match the number in the columnTitles array.  If our current row has a different number of fields, it’s an invalid row. For an invalid row in this demo, I ignore the row by continuing the loop to the next iteration. We can get much fancier though, and warn the app or the user that there is an invalid row or even attempt to fix the row.

Under that, add the next loop

var newRow = [String:AnyObject]()
for index in 0..<fields.count{
    let column = columnTitles[index]
    let field = fields[index]
}
data += [newRow]

This loops through the fields. This time we have multiple arrays with the same index. The loop therefore uses an integer index. We also have a dictionary newRow where we’ll store the fields for this row. After the loop of the fields completes, newRow is added as a new entry to the array data.

I could add just one more line and this would work:

 newRow[column]= field

Since the value of newRow is an AnyObject, the strings would store. In the last lesson we did that, storing everything in a string. I could change the string to a more useful type later in a separate function.

However for memory management reasons, it is better to convert to appropriate types now. The number 314159 is six bytes as a String and one as an Int. We get huge memory and speed savings in bigger files by converting. Therefore we’ll add a parser that converts and saves the data in the type we want.

With CSV, there’s several approaches to this. If you wanted to do this automatically, you could try telling the difference between a String, an Int, and a Double. Strings will have the quotes around them, Assuming we didn’t strip the quotes earlier.  Doubles will have a decimal point. However that’s lot of exceptions and problems. Take the number 1,234,567. If the data contains that number, and your field delimiter is a comma,  it breaks into three integers. Suppose you have these two rows

"pizza",12,15
"pasta",12.2,56

You have in one field the numbers 12 and 12.2. The field should be Double but the system might read one as an Int and one as a Double, something any Swift developer who uses implicit typing can appreciate. As I’ll show shortly, dates cannot be converted automatically. For me, it’s too much of a headache. Unless the souce of this data publishes an API for it(and even then I’m suspicious) I skip all this and use explicit typing on my dictionary with columnType.

Add this to your code under let field = fields[index].

switch columnType[index]{
    case "Int":
        newRow[column] = Int(field)
    case "NSDate":
        guard let newField = dateFormatter.dateFromString(field) else {
            print ("\(field) didn\'t convert")
            continue
        }
        newRow[column] = newField
    default: //default remains a string
       let newField = field
       newRow[column] = newField
}

We use a switch on columnType. Remember, columnType has the following values:

var columnType:[String] = ["NSDate","Int","Int"]

columnType sets up cases for the types we want, an Int and NSDate. We can add more types here to our hearts desire. We default to a String if the field is not a type we listed here.

Converting to Int is simple. Cast the field to Int then store it in the newRow dictionary with the key column.

newRow[column] = Int(field)

The date is a bit more complicated, and a good example why I used this approach to typing. We use the NSDateFormatter method dateFromString. This will require us to set a specific date format. Dates have too many formats, and while standards exist, there are almost as many exceptions as standards. Is any given date represented by date MM-dd-yyyy, MM/dd/yy or yyyy-MM-dd? Is the time 12-hour with AM/PM or 24 hour? How many decimal points on the seconds? What in the strong seperates the time from the date? The answers get complex if you have code figuring it out. For converting an external file to an NSDate using NSDateformatter, we have to get explicit with our date format. At the top of the CSVDemo class add this property:

var dateFormat = "yyyy-MM-dd'T'HH:mm:ss.sss"

You can find a full table of possible values for the formatting string in the Unicode Technical Standard #35. In our first iteration, the simulator printed the date as 2016-04-01T00:00:00.000. Set it the same using yyyy-MM-dd'T'HH:mm:ss.sss. If anything doesn’t match this time pattern, such as skipping the three decimal point places, NSDateFormatter will not convert it.

Just after declaring the function convertCSV, add this code to initialize and set the properties of the NSDateFormatter.

let dateFormatter = NSDateFormatter()
dateFormatter.dateFormat = dateFormat
dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")  

We need to make one important but not obvious setting here: we set the time zone to GMT so we have no correction for time zone in our data structure. Otherwise, iOS will input this as GMT time, and then convert it to your local time zone.

Our completed method looks like this:

 func convertCSV(stringData:String) -> [[String:AnyObject]] {
    let dateFormatter = NSDateFormatter()
    dateFormatter.dateFormat = importDateFormat
    dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
    let rows = cleanRows(stringData)
    if rows.count > 0 {
       data = []
       columnTitles = cleanFields(rows.first!)
       for row in rows{
           let fields = cleanFields(row)
           if fields.count != columnTitles.count {continue}
           var newRow = [String:AnyObject]()
           for index in 0..<fields.count{
              let column = columnTitles[index]
              let field = fields[index]
               switch columnType[index]{
                   case "Int":
                       newRow[column] = Int(field)
                   case "NSDate":
                       guard let newField = dateFormatter.dateFromString(field) else {
                            print ("\(field) didn\'t convert")
                            continue
                        }
                    newRow[column] = newField
                    default: //default keeps as string
                        newRow[column] = field
                    }
                }
                data += [newRow]
            }
        } else {
            print("No data in file")
        }
        return data
    }

Making Some Useful Output

We’ve converted the data into a useful data structure. Now we can use that structure for some computations. Let’s look at the frequencies and averages of crossing the bridge. Add this code:

    func printTotalsAndRatio()-> String{
        var north = 0
        var south = 0
        for index in 1..<data.count{
            let row = data[index]
            north += row["fremont_bridge_nb"] as! Int
            south += row["fremont_bridge_sb"] as! Int
        }
        let totalCrossings = north + south
        let totalAverageCrossings = totalCrossings / 30
        let averageNorthCrossings = north / 30
        let averageSouthCrossings = south / 30
        let crossingRatio = Double(north) / Double(south)
        var displayString = "Fremont Bridge April 2016 Data\n"
        displayString += String(format:"North Side Count:%8i  SouthSide Count%8i\n",north,south)
        displayString += String(format:"Total Crossings:%8i\n",totalCrossings)
        displayString += String(format:"Average Crossings per day:%8i\n",totalAverageCrossings)
        displayString += String(format:"North Side Average:%8i  SouthSide Average%8i\n", averageNorthCrossings,averageSouthCrossings)
        displayString += String(format:"North/South ratio:%8.3f",crossingRatio)
        return displayString
    }

The first part of the method totals the number of northbound and southbound crossings of the bridge. The second part uses those totals to compute a few monthly statistics. The third creates a string presenting that information.

Go to the ViewController class. Change viewDidLoad to this:

override func viewDidLoad() {
        super.viewDidLoad()
        let csvDemo = CSVDemo()
        let stringURL = "https://data.seattle.gov/resource/4xy5-26gy.csv?$where=date%20between%20%272016-04-01T00:00:00%27%20and%20%272016-04-30T23:00:00%27&$order=date"
        textView.text = csvDemo.readStringFromURL(stringURL)
        csvDemo.convertCSV(textView.text)
        textView.text = csvDemo.printTotalsAndRatio()
    }

Build and run. We get some results.

2016-05-30_15-39-33

This is a bridge used primarily by commuters to downtown Seattle. It’s therefore not surprising that there is close to an even distribution of people going both north and south.

There’s Always One More Bug

You’ll also notice the console had this:
date didn't convert
We need to make a small change to our code. Find this line in the convertCSV method:

for index in 0..<fields.count{

This reads the title row along with everything else. We don’t want to read the header row when converting data. change the code to this:

for index in 0..<fields.count{

With this simple change, the header is excluded data, and not converted to dates and integers.

What CSV Lacks

CSV is very popular with those transferring data into or out of spreadsheets. Most spreadsheets use only a few data types and is thus easy to use. However, it fails miserably once you get to more complex data types. Some have tried an array (see the Violations column in this data base for example) but the results are rather painful to work with. Dictionary types are near impossible in CSV. What if you want to need those types in your dataset? For dealing with such complex issues our next few lessons on XML and JSON will give us solutions.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  CSVWebDemo
//
//  Created by Steven Lipton on 5/27/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var textView: UITextView!
    override func viewDidLoad() {
        super.viewDidLoad()
        let csvDemo = CSVDemo()
        let stringURL = "https://data.seattle.gov/resource/4xy5-26gy.csv?$where=date%20between%20%272016-04-01T00:00:00%27%20and%20%272016-04-30T23:00:00%27&$order=date"
        textView.text = csvDemo.readStringFromURL(stringURL)
        csvDemo.convertCSV(textView.text)
        textView.text = csvDemo.printTotalsAndRatio()
    }
}

CSVDemo.swift

//
//  CSVDemo.swift
//  CSVWebDemo
//
//  Created by Steven Lipton on 5/27/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class CSVDemo: NSObject {
    var  data:[[String:AnyObject]] = []
    var  columnTitles:[String] = []
    var  columnType:[String] = ["NSDate","Int","Int"]
    var  importDateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS"
    
    func cleanRows(stringData:String)->[String]{
        //use a uniform \n for end of lines.
        var cleanFile = stringData
        cleanFile = cleanFile.stringByReplacingOccurrencesOfString("\r", withString: "\n")
        cleanFile = cleanFile.stringByReplacingOccurrencesOfString("\n\n", withString: "\n")
        return cleanFile.componentsSeparatedByString("\n")
    }

    func cleanFields(oldString:String) -> [String]{
        let delimiter = "\t"
        var newString = oldString.stringByReplacingOccurrencesOfString("\",\"", withString: delimiter)
        newString = newString.stringByReplacingOccurrencesOfString(",\"", withString: delimiter)
        newString = newString.stringByReplacingOccurrencesOfString("\",", withString: delimiter)
        newString = newString.stringByReplacingOccurrencesOfString("\"", withString: "")
        return newString.componentsSeparatedByString(delimiter)
    }
    
    func convertCSV(stringData:String) -> [[String:AnyObject]] {
        //for date formatting
        let dateFormatter = NSDateFormatter()
        dateFormatter.dateFormat = importDateFormat
        //dateFormatter.timeZone = NSTimeZone(abbreviation: "GMT")
        
        let rows = cleanRows(stringData)
        if rows.count > 0 {
            data = []
            columnTitles = cleanFields(rows.first!)
            for row in rows{
                let fields = cleanFields(row)
                if fields.count != columnTitles.count {continue}
                var newRow = [String:AnyObject]()
                for index in 1..<fields.count{ let column = columnTitles[index] let field = fields[index] switch columnType[index]{ case "Int": newRow[column] = Int(field) case "NSDate": guard let newField = dateFormatter.dateFromString(field) else { print ("\(field) didn\'t convert") continue } newRow[column] = newField default: //default keeps as string newRow[column] = field } } data += [newRow] } } else { print("No data in file") } return data } func printTotalsAndRatio()-> String{
        var north = 0
        var south = 0
        for index in 1..<data.count{ let row = data[index] north += row["fremont_bridge_nb"] as! Int south += row["fremont_bridge_sb"] as! Int } let totalCrossings = north + south let totalAverageCrossings = totalCrossings / 30 let averageNorthCrossings = north / 30 let averageSouthCrossings = south / 30 let crossingRatio = Double(north) / Double(south) var displayString = "Fremont Bridge April 2016 Data\n" displayString += String(format:"North Side Count:%8i SouthSide Count%8i\n",north,south) displayString += String(format:"Total Crossings:%8i\n",totalCrossings) displayString += String(format:"Average Crossings per day:%8i\n",totalAverageCrossings) displayString += String(format:"North Side Average:%8i SouthSide Average%8i\n", averageNorthCrossings,averageSouthCrossings) displayString += String(format:"North/South ratio:%8.3f",crossingRatio) return displayString } func readStringFromURL(stringURL:String)-> String!{
        if let url = NSURL(string: stringURL) {
            do {
                 return try String(contentsOfURL: url, usedEncoding: nil)
                
            } catch {
                print("Cannot load contents")
                return nil
            }
        } else {
            print("String was not a URL")
            return nil
        }
    }


}

Adding Annotations and Overlays to Maps

In the last lesson, we explored the  joys of making maps using the UIMapView and MapKit. While showing maps is great, we often want to add things to a map, such as locations and graphics.  These are known as Annotations and Overlays. In this lesson we’ll make some of each. We’ll start by locating pizza restaurants in Chicago, then adding a 2 kilometer pizza delivery region for a pizza chain using overlays. Finally we’ll map out how to get from the former Marshall Field’s (now Macy’s) to the first Deep Dish pizza.

Make a New Project

I’ll quickly go through the the basic setup of a map. For more detail see the previous lesson. Start by making a New Single-view  project Called MapAnnotationsOverlayDemo. Use a Universal device and Swift as the language.   When it loads, we need to turn on MapKit.  In the Project Properties, click on the the Capabilities tab. You will see a list of functions we can use. About halfway down the list you’ll See Maps

2016-05-04_05-42-52

Turn on maps and the menu opens.

2016-05-04_05-42-53

We won’t be changing anything here, but the framework will load when we need it.

Go to the storyboard.  In the Object Library, find the  Map  Kit View object

2016-05-04_05-47-46

Drag it somewhere on the story board.

2016-05-04_05-48-39

Select the Map view. Click the pinMenuButton  button in the Auto Layout  menu on the lower right of the storyboard. This will bring up the the pin menu. Click off Constrain to margins. Set the margins to be 0 on all sides. Change the Update Frames to Items of New Constraints.  Your pin menu should look like this.

2016-05-04_05-49-37

Click Add 4 Constraints. The map view takes the entire view on the story board.

2016-05-04_05-51-02

With the map view still selected, look at the attribute inspector.  At the top is some attributes specific to map views:

2016-05-04_05-59-11

Turn off Points of Interest. They will get in the way of our app. 

Open the Assistant editor.  Go to the ViewController.swift file.  Before we do anything else, we needto import  MapKit. It is a separate framework than UIKit.  Add import MapKit

import UIKit
import MapKit

Control-drag from the map view on the storyboard to the code. Add the outlet mapView.  We also need to add the delegate MKMapViewDelegate. Just to keep things organized, I added a few marks. When done your  ViewController code should look like this:

//MARK: Global Declarations
class ViewController: UIViewController,MKMapViewDelegate {
    //MARK: Properties and Outlets
    @IBOutlet weak var mapView: MKMapView!
    //MARK: - Annotations
    //MARK: - Overlays 
    //MARK: - Map setup
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

There’s a coordinate we’ll use as a reference point for the city of Chicago. The city, and some of the suburbs use the same grid system for addresses. There is an origin point for this grid in downtown Chicago, where we can say we are at 0,0. That is the intersection of State and Washington Streets. We’ll use it in a few places, thus I’ll use a global constant. Add this code above the class definition for ViewController  just below the Global Declarations mark:

//MARK: Global Declarations
let chicagoCoordinate = CLLocationCoordinate2DMake(41.8832301, -87.6278121)// 0,0 Chicago street coordinates

Add the following method to ViewController to set the initial region for our app with 5 kilometers (3 miles) boundaries:

//MARK: - Map setup
func resetRegion(){
    let region = MKCoordinateRegionMakeWithDistance(chicagoCoordinate, 5000, 5000)
    mapView.setRegion(region, animated: true)
}

Add the region to viewDidLoad:

override func viewDidLoad() {
    super.viewDidLoad()
    resetRegion()
}

Build and run. You get a map of downtown Chicago.

2016-05-12_15-56-16

Adding a Single Annotation Pin

The most common and simplest annotation is a pin. There is a special class of annotations MKPinAnnotationView for displaying pins. This is a subclass of the more generic MKAnnotationView, which uses a  custom class that adopts the MKAnnotation Protocol for its data source. Between the chicagoCoordinate and the ViewController declarations, add this class:

class ChicagoCenterCoordinate: NSObject,MKAnnotation{
    var coordinate: CLLocationCoordinate2D = chicagoCoordinate
    var title: String? = "0,0 Street Numbers"
}

Any object can be an annotation object. Here we have a  NSObject adopting the MKAnnotation protocol. Unlike other protocols, MKAnnotation does not have required methods, but required properties. You must declare a property coordinate. You can optionally declare title and subtitle as well. If you have other information, you can pass that along too.

The annotation must be added to mapView. Change viewDidLoad like this:

override func viewDidLoad() {
    super.viewDidLoad()
    resetRegion()
    mapView.addAnnotation(ChicagoCenterCoordinate())
}

Build and run. You get a pin at the center coordinates:

2016-05-12_15-57-27

Tap the pin and you get a title.

2016-05-12_15-57-36

Using More Than One Pin

If you had only one annotation, this would be an acceptable way of using them. However you will likely have many annotations. Annotations work like table views, they reuse existing annotations to conserve resources. Unlike table views, they can work automatically if you want the default set of features.

A New Data Set

We’ll need a new data set of several locations. In this demo We’ll use a few deep dish pizza restaurants in Chicago. Press Command-N and make a new Cocoa Touch Class named PizzaLocation.swift, subclassing NSObject. Add the following code to the new class:

import UIKit
import MapKit
class PizzaLocation: NSObject, MKAnnotation{
    var identifier = "pizza location"
    var title: String?
    var coordinate: CLLocationCoordinate2D
    init(name:String,lat:CLLocationDegrees,long:CLLocationDegrees){
        title = name
        coordinate = CLLocationCoordinate2DMake(lat, long)
    }
}

Once again we import MapKit to get the data types we need for locations. In line 3, We add the MKAnnotation protocol to the class. This time we have three properties: the required coordinate, title, and the reuse identifier for this annotation. In lines 7 – 10 we have an initializer taking a name, a latitude and longitude to populate the title and coordinate for each instance of this object.

This is the code for the annotation. Usually, you would have this class read an internal file or get annotation from a server. However, I want to keep this simple and on topic, so I’ll make a constant array for our annotations. Under this class we’ll add one more class. In this class, we’ll have one property: an array of PizzaLocations. Add this(which I seriously suggest cutting and pasting):

class PizzaLocationList: NSObject {
    var restaurant = [PizzaLocation]()
    override init(){
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.84937922,long:-87.6410584  )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.90146341,long: -87.62848137 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.85110748,long: -87.61286663 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.89224916,long:-87.60951805  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:42.00302015,long:-87.81630768  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.79915575,long:-87.59028088)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.85776469,long:-87.66138509)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.95296188,long:-87.77541371)]
        restaurant += [PizzaLocation(name:"Pequod's",lat:41.92185084,long:-87.66451631)]
        restaurant += [PizzaLocation(name:"Pizzeria DUE",lat:41.89318499,long:-87.62661003)]
        restaurant += [PizzaLocation(name:"Pizzeria UNO",lat:41.8924923,long:-87.626859)]
        restaurant += [PizzaLocation(name:"Gino's East",lat:41.8959379,long:-87.6229284)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.95340615,long:-87.73214376)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.87169869,long:-87.62737565)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.96074325,long:-87.6835484)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.88411358,long:-87.65808167)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.85186556,long:-87.72202439)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.90239382,long:-87.62846892)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.89031406,long:-87.63388913)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.92911995,long:-87.65359186)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.9089214,long:-87.6775678)]
    }
}

I did the work of looking up all those locations. The initializer here populates the array restuarant.

Adding an Array of Annotations to the Map

Go back to ViewController. Add a constant restaurants to the class

let restaurants = PizzaLocationList().restaurant

Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    resetRegion()
    mapView.addAnnotation(ChicagoCenterCoordinate())
    mapView.addAnnotations(restaurants)
}

We added the  addAnnotation counterpart addAnnotations, which takes an array of MKAnnotations instead of a single annotation.

Build and run. You get a series of pins.

2016-05-15_09-47-11

Head south and you’ll find less pins. Click on the one I did and you’ll find it is Connie’s Pizza.

2016-05-15_09-47-12

Using the Map Kit Delegate for Annotations

Annotations default to a red pin with a call out for the title and subtitle. If all you want to do is put a set of red pins on the map, populate the array annotation with objects adopting the MKAnnotation protocol. You’ll notice however you can’t tell what these pins are without clicking them. To customize the annotations in any way, you’ll need a method in MKMapViewDelegate. The delegate method mapview:viewForAnnotation works like tableview:cellForRowAtIndexPath works for tables. It creates the annotations you need at any given time. Add this to the ViewController class

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    var view : MKPinAnnotationView
    guard let annotation = annotation as? PizzaLocation else {return nil}
    if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
        view = dequeuedView
    }else { //make a new view
        view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier
    }
    return view
}

The function mapview:viewForAnnotation has a map view and an annotation for parameters, which we return a optional MKAnnotoationView. nil means we don’t have a pin here for this annotation for some reason. MKAnnotationView is a UIView for a more flexible annotation. For convenience MapKit has a subclass of MKAnnotationView named MKPinAnnotationView which sets a pin at a annotation’s location.

We start this function with declaring a view of class MKPinAnotationView, since they are the easiest type of annotation view. I’m a bit careful about my annotations, and use guard to make sure I get the correct type of annotation, a PizzaLocation. Like table cells, annotations dequeue. For that to work properly we need an identifier, which we find in annotation.identifier. I check for an existing dequeued view, using that identifier property of PizzaLocation. If the annotation has already been created I assign it to the view. If it isn’t I can make a new view, assigning the reuse identifier to the annotation view.
As this is a delegate, don’t forget to set the delegate in viewDidLoad.

mapkit.delegate = self

Build and run. You get the same map as before…almost.

2016-05-15_09-47-11

You cannot get the title in a call out if you click on the pin. Once we start making our own MKAnnotationviews, we have to specify how we want annotation view to look and behave.

Changing Pin Color

Starting in iOS9, we can make the pin color any color we wish using the pinTintColor property of MKPinAnnotation. Before we do, add this function to ViewController

    func pinColor(name:String) -> UIColor{
        var color = UIColor.purpleColor()
        switch name{
        case "Connie's Pizza":
            color = UIColor.blueColor()
        case "Lou Malnati's":
            color = UIColor.greenColor()
        case "Giordano's":
            color = UIColor.yellowColor()
        default:
            color = UIColor.orangeColor()
        }
        return color
    }

This changes the color of the pin based on the pizza chain. Just before the return view in mapview:viewForAnnotation add the highlighted line:

    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        var view : MKPinAnnotationView
        guard let annotation = annotation as? PizzaLocation else {return nil}
        if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
             view = dequeuedView
        }else {
            view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
        }
        view.pinTintColor = pinColor(annotation.title!)
        return view
    }

Once we get the view, we set the pin color. Build and run. Now the pins are colorful.

2016-05-15_10-14-56

One thought about style and user interfaces when changing the pin colors. Looking at these pins can you tell which pins are Lou Malnati’s? Without a key on the map it’s difficult. Use color apraingly and for uses that color makes sense. If I was rating these restaurants. using red, green and and yellow tints tell me which ones to go to and which ones to avoid

Multiple Classes of Annotations

You’ll see one red pin. In our code, we never specified a red pin. Why it it there? Zoom in on the location (option-drag in the simulator) and you’ll find it is the center coordinate of State and Washington streets.

2016-05-13_06-56-39

Look back a the delegate methods and find this line of code:

guard let annotation = annotation as? PizzaLocation else {return nil}

This code returns nil if we cannot downcast to PizzaLocation, which we can’t with  class ChicagoCenterCoordinate. For any nil annotation view, we get our default red pin. We need to change the code to downcast for ChicagoCenterCoordinate, then set properties on that pin. Change the code to this:

 func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    if let annotation = annotation as? PizzaLocation{
        if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
            return view
        }else{
            let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
            view.pinTintColor = pinColor(annotation.title!)
            return view
        }
    }else{
        if let annotation = annotation as? ChicagoCenterCoordinate{
            if let view = mapView.dequeueReusableAnnotationViewWithIdentifier("center") as? MKPinAnnotationView {
                return view
            }else {
                let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "center")
                view.pinTintColor = UIColor.blackColor()
                return view
            }
        }
    }
    return nil
}

We test for an annotation to cast properly to ChicagoCenterCoordinate, and then make a black pin if necessary. Build and run. You now have the black pin.

 2016-05-15_09-47-14

Customizing the Annotation

There are several properties in MKAnnotationView we can use to customize the annotation view. We can get back the call out with two lines of code. Add this under the pinTintColor of black.

view.pinTintColor = UIColor.blackColor()
view.enabled = true
view.canShowCallout = true

Build and Run. Click the black pin and you get a call out.

2016-05-13_15-07-42

We can customize the appearance of the pin. The pin is a UIImage. We can add an image to the annotation in its image property. Here are two images we can use for the annotations we have

pizza pin                   crosshairs

Right click each and save the images as pizza pin and crosshairs respectively.
Go to Assets.xcassets. From where you saved the images, drag the pizza pin into the assets folder, so it creates a new image.

2016-05-15_09-47-15

Check the image set attributes to make sure this image set has the name pizza pin.

2016-05-15_09-47-16

Do the same for the cross hairs, making sure the image set name is crosshairs.

2016-05-15_09-47-17

Add two news constants to the beginning of the ViewController code. This speeds loading the image later in the delegate.

let pizzaPin = UIImage(named: "pizza pin")
let crossHairs = UIImage(named: "crosshairs")

Change the code for mapView:viewForAnnotation: to this

func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
    if let annotation = annotation as? PizzaLocation{
        if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier){
            return view
        }else{
            let view = MKAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
            view.image = pizzaPin
            view.enabled = true
             view.canShowCallout = true
             view.leftCalloutAccessoryView = UIImageView(image: pizzaPin)
             return view
         }
     }else{
         if let annotation = annotation as? ChicagoCenterCoordinate{
             if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier("center"){
                return dequeuedView
             }else {
                let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "center")
                view.image = crossHairs
                view.enabled = true
                view.canShowCallout = true
                return view
            }
        }
    }
    return nil
}

Lines 3,6,14,and 18 usesMKAnnotationView instead of MKPinAnnotationView in order to use a custom image. A pin will replace your image if you use MKPinAnnotationView In line 7, we set the image property of the annotation view to our pizza pin. Similiarly line 19 add our crosshairs. Lines 8 and 9 enable the call out with the title. Line 10 adds a view to the callout. This can be any subclass of UIView, including controls such as buttons. We make a UIImageView from the pizza pin image.

Build and run. You get the pizza image instead of the pin, with cross hairs at State and Washington streets.

2016-05-15_11-43-44

Click a Pizza restaurant and you get its name and the pizza icon

2016-05-15_09-47-18

Working with Overlays

In some cases we don’t need a point for an annotation but an area, line or tile. To show those on a map, we use an overlay. Overlays set up a lot like annotations. We start with a overlay object and in the MapKitDelegate method for overlays, tell the system what overlay view goes where.

Overlays can have several standard shapes.You can use circles, polygons, and polylines as built in shapes, plus the tile overlay. We’ll look at the polyline and circle for our examples.

Circle Overlays

Suppose we want to show a delivery area based on a distance from the restaurant. We can display that with a circle for the delivery area. We need an instance of the MKCircle class to do that. MKCircle has several initializers depending on your measurement system. We’re sticking to coordinates, so we’ll use the coordinate of the restaurant as our center and a distance in meters which becomes the radius of our circle. Add this code:

 func deliveryOverlay(restaurantName:String, radius:CLLocationDistance){
        for restaurant in restaurants{
            if restaurant.title == restaurantName{
                let center = restaurant.coordinate
                let circle = MKCircle(centerCoordinate: center, radius: radius)
                mapView.addOverlay(circle)
            }
        }
    }

We do a linear search for the specific restaurant, adding the circle overlay to mapView if it is the restaurant we are looking for. We’ll also need the delegate method to make a overlay renderer, just like an annotation needed an annotation view. Add this code to ViewController:

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
    if overlay.isKindOfClass(MKCircle){
        let circleRenderer = MKCircleRenderer(overlay: overlay)
        circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
        circleRenderer.strokeColor = UIColor.blueColor()
        circleRenderer.lineWidth = 1
        return circleRenderer
    }
        return MKOverlayRenderer(overlay: overlay)
    }

This delegate method returns a MKOverlayRenderer, from the MKOverlay parameter. MKOverlayRenderer is a form of UIView, which means most core graphics properties and function work on it. Since we may have more than one subclass of MKOverlay in the delegate, Line 2 checks if we have an MKCircle before we make it a MKCircleRenderer. Lines 3 through 5 set the color and stroke of the circle. Line 6 returns our circle to the map for rendering. In case this was not a circle, we have a catch all return to end the code.

Add the function at 2 Kilometers for Connie’s pizza into viewDidLoad. This really isn’t their delivery area, we’re making this up for the example.

override func viewDidLoad() {
        super.viewDidLoad()
        resetRegion()
        mapView.addAnnotation(ChicagoCenterCoordinate())
        mapView.addAnnotations(restaurants)
        deliveryOverlay("Connie's Pizza",radius: 2000)
        mapView.delegate = self
    }

Build and run. You get the circles, but they are a bit big for our view.

2016-05-15_11-57-33

Zoom out a little (use the option key to pinch on the simulator) to see the full circles.

2016-05-15_11-57-35

Rendering Polylines in Overlays

Both poly lines and polygons are an array of coordinates making up a line. The difference is the polygon closes the shapes and allows filling while the the polyline doesn’t Polylines are the way we mark routes on a map.

uno'sUnlike the last lesson, the only Chicago trivia I’ve given is the address center of Chicago at State and Washington streets. Let’s make a walking path from the center of the addresses to the center of the Deep Dish pizza universe: Pizzeria Uno. There’s plenty of debate wiether it was Owner Ike Sewell or Chef Rudy Malnati who invented deep dish pizza, but it’s clear this was the restaurant that it happened. As you can tell from the pins on our map, Rudy’s son Lou (who was also a manager at UNO’s) started his own chain first in the North and northwest suburbs and then pretty much spread to most of Chicago. I didn’t include all 45 Chicago area Malnati’s locations in this data set. Lou Malnati’s #46 in Phoenix Arizona opens the same day I’m posting this.

One of the great things about Chicago is the grid system. We need to walk in one direction for a while, make a turn and then walk in the new direction. Without using the directions API, we can get there with only three coordinates. We already have two, and I’ll give you the third. Add this function to our code:

func UnoDirections(){
    var coordinates = [CLLocationCoordinate2D]()
    coordinates += [ChicagoCenterCoordinate().coordinate] //State and Washington
    coordinates += [CLLocationCoordinate2D(latitude: 41.8924847, longitude: -87.6280187)]
    coordinates += [restaurants[10].coordinate] //Uno's
    let path = MKPolyline(coordinates: &coordinates, count: coordinates.count)
    mapView.addOverlay(path)
}

MKPolyline and MKPolygon need an array of CLLocationCoordinate2D. We make that array by taking our center coordinate, the coordinate of the traffic intersection we make our turn and the restaurant coordinates. We get a MKPolyLine object path from the coordinates and a count of the number of coordinates. You’ll notice we used &coordinates and not coordinates. This parameter takes the array as an evil (or at least dangerous) UnsafeMutablePointer. We don’t pass values to this, we share them with an &. Once we have the path, we add it to the overlay array in mapView.

Next we make a few additions to the delegate method.

func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
    if overlay.isKindOfClass(MKCircle){
        let circleRenderer = MKCircleRenderer(overlay: overlay)
        circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
        circleRenderer.strokeColor = UIColor.blueColor()
        circleRenderer.lineWidth = 1
        return circleRenderer
     }    
     if overlay.isKindOfClass(MKPolyline){
        let polylineRenderer = MKPolylineRenderer(overlay: overlay)
        polylineRenderer.strokeColor = UIColor.blueColor()
        polylineRenderer.lineWidth = 2
        return polylineRenderer
    } 
    return MKOverlayRenderer(overlay: overlay)
}

The MKpolylineRenderer works much like the MKCircleRenderer. We check if overlay is the correct class. If so, we get an instance of the renderer, and set some properties of the line. In this case, we have no fill.
Add UnoDirections to viewDidload:

UnoDirections()

Build and run. You’ll see a line on the map now.

2016-05-15_09-47-19

Since Uno’s is only a block east of State Street. We are losing our line under the annotations. Zoom in on the map and you can see it better.

2016-05-15_09-47-20

Where to Go from Here

This is the basics of annotations and overlays. From this you can do a lot, as long as you have good coordinate data. What I’ve covered in this and the last lesson was how to make a user interface with the map. What I didn’t cover here was any of the core location stuff. I didn’t tell you how to find the location of the device, nor did I show you how to get directions. Those are lessons unto themselves.

I’m going to use maps and tables in upcoming lessons to discuss the form for large data sets, such as JSON and XML. You’ll see more about annotations and the like in upcoming lessons as we get data from external sources.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  MapAnnotationsOverlayDemo
//
//  Created by Steven Lipton on 5/10/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit
import MapKit
//MARK: Global Declarations
let chicagoCoordinate = CLLocationCoordinate2DMake(41.8832301, -87.6278121)// 0,0 Chicago street coordinates

class ChicagoCenterCoordinate: NSObject,MKAnnotation{
    var coordinate: CLLocationCoordinate2D = chicagoCoordinate
    var title: String? = "0,0 Street Numbers"
}


class ViewController: UIViewController,MKMapViewDelegate {
    //MARK: Properties and outlets
    let pizzaPin = UIImage(named: "pizza pin")
    let crossHairs = UIImage(named: "crosshairs")
    let restaurants = PizzaLocationList().restaurant
    @IBOutlet weak var mapView: MKMapView!
    
    
    //MARK: - Annotations
    /* Since the annotations get loaded in viewDidLoad, this is three iterations of the delegate mapView:viewForAnnotation: */
    
    func pinColor(name:String) -> UIColor{
        var color = UIColor.purpleColor()
        switch name{
        case "Connie's Pizza":
            color = UIColor.blueColor()
        case "Lou Malnati's":
            color = UIColor.greenColor()
        case "Giordano's":
            color = UIColor.yellowColor()
        default:
            color = UIColor.orangeColor()
        }
        return color
    }

     /* Iteration 1 -- single annotation type */
    /*
     func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        guard let annotation = annotation as? PizzaLocation else {return nil}
        if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
            return view
        }else {
            let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
            view.pinTintColor = pinColor(annotation.title!)
            return view
        }
     }
 */
    
 
    
    /* Iteration 2 -- two classes of annotation using MKPinAnnotationView */
    /*
    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        if let annotation = annotation as? PizzaLocation{
            if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier) as? MKPinAnnotationView {
                return view
            }else{
                let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
                view.pinTintColor = pinColor(annotation.title!)
                return view
            }
        }else{
            if let annotation = annotation as? ChicagoCenterCoordinate{
                if let view = mapView.dequeueReusableAnnotationViewWithIdentifier("center") as? MKPinAnnotationView {
                    return view
                }else {
                    let view = MKPinAnnotationView(annotation: annotation, reuseIdentifier: "center")
                    view.pinTintColor = UIColor.blackColor()
                    return view
                }
            }
        }
        return nil
    }

    */
    /* iteration 3 Using MKAnnotationView and custom images */
    func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
        
        if let annotation = annotation as? PizzaLocation{
            if let view = mapView.dequeueReusableAnnotationViewWithIdentifier(annotation.identifier){
                return view
            }else{
                let view = MKAnnotationView(annotation: annotation, reuseIdentifier: annotation.identifier)
                view.image = pizzaPin
                view.enabled = true
                view.canShowCallout = true
                view.leftCalloutAccessoryView = UIImageView(image: pizzaPin)
                return view
            }
        }else{
            if let annotation = annotation as? ChicagoCenterCoordinate{
                if let dequeuedView = mapView.dequeueReusableAnnotationViewWithIdentifier("center"){
                    return dequeuedView
                }else {
                    let view = MKAnnotationView(annotation: annotation, reuseIdentifier: "center")
                    view.image = crossHairs
                    view.enabled = true
                    view.canShowCallout = true
                    return view
                }
            }
        }
        return nil
    }
    //MARK: - Overlays 
    
    
    func deliveryOverlay(restaurantName:String, radius:CLLocationDistance){
        for restaurant in restaurants{
            if restaurant.title == restaurantName{
                let center = restaurant.coordinate
                let circle = MKCircle(centerCoordinate: center, radius: radius)
                mapView.addOverlay(circle)
            }
        }
    }
    
    func UnoDirections(){
        var coordinates = [CLLocationCoordinate2D]()
        coordinates += [ChicagoCenterCoordinate().coordinate] //State and Washington
        coordinates += [CLLocationCoordinate2D(latitude: 41.8924847, longitude: -87.6280187)]
        coordinates += [restaurants[10].coordinate] //Uno's
        let path = MKPolyline(coordinates: &coordinates, count: coordinates.count)
        mapView.addOverlay(path)
    }
    //MARK: Overlay delegate
    
    func mapView(mapView: MKMapView, rendererForOverlay overlay: MKOverlay) -> MKOverlayRenderer {
        if overlay.isKindOfClass(MKCircle){
            let circleRenderer = MKCircleRenderer(overlay: overlay)
            circleRenderer.fillColor = UIColor.blueColor().colorWithAlphaComponent(0.1)
            circleRenderer.strokeColor = UIColor.blueColor()
            circleRenderer.lineWidth = 1
            return circleRenderer
        }
        
        if overlay.isKindOfClass(MKPolyline){
            let polylineRenderer = MKPolylineRenderer(overlay: overlay)
            polylineRenderer.strokeColor = UIColor.blueColor()
            polylineRenderer.lineWidth = 2
            return polylineRenderer
        }
        
        return MKOverlayRenderer(overlay: overlay)
    }
    //MARK: - Map setup
    func resetRegion(){
        let region = MKCoordinateRegionMakeWithDistance(chicagoCoordinate, 5000, 5000)
        mapView.setRegion(region, animated: true)
    }
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        resetRegion()
        mapView.addAnnotation(ChicagoCenterCoordinate())
        mapView.addAnnotations(restaurants)
        deliveryOverlay("Connie's Pizza",radius: 2000)
        UnoDirections()
        mapView.delegate = self
    }

}

PizzaLocation.swift

//
//  PizzaLocation.swift
//  mapAnnotationsDemo
//
//  Created by Steven Lipton on 5/10/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit
import MapKit

class PizzaLocation: NSObject, MKAnnotation{
    var identifier = "pizza location"
    var title: String?
    var coordinate: CLLocationCoordinate2D
    init(name:String,lat:CLLocationDegrees,long:CLLocationDegrees){
        title = name
        coordinate = CLLocationCoordinate2DMake(lat, long)
    }
}


class PizzaLocationList: NSObject {
    var restaurant = [PizzaLocation]()
    override init(){
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.84937922,long:-87.6410584  )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.90146341,long: -87.62848137 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.85110748,long: -87.61286663 )]
        restaurant += [PizzaLocation(name:"Connie's Pizza",lat:41.89224916,long:-87.60951805  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:42.00302015,long:-87.81630768  )]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.79915575,long:-87.59028088)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.85776469,long:-87.66138509)]
        restaurant += [PizzaLocation(name:"Giordano's",lat:41.95296188,long:-87.77541371)]
        restaurant += [PizzaLocation(name:"Pequod's",lat:41.92185084,long:-87.66451631)]
        restaurant += [PizzaLocation(name:"Pizzeria DUE",lat:41.89318499,long:-87.62661003)]
        restaurant += [PizzaLocation(name:"Pizzeria UNO",lat:41.8924923,long:-87.626859)]
        restaurant += [PizzaLocation(name:"Gino's East",lat:41.8959379,long:-87.6229284)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.95340615,long:-87.73214376)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.87169869,long:-87.62737565)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.96074325,long:-87.6835484)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.88411358,long:-87.65808167)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.85186556,long:-87.72202439)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.90239382,long:-87.62846892)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.89031406,long:-87.63388913)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.92911995,long:-87.65359186)]
        restaurant += [PizzaLocation(name:"Lou Malnati's",lat:41.9089214,long:-87.6775678)]
    }
}


How to Use MapKit for 2D and 3D Map Views.

Everyone may remember when Apple first introduced MapKit to replace Google Maps on iPhones, they ended up to apologizing. However over time, developers have found how easy it is to use MapKit. This API provides features which make using  both 2D and 3D maps very easy. More importantly, Google charges for map views  over a few thousand views and Apple doesn’t. For many applications requiring a lot of map views or when you have over a few thousand  users,  MapKit might make better sense for a developer not willing to pay the costs for an external API.

In this lesson, we’ll introduce MapKit, and how display a map in both 2d and 3d. We’ll discuss many of the attribute you have in the UIMapView class to make a great map. We’ll also talk about a small cheat using Google maps if you need only a few map points. Along the way I’ll throw in some Chicago history trivia.

Make a New Project

Start by making a New Single-view  project Called MyMapKitDemo. Use a Universal device and Swift as the language.   When it loads, we need to turn on MapKit.  In the Project Properties, click on the the Capabilities tab.

2016-05-04_05-37-49

You will see a list of functions we can use. About halfway down the list you’ll See Maps

2016-05-04_05-42-52

Turn on maps and the menu opens.

2016-05-04_05-42-53

We won’t be changing anything here, but the framework will load when we need it.

Go to the storyboard.  In the Object Library, find the  Map  Kit View object

2016-05-04_05-47-46

Drag it somewhere on the story board.

2016-05-04_05-48-39

Select the Map view. Click the pinMenuButton  button in the Auto Layout  menu on the lower right of the storyboard. This will bring up the the pin menu. Click off Constrain to margins. Set the margins to be 0 on all sides. Change the Update Frames to Items of New Constraints.  Your pin menu should look like this.

2016-05-04_05-49-37

Click Add 4 Constraints. The map view takes the entire view on the story board.

2016-05-04_05-51-02

With the map view still selected, look at the attribute inspector.  At the top is some attributes specific to map views:

2016-05-04_05-59-11

The Type attribute sets the map to be either  Standard, Satellite or Hybrid, which is combination of the two (a satellite with street names).  The Allows attributes control if the user can use zooming,scrolling rotation or 3D view. By default, the 3D view is on, and we’ll see this is a good thing.  The Shows attributes control extra items on the map. You’ll note User Location is off.  User location shows a dot where the user is. However that dot only shows up if the map shows a region the user happens to be in. Unless you live in the Lincoln Park or Chinatown neighborhoods of Chicago, in our app you won’t be visible.

We’ll be changing a few of these through properties in code. You can leave them alone for now.

Add  seven buttons to the view.  Select all seven buttons.  In the attributes inspector, find the Text color button, and click the color swatch in the button.

2016-05-04_06-17-48

A color palette appears. Using the RGB colors, change the color to a Dark Blue (#000088) color.

2016-05-04_06-19-16

In the attributes inspector,  scroll down to View.  Click the swatch for the background color. Change the Background to White  #FFFFFF and set the Opacity to 50%

2016-05-04_06-28-34

Change the title on the seven buttons to CPOGWrigley, Connie’s, Satellite, 2dMapFlyOver,  and Clean Map. Arrange everything like this.

2016-05-04_06-32-26

Select the CPOG,Wrigley and Connie’s buttons.  Click the Stack view stack view buttonButton in the auto layout buttons. In the attributes inspector, change the stack view to a Horizontal Axis, Alignment of Fill, Distribution of Fill Equally and Spacing of 0:

2016-05-04_06-47-17

Click the pin button pinMenuButton.  Turn off Constrain to Margins. Set the top constraint to 20 points and the left and right to 0 points, leaving the bottom  unchecked. Set Update Frames to Items of New Constraints.

2016-05-04_06-47-17_01

Add the constraints.

2016-05-04_06-49-14

Select the Satellite, 2D Map, Flyover, and Clean Map buttons.  Click the Stack view stack view buttonButton in the auto layout buttons. In the attributes inspector, change the stack view to a Horizontal Axis, Alignment of Fill, Distribution of Fill Equally and Spacing of 0. Click the pin button pinMenuButton.  Turn off Constrain to Margins. Set the bottom constraint to 20 points and the left and right to 0 points, leaving the top  unchecked. Update Frames to Items of New Constraints.

2016-05-04_06-59-14

Add the three constraints. The final layout looks like this:

2016-05-04_07-08-54

We need to wire up the outlets and actions. Go to the ViewController.swift file.  Before we do anything else, MapKit is a separate framework than UIKit.  Just under the import UIKit add import MapKit

import UIKit
import MapKit

Once you do that, change the viewController class to add all of our outlets and actions:

class ViewController: UIViewController {
    //MARK: Properties and Outlets
    
    @IBOutlet weak var mapView: MKMapView!
    
    //MARK: - Actions
    
    //MARK: Location actions
    @IBAction func gotoCPOG(sender: UIButton) {
    } 
    @IBAction func gotoWrigley(sender: UIButton) {
    }
    @IBAction func gotoConnies(sender: UIButton) {
    }
    
    //MARK: Appearance actions
    @IBAction func toggleMapType(sender: UIButton) {
    }
    @IBAction func overheadMap(sender: UIButton) {
    }
    @IBAction func flyoverMap(sender: UIButton) {
    }
    @IBAction func toggleMapFeatures(sender: UIButton) {
    }
    
    //MARK: Instance methods
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
}

Go back to the storyboard, and open the assistant editor in Automatic. Drag from the circle next to gotoCPOG to the CPOG button on the storyboard. Do the same from gotoWrigley to Wrigley, gotoConnies to Connie’s, toggleMapType to Satellite, overheadMap to 2D Map, flyoverMap to FlyOver, and toggleMapFeatures to Clean Map. Finally, drag from the outlet mapView to the mapView.

Build and run. You get a map of the continent you happen to be in.

2016-05-04_07-29-57

This is the default setting of a map view – a region that takes in a continent closest to the current location according to the simulator. Since all the attributes were left on, you can pan and zoom on this map.  To zoom on a simulator, hold down the Option key and drag with the mouse.  I zoomed in on Chicago, where we’ll be in the app.

2016-05-04_07-30-54

Getting Sample Location Data From Google Maps

We’ll need some sample location data. I’m going to pick  my favorite baseball field and two favorite pizza restaurants to for this.  MapKit uses several coordinate systems, but the most important is  latitude and longitude. If you need only a few points to test it’s easy to get them by looking up the location in Google Maps. Maps has the location information embedded in the URL for the view.

Go to the web and in Google maps, search for Wrigley Field. If you want the address to search, it’s the one Elwood Blues uses in the Blues Brothers: 1060 W. Addison.

 

Or you can just go to https://www.google.com/maps/place/Wrigley+Field. When it appears, Click your mouse in the middle of the intersection of Addison and Clark Streets.

If you look at the URL you find something similar to this

https://www.google.com/maps/place/Wrigley+Field/@41.9472901,-87.6565357,21z/data=!4m2!3m1!1s0x880fd3b2e59adf21:0x1cea3ee176ddd646

The important part is from the /@ to the next Slash.

/@41.9472901,-87.6565357,21z

That’s the map coordinates in latitude and longitude of that intersection. For Apple maps we need one other piece of data: what direction we are pointing, known as the heading. To get that, drop the little guy for Street Siew onto the same intersection, pointing towards the big red Wrigley Field sign.  You get this data

@41.9471939,-87.6565108,3a,75y,41.73h,90.81t/

The first two are the map coordinates again. They may not match exactly our first pair.  the important number for us is the heading 41.73h which tells us the compass direction we are pointing, 41.73 degrees from north.

The three pieces of data we need are latitude 41.9471939 longitude -87.6565108, and heading 41.73 degrees. You can use this method to get coordinates if you have no other way to get the data. In upcoming lessons, we’ll take coordinate data directly from City of Chicago databases and remove this step.

Core Location Data Types

We represent data for maps in the Core Location data types. Here’s a table to summarize:

CL Type Type Description/Notes
CLDegrees Double A latitude or Longitude
CLDirection Double A heading in degrees based on the distance from North 0 degrees
CLDistance Double A Distance in meters
CLSpeed Double A speed in meters/Second
CLLocationCoordinate2D struct {
var latitude: CLLocationDegrees,
var longitude: CLLocationDegrees,}
A coordinate on a map based on latitude and longitude
CLAccuracy Double The accuracy of a coordinate value in meters.

We’ll use most of these as we build our app. We’d like some way of storing the data we collected from Google Maps in some constants. I made a struct to do that. Add this to ViewController.

//MARK: Location constants
struct cameraInfo{
    var location = CLLocationCoordinate2D()
    var heading = CLLocationDirection()     
    init(
        latitude:CLLocationDegrees,
        longitude:CLLocationDegrees,        
        heading:CLLocationDirection
    ){
        self.location = CLLocationCoordinate2D(
            latitude: latitude,
            longitude: longitude)
        self.heading = heading
    }
}

We store a CLLocationCoordinate2D and a CLLocationDirection. To make location, we use two CLLocationDegrees, one for latitude and one for longitude.

We can use this to save our Wrigley data. Add this to ViewController under the struct.

let wrigleyLocation = cameraInfo(
    latitude: 41.9471939,
    longitude: -87.6565108, 
    heading: 41.73)

To save you from looking up the two pizza restaurants, I’ll add them for you. Add this  to your code under the wrigleyLocation.

let CPOGLocation = cameraInfo(
    latitude: 41.920744, 
    longitude: -87.637542, 
    heading: 338.0)
let conniesLocation = cameraInfo(
    latitude: 41.849294, 
    longitude: -87.6414665, 
    heading: 32.12)

Setting the Map Region

The next step in writing a map app is to set a region. Regions define the visible area based on a center point and a diameter in latitude and longitude. If we were in the real world you think of it as the circle you could visibly see. Since device screens are rectangular, they create a kind of rectangle and set scaling for the map like this.

maps regions

I said sort of because this is not planar geometry, it’s the spherical geometry of the planet. This region has a type of MKCoordinateRegion There is an intializer to get this region, but it uses the differences in map coordinates to define the region. The easier one to use for our purposes is the function MKCoordinateRegionMakeWithDistance which takes the three parameters in the illustration above.
We’ll use this function to get the region defined by a radius from the circle, then assign it to our map. Add this to the code as an instance method:

//MARK: Instance methods
func setRegionForLocation(
    location:CLLocationCoordinate2D,
    spanRadius:Double, 
    animated:Bool)
{
    let span = 2.0 * spanRadius
    let region = MKCoordinateRegionMakeWithDistance(location, span, span)
    mapView.setRegion(region, animated: animated)
    }

We set the region in our map view with the setRegion method. Since it can be animated we included a parameter in our function to animate the region change. Add this to viewDidload:

setRegionForLocation(
    wrigleyLocation.location,
    spanRadius: 150,
    animated: false)

When we start our application, we’ll start the application with a radius of 150 meters. Build and run.

2016-05-05_06-58-30

There’s the stadium in the upper right. While most people know about the Chicago Cubs, they don’t know about the other team that used to play there, founded by a guy who missed the boat.  In 1915 this guy ran late and  missed the boat for his company picnic. The boat, the Eastland capsized in the Chicago River at the Clark Street bridge killing 855 people.

Five years later, this guy would co-found   American football’s professional league the  NFL. George Halas’  team started playing in Wrigley field in 1922, deriving  their name- The Chicago Bears  – from the baseball team.

Using Cameras

Besides Papa Bear Halas’ origin story,  what you might not know is this is a 3d map.  You are just looking at it from overhead. In MapKit we use cameras to look at 3d maps. It allows us to change the angle and perspective we are looking at the object.  Change the flyoverMap action to this:

@IBAction func flyoverMap(sender: UIButton) {
    let camera = MKMapCamera()
    camera.centerCoordinate = mapView.centerCoordinate
    camera.pitch = 80.0
    camera.altitude = 100.0
    camera.heading = 45.0
    mapView.setCamera(camera, animated: true)

The camera property of MapKit is the view we see of the map. It has four properties, which we set all of them in this method. The centercoordinate is a coordinate the camera centers its view. pitch is the angle up or down of the camera. altitude is how high in the air is the camera. heading is the compass direction the camera faces. We set this camera 80 degrees from vertical, 100 meters in the air with a heading northeast. Build and run. Tap the Flyover button and you get the following in the simulator.

2016-05-06_05-52-55

If you run on a phone instead of a simulator, you get a more robust image, with 3d buildings.

2016-05-06_06-11-53

While you can set all three camera properties on your own, you can also use a few methods as well. Change the gotoCPOG action to this:

@IBAction func gotoCPOG(sender: UIButton) {
    let camera = MKMapCamera(
        lookingAtCenterCoordinate: CPOGLocation.location,
        fromDistance: 0.01,
        pitch: 90, 
        heading: CPOGLocation.heading)
    mapView.setCamera(camera, animated: true)
}

Here we use the initializer MKMapCamera:lookingAtCenterCoordinate:fromDistance:pitch:heading: method. We use the CPOGLocation.heading to get the heading.   This method just takes all our properties and makes a camera that is 1 centimeter from the location, pitching the camera at the horizon, and setting the heading according to our constant . This should be close to a human’s eye view.

This location is in front of Chicago Pizza and Oven Grinder, the inventors of the bowl pizza. Ingredients are placed in a ceramic bowl and the crust is placed over the top of the bowl. The bowl is baked and then inverted, making a pizza. There’s something else about this location, but build and run first. Click The CPOG button. On the simulator you get this:

2016-05-06_06-33-55

On a phone, you’ll find the iPhone is more robust with graphics  than the simulator.:

2016-05-06_06-32-43

First one more bit of Chicago history. You’ll notice a inset from the buildings I marked with a red arrow.

2016-05-06_06-18-13

There’s a gap between the buildings that’s now a parking lot. There was a garage there once. This was the site of the infamous St. Valentines Day Massacre.  The blue arrow is Pizza and Oven Grinder at 2121 N. Clark, and legend has it Jack McGurn, Al Capone’s lieutenant rented a room on the second floor a few weeks before the massacre to scope out the garage.

Okay enough history. You’re probably noticing the big software problem. We should be getting a human’s eye view pf the street. Instead we get a pigeon.  This is one of the problems with Apple Maps in 3D: there is a minimum altitude that’s about 30 meters. Apple didn’t call it flyover for nothing. On a phone if you two-finger drag up, and you notice the display does not want to pitch any more.

2D Views Revisited

If the code gets a value that makes no sense from a 3Dview, it places a 2D view instead.  We can see this with  another way of positioning the camera. This method takes a center coordinate, a coordinate for the camera and an altitude. It finds the pitch by doing math to the eye coordinate and altitude.  Add this code

@IBAction func gotoWrigley(sender: UIButton) {
    let camera = MKMapCamera(
        lookingAtCenterCoordinate: wrigleyLocation.location, 
        fromEyeCoordinate: wrigleyLocation.location,
        eyeAltitude: 200)
    mapView.setCamera(camera, animated: true)
}

This code uses the same two coordinates for the location.  With the same two coordinates, MapKit makes the camera into a overhead camera. Build and run, then press the Wrigley button

2016-05-06_07-02-59

All we’ve done is zoom in on our Wrigley coordinates.  Change the action to this:

@IBAction func gotoWrigley(sender: UIButton) {
    var eyeCoordinate = wrigleyLocation.location
    eyeCoordinate.latitude += 0.005
    eyeCoordinate.longitude += 0.005
    let camera = MKMapCamera(
        lookingAtCenterCoordinate: wrigleyLocation.location, 
        fromEyeCoordinate: eyeCoordinate, 
        eyeAltitude: 50)
    mapView.setCamera(camera, animated: true)
}

We increased our eye coordinate to be 0.005 degrees north and 0.005 degrees west of the center coordinate. Build and run. When we tap the Wrigley button now, we get this in the simulator:

2016-05-06_13-54-50

We’ve moved around to the northeast  corner of the ballpark. Using this method, the heading will always be  looking at the  center coordinate, in this case  Clark and Addison.

Another way to show a 2Dmap by camera is set the pitch to 0. Add this code:

@IBAction func gotoConnies(sender: UIButton) {
    let camera = MKMapCamera(
       lookingAtCenterCoordinate: conniesLocation.location, 
       fromDistance: 1300, pitch: 0,
       heading: 0.0)
    mapView.setCamera(camera, animated: true)
}

When you  build, run and try the Connie’s button, you get this:

2016-05-06_14-00-29

Connie’s Pizza marks mile 21 of the Chicago Marathon, one of the flattest courses  and oddly the highest average elevation (590m) of the six major league marathons. If you want to set world records for running 26.2 miles or get that Boston Qualifier, this is the race to do it, and many do.

You can of course set the map view’s camera directly. Add this code to show a 2D map of any point,  at an attitude of 1000 meters.

@IBAction func overheadMap(sender: UIButton) {
    mapView.camera.pitch = 0.0
    mapView.camera.altitude = 1000.0
}

Build and run. First show CPOG as a 3D map, then click the 2D Map button.

2016-05-06_14-09-20

Clark is a diagonal street heading northwest out of Chicago. It may be a good idea to set heading to 0 and show north as up. Change the code to this

@IBAction func overheadMap(sender: UIButton) {
        mapView.camera.pitch = 0.0
        mapView.camera.altitude = 1000.0
        mapView.camera.heading = 0.0
    }

Now you get something that makes more sense as a static map on a phone:

2016-05-07_07-31-47

Using Satellite Imagery

In MapKit there are three types of maps: Standard, Satellite and Hybrid.  Standard is the default type we’ve been using already. Satellite gives satellite imagery, but no road information. Hybrid combines the road information of standard with the satellite image.

We control the map type with the maptype property.  Add this to the gotoConnies action.

mapView.mapType = .Satellite

Now run the app, and tap the Connie‘s button. you get a satellite overhead map.

2016-05-07_07-47-17

Change .Satellite to .Hybrid and you get this

2016-05-07_07-51-07

You can easily see both the pizza icon for my favorite pizza in Chicago and the  south branch of the Chicago river in this photo.  The south branch of the Chicago river is part of one of the marvels of modern engineering: it flows backwards from its natural course.  The river flow project, completed in 1900, reverses the river flow so sewage flows down to the Mississippi River  entering not far from St. Louis  instead of into Chicago’s water supply of Lake Michigan. Just In case you are worried, Chicago now cleans their water before they do that today.

Satellite and Flyover

That’s however is not the whole story. To use flyover 3d on a satellite or hybrid map there are two more types: .SatelliteFlyover and .HybridFlyover.  Change  gotoWrigley, to this

@IBAction func gotoWrigley(sender: UIButton) {
    var eyeCoordinate = wrigleyLocation.location
    eyeCoordinate.latitude += 0.004
    eyeCoordinate.longitude += 0.004
    let camera = MKMapCamera(
        lookingAtCenterCoordinate: wrigleyLocation.location,
        fromEyeCoordinate: eyeCoordinate, 
        eyeAltitude: 50)
    mapView.mapType = .HybridFlyover
    mapView.setCamera(camera, animated: true)
}

I moved in the coordinates a bit more to get a better look at the ballpark. Build and run. Tap the Wrigley button. You get this on a phone:

2016-05-07_13-58-26

So you can experiment with the different types and what they will do, let’s add some code to toggle the type. Change the toggleMapType to this

@IBAction func toggleMapType(sender: UIButton) {
        let title = sender.titleLabel?.text
        switch title!{
        case "Satellite":
            mapView.mapType = .Satellite
            sender.setTitle("Hybrid", forState: .Normal)
        case "Hybrid":
            mapView.mapType = .Hybrid
            sender.setTitle("Standard", forState: .Normal)
        case "Standard":
            mapView.mapType = .Standard
            sender.setTitle("Satellite", forState: .Normal)
        default:
            mapView.mapType = .Standard
            sender.setTitle("Satellite", forState: .Normal)
        }
    }

We use a switch statement from the titleLabel.text of the button to toggle between the types. These are the 2d types for .Satellite and .Hybrid. We need a little code to to use the 3d types when we turn on flyover.  Change the flyoverMap action to this.

@IBAction func flyoverMap(sender: UIButton) {
     //change to the correct type for flyover       
     switch mapView.mapType{
         case .Satellite:
             mapView.mapType = .SatelliteFlyover
        case .Hybrid:
            mapView.mapType = .HybridFlyover
        case .Standard:
            mapView.mapType = .Standard
        default:
            break
        }
        
        let camera = MKMapCamera()
        camera.centerCoordinate = mapView.centerCoordinate
        camera.pitch = 80.0
        camera.altitude = 100.0
        camera.heading = 45.0
        mapView.setCamera(camera, animated: true)
    }

Build and run. Try a few combinations of the buttons. Here’s a one minute video if you don’t have a phone handy.

The time to render some of these images is rather long. There’s a lot of processing and data transmission. Each location changes the region dramatically, so everything has to load over again. The rendering time is short only for the .Standard flyover version. Keep this in mind as you are using these types.

Toggling features

While .Satellite tuns off all the features like attractions, rendering buildings and the like, there are a few properties you can use in the .Standard mode to control these. Change toggleMapFeatures to this

    @IBAction func toggleMapFeatures(sender: UIButton) {
        let flag = !mapView.showsBuildings
        mapView.showsBuildings = flag
        mapView.showsScale = flag
        mapView.showsCompass = flag
        mapView.showsTraffic = flag
    }

Play with this a little.  Go to Connie’s Pizza and set to .Standard Mode. Tap Clean Map. Everything but road names disappears.

2016-05-07_13-54-50

Tap Busy Map.  We get back our attractions, plus the scale and traffic.

2016-05-07_13-54-48

You’ll notice the compass doesn’t show. When the .heading is 0. there is no compass. If the user rotates the map, then the compass will show.

2016-05-07_14-11-43

Tap Flyover and we get the 3d view with buildings

2016-05-07_13-54-52

Tap Clean Map and we get just the roads and river.

2016-05-07_13-54-51

 

Play around with these. You may find some of the combinations don’t work. Some of the mapTypes set and use these features in specific ways. Check the  MKMapView Class Reference for more information.

You can display a lot of information through a map view. We’ve seen that coding a map in 2d or 3d is not that difficult as long as you have some coordinate data. We’ve also seen that Apple provides a lot of attractions such as restaurants and sports stadiums for you. We have not yet added our own locations and features, known as annotations and overlays. In our next lesson, we’ll take a 2d map and annotate it with some data.

The Whole Code

//
//  ViewController.swift
//  MyMapKitDemo
//
//  Created by Steven Lipton on 5/4/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit
import MapKit

class ViewController: UIViewController {
    //MARK: Location constants
    struct cameraInfo{
        var location = CLLocationCoordinate2D()
        var heading = CLLocationDirection()
        init(latitude:CLLocationDegrees,longitude:CLLocationDegrees,heading:CLLocationDirection){
            self.location = CLLocationCoordinate2D(latitude: latitude, longitude: longitude)
            self.heading = heading
        }
    }
    
    let wrigleyLocation = cameraInfo(latitude: 41.9471939, longitude: -87.6565108, heading: 41.73)
    let CPOGLocation = cameraInfo(latitude: 41.920744, longitude: -87.637542, heading: 338.0)
    let conniesLocation = cameraInfo(latitude: 41.849294, longitude: -87.6414665, heading: 32.12)
    //MARK: Properties and Outlets
    @IBOutlet weak var mapView: MKMapView!
    //MARK: - Actions
    
    //MARK: Location actions
    
    @IBAction func gotoCPOG(sender: UIButton) {
        let camera = MKMapCamera(lookingAtCenterCoordinate: CPOGLocation.location, fromDistance: 0.01, pitch: 90, heading: CPOGLocation.heading)
        mapView.setCamera(camera, animated: true)
    }
    
    @IBAction func gotoWrigley(sender: UIButton) {
        var eyeCoordinate = wrigleyLocation.location
            eyeCoordinate.latitude += 0.004
            eyeCoordinate.longitude += 0.004
        let camera = MKMapCamera(lookingAtCenterCoordinate: wrigleyLocation.location, fromEyeCoordinate: eyeCoordinate, eyeAltitude: 50)
        mapView.mapType = .HybridFlyover
        mapView.setCamera(camera, animated: true)
    }
    
    @IBAction func gotoConnies(sender: UIButton) {
        let camera = MKMapCamera(lookingAtCenterCoordinate: conniesLocation.location, fromDistance: 1300, pitch: 0, heading: 0.0)
        mapView.mapType = .Hybrid
        mapView.setCamera(camera, animated: true)
    }
    
    //MARK: Appearance actions
    
    @IBAction func toggleMapType(sender: UIButton) {
        let title = sender.titleLabel?.text
        switch title!{
        case "Satellite":
            mapView.mapType = .Satellite
            sender.setTitle("Hybrid", forState: .Normal)
        case "Hybrid":
            mapView.mapType = .Hybrid
            sender.setTitle("Standard", forState: .Normal)
        case "Standard":
            mapView.mapType = .Standard
            sender.setTitle("Satellite", forState: .Normal)
        default:
            mapView.mapType = .Standard
            sender.setTitle("Sat Fly", forState: .Normal)
        }
    }
    
    @IBAction func overheadMap(sender: UIButton) {
        mapView.camera.pitch = 0.0
        mapView.camera.altitude = 1000.0
        mapView.camera.heading = 0.0
    }
    
    
    @IBAction func flyoverMap(sender: UIButton) {
            switch mapView.mapType{
            case .Satellite:
                mapView.mapType = .SatelliteFlyover
            case .Hybrid:
                mapView.mapType = .HybridFlyover
            case .Standard:
                mapView.mapType = .Standard
                break
            default:
                break
        
        }
        
        let camera = MKMapCamera()
        camera.centerCoordinate = mapView.centerCoordinate
        camera.pitch = 80.0
        camera.altitude = 100.0
        mapView.setCamera(camera, animated: true)
    }
    
    
    @IBAction func toggleMapFeatures(sender: UIButton) {
        let flag = !mapView.showsBuildings
        mapView.showsBuildings = flag
        mapView.showsScale = flag
        mapView.showsCompass = flag
        mapView.showsTraffic = flag
        mapView.showsPointsOfInterest = flag
        if flag {
            sender.setTitle("Clean Map", forState: .Normal)
        } else {
            sender.setTitle("Busy Map", forState: .Normal)
        }
    }
    
    //MARK: Instance methods
    func setRegionForLocation(location:CLLocationCoordinate2D,spanRadius:Double,animated:Bool){
        let span = 2.0 * spanRadius
        let region = MKCoordinateRegionMakeWithDistance(location, span, span)
        mapView.setRegion(region, animated: animated)
    }
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        setRegionForLocation(wrigleyLocation.location, spanRadius: 150,animated: false)
        // Do any additional setup after loading the view, typically from a nib.
    }
}



Adding Actions to iOS and WatchOS Local Notifications

Even if you never want to make an app for the Apple Watch, there’s one place you might want to think about supporting: Notifications. In a previous lesson, we explored how to make local notifications for iOS devices. That lesson ended with a free bonus: If a phone is sleeping and you have an Apple Watch, the notification will go to the watch.

Starting in iOS8, notifications get one big change: they can have actions of their own. On phone, tablet or watch this gives you more flexibility in how to respond to a notification. Suppose you had an app that after an appointment comes up, nags you about it every ten seconds until you stop the nagging. You could of course go into the app and shut it off, but it might be easier to have a stop nagging button on the notification. In this lesson, we’ll make that stop nagging button first for iOS devices, then for the Apple Watch. Along the way we’ll discover why watch notifications are the one place iOS developers should seriously think about for their phone apps.

Making a Demo Program – A Review of Notifications

In a previous lesson we went into detail about local notifications. We’ll be using a lot of what was learned there. In this tutorial, we’ll build a simple app to make a series of notifications similar to the HIIT app at the end of that lesson. We’ll use this as a review and quick introduction to local notifications if you are not familiar with them.

Since we will eventually get to some watch related notifications, make a new project using the iOS with Watch OS App template.

2016-04-26_15-32-23

Name the project PhoneWatchNotificationDemo and make sure watch notifications is checked on and everything else is off.

2016-04-26_15-32-22

Once you save the project, add a new file by pressing Command-N called NaggyNotification subclassing NSObject. Add these properties to the class

class NaggyNotification: NSObject {
    private let basicNags = ["Nag", "Nag nag","Naggity nag"]
    private let emojiNags = ["😀","😉","🙄","😒","😠","😡"]
    private var nags = ["Nag", "Nag nag","Naggity nag"]
    private var counter = 0
    private var timer = NSTimer()
    private var backgroundTask = 0
    private var localNotification = UILocalNotification()
    var nagging:Bool {return timer.valid } //read only
}

If you don’t know how to type emojis, press Control-Command-Spacebar to get the symbols palette.

We are going to do something insane a little later with this class. To protect against some of the problems with that, all but one of our properties is private and inaccessible outside the class. There is one read-only property nagging which uses a computed read-only value.

Most of these properties we’ll get to. We’ll start with the most important. The localNotification will contain our notification. Add the following method to present it immediately:

    func presentNotificationNow(){
        UIApplication.sharedApplication().presentLocalNotificationNow(localNotification)
    }

There are two ways to present notifications. We can present immediately as we do with the presentLocalNotificationNow method above or at some scheduled time in the future with the scheduleLocalNotification method. We could schedule that time using the fireDate property of UILocalNotification, but that presents a problem. We can only schedule 64 notifications, which in a nagging app might be too little space. We will take a different approach: Use a NSTimer loop in the background. Add the following method to start a timer:

func startTimer(timeInterval:NSTimeInterval){
        if !timer.valid{ //prevent more than one timer on the thread
            counter = 0
            backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
            timer = NSTimer.scheduledTimerWithTimeInterval(
                timeInterval,
                target: self,
                selector: #selector(timerDidFire),
                userInfo: nil,
                repeats: true)
        }
        
    }

We do three things in this code. We reset a counter for counting how many nags the user has been subjected to, then set this task into the background. While on a simulator you can leave things in the background without this, your app will suspend operation without beginBackgroundTaskWithExpirationHandler on a live device. Finally we start a timing loop, getting the timing interval from the parameter timeInterval. The timing loop ends on a selector timerDidFire. Let’s add the code for timerDidfire. Add this to the class:

    func timerDidFire(timer:NSTimer){
            counter += 1
            let currentNag = counter % nags.count //cycle through messages
            let nagString = String(format:"# %i %@",counter,nags[currentNag])
            localNotification.alertBody = nagString
            localNotification.alertTitle = "Naggy Notification"
            localNotification.category = "Nags"
            localNotification.soundName = UILocalNotificationDefaultSoundName
            localNotification.userInfo = ["NagNum":counter,"NagString":nags[currentNag]]
            presentNotificationNow()
    }

If you are not familiar with timing loops you might want to refer to this lesson. Basically, the timer object is set to run the timerDidFire function every timeInterval seconds. Once in the timerDidFire function, we increment counter and compose a string we’ll post to the notification. The property alertBody of UILocalNotification sets the text for the notification. We also set the sound to the default sound, which will also set off vibrations and haptics. For later use, we all send a dictionary containing our count and our current nag as userInfo we can use later in this lesson. Finally we present the notification.

We’ll also need a way to shut down the timer. Add this method to your code:

func stopTimer(){
        timer.invalidate()
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
}

We shut down the timer and remove the task from the background.

Now for the small, but insane bit of code. Add this above the class declaration:

let naggingNotification = NaggyNotification()

class NaggyNotification: NSObject {
...

A variable declared outside a class is global. Global values or instances can be used anywhere in the target code. There are other, better ways of handling the NaggyNotification class, but I could write full tutorials on them. As we’ll see, this instance will show up in several classes. This is a simple way to get it into all of them. As I always do when I use a global, I warn people not to use globals except in special circumstances. They are notoriously hard to track down in code. It’s why I made all the properties private, only methods are accessible to other classes, which are easier to deal with.

In order to use a notification, you must tell the application you will be using the notification in the AppDelegate.swift file. Go to the AppDelegate class Change the application:didFinishLaunchingWithOptions:launchOptions: method to this:

 func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound],
                categories: nil
            )
        )
        return true
    }

We register the notification settings for the two types of notifications: Alerts(or banners) and sounds(or haptics and vibrations).

Our app has all the internal parts set, we need a user interface. We’ll make a very simple one of a single button. Go to the ViewController.swift file. Change ViewController to this:

class ViewController: UIViewController {  
    @IBOutlet weak var nagButton: UIButton!
    @IBAction func nagButton(sender: UIButton) {
    }
 }

We’ll have one button that we’ll assign a outlet and action to. Change the nagButton action to this:

@IBAction func nagButton(sender: UIButton) {
    if naggingNotification.nagging{
       naggingNotification.stopTimer()
    }else{
       naggingNotification.startTimer(10.0)
    }
      stateDidChange()
}

This code uses the naggingNotification read-only property nagging to determine if the timer is on. We toggle the timer depending on the result, using the naggingNotification methods stopTimer and startTimer. We’ve set up this demo with a 10 second timer.

Our last statement calls a method we have yet to define stateDidChange(). We’ll update the button title with this. Add this code to ViewController:

func stateDidChange(){
    if !naggingNotification.nagging{
       nagButton.setTitle"Nag", forState: .Normal)
    }else{
       nagButton.setTitle("Stop Nagging", forState: .Normal)
    }
}

We have our code. Go to the storyboard. Drag one button to the center of the storyboard. Title it Nag, and set the font to 46 point Bradley Hand.

2016-05-01_07-49-18

In the lower right corner of the storyboard, you will find the auto layout controls. With the Nag button selected, click the align alignment icon icon to get the align menu. Towards the bottom of this menu you will find the center Horizontally in Container and Vertically in container settings. Click both on with values of zero. Set the Update frames to Items of New constraints.

2016-04-26_15-32-25

Open the assistant editor. Drag from both circles in the code to the button to connect the action and the outlet to the button.  Close the assistant editor.

Before we test this, launch both the iPhone and watch simulators. If you have never launched these before running an app, check the directions here Especially with the watch, it helps to have them running before you run an app on them.

Build and run using a iPhone 6s plus simulator. You get the notification message the first time you run this:

2016-04-29_10-09-50

Press OK to allow notifications.You get a simple screen of a single button:

2016-05-01_07-54-37

Tap the Nag button.

2016-05-01_07-54-44

The button toggles but doesn’t do more than that. we need to be outside the app to see a notification.

Press Command-Shift-H or on the hardware simulator menu, Hardware>Home. Wait a few seconds and you will see this:

2016-04-29_10-09-53

Swipe down from the status bar once the banner disappears. Select Notifications and you will see the notifications appear.

2016-04-29_10-09-54

Tap one of the notifications and you are back in the app. Tap Stop Nagging and the notifications turn off.

Adding Categories and Actions to your Notifications

Introduced in iOS8, Notifications  now have their own actions, which appear as buttons on a notification. Often you’ll find these in the AppDelegate.  I’ll however keep everything on one class for the notification. Actions are of class UIUserNotificationAction and it’s subclass UIMutableUserNotificationAction. Since you can’t change anything in a UIUserNotificationAction, we define actions in UIMutableUserNotificationActions. We’ll define two actions, one to stop the timer and one to change the nag message to emoji. In the NaggyNotification class add the following method:

func makeActions()-> [UIMutableUserNotificationAction]{
    let stopAction = UIMutableUserNotificationAction()
    stopAction.title = "Stop"
    stopAction.identifier = "StopAction"
    stopAction.destructive = true
    stopAction.authenticationRequired = true
    stopAction.activationMode = .Foreground
     
    let moreAction = UIMutableUserNotificationAction()
    moreAction.title = "Emoji"
    moreAction.identifier = "EmojiAction"
    moreAction.destructive = false
    moreAction.authenticationRequired = false
    moreAction.activationMode = .Background
        
    return [stopAction,moreAction]
    }

The title parameter is the title that will appears on buttons of the notification. The identifier is the string used to internally identify the action. You’ll notice that there is no code here to execute the action. We’ll use that identifier to code the action in AppDelegate. We can add a red warning to a button if it is a destructive button, one that deletes something. I used it here as the Stop button, but left destructive as false for the Emoji button. The activationMode has two choices: .Foreground or .Background and indicates if the app will execute the action in the foreground or the background. To maintain security of the user’s phone we can also require an authentication before we execute the action. For actions in the foreground, this is mandatory and activationMode is always true. The statement in our code is redundant.

We return an array of the actions. We will use these actions in a category. Categories contain all information about a specific type of notification. Our category, which we’ll call Nags contains the actions that will show in the nag notification. Add the following code to the NaggyNotification class.

 //MARK: - Categories and Actions
    func makeCategory()-> UIMutableUserNotificationCategory {
        let category = UIMutableUserNotificationCategory()
        category.identifier = "Nags"
        category.setActions(makeActions(), forContext: .Default)
        return category
    }

We make set two properties on a UIMutableUserNotificationCategory. We set an identifier Nags and using the setActions:forContext: method add the actions to the category.

We’ve set up our category and actions. Now to use them. We’ll do that in AppDelegate. Find the method we changed earlier. Change the highlighted lines to add the category to our current code.

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) ->; Bool {
        // Override point for customization after application launch.
        let category = naggingNotification.makeCategory()
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound, .Badge],
                categories: [category]
            )
        )
        return true
    }

If we ran our code now, we would have our actions listed in the notification, but nothing would happen. We need to add a callback method to AppDelegate containing the code to execute the actions. Add this to AppDelegate

    func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
        
        if notification.category == "Nags"{
            if identifier == "StopAction"{
                naggingNotification.stopTimer()
            }
            if identifier == "EmojiAction"{
                naggingNotification.toggleAlertMessages()
            }
        }
        completionHandler()
    }
  

The application:handleActionWithIdentifier:forlocalNotification method has a actionIdentifier and a UILocalNotification. for parameters.   The code checks the notification’s title. If it is our notification, we check the action identifier and perform the action we need.

We need one more method back in NaggyNotification to toggle the alert messages. Add this to the code.

func toggleAlertMessages(){
        if nags == basicNags {
            nags = emojiNags
        } else {
            nags = basicNags
        }
    }    

Fixing a Bug Before It Happens

This is all the code we need to run our application. However, there’s one bug that we’ll fix first. The problem is the Stop action when we are in the background. It will stop the the timer and shut down the nags. The button on our view controller does not know this. With the code as it is written the button still says Stop Nagging when we enter the foreground. We need to update the button any time we do enter the foreground. Find the applicationDidBecomeActive in AppDelegate. This method runs when we need to do that refresh of our user interface. Change it to this

    func applicationDidBecomeActive(application: UIApplication) {
        let rvc = window?.rootViewController as! ViewController
        rvc.stateDidChange() //Update the button
    }

Line 2 of the code gets the view controller. We have the view controller as our root view controller, so it’s easy to access. Once we have a reference to the root controller, we can call its method. statDidChange which checks the status of the timer and changes the title to the correct one.

Build and run.

2016-05-01_07-54-37

Press the nag button. Press Command-Shift-H. Eventually the notification appears:

2016-05-01_07-09-56

Drag the small oval at the bottom of the notification down.

2016-05-01_07-37-48

The actions appear.

2016-05-01_07-11-10

Tap the emoji button.  The next notification you get is an emoji.

2016-05-01_07-11-41

Drag down from the status bar and get the notifications. Swipe right to left on one of the notifications.

2016-05-01_07-15-52

Tap the Stop button. The app stops and opens up to the Nag screen again.

2016-05-01_07-54-37

The Apple Watch for All Developers

While that’s what you need to know for iOS, it’s not the whole story. Unless notifications are turned off by the user of an Apple watch, the watch will receive the notification if the phone is asleep.  Sadly, the watch simulator does not always reflect what happens on the watch.  You can try using the simulator if you  don’t have a watch handy, I’ll be using both since they have slightly different behaviors.

If your app is still running, get the watch and phone  simulator visible

2016-05-01_16-39-08

Start nagging with the phone, then press Command-L to Lock the phone. The simulator goes black. Wait a few seconds,  And you will see the Short-Look Notification appear:

Photo May 01, 9 26 23 AM

A second later, the  Long-Look notification appears.

2016-05-01_16-39-07

You’ll notice the buttons for the actions don’t quite fit on the watch. scroll up and you’ll find one more button.

2016-05-01_16-45-22

Test the emoji button.  Tap it, and the next notification changes to this:

2016-05-01_16-51-29

Press the stop button and you get a blank screen with the time. Actually, you are running a blank watch app.

2016-05-01_16-53-02

 You get slightly different  results with a real phone. Running the app on my phone, I start nagging by pressing the Nag button. I then lock my phone  by pressing the switch. SInce I turned on sounds and used the system sound in my actions, I’ll get a Haptic vibration on my wrist,  and then the notification.

Photo May 01, 9 25 40 AM

The notification includes the Emoji button and a dismiss button, which is cut off.  You can scroll up to get the rest of the dismiss button. On the simulator, we had the stop button, but on the watch it’s missing.

The difference has to do with activationMode. If you have an action with an activationMode of .Foreground the watch does not display it automatically. The foreground mode on the watch starts the watch app associated with this notification. In the simulator it lets us run that blank app, but on a physical watch it knows there is no app and doesn’t show the button.  On the other hand, an activationMode of .Background on the watch sends to the background of the phone. That’s why with no coding, the Emoji button works perfectly. Go to the NaggyNotification class. In makeActions, change our code to put the Stop button in the background

stopAction.activationMode = .Background

Now run the app again and when we lock the phone we get both buttons on the watch:

Photo May 01, 10 05 16 AM

With more buttons, the dismiss button is missing. Drag up and you will find it:

Photo May 01, 10 10 27 AM

Tap the Emoji Button. The next notification changes to emoji

Photo May 01, 10 05 31 AM

Tap the Stop button. On both the live watch and the simulator, the timer stops.  All of our actions work on the watch without a single new line of code.

Notifications will happen on an Apple Watch even if you don’t program for it. For Apple watch developer, this provides one way to launch your watch app. For iOS developers using notifications and actions, it means you have one more thing to think about when writing an app.  Think out what you want to show and not to show  for a watch user. If you are not writing a watch app, you can exclude actions from the watch but not the phone by making the  activationMode go to the foreground.

The whole code

NaggyNotification.swift

//
//  NaggyNotification.swift
//  PhoneWatchNotificationDemo
//
//  Created by Steven Lipton on 5/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

//Global variable -- not always a good idea,
// but makes running this code much simpler
//
let naggingNotification = NaggyNotification()

class NaggyNotification: NSObject {
    
    //All properties are private or read only 
    //Good insurance for using the global instance
    //nothing gets messed up. 
    
    private let basicNags = ["Nag", "Nag nag","Naggity nag"]
    private let emojiNags = ["😀","😉","🙄","😒","😠","😡"]
    private var nags = ["Nag", "Nag nag","Naggity nag"]
    private var counter = 0
    private var timer = NSTimer()
    private var backgroundTask = 0
    private var localNotification = UILocalNotification()
    var nagging:Bool {return timer.valid } //read only
    
    func presentNotificationNow(){
        UIApplication.sharedApplication().presentLocalNotificationNow(localNotification)
    }
    func toggleAlertMessages(){
        if nags == basicNags {
            nags = emojiNags
        } else {
            nags = basicNags
        }
    }
    
    func startTimer(timeInterval:NSTimeInterval){
        if !timer.valid{ //prevent more than one timer on the thread
            counter = 0
            backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
            timer = NSTimer.scheduledTimerWithTimeInterval(
                timeInterval,
                target: self,
                selector: #selector(timerDidFire),
                userInfo: nil,
                repeats: true)
        }
        
    }
    
    func timerDidFire(timer:NSTimer){
        counter += 1
        let currentNag = counter % nags.count //cycle through messages
        let nagString = String(format:"# %i %@",counter,nags[currentNag])
        localNotification.alertBody = nagString
        localNotification.alertTitle = "Naggy Notification"
        localNotification.category = "Nags"
        //localNotification.alertTitle = localNotification.category
        localNotification.soundName = UILocalNotificationDefaultSoundName
        localNotification.userInfo = ["NagNum":counter,"NagString":nags[currentNag]]
        presentNotificationNow()
        
    }
    
    func stopTimer(){
        timer.invalidate()
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
    }
    
    //MARK: - Actions and Categories
    func makeActions()-> [UIMutableUserNotificationAction]{
        let stopAction = UIMutableUserNotificationAction()
        stopAction.title = "Stop"
        stopAction.identifier = "StopAction"
        stopAction.destructive = true
        stopAction.authenticationRequired = true
        stopAction.activationMode = .Background
        
        let moreAction = UIMutableUserNotificationAction()
        moreAction.title = "Emoji"
        moreAction.identifier = "EmojiAction"
        moreAction.destructive = false
        moreAction.authenticationRequired = false
        moreAction.activationMode = .Background
        
        return [stopAction,moreAction]
    }
    
    func makeCategory()-> UIMutableUserNotificationCategory {
        let category = UIMutableUserNotificationCategory()
        category.identifier = "Nags"
        category.setActions(makeActions(), forContext: .Default)
        return category
    }
}

AppDelegate.swift

//
//  AppDelegate.swift
//  PhoneWatchNotificationDemo
//
//  Created by Steven Lipton on 5/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?


    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        let category = naggingNotification.makeCategory()
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound],
                categories: [category]
            )
        )
        return true
    }
    
    func application(application: UIApplication, handleActionWithIdentifier identifier: String?, forLocalNotification notification: UILocalNotification, completionHandler: () -> Void) {
        
        if notification.category == "Nags"{
            if identifier == "StopAction"{
                naggingNotification.stopTimer()
            }
            if identifier == "EmojiAction"{
                naggingNotification.toggleAlertMessages()
            }
        }
        completionHandler()
    }
    

      func applicationDidBecomeActive(application: UIApplication) {
        let rvc = window?.rootViewController as! ViewController
        rvc.stateDidChange() //Update the button
    }

   
}



ViewController.swift

//
//  ViewController.swift
//  PhoneWatchNotificationDemo
//
//  Created by Steven Lipton on 5/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit


class ViewController: UIViewController {
    @IBOutlet weak var nagButton: UIButton!
    @IBAction func nagButton(sender: UIButton) {
        if naggingNotification.nagging{
            naggingNotification.stopTimer()
        }else{
            naggingNotification.startTimer(10.0)
        }
        stateDidChange()
    }
    func stateDidChange(){
        if !naggingNotification.nagging{
            nagButton.setTitle("Nag", forState: .Normal)
        }else{
            nagButton.setTitle("Stop Nagging", forState: .Normal)
        }
    }
}

Adding iOS Local Notifications in Swift

The word notification gets a bit abused in the world of mobile development, especially in the world of Apple development. Notifications could mean internal notifications, where classes use observers to watch unrelated code. When a notification appears, the observer code executes special code for that event.

Another type of notification notifies a user that something has happened, even if they are not in the app the sent the notification – or the device  is sleeping. That kind of user notification has two varieties: push notifications and local notifications. To  the user, these two are the same. For the developer, the push notification uses an external server to push data from the outside world. The Messages app, Facebook and Twitter all use push messages. Due to using an external server, things get complicated with push notifications. There is a registration process with Apple, extra libraries to use them, and you have to write or import code to handle the server data.

The much simpler local notification needs none of that. With a few lines of code, you can fire off a notification. Often used on scheduling and timer applications, this can adapt for many other uses where external data isn’t necessary. In this lesson, we’ll look at the local notification. We’ll make a HIIT timer app to show aspects of the local notification.

Set Up a New Project.

Start a new single view project called SwiftLocalNotificationDemo with Swift as the language and a Universal device.  Save the file, and then go to the story board. We’ll eventually use a navigation controller for this app. Select the existing view controller and from the menu select Editor>Embed in>Navigation Controller.  You’ll have a navigation controller and a view controller on the storyboard.

2016-04-20_06-19-15

On the storyboard add three buttons,  three labels  and a segmented control.  Arrange them roughly like this.

2016-04-20_06-28-04

Set the titles and text like this for the controls.

2016-04-20_06-36-53

I added color an fancied it up. You don’t have to do this step, but it makes it a bit nicer to look at. I used a palette of #220066 for the dark  and #FFDDFF for the light. I also changed up and down for symbols.  Use the keyboard shortcut  Control-Command-Space to get the symbol popup.

2016-04-20_07-02-41

Using Stack Views

We’ll use some embedded stack views to align everything quickly.  I’m not going to explain stack views here, but for those who have never used them, I’ll go step by step. If you are interested you can read this about them or go buy my book on auto layout. Select the Seconds and 00 labels  On the bottom right of the story board, You’ll find the auto layout controls.

stackButton

Click the stack view iconstack view button. It immediately makes a stack view of the two labels.  Select the two arrows and the seconds stack view.

2016-04-20_07-09-35

Click the stack view button stack view buttonagain. The three combine into one stack view.

2016-04-20_07-12-50

Select everything on the storyboard. Hit the stack view button stack view buttonagain. Now everything is one stack view.

2016-04-20_07-14-34

Find the pin buttonpinMenuButton in the auto layout controls. In the popup that appears, pin all four sides 10 points in the popup. Also set the Update Frames with Items of New Constraints.

2016-04-20_07-18-14

Add the 4 constraints. Depending on where your controls were, th storyboard now looks something like this.

2016-04-20_07-18-47

You’ll notice in the attributes inspector the stack view attributes. Change them to An axis of Vertical, Alignment of Fill and Distribution of Fill Equally:

2016-04-20_07-23-31

Open the document outline. Open up all the arrows so you can see the hierarchy of the stack views. It’s a lot easier to click on the document outline than the storyboard to select stack views.  Select the second stack view down.

2016-04-20_07-27-18

In the attributes inspector, change the stack view to this:

2016-04-20_07-29-32

Select the last stack view.

2016-04-20_07-34-39

Change the attributes to this:

2016-04-20_07-23-31

You now have a storyboard  looking like this:

2016-04-20_07-36-23

Select the Seconds and 00 labels. Center align them.

2016-04-20_07-40-52

We now have a fully aligned layout.

2016-04-20_07-41-18

Setting Up the Code

Go to the ViewController.swift code.  Add the following outlets and actions.

//MARK: - Outlets
@IBOutlet weak var statusLabel: UILabel!
@IBOutlet weak var workoutType: UISegmentedControl!
@IBOutlet weak var secondsIndicator: UILabel!
//MARK: - Actions
@IBAction func upArrow(sender: UIButton) {
}
@IBAction func downArrow(sender: UIButton) {
}
@IBAction func setInterval(sender: UIButton) {
}

Go back to the storyboard and open the assistant editor. Drag from the circle next to statusLabel to The Quick HIIT Timer Label. Drag from the circle to the left of secondsIndicator to the 00 label. In the same way Connect workoutType to the segmented control, upArrow to the up arrow(Up) button, downArrow to the down arrow(Down) button, and setInterval to the Set Interval button. Close the assistant editor and go to the Viewcontroller.swift code. Above the outlets, add a property:

 //MARK: Properties
    var seconds:NSTimeInterval = 0.0

We’ll use the up and down buttons to change a counter seconds. Change the actions upArrow and downArrow like this:

@IBAction func upArrow(sender: UIButton) {
     seconds += 1.0
     secondsIndicator.text = displaySeconds()      
}
@IBAction func downArrow(sender: UIButton) {
     seconds -= 1
     if seconds < 0{
          seconds = 0.0
     }
     secondsIndicator.text = displaySeconds()
}

In these actions, our last statement sends the current seconds to the label. It converts the time interval to a string in a function displaySeconds. Add displaySeconds to your code:

//MARK: Instance Methods
func displaySeconds() -> String{
    let mySeconds = String(format:"%02i",Int(seconds))
    return mySeconds
}

We now have a working counter in our app. Add the following code to setInterval.

@IBAction func setInterval(sender: UIButton) {
    //make a status string
    let index = workoutType.selectedSegmentIndex
    let workout = workoutType.titleForSegmentAtIndex(index)!
    let status = displaySeconds() + " seconds " + workout
    statusLabel.text = status + " set"
}

Nothing visible happens when we schedule a notification. We now have some feedback of what notification we just set.

Setting a Notification

We’re ready to set a local notification. Add some more code to the setInterval action

@IBAction func setInterval(sender: UIButton) {
    //make a status string
    let index = workoutType.selectedSegmentIndex
    let workout = workoutType.titleForSegmentAtIndex(index)!
    let status = displaySeconds() + " seconds " + workout
    statusLabel.text = status + " set"
        
    //make the local notification
    let localNotification = UILocalNotification()
    localNotification.fireDate = NSDate(timeIntervalSinceNow:seconds)
    localNotification.alertBody = status + " complete"
    localNotification.timeZone = NSTimeZone.defaultTimeZone()
    //set the notification
    UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
}

Line 9 of this code makes an instance localNotification of the UILocalNotification class. Local notifications can be immediate or time based. We set the time we will launch the notification, its fireDate, with an NSDate object. If this was a scheduling app I’d set the scheduled time for the notification to fire. In line 10 I want a time seconds in the future from now. The alertBody property of line 11 sets the message that will appear on the notification. We show we completed that interval. While not mandatory, since we are using a time, we set the time zone the notification will use for its clock, usually the default time zone for the system.

Our last line of code in line 14 is the first of two tricky things about notifications. The notification isn’t running in our class. Instead, it’s part of a bigger structure within the device’s OS. If the app isn’t running in the foreground, we get our notification. The object UIApplication.sharedApplication() is the application object we need to schedule this with. We schedule that notification with scheduleLocalNotification.

The second tricky part is we tell the UIApplication object we are using notifications and how we will be using them. We do that in its delegate, the often ignored AppDelegate. Go to the AppDelegate.swift file in Xcode. Toward the top, you’ll find the application:didFinishLaunchingWithOptions:LaunchOptions: method. Add the highlighted code:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
    // Override point for customization after application launch.
    application.registerUserNotificationSettings(
        UIUserNotificationSettings(
            forTypes:[.Alert, .Sound, .Badge],
            categories: nil
        )
    )
    return true
}

We register our notification with a UIUserNotificationSettings object. We have two parameters to set in the UIUserNotificationSettings object: the type of notification and the actions we might take. These actions are known as categories. We’ll discuss categories in our next lesson, so we set categories to nil and focus on types. There are four types available to us:

  • .Alert sends an alert or banner
  • .Sound plays a sound
  • .Badge shows the small red dot with a counter on the app icon
  • .None which does not present a notification.

We set up all three notifications. We are ready to build and run. As soon as the launch screen disappears from the app we see the alert:

2016-04-21_06-53-35

Notifications are one feature that the user chooses. Tap OK. Tap the up arrow until the counter reads 15 seconds.

2016-04-21_06-58-37

Tap the Set Interval button. The status tells us we added an interval.

2016-04-21_07-27-47

Press Command-Shift-H to go to the home screen. In a few seconds, the interval will fire and you’ll see the notification.

2016-04-21_07-03-21

Swipe down to get the notification center, and you’ll see your notification there.

2016-04-21_07-03-42

User Settings and Notifications

Go to the Settings app in the simulator. Scroll down towards the bottom and you’ll find our app.

2016-04-21_07-10-45

Select the SwiftNotificationDemo entry in Settings and we find one entry  for Notifications.

2016-04-21_07-16-44

Click that and we get the standard notifications settings page. Currently it is at its default settings.

2016-04-21_07-16-45

Toward the bottom you’ll  find the Alert Style When Unlocked setting. Change it from Banners to Alerts.

2016-04-21_07-11-34

Now go back to the app. Let’s do this and demonstrate one more feature of notifications. Swipe down from the top of the screen to get the notification center.

2016-04-21_07-03-42

Tap the notification. You are back in the app. Notifications when tapped launch the app or bring the app to the foreground.

2016-04-21_07-27-47

Tap the Set Interval again, then press Command-Shift-H to go back to the home screen. Wait, and you get an alert style notification.

2016-04-21_07-32-02

Unlike the banner type, which will dismiss itself, the alert waits for a response.  You can close the alert or open the app. Close the notification.

Are Notifications Allowed?

There is one user setting that all developers must be aware of. In the simulator go back to our apps notification settings. At the top you will find the Allow Notifications button.

2016-04-21_07-35-34

Switch it off. All the other settings disappear.

2016-04-21_07-35-51

Shut down the app in Xcode.  Build and run.  Set a 10 second Notification.  The system will let you do this but you will be waiting a long time for that notification –  it doesn’t exist.  The system shuts down our request for a notification.

In an app where notifications have major functionality, such as this timer app, we may want to tell the user that this is a bad idea.

We’d first have to know ourselves that notifications are off. We told the application which ones to turn on in the AppDelegate. There is a property that lets us look at those settings, currentUserNotificationSettings. It has a property type, which has a value of UIUserNotification type. This type uses a bitmask. If you’ve never used a bitmask before, it’s a way of compressing several Bool values into a single number. The three types we have each have one digit of a binary number. We thus can make an unsigned integer value from that value according to this table

Value .Badge (4) .Sound(2) .Alert (1)
0
(.None)
0 0 0
1 0 0 1
2 0 1 0
3 0 1 1
4 1 0 0
5 1 0 1
6 1 1 0
7 1 1 1

Notice the zero row. Remember we had four, not three options for UIUserNotification. The fourth option is .None. That is when all three of the others are off. When a user has notifications off in the user settings, the system always preempts our currentUserNotificationSettings.type setting it to .None. For an easy comparison, it often best to change this to a rawValue which gives us direct access to that integer value. Add this line under the one you just added:

let noNotifications = UIUserNotificationType.None.rawValue

This is just assigning a constant to the value 0, but for good documentation we write the longer way.
All this gets us to a simple if. If the user notifications is off, the type will be zero, otherwise it is on. Change the setInterval action to this:

@IBAction func setInterval(sender: UIButton) {
     //make a status string
     let index = workoutType.selectedSegmentIndex
     let workout = workoutType.titleForSegmentAtIndex(index)!
     let status = displaySeconds() + " seconds " + workout
     statusLabel.text = status + " set"
        
     let notificationTypes = UIApplication.sharedApplication().currentUserNotificationSettings()?.types
     let noNotifications = UIUserNotificationType.None.rawValue
     if notificationTypes!.rawValue == noNotifications {
         statusLabel.text = "Turn on notifications in settings"
     } else{
        //make the local notification
        let localNotification = UILocalNotification()
        localNotification.fireDate = NSDate(timeIntervalSinceNow:seconds)
        localNotification.alertBody = status + " complete"
        localNotification.timeZone = NSTimeZone.defaultTimeZone()
        
        //set the notification
        UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
     }

We check if notifications are turned off. If off we send a message to the status label to tell the user the app doesn’t work. In a more sophisticated app I’d use an alert, explaining that the app doesn’t work without notifications, and include an action in the alert to take the user to the settings app.
Go back to the simulator, and make sure notifications are turned off.

2016-04-21_07-35-51

Build and run.  Make a notification, and you get a message

2016-04-22_06-14-04

While it give us the right message, It runs off the screen. Exit the app, and go to the storyboard. Select the status label.  In the attributes, change Lines to 0, Line Breaks to Word Wrap.

2016-04-22_06-14-06

This sets the number of lines in the label to automatic.

Change the message to be a bit more verbose:

statusLabel.text = "Please turn on notifications in Settings"

Build and run. When we try to set a notification, we get this:

2016-04-22_06-34-15

Enable notifications in the settings app. Go back to the app and it works.

2016-04-22_06-36-00

Listing Notifications

We may want to see all notifications currently waiting to fire.  There is an array scheduledLocalNotifications that lets us see this. We’ll set up a quick table view to display these. If you are not familiar with table views you might want to read this. Shut down the app in Xcode, and go to the storyboard.  Add a UIBarButtonItem to the right side of the navigation bar. Title it  List. Drag out a table view controller to the storyboard. Control drag from the List bar button to the view controller to get a segue. Make it a Show segue.

2016-04-22_06-48-20

In the document outline, Select the Table View Cell

2016-04-22_06-31-42

In the attributes inspector set the Identifier to cell and the Style to Basic.

2016-04-22_06-31-55

We need to make the controller. I usually avoid the table view template. Press Command-N to make a new file.  Make a new Cocoa Touch Class file subclassing UIViewController.  Name the file NotificationsTableViewController. When the file appears, change the view controller to this:

class NotificationsTableViewController: UITableViewController {

    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
}

Go to the storyboard and select the table view controller. In the identity inspector, Change the class to NotificationsTableViewController.

Go to the NotificationsTableViewController class. Add two of the three data sources and a constant to the code like this:

class NotificationsTableViewController: UITableViewController { 
    var  notifications = UIApplication.sharedApplication().scheduledLocalNotifications
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return notifications!.count
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }
}

Our constant notifications contains an array of the scheduled notifications. We’ll use one section in the table, and our number of rows will be the number of scheduled notifications. Now add the last method we need to the class:

override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
        let row = indexPath.row
        let fireDate = (notifications![row].fireDate)!
        let dateString = NSDateFormatter.localizedStringFromDate(fireDate, dateStyle: .ShortStyle, timeStyle: .LongStyle)
        cell.textLabel?.text = "Notification at " + dateString
        if row % 2 == 0 {cell.backgroundColor = UIColor.lightGrayColor()}
        return cell
    }

This sets up each cell of the table. Since notifications is an array of UILocalNotificaton,s we go down the array getting the fireDate from the notification. We format the fireDate as a string and return it as a cell. I added a line to alternate the colors of the rows to make it slightly more readable.

Build and run. Add several notifications of 30 seconds or more. Press the List button, and you will see your notifications.

2016-04-22_07-32-29

Deleting Notifications

You may need to delete notifications. We’ll delete any selected notification. Add the following code to the NotificationsTableViewController class.

   override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let row = indexPath.row
        if  let notification = notifications?[row]{
            UIApplication.sharedApplication().cancelLocalNotification(notification)
            notifications = UIApplication.sharedApplication().scheduledLocalNotifications
            tableView.reloadData()
        }

To delete a notification, you use the UIApplication method cancelLocalNotification. We get that by row, cancel it, then refresh our notifications list and table. While I’m usually a bit careless about optionals to make the code easier to understand here’s one exception we need to be careful. Our table is not refreshing itself when a notification occurs. Between the time we look at the table and select a notification to delete, it may have already fired and dismissed itself. We check to make sure it is still there. Build and run. Add a few notifications and try deleting them.

The Local Notification Limit

There is one limitation to local notifications: you can only have 64 waiting to fire at one time. The most recent 64 will remain in the scheduledLocalNotifications, the rest are cancelled.

We’ve made an app that’s supposed to be a HIIT app. If you are not familiar with HIIT, it stands for High Intensity Interval Training. Usually for cardio training, you do a very intense exercise for a short amount of time then rest for a short amount of time to recover, then repeat several times. However, there’s a problem. If I do run/walk/run intervals for HIIT I might have over a hundred intervals. Other apps have nagging modes which remind you every minute or every hour to do something. Both of these would kill the 64 notification limit quickly.

A Solution: presentLocalNotificationsNow

We’ve used scheduleLocalNotification to schedule our notifications. If we don’t schedule them we don’t run up against the 64 notification limit. Instead we use presentLocalNotificationNow and let our code do the timing. We’ll use an NStimer to do this. If you are not familiar with NSTimer and the timing loop we will use, you might refer to my post on them

Go to the ViewController class. Add the following properties:

var timer = NSTimer()
let timeInterval:NSTimeInterval = 10.0
var workout = false
var workoutIntervalCount = 5

The variable timer will be our timer with a firing time of every 15 seconds, which I set in a constant timeInterval. The code will alternate between working out and resting for a set number of intervals found in workoutIntervalCount. To set up the timer we’ll use this function:

//MARK: -  NSTimer based notifications
func startTimer(){
    if !timer.valid{ //prevent more than one timer on the thread
        timer = NSTimer.scheduledTimerWithTimeInterval(
            timeInterval,
            target: self,
            selector: #selector(timerDidEnd),
            userInfo: nil,
            repeats: true)
        }
    }

The timer will repeat every 15 seconds, executing the selector timerDidEnd. We now have to add that function:

func timerDidEnd(timer:NSTimer){
    if workoutIntervalCount == 0 { //finished intervals
        timer.invalidate()
        statusLabel.text = "Workout complete"
    } else {
        workout = !workout
        if !workout {
            statusLabel.text = String(format:"Interval %i Rest",workoutIntervalCount)
            workoutIntervalCount -= 1
        }else{
            statusLabel.text = String(format:"Interval %i Work Out",workoutIntervalCount)    
        }
            
     }
}

This figures out which kind of notification we need to give, alternating between a workout and a rest. At each workout, we decrease workoutIntervalCount  by  one until we are at zero, where we shut off the timer. Under this code in the function timerDidEnd place the following to make our notification:

 //make the local notification
 let localNotification = UILocalNotification()
 localNotification.alertBody = statusLabel.text!
 localNotification.timeZone = NSTimeZone.defaultTimeZone()
 localNotification.applicationIconBadgeNumber = workoutIntervalCount
 //set the notification
 UIApplication.sharedApplication().presentLocalNotificationNow(localNotification)
}

Line 7 in this code uses presentLocalNotificationNow to immediately send the notification. Since this notification sends immediately, it ignores fireDate so we didn’t assign it this time. We did include line 5 however. Notifications are also badges, those little numbers on the corners of some icons, such as the one for mail. Using the property applicationIconBadgeNumber, We set it to the current workout interval count.
Instead of making a new button for this , we’ll run the code when seconds = 0 . Change setInterval to this:

    @IBAction func setInterval(sender: UIButton) {
        //make a status string
        if seconds > 0 {
            let index = workoutType.selectedSegmentIndex
            let workout = workoutType.titleForSegmentAtIndex(index)!
            let status = displaySeconds() + " seconds " + workout
            statusLabel.text = status + " set"
        
            let notificationTypes =  UIApplication.sharedApplication().currentUserNotificationSettings()?.types
            let noNotifications = UIUserNotificationType.None.rawValue
            if notificationTypes!.rawValue == noNotifications {
                statusLabel.text = "Please turn on notifications in Settings."
            } else{
                //make the local notification
                let localNotification = UILocalNotification()
                localNotification.fireDate = NSDate(timeIntervalSinceNow:seconds)
                localNotification.alertBody = status + " complete"
                localNotification.timeZone = NSTimeZone.defaultTimeZone()
                //set the notification
                UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
            
            }
        }else{
            workoutIntervalCount = 5
            startTimer()
        }

    }

Now when we have a value of 0 for seconds our automatic interval counter will send notifications every 15 seconds. Build and run using the simulator. Tap the set interval button. Press Command-Shift-H and watch the results:

2016-04-23_09-41-352016-04-23_09-41-44

2016-04-23_09-43-15

You’ll also notice the badge on the icon.

2016-04-23_09-41-50

Running on an iPhone: Using the Background

Up to this point we have been running this application on a simulator. You may get different results on an iPhone for the NSTimer based notifications. You may get nothing. Real devices suspend background operations unless you state otherwise. Fortunately, we can do that with a few more lines of code.
Add the highlighted line of code.

//NSTimer executed functions
    func startTimer(){
        if !timer.valid{ //prevent more than one timer on the thread
            backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
            timer = NSTimer.scheduledTimerWithTimeInterval(
                timeInterval,
                target: self,
                selector: #selector(timerDidEnd),
                userInfo: nil,
                repeats: true)
            
        }
        
        
    }

The beginBackgroundTaskWithExpirationHandler method registers our code as a background task. iOS gives this background task an amount of time to finish. If it expires, the code in the closure runs. You can then gracefully shout down things. We’ll keep our app simple and leave the closure nil, which will end the app. This is only for simplicity. It is much better to write a handler.

The beginBackgroundTaskWithExpirationHandler method returns a unique ID as an Int. We’ll store this ID as a property of our classbackgroundTask. At the top of our class, add this

 var backgroundTask = 0

When our timer loop completes, we dismiss our app from the background. Add the highlighted line to timerDidEnd

func timerDidEnd(timer:NSTimer){
        //decrement the counter
        if workoutIntervalCount == 0 { //finshed intervals
            timer.invalidate()
            statusLabel.text = "Workout complete"
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)

This deregisters the background task using the ID of the task.
With this code, you can build and run a a iPhone and get the same results as the simulator.

Local Notifications and Apple WatchOS

If you have an Apple Watch and run this App on a phone, try running the app on your phone for the five intervals. Once started, put your phone to sleep by tapping the sleep switch. If your phone is asleep, your watch will display the notifications that were going to your phone.

Photo Apr 23, 10 10 52 AM

Notifications to on the Apple Watch are completely free and automatic. No extra programming is required.

Photo Apr 24, 7 27 25 AM

However, you may have noticed that notification in some apps give you haptics and sounds. Some notifications have extra buttons. These are controlled by more actions we can add to both iOS and WatchOS. In the next lesson,  we’ll look at how to make more buttons on your notification first in iOS and then in Watch OS.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  SwiftNotificationDemo
//
//  Created by Steven Lipton on 4/20/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    //MARK: Properties
    var seconds:NSTimeInterval = 0.0
    var backgroundTask = 0
    //MARK: - Outlets
    @IBOutlet weak var statusLabel: UILabel!
    @IBOutlet weak var workoutType: UISegmentedControl!
    @IBOutlet weak var secondsIndicator: UILabel!
    
    
    //MARK: - Actions
    @IBAction func upArrow(sender: UIButton) {
        seconds += 1.0
        secondsIndicator.text = displaySeconds()
        
    }
    @IBAction func downArrow(sender: UIButton) {
        seconds -= 1
        if seconds < 0{ seconds = 0.0 } secondsIndicator.text = displaySeconds() } @IBAction func setInterval(sender: UIButton) { //make a status string if seconds > 0 {
            let index = workoutType.selectedSegmentIndex
            let workout = workoutType.titleForSegmentAtIndex(index)!
            let status = displaySeconds() + " seconds " + workout
            statusLabel.text = status + " set"
        
            let notificationTypes =  UIApplication.sharedApplication().currentUserNotificationSettings()?.types
            let noNotifications = UIUserNotificationType.None.rawValue
            if notificationTypes!.rawValue == noNotifications {
                statusLabel.text = "Please turn on notifications in Settings."
            } else{
                //make the local notification
                let localNotification = UILocalNotification()
                localNotification.fireDate = NSDate(timeIntervalSinceNow:seconds)
                localNotification.alertBody = status + " complete"
                localNotification.timeZone = NSTimeZone.defaultTimeZone()
                //set the notification
                UIApplication.sharedApplication().scheduleLocalNotification(localNotification)
            
            }
        }else{
            workoutIntervalCount = 5
            startTimer()
        }

    }
    //MARK: - Instance Methods
    func displaySeconds() -> String{
        let mySeconds = String(format:"%02i",Int(seconds))
        return mySeconds
    }
    
    
    //MARK: -  NSTimer based notifications
    var timer = NSTimer()
    let timeInterval:NSTimeInterval = 5.0
    var workout = false
    var workoutIntervalCount = 5

    
    //NSTimer executed functions
    func startTimer(){
        if !timer.valid{ //prevent more than one timer on the thread
            backgroundTask = UIApplication.sharedApplication().beginBackgroundTaskWithExpirationHandler(nil)
            timer = NSTimer.scheduledTimerWithTimeInterval(
                timeInterval,
                target: self,
                selector: #selector(timerDidEnd),
                userInfo: nil,
                repeats: true)
            
        }
        
        
    }
    
    func timerDidEnd(timer:NSTimer){
        //decrement the counter
        if workoutIntervalCount == 0 { //finshed intervals
            timer.invalidate()
            statusLabel.text = "Workout complete"
        UIApplication.sharedApplication().endBackgroundTask(backgroundTask)
        } else {
            workout = !workout
            if !workout {
                statusLabel.text = String(format:"Interval %i Rest",workoutIntervalCount)
                workoutIntervalCount -= 1
            }else{
                statusLabel.text = String(format:"Interval %i Work Out",workoutIntervalCount)
                
            }
            
        }
        //make the local notification
        let localNotification = UILocalNotification()
        localNotification.alertBody = statusLabel.text!
        localNotification.timeZone = NSTimeZone.defaultTimeZone()
        localNotification.applicationIconBadgeNumber = workoutIntervalCount
        
        //set the notification
        UIApplication.sharedApplication().presentLocalNotificationNow(localNotification)
}
    //MARK: - Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
       
        // Do any additional setup after loading the view, typically from a nib.
    }

}


AppDelegate.swift

//
//  AppDelegate.swift
//  SwiftNotificationDemo
//
//  Created by Steven Lipton on 4/20/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    var window: UIWindow?

//methods not overridden not included. 
    func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        // Override point for customization after application launch.
        application.registerUserNotificationSettings(
            UIUserNotificationSettings(
                forTypes:[.Alert, .Sound, .Badge],
                categories: nil
            )
        )
        return true
    }
}

NotificationsTableViewController.swift

//
//  NotificationsTableViewController.swift
//  SwiftNotificationDemo
//
//  Created by Steven Lipton on 4/22/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class NotificationsTableViewController: UITableViewController {
    
    var  notifications = UIApplication.sharedApplication().scheduledLocalNotifications
    
    override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return notifications!.count
    }
    
    override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath)
        let row = indexPath.row
        let fireDate = (notifications![row].fireDate)!
        let dateString = NSDateFormatter.localizedStringFromDate(fireDate, dateStyle: .ShortStyle, timeStyle: .LongStyle)
        cell.textLabel?.text = "Notification at " + dateString
        if row % 2 == 0 {cell.backgroundColor = UIColor.lightGrayColor()}
        return cell
    }
    
    override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
        let row = indexPath.row
        if  let notification = notifications?[row]{
            UIApplication.sharedApplication().cancelLocalNotification(notification)
            notifications = UIApplication.sharedApplication().scheduledLocalNotifications
            tableView.reloadData()
        }
        
    }
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view.
    }

    

}

The Step by Step Guide to Custom Presentation Controllers

Ever wanted that sliding sidebar or an alert with a image picker? Apple has many great ways of presenting view controllers,  but sometimes we want something different, something new. For that we subclass UIPresentationController. However there’s a few concepts that you’ll need to wrap your head around first. In this lesson we’ll create a few standard controllers to explain the custom controllers, then make and animate our own custom controller sliding  side bar. We’ll build the presentation controller step by step, so you know how it really works.

 Custom Presentation Anatomy in Alert Views.

Make a new project called SwiftCustomPresentation, with a universal device and Swift as the language.  Once loaded go to the launchscreen.storyboard. I like to have some indication that things are working, and since Apple removed the default launch screen layout, I tend to add my own. Set the background color to Yellow(#FFFF00). Drag a label on the launch screen  and title it Hello in 26 point size.  Click the alignment auto layout buttonalignment icon at the bottom of the storyboard and check on Horizontally in Container and Vertically in Container set to 0. Update the frames for  Items  of  new constraints, then click Add 2 constraints.

2016-04-07_06-09-52

You now have a nice label in the center of the launch screen

2016-04-07_06-14-05

Go to the storyboard. Make the background Yellow(#FFFF00). Drag a button to the center of the storyboard. Title the label Hello, Pizza as a 26 point size system font. Make the text color Dark Blue(#0000AA) Just like the label on the launch screen, align the button to the center of the storyboard.  Click the alignment auto layout buttonalignment icon at the bottom of the storyboard and check on Horizontally in Container and Vertically in Container set to 0. Update the frames for  Items  of  new constraints, then click Add 2 constraints.

2016-04-07_06-36-27

Open the assistant editor. Control drag from the button to the code. Create an action entitled showAlert. Close the assistant editor for now. Ooen the ViewController.swift file.  Change the action to this:

@IBAction func showAlert(sender: UIButton){
helloAlert()
}

Now add the following code to create an alert

func helloAlert(){
     let alert = UIAlertController(
          title: "Hello Slice",
          message: "Ready for your slice?",
          preferredStyle: .Alert)
      let action = UIAlertAction(
          title: "Okay",
          style: .Default,
          handler: nil)
      alert.addAction(action)
      presentViewController(alert,
          animated: true,
          completion: nil)
}

This is  code to present the simplest alert possible.  We create a simple alert and give it one action, an Okay button that dismisses the alert. The last line is the important one for our discussion:


presentViewController(alert,
     animated: true,
     completion: nil)

As of iOS8, all modal view controllers present with this one method.  You tell the method which controller you want to present, set a few properties on it and it does the work, no matter what your device. It relieves a lot of the hard work of developers specifying exceptions for individual devices.

Set your simulator for an iPhone 6s. Build and run.  When the Hello pizza button shows in the simulator, tap it. You get your alert.

2016-04-07_07-01-03

We’ve made this alert to illustrate the three parts you need to know about any custom view controller.

2016-04-07_07-01-10

Our yellow background is the presenting view controller, the controller that calls the presentViewController method The alert, which is the presented view controller, is the controller presentViewController uses in its parameters.

presentingViewController.presentViewController(presentedViewController, animated:true,completion:nil)

In an alert and in other view controllers such as popovers, the presentViewController method adds a special view  between the presented and presenting view controllers called a chrome. The chrome is the difference or padding between the bounds of the presented controller, and the bounds of the presenting controller. Usually it is a solid color with some alpha value. Apple tends to use a gray color for chrome, which is why yellow is looking like a dark orange yellow.

Adding a Modal View

Alerts help us explain the anatomy of a presentation controller. However they are rather automatic in their execution. Let’s add a simple modal view controller to play with custom view controllers.  Press Command-N and select a new Cocoa Touch Class ModalViewController. Do check on Also Create XIB file. This is one of those places I prefer xibs due to portability over the storyboard.

2016-04-07_08-16-39

Save the file. Go to  the ModalViewController.xib file. Change the background to Dark Blue(#0000AA). Add two labels to the xib. In one make the text Ready for your slice? , a Font of 26.0 Bold, and White(#FFFFFF) text. Set the Alignment to Left justified. Set the  Lines  property to 0 and the Line Breaks  to Word Wrap. This will let the label vary the number of lines to fit the text, and word wrap the results. Your attributes should look like this:

2016-04-07_08-32-54

For the second label, add a short paragraph. I used a passage from Episode 4 of my podcast A Slice of App Pie.

This is the other thing about persistence: It does not see success as an absolute. It is not a Bool, a true/false variable, but a Double,  a real number. It is not just black or white, but every color.  You are not a success, but you are at a degree of success. Your past experience fuels your future success.

Set the  Font to 16.0 Regular, and White(#FFFFFF) text. Set the Alignment to Full  Justified. Set the  Lines  property to 0 and the Line Breaks  to Word Wrap.

Select the text paragraph and Control drag  down and to the right until you are over the blue of the background.

2016-04-07_08-59-30

Release the mouse button and you will see an auto layout menu. Hold down shift and Select  Trailing Space to Container, Bottom Space to Container and Equal Widths:

2016-04-07_08-46-29

 Click the Add Constraints button. Now control-drag from the Ready label to the paragraph label.  In the auto layout menu that appears, Shift-Select Vertical Spacing, Trailing, and Equal Widths

2016-04-07_08-53-44

 Click the Add Constraints button. Select the paragraph and go to  the size inspector 2016-04-07_08-53-46.  You’ll see a list of constraints:

2016-04-07_08-55-49

Your values will most likely be different than the illustration. Click the edit button for the Trailing Space to: Superview. A popup appears. Change the Constant to 8.

2016-04-07_08-56-11

Click the Equal Width: to Superview edit button. Change the Multiplier to 0.9.

2016-04-07_08-56-32

Edit the Bottom Space to: Superview constraint. Change the Constant to 20.

Edit the Align Trailing Space to: Ready for yo… constraint. Change the Constant to 0.

Edit the Top Space to: Ready for yo… constraint. Change the Constant to 20.

Edit the Equal Width to: Ready for yo… constraint. Change the Multiplier to 0.5.

If you’ve never worked with auto Layout before, what we just did is anchor the bottom right corner of the paragraph to the bottom right corner of the xib. with a margin of 20 points on the bottom and 8 points on the right. We set the width of the paragraph to 90% of the width of the xib. The Label above it, acting as a title, we made half as long as the paragraph, and aligned to the right side of the paragraph, 20 points up.

Click  the triangle tie fighter 2016-04-07_09-05-05 which is the auto layout  resolve button. You get a menu like this:

2016-04-07_09-04-46

Select for the All Views in View section Update Frames. You now have a layout like this:

2016-04-07_09-04-47

We’ll dismiss this modal with a swipe to the right. In the object library,  find the swipe gesture:

2016-04-07_10-00-48

Drag it to the blue of the xib and release. Nothing will happen there but in the document outline (if you don’t have it open click the  2016-04-07_10-07-37button)  you will see a new object listed

2016-04-07_10-10-08

Select the Swipe Gesture Recognizer and in the attributes menu, you should see the following:

2016-04-07_10-11-47

These are the defaults and what we want: a single finger swipe to the right.  Open the assistant editor and control-drag the Swipe Gesture Recognizer from the document outline to the code. Create an action named dismissModal.

2016-04-07_10-16-20

In the code that appears, dismiss the vew controller:

@IBAction func dismissModal(sender: AnyObject) {
dismissViewControllerAnimated(true, completion: nil)
}

Presenting the View Controller.

Go back to the ViewController.swift File.  Add the following method:

 func presentModal() {
let helloVC = ModalViewController(
    nibName: "ModalViewController",
    bundle: nil)
    helloVC.modalTransitionStyle = .CoverVertical
presentViewController(helloVC,
    animated: true,
    completion: nil)
}

The code gets the view controller  and the xib then presents it. We’re using the default settings for a modal in this case.

Go to the storyboard.  Open the assistant editor if not already open. Drag a Swipe Gesture recognizer to the storyboard. Select it in the the document outline and change the Swipe attribute  to Up. Control-drag from the Swipe Gesture recognizer and make an outlet

@IBAction func swipeUp(
    sender: UISwipeGestureRecognizer) {
    presentModal()
}

Build and run.You should be able to swipe up and show the blue controller.

2016-04-07_10-43-02

The title is too small to fit on one line and adapts to two. Rotate the phone (in the simulator press Command right-arrow) and you get a slightly different layout:

2016-04-07_10-43-26

Swipe right and the view dismisses

2016-04-07_13-43-29

Custom Presentation #1:
A  Big Alert View

We’ve got a working modal view controller. We did this for several reasons. This proves there’s no magic in what we are about to do. It also reinforces how we do this in a standard presentation controller.

Adding the Custom Presentation Controller

Create a new class by pressing Command-N on the key board in Xcode. Make a new cocoa touch class MyCustomPresentationController subclassing UIPresentationController.  You end up with an empty class:

class MyCustomPresentationController: UIPresentationController {
}

Our first task is to add the chrome.  We’ll also add a constant for the chrome color.  Add this to the class

let chrome = UIView()
let chromeColor = UIColor(
     red:0.0,
     green:0.0,
     blue:0.8,
     alpha: 0.4)

A presentation controller needs to do three things: Start the presentation, end the presentation and size the presentation. The UIPresentationController class has three methods we override to do this: presentationTransitionWillBegin, dismissalTransitionWillBegin and frameOfPresentedViewInContainerView. There is a fourth method containerViewWillLayoutSubviews which may sound familiar to those who have worked with code and auto layout. When there is a change to the layout, usually rotation, this last method will make sure we resize everything.

First we present the controller. In our code we control what the chrome does. To our custom presentation controller, add the following method

override func presentationTransitionWillBegin() {
    chrome.frame = containerView!.bounds
    chrome.alpha = 1.0
    chrome.backgroundColor = chromeColor
    containerView!.insertSubview(chrome, atIndex: 0)
}

First we set the size of the chrome by the size of containerView. The containerView property is a view which is an ancestor of the presenting view in the view hierarchy. containerView gives us a view that is bigger to or the same size as the presenting view. Setting the chrome to this size means the chrome will cover everything. We then set the color and alpha of chrome, and then add chrome to the container view.

Our next method to implement is dismissalTransitionWillBegin. We’ll remove the chrome from the view hierarchy here. Add this code:

 override func dismissalTransitionWillBegin() {
    self.chrome.removeFromSuperview()
 }   

We’ll change the size of the presented view so we can see the chrome. Size changes take two methods. The first is frameOfPresentedViewInContainerView. Add the following code:

override func frameOfPresentedViewInContainerView() -> CGRect {
    return containerView!.bounds.insetBy(dx: 30, dy: 30)
}

This returns the frame of the container. We used the insetBy function to shrink it 30 points within the container view. This will give us a effect similar to an alert.

To make sure adaptive layout changes everything, we need one more method. add this:

override func containerViewWillLayoutSubviews() {
    chrome.frame = containerView!.bounds
    presentedView()!.frame = frameOfPresentedViewInContainerView()
}

Our first line changes the chrome’s size to fit the new size of the view. The second one makes sure that the presented view is the correct size by calling the method we just wrote frameOfPresentedViewInContainerView.

Adding the Delegate

Custom presentation controllers run through a delegate. Above the MyCustomPresentationController class, add the following:

class MyTransitioningDelegate : NSObject, UIViewControllerTransitioningDelegate {
    func presentationControllerForPresentedViewController(
        presented: UIViewController,
        presentingViewController presenting: UIViewController,
        sourceViewController source: UIViewController
    ) -> UIPresentationController? {
        return MyCustomPresentationController(
            presentedViewController: presented,
            presentingViewController: presenting)
    }
}

This is a subclass of the UIViewControllerTransitioningDelegate. Though the parameters are long, the single function in the delegate returns our presentation controller, using the designated initializer for the class.

That is all we need for a bare bones custom controller. Our next step is to use it.

Using the Custom Presentation Controller

We’ll add a left swipe gesture for presentation this controller. Go to the storyboard. Drag a swipe gesture control on the view. Set the Swipe attribute this time for Left. Open the assistant editor and control-drag this gesture to the ViewController class, making a new action swipeLeft. Change the new function to this:

@IBAction func swipeLeft(sender: UISwipeGestureRecognizer) {
    presentCustomModal()
    }

Close the assistant editor and go to the ViewController.swift code. Add the following to the ViewController class, under to the presentModal function so you can compare the two

func presentCustomModal(){
    let helloVC = ModalViewController(
        nibName: "ModalViewController",
        bundle: nil)
    helloVC.transitioningDelegate = myTransitioningDelegate
    helloVC.modalPresentationStyle = .Custom
    helloVC.modalTransitionStyle = .CrossDissolve
    presentViewController(helloVC,
        animated: true,
        completion: nil)
    }

Like the standard modal controller, we get the controller and present it. We did change the transition style to tell the difference between the two controllers, and as you’ll see a cross dissolve makes a better transition for this type of view.

Unlike presentModal, we set the modalPresentationStyle to .Custom so presentViewController knows to look for a custom presentation controller. However it won’t work without setting the view controller’s transitioningDelegate property. The transitioning delegate tells the view controller where the custom transition is. If this is nil, the presentation is a standard presentation, not a custom one.

We haven’t set the delegate yet. At the top of the ViewController class, add this line:

let  myTransitioningDelegate = MyTransitioningDelegate()

Build and Run. Once Hello,Pizza appears, swipe left and the modal view appears with chrome around it.

2016-04-08_07-14-47

Rotate the device .

2016-04-08_07-15-00

Swipe right and the view disappears

Why Apple Uses Gray Chrome

You’ll notice that the chrome does not turn blue, but instead tuns green. As a transparent color, it blends with the color under it, sometimes not in the most attractive way. This is why Apple uses gray for chrome — you never have the problem with gray. If we change the chrome’s backgroundColor like this:

//let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.4) //mixes with background colors
let chromeColor = UIColor(white: 0.5, alpha: 0.6) //gray dims the background

Then build and run. We get a  slightly different effect:

2016-04-08_07-20-12

You’ll notice in both these cases however, the presented controller seems to glow. This is not because of code, but a trick of your brain called Simultaneous contrast . The brain has a hard time dealing with two hues that are complements next to each other, so it starts to make things up to compensate. I used two colors most likely to do this to each other, blue and yellow. There are two strategy you can use to prevent this. One is use less jarring colors in your color scheme for your app. The second is to change the chrome to have a high alpha value and not be as transparent. Change the code to this:

//let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.4) //mixes with background colors
    //let chromeColor = UIColor(white: 0.4, alpha: 0.6) //gray dims the background
    let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.7) // not as transparent

The chrome is very blue, and the effect is far less.

2016-04-08_07-13-34

Set the chrome back to the gray for the rest of the tutorial.

Animating the Chrome

You’ll notice that the chrome appears and disappears rather abruptly. We need to add some animation to the chrome so it transitions smoothly. There is  an object called a transition coordinator that automatically loads into the view controller during presentation. It gives you a way to do animation parallel to the main animation.  There’s a method in the transition coordinator animateAlongTransition:completion: that we’ll use to fade the chrome as the presented controller fades in.

In the MyCustomPresentationController class, change the method to this

override func presentationTransitionWillBegin() {
    chrome.frame = containerView!.bounds
    chrome.alpha = 0.0  //start with a invisible chrome
    //chrome.alpha = 1.0
    chrome.backgroundColor = chromeColor
    containerView!.insertSubview(chrome, atIndex: 0)
    presentedViewController.transitionCoordinator()!.animateAlongsideTransition(
        {context in
            self.chrome.alpha = 1.0
         },
         completion: nil)
}

We changed to code to start the chrome with an alpha of 0. The transitionCoordinator function returns the transition coordinator as an optional value. We use its animateAlongsideTransition method to transition for the invisible chrome to a visible one.

In the presentationTransitionWillBegin, we do nothing in the completion closure of animateAlongsideTransition. On the other hand, we’ll remove the chrome in the dismissalTransitionWillBegin. Add this code.

override func dismissalTransitionWillBegin() {
        presentedViewController.transitionCoordinator()!.animateAlongsideTransition(
            { context in
                self.chrome.alpha = 0.0
            },
            completion: {context in
                self.chrome.removeFromSuperview()
            }
        )
        //self.chrome.alpha = 0.0
        //self.chrome.removeFromSuperview()
    }

We fade out the chrome, and once the chrome completely fades at the end of the main animation, we remove the view.

With those changes, build and run. The chrome fades in and out smoothly

Custom Presentation #2:
A Sidebar

A comma use for a custom presentation controller is a toolbar or side bar. Let’s modify the code to make a sidebar on the right side of our device.

Changing the frame

For a side bar we are setting the view on one side of the contentView instead of the center as we do with insetBy. Change frameOfPresentedViewInContainerView to

override func frameOfPresentedViewInContainerView() -> CGRect {
        //return containerView!.bounds.insetBy(dx: 30, dy: 30)  //center the presented view
        let newBounds = containerView!.bounds
        let newWidth:CGFloat = newBounds.width / 3.0 // 1/3 width of view for bar
        let newXOrigin = newBounds.width - newWidth //set the origin from the right side
        return CGRect(x: newXOrigin, y: newBounds.origin.y, width: newWidth, height: newBounds.height)
    }

We commented out the insetBy, and took control of the frame directly. We take the size of the container view, and divide by three to make a frame size  a third the width of the container. We also set the origin to one-third less the width of the container view to make the bar on the right. Build and run. It’s there, but portrait does not look so good.

2016-04-08_07-57-11

Landscape looks a lot better, since it has more space

2016-04-08_07-57-25

We can change the proportions to fit better. We’ll make it 3/4 of the width. Change the code to this:

var newWidth:CGFloat = newBounds.width * 0.75 // 3/4 width of view for bar

This will work, but will be way too big on an iPad or iPhone 6 plus in landscape. We can use a trait collection to reduce the size to one third on the bigger devices. add this to the code, just under the newWidth declaration:

if containerView!.traitCollection.horizontalSizeClass == .Regular{
            newWidth = newBounds.width / 3.0 //1/3 view on regular width
        }

Change your simulator to an iPhone 6s plus. Build and run. In  portrait, we get a 3/4 view

2016-04-08_08-16-42

In landscape, with the regular width size class, we get a  1/3.

2016-04-08_08-17-04

Change to an iPad Air 2 simulator. We get 1/3 on both landscape and portrait.

2016-04-09_05-54-21 2016-04-09_05-53-58

 Animating the Side Bar

Side bars look best when they transition by sliding in. While we can animate the chrome with animateAlonsideTransition, we cannot do so for the frame of the presented controller. To do that we need to use another class,  UIViewControllerAnimatedTransitioning.

While making a new class is probably a better way of doing this, I’m going to keep it  together with the presentation controller.  Just under  the import UIKit in MyCustomPresentationController.swift, add the following:

class MyAnimationController: NSObject,UIViewControllerAnimatedTransitioning{
    var isPresenting :Bool
    let duration :NSTimeInterval = 0.5
}

The  UIViewControllerAnimatedTransitioning  protocol contains a series of functions to create an animation for both dismissal and presentation.  It has two required functions, where we use these two properties.

We added two properties to our class. isPresenting will be our indicator if this is a dismissal or presentation. To make it easier to use, add this to the class:

init(isPresenting: Bool) {
    self.isPresenting = isPresenting
    super.init()
}

This will set the value when we initialize the class. We’ll use it in a required method animateTransition. This method tells the system what to do when there is a transition. We’ll set it up to do both a presentation animation and a dismissal. Add this code:

func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
    if isPresenting {
        animatePresentationTransition(transitionContext)
    } else {
        animateDismissalTransition(transitionContext)
    }
}

The parameter transitionContext type UIViewControllerContextTransitioning contains all the information you need to do the animation transition, including the presented and presenting controllers and the container view. We’ll pass those on to two more functions we’ll write shortly to animate presentation and dismissal of the presenting view controller.

Our other property, duration will set the time for the animation duration in the other required function. Add this code:

 func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }

This satisfies the two required functions for the protocol. We are still getting errors for out two animation functions though. Add these to the class:

func animatePresentationTransition(transitionContext: UIViewControllerContextTransitioning){
}
func animateDismissalTransition(transitionContext: UIViewControllerContextTransitioning){
}

Adding a presentation Animation

Our first step is to add a presentation animation in animatePresentationTransition. We animate using one of the UIView animation class methods animateWithDuration. There are different version for different effects, whihc you can read more about in the UIView Class Refrence. All of these will use closures to describe the end state of the animation. We set the presenting view controller to a state where we will star the animation, then in the animation block give its final state. The animateWithDuration does the rest.

To present the controller’s animation, we’ll need a few thing from the the transition context. Add this to the code for animatePresentationTransition

        // Get everything you need
        let presentingViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
        let finalFrameForVC = transitionContext.finalFrameForViewController(presentingViewController)
        let containerView = transitionContext.containerView()
        let bounds = UIScreen.mainScreen().bounds

We get the presetingviewController to be able to set its state before and after animation. We also get the container view for adding the presenting view controller. The finalFrameForVC is the CGFrame that will be the end of the animation. We’ll use bounds to give us the bounds of the visible screen.

The next step is to change the frame of the presenting view controller to where it should be before the animation begins. we want it off to the right side of the visible view. Since we are not moving anything in the Y direction, we want to have our starting point as the width of the visible view. Add this code:

        //move our presenting controller to the correct place, and add it to the view
presentingViewController.view.frame = CGRectOffset(finalFrameForVC, bounds.size.width, 0)
presentingViewController.view.alpha = 0.0
containerView!.addSubview(presentingViewController.view)

Besides the presenting view moving off of stage right, I also dimmed the view to an alpha of 0.0. I then added the view to the container view. The next step is the big one — add the animation with this code:

//animate the transition
UIView.animateWithDuration(
    transitionDuration(transitionContext), //set above
    delay: 0.0,
    options: .CurveEaseOut, //Animation options
    animations: { //code for animation in closure
        presentingViewController.view.frame = finalFrameForVC
        presentingViewController.view.alpha = 1.0
    },
    completion: { finished in  //completion handler closure
        transitionContext.completeTransition(true)
    }
)

There’s a lot of unpack in this method’s parameters. We start with a duration we get from the transitionDuration method we defined earlier. Next thre is a delay to begin the animation, which we leave at ). The next parameter options takes a value from UIViewAnimationOptions to describe the behavior of the animation.

The next parameter, animations, describes the final state of the animation. In our code it sets the view controller’s frame to the final frame fro the presentation, and sets the alpha to 1.

Once the animation is complete there is a completion handler, where we set in the transition context a flag that says we are done animating.

For a dismissal, we do the same in reverse.Add this to animateDismissalTransition:

    
let presentedControllerView = transitionContext.viewForKey(UITransitionContextFromViewKey)
let containerView = transitionContext.containerView()

We first get our presented controller view and the container view. This time, the current position of the frame is the position we start from, so there is no setting the initial frame of the animation. Instead we got straight into the animation, which animates the presented controller view to the edge of the container view. Add the animation:

        // Animate the presented view off the side
UIView.animateWithDuration(
    transitionDuration(transitionContext),
    delay: 0.0,
    options: .CurveEaseIn,
    animations: {
         presentedControllerView!.frame.origin.x += containerView!.frame.width
         presentedControllerView!.alpha = 0.0
    },
    completion: {(completed: Bool) -> Void in
         transitionContext.completeTransition(completed)
    }
)

Add the Animations to the Delegate

The UIViewControllerTransitioningDelegate does more than just hold the custom transition controller. It has optional methods for animation. Add to MyTransitioningDelegate the following

func animationControllerForPresentedController(
    presented: UIViewController,
    presentingController presenting: UIViewController,
    sourceController source: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
        return MyAnimationController(isPresenting:true)
    }

After a long list of parameters, we return a MyAnimationController with isPresenting set to true. We set to false for a dismissal in this function

func animationControllerForDismissedController(
    dismissed: UIViewController
) -> UIViewControllerAnimatedTransitioning? {
        return MyAnimationController(isPresenting:false)
}

Build and run. We now have an animated custom transition

Untitled

More Things to Try.

This rather long tutorial just breaks the ice in what you can do with a custom presentation. You do not have to use a xib, for example, Storyboards works just as well, with either segues or storyboard id’s plus a reference to the delegate. There are many more animation options as well.

Also a word of caution. I intentionally used a lot of optional values as explicit values to keep things simple and readable. You might not want to be as careless as I am here, and check for nil or optional chain much more often than I did in this code.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  SwiftCustomPresentation
//
//  Created by Steven Lipton on 4/7/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController, UIViewControllerTransitioningDelegate {
    let  myTransitioningDelegate = MyTransitioningDelegate()
    
    //MARK: Actions
    @IBAction func showAlert(sender: UIButton){
        helloAlert()
    }
    @IBAction func swipeLeft(sender: UISwipeGestureRecognizer) {
        presentCustomModal()
    }
    
    @IBAction func swipeUp(sender: UISwipeGestureRecognizer) {
        presentModal()
    }
    //MARK: Instance methods
    func helloAlert(){
        let alert = UIAlertController(
            title: "Hello Slice",
            message: "Ready for your slice?",
            preferredStyle: .Alert)
        let action = UIAlertAction(
            title: "Okay",
            style: .Default,
            handler: nil)
        alert.addAction(action)
        presentViewController(alert,
            animated: true,
            completion: nil)
    }
    
    func presentModal() {
        let helloVC = ModalViewController(nibName: "ModalViewController", bundle: nil)
        helloVC.modalTransitionStyle = .CoverVertical
        presentViewController(helloVC, animated: true, completion: nil)
    }
    
    func presentCustomModal(){
        let helloVC = ModalViewController(nibName: "ModalViewController", bundle: nil)
        helloVC.transitioningDelegate = myTransitioningDelegate
        helloVC.modalPresentationStyle = .Custom
        helloVC.modalTransitionStyle = .CrossDissolve
        presentViewController(helloVC, animated: true, completion: nil)
    }
    
    //MARK: Life Cycle
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }

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


}


MyCustomPresentationController.swift

//
//  MyCustomPresentationController.swift
//  SwiftCustomPresentation
//
//  Created by Steven Lipton on 4/7/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class MyAnimationController: NSObject,UIViewControllerAnimatedTransitioning{
    
    var isPresenting :Bool
    let duration :NSTimeInterval = 0.75
    
    init(isPresenting: Bool) {
        self.isPresenting = isPresenting
        super.init()
    }
    
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }
    
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
        if isPresenting {
            animatePresentationTransition(transitionContext)
        } else {
            animateDismissalTransition(transitionContext)
        }
    }
    
    func animatePresentationTransition(transitionContext: UIViewControllerContextTransitioning){
        // Get everything you need
        let presentingViewController = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)!
        let finalFrameForVC = transitionContext.finalFrameForViewController(presentingViewController)
        let containerView = transitionContext.containerView()
        let bounds = UIScreen.mainScreen().bounds
        
        //move our presenting controller to the correct place, and add it to the view
        presentingViewController.view.frame = CGRectOffset(finalFrameForVC, bounds.size.width, 0)
        presentingViewController.view.alpha = 0.0
        containerView!.addSubview(presentingViewController.view)
        
        //animate the transition
        UIView.animateWithDuration(
            transitionDuration(transitionContext), //set above
            delay: 0.0,
            options: .CurveEaseOut, //Animation options
            animations: { //code for animation in closure
                presentingViewController.view.frame = finalFrameForVC
                presentingViewController.view.alpha = 1.0
            },
            completion: { finished in  //completion handler closure
                transitionContext.completeTransition(true)
            }
        )
    }
    
    func animateDismissalTransition(transitionContext: UIViewControllerContextTransitioning){
        
        let presentedControllerView = transitionContext.viewForKey(UITransitionContextFromViewKey)
        let containerView = transitionContext.containerView()
        

        // Animate the presented view off the side
        UIView.animateWithDuration(
            transitionDuration(transitionContext),
            delay: 0.0,
            options: .CurveEaseInOut,
            animations: {
                presentedControllerView!.frame.origin.x += containerView!.frame.width
                presentedControllerView!.alpha = 0.0
            },
            completion: {(completed: Bool) -> Void in
                transitionContext.completeTransition(completed)
            }
        )
    }
}

class MyTransitioningDelegate : NSObject, UIViewControllerTransitioningDelegate {
    func presentationControllerForPresentedViewController(
        presented: UIViewController,
        presentingViewController presenting: UIViewController,
        sourceViewController source: UIViewController
    ) -> UIPresentationController? {
        return MyCustomPresentationController(
            presentedViewController: presented,
            presentingViewController: presenting)
    }
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return MyAnimationController(isPresenting:true)
    }
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return MyAnimationController(isPresenting:false)
    }
}

class MyCustomPresentationController: UIPresentationController {
    let chrome = UIView()
    //let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.4) //mixes with background colors
    let chromeColor = UIColor(white: 0.4, alpha: 0.6) //gray dims the background
    //let chromeColor = UIColor(red:0.0,green:0.0,blue:0.8,alpha: 0.7) // not as transparent
    
    override func presentationTransitionWillBegin() {
        chrome.frame = containerView!.bounds
        chrome.alpha = 0.0
        //chrome.alpha = 1.0
        chrome.backgroundColor = chromeColor
        containerView!.insertSubview(chrome, atIndex: 0)
        var newframe = frameOfPresentedViewInContainerView()
        newframe.origin.x = newframe.width
        presentedViewController.view.frame = newframe
        presentedViewController.transitionCoordinator()!.animateAlongsideTransition(
            { context in
                self.chrome.alpha = 1.0
                self.presentedViewController.view.frame = self.frameOfPresentedViewInContainerView()
            },
            completion: nil)
    }
    

    override func dismissalTransitionWillBegin() {
        presentedViewController.transitionCoordinator()!.animateAlongsideTransition(
            { context in
                
                self.chrome.alpha = 0.0
            },
            completion: {context in
                self.chrome.removeFromSuperview()
            }
        )
        //self.chrome.alpha = 0.0
        //self.chrome.removeFromSuperview()
    }
    
    override func frameOfPresentedViewInContainerView() -> CGRect {
        //return containerView!.bounds.insetBy(dx: 30, dy: 30)
        let newBounds = containerView!.bounds
        var newWidth:CGFloat = newBounds.width * 0.75 // 3/4 width of view for bar
        //var newWidth:CGFloat = newBounds.width / 3.0 // 1/3 width of view for bar
        if containerView!.traitCollection.horizontalSizeClass == .Regular{
            newWidth = newBounds.width / 3.0 //1/3 view on regular width
        }
        let newXOrigin = newBounds.width - newWidth //set the origin 1/3 from the right side
        return CGRect(x: newXOrigin, y: newBounds.origin.y, width: newWidth, height: newBounds.height)
    }
    
    
    /*
     if containerView!.traitCollection.horizontalSizeClass == .Regular {
     newWidth = newBounds.width * 0.33
     } else { //compact and unknown 80%
     newWidth = newBounds.width * 0.8
     }
     */
    override func containerViewWillLayoutSubviews() {
        chrome.frame = containerView!.bounds
        presentedView()!.frame = frameOfPresentedViewInContainerView()
    }
}

ModalViewController.swift

//
//  ModalViewController.swift
//  SwiftCustomPresentation
//
//  Created by Steven Lipton on 4/7/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ModalViewController: UIViewController {

    @IBAction func dismissModal(sender: AnyObject) {
        dismissViewControllerAnimated(true, completion: nil)
    }
    
}