Category Archives: ios8

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

Tab Bar Controllers in Xcode 8 Storyboards

In this lesson, we’ll take a look at tab bar controllers and how to add them in the storyboard.  For more on implementing them completely in code see Swift Swift: Using Tab Bar Controllers in Swift. For more on data sharing once set up in the storyboard, see Passing Data in Tab Bar controllers

The Real Difference between Navigation and Tab Bar Controllers

Before we begin, It’s important to remember the difference between  tab bar and navigation controllers. Navigation controllers are stack based, while tab controllers are parallel in execution. Think of a navigation controller as a stack of cards. You can only play the visible card. If you put a card on top of another card, only the top card is active.

2016-07-06_06-20-22

Now imagine you take several cards and deal them face up. You can play any face up card. They all are active. This is a tab bar controller.

2016-07-06_06-19-45

As we go through lessons on tab bar controllers, it is very important to remember this difference.

Creating From the Template

The easiest way to make a tab bar controller is through Xcode’s template. In Xcode, press Command-Shift-N or File>New>Project. Select the tabbed application.
This will give you a window with two tabs installed with two labels each and a tab item on the bottom of each controller. The template tends to make things more complicated instead of less, so it is rarely used.

Creating From A Single View

The most common way to make a tab bar controller is to start from a single view and embed it in a controller. We’ll try an example of this one in Xcode. Press Command-Shift-N and select Single View. Make a file named SwiftTabBarEmbed, using Swift as the language and with a Universal device. Once created, go over to the Storyboard.  Make sure the Preview setting is  View as: iPhone6s(wC hR) in the lower left corner of the storyboard:

2016-07-05_06-18-15

Change the color of the view controller’s background.

2016-07-05_06-08-41

Select the view controller by clicking the view controller icon or title, and select Editor>Embed in>Tab Bar Controller.

2016-07-05_06-06-10

This will turn the single view controller into the first view controller of the tab bar.

2016-07-05_06-12-22

Add another view controller by dragging out a view controller then control-dragging from the tab bar controller to the new controller. In the pop-up, select under Relationship Segue the view controllers option.

2016-07-05_06-14-57

Another segue appears on the story board.

2016-07-05_06-16-22

You’ll see a second tab in the tab bar controller.

2016-07-05_06-21-14

Adding More Tabs with System Icons

To configure the tab bar icon, go to the Item icon in the  Yellow View Controller (not on the tab view controller) we just made and click it.

2016-07-05_06-23-33

This will bring up in the properties inspector the Tab Bar Item and Bar Item properties. In the Tab Bar Item properties is the System Item drop down. This is where we configure the tab bar button.

2016-07-05_06-25-14

In the System Item menu, Click the drop-down for System Item. you will see a  list of system icons.

2016-07-05_06-26-37

Select Favorites. The Icon changes both on the view controller and the tab bar controller.

2016-07-05_06-28-51

2016-07-05_06-28-26

Click the other view controller’s tab bar, and change the system Icon to Recents.

2016-07-05_06-31-06

2016-07-05_06-31-27

Using  the More Feature

Click on the Preview size Button which reads iPhone 6s(wC hR). A new toolbar appears underneath it. Select the iPad 9.7″.

2016-07-05_06-36-26

The view controllers in the storyboard change to iPads, so you may need to change the zoom level.

2016-07-05_06-40-45

Drag out  six more view controllers, so we have a total of eight controllers.  Change their background colors to make then easily distinguishable.

2016-07-05_06-47-05

Control-drag from the tab bar controller to each new view controller and assign ContactsBookmarksSearch, Downloads, and Most Viewed to the controllers, leaving one more as a custom item. The tab bar controller should look like this:

2016-07-05_06-52-27

Change the Preview mode back to to an iPhone 6s. While your icons may be in a  different order, you will get something like this for the tab bar at the bottom of the phone:

2016-07-05_06-56-32

Compact widths, the width on most phones, cannot handle more than five tabs. If you add more than five tabs, it places a More tab as the fifth tab. What does the More tab do? Build and run as an iPhone 6s in the simulator.

2016-07-05_07-04-24

Tap on the More icon in the lower right. A table appears with the missing tabs.

2016-07-05_07-07-21

 More gives you access to all your tabs. If you tap the tab in the table’s cell, the tab’s controller will appear. Select Downloads, and you get the downloads tab.

2016-07-05_07-10-13

Tabs selected by More act slightly differently than ones in a regular tab. They are embedded in a navigation controller. You’ll see at the top a navigation bar.

2016-07-05_07-14-57

The navigation bar goes back to the More table.  Tap Bookmarks, and notice the navigation bar is not there, then tap More again and the Downloads controller is still there.

   2016-07-05_07-18-21    2016-07-05_07-10-13

Select More  in the navigation bar to go back to the Table view

2016-07-05_07-07-21

User Editing the Tab Bar with More

Without any extra code, tab bar controllers let users customize their tab bars. Tap the Edit button, and you get a configuration view:

2016-07-05_07-23-00

Looking closer, you’ll see that the hidden tabs are highlighted and the visible tabs are dimmed

2016-07-05_07-23-20

Drag the Item tab down to the tab bar. Drop it on top  of Recents. It replaces Recents.

2016-07-05_07-27-25

You can also drag tabs in the tab bar to change the order. Drag Downloads on top of Contacts. Drag  Downloads to the right, swapping it with Bookmarks.  Your tab bar now looks like this:

2016-07-05_07-37-20

I want to add two cautions to this: One of the system icons you can use is a More icon. Be careful when using the More icon in a tab bar. It can cause user confusion between the More tab and your own use for More. Make sure it is clear what you are doing.

The second caution is more insidious. When programming for tab controllers, you can get the tabs from an array. However, the order and visible tabs can change by user request with  the More tab. Avoid hardwiring the tabs by their location in the array since users can change them.

Close the simulator. In the storyboard, delete the Bookmarks, Recents and Favorites view controllers (or their segues if you don’t mind the warnings)  so you get a tab bar like this:

2016-07-05_07-45-54

As we now have five icons, this will keep off the More icon, which requires six tabs to show up. You can reorder the tabs in the tab view controller by drag and drop.   Reorder using drag and drop to this:

2016-07-05_07-50-02

Custom Tab Bar Items

Click on the tab bar for the Item view  controller. We can see all the tab bar properties.

2016-07-05_06-25-14

The lower half has bar item properties, which directly control the title and icon for the tab. We can easily change the text. Change the Bar Item Title to Pie.

2016-07-05_07-55-00

The title changes on the tab bar:

2016-07-05_07-55-36

If we change a System items’s title, it becomes a custom item, and the icon disappears. Do the same to change Downloads to Pizza. Also change Contact to Square.  SquarePizza and Pie have squares for icons.

2016-07-05_08-00-30

Icon Images

Under Title in the properties inspector,  we have a drop down for Image.

2016-07-05_07-55-00

For a custom bar item we can supply the icon. Image icons are not like other images. They are monochrome images,  unlike anything else you have ever seen. They are set not by color but by the alpha value , or transparency of the image. You can make any image here any color, but it will show up as a solid tint color if you select an alpha value of 100% and background color if you select 0% alpha. For example I created the following icons in both high and low resolutions:

gradient bar Pizza and Pie Bat items pizza bar icon pizza bar icon@2x pie bar icon pie bar icon@2

Icons are 30 points by 30 points, which means for retina images make the icon a 60 pixel by 60 pixel image and for low resolution images 30 pixel by 30 pixel. You can download them individually by right clicking the images above, and saving them to a folder.  Use these file names:

Screenshot 2015-01-27 07.37.51

You can also download a.zip file Of these here:Tab Bar Assets

For those not familiar with Apple image naming conventions, low resolution images have a file name. Retina Images have @2x appended to that file name. This tells Xcode to know what resolution it is. We have both 60×60 pixel retina and 30×30 pixel standard images. By loading both into Xcode, the system will use the correct one automatically for the device.
In Xcode’s navigator panel, open up Assets.xcassets. Select all the icon files. Drag them into the center panel in Xcode, which will highlight. Release the mouse button, and you will see a new collection in the image assets.

2016-07-05_08-06-31

Return to the storyboard. On the tab we titled Square,click anywhere on the tab bar.  Select the image drop-down in the tab bar properties and select the gradient bar icon.

2016-07-05_08-09-01

The Gradient bar now replaces the place marker  square:

2016-07-05_08-11-16

Find the Pizza and Pie tab views on the storyboard, and change them the same way. The Tab bar controller now has three custom icons.

2016-07-05_08-12-21

Build and run with the iPhone 6 simulator, and you will see our three icons, with  Pie  highlighted as selected.

Using View Controllers in Tab Bar Controllers

As mentioned earlier, tab bar controllers make a set of single view controllers running parallel to each other. The tab bar switches the view, but the view we leave does not get dismissed, just hidden. there are two uses for such a controller. The first is a series of small independent applications related to each other. Apple’s clock app is an exmaple. The timer, alarm clock, world clock, and stopwatch all are related by a theme of time. All run independently of each other. The second use of Tab bar controllers is to use a shrared data set in different ways. Social media apps like Twitter or Instagram for example use tab bars to have different functions of displaying posts, searches, or displaying notifications that all use the same database. For this second type of use, see see Passing Data in Tab Bar controllers. We’ll look at the first type here.

Basic View Controllers

Connecting a view controller in tab bars is the same as hooking up any other view controller. Make a new view controller class by pressing Command-N. Make a new Cocoa Touch Class view controller named SearchViewController. Change the class code to this.

class SearchViewController: UIViewController {
    var count = 0
    @IBOutlet weak var myLabel: UILabel!
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        myLabel.text = "Count \(count)"
        count += 1
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        count = 1
    }
}

Note how I used viewDidLoad and viewWillAppear here. In tab bar controllers, ViewDidLoad only fires once, when we first load the view controller. Use it for first initializations only. The code above initializes the count to 1. The method viewWillAppear fires every time the view appears, and so we use that to do any updating. This code assumes that this view controller will remain active even when it is not visible.

Go to the storyboard and select the Search view controller on your storyboard. In the identity inspector set the class to SearchViewController.

2016-07-06_05-43-22

On the search view controller  in the storyboard drag a label to the center of the view.

2016-07-06_05-34-14

Set the label font size to 32 points. Click the auto layout align button alignment icon  at the bottom right of the storyboard. In the menu that appears, check Horizontally in Container and Vertically in Container  with values of 0 points. Set Update Frame to Items of New Constraints.

2016-07-06_05-36-05

Add the two constraints. Open up the assistant editor. Drag from the circle next to the outlet to the label to connect it.

2016-07-06_05-49-06

Build and run. Select the search tab, then another tab a few times. You will see the count increase every time you return to the search tab.

2016-07-06_05-51-50     2016-07-05_08-15-11     2016-07-06_05-52-13

Embedding Navigation Controllers in Tab Bar Controllers

You can also embed navigation controllers into tab controllers. Stop the simulator and go back to the storyboard. Close the Assistant editor if it is open. Select the Pizza View Controller’s icon on the storyboard. In the drop down menu, select Editor>Embed in>Navigation Controller

2016-07-06_05-58-46

The view controller changes into a Navigation controller with the tab bar icon. Attached to the navigation controller is a view controller.

2016-07-06_06-05-43

 On the view controller, Double click the center of the navigation controller bar. Change the title to Pizza!!!. Drag a bar button item to the upper left of the controller on the storyboard. Title it More Pizza.

2016-07-06_06-08-10

Add another view controller, next to  the one we just configured. Control-Drag from the bar button item to the new view controller. Set up a show segue. In the new view controller, Drag a Navigation item to the navigation bar. Change the item’s title to More Pizza!!!

2016-07-06_06-10-09

Build and run. Select the Pizza tab and we have our navigation controller. Tap the More Pizza Button. The More Pizza!!! View Appears. Now go to another tab and then back to the Pizza tab. We remain on the More Pizza!!! view.

2016-07-06_06-13-41

You can use navigation controllers inside a tab bar controller to your heart’s content. However, tab bar controllers can only be used once in a project, as the top level controller. Embedding more tab bar controllers does not work.

This is the basics of tab bar controllers. For more information on sharing between the controllers and a more extensive coding example, see Passing Data in Tab Bar controllers.

How to Use UIImagePickerController for a Camera and Photo Library in Swift 3.0.

2016-06-28_08-20-54

Almost every iPhone and iPad now has a camera. Many people want to use a camera in their apps for various reasons. While you may not be building the next Instagram or Twitter, there are many places where photo documentation comes in handy.

There are two ways to use the camera. The more powerful and advanced method is using AVFoundation, but it is also a very complicated way to get a photo out of the camera.

The simpler way is the UIImagePicker. This is a quick way as Swift has simplified much of the code for it.  It is also the best way to fetch photos from the photo album. In this lesson, we’ll set up a basic camera and library app. In upcoming posts we’ll add a few embellishments to it that will make for a more effective camera.

Set Up The Layout

We’ll be talking about some of the issues with camera on both iPad and iPhone, so we’ll make this a universal app.  We will be using a dab of auto layout as well. If you want a brief introduction to auto layout go over and take a look at my book Practial Autolayout. You won’t need it for this lesson, but it may make what I do a little more understandable.

Make a new project SwiftPizzaCam with a single view and using Swift. As already mentioned, make the device Universal.

If you are not familiar with Xcode 8’s interface builder bar and the auto layout icons, here is a guide to help you as we go through setting up the app.

2016-06-28_05-29-33

Click on the iPhone 6s class preview. A new selection of previews appears.

2016-06-28_05-39-43

Select the iPad 9.7″ device.  then zoom out to 50%

2016-06-28_05-55-24

Xcode 8 come with a series of easily accessible preview modes in interface builder.  We will put a photo into an UIImageView when we first  use the image, and doing so on an iPad is easier than an iPhone, though it will work on both.  Right-click and save the image below:

pizza

Click on Assets.Xcassets and drag the pizza file into the assets folder. Go back to the story board and select the clip icon.  You will find the pizza media there.

2016-06-28_05-49-14

Drag out the pizza to the view and drop it into the view controller.

2016-06-28_05-58-42

Click the pin pinMenuButtonbutton.  Click off Constrain to Margins. Click on all the I-beams, then set their value to 0 points  like the image below:

2016-06-28_06-01-52

Be sure to press tab after you type in any number in this popover. It has an annoying habit of forgetting them if you don’t. Select Update Frames: Items of New Constraints towards the bottom.  Click  Add 4 Constraints. In the properties change the View Mode to AspectFit to properly size the photo.

Screenshot 2014-12-03 08.48.32

Now drag out a label to the storyboard. Set the background property to a Light Gray( #AAAAAA) color with a 65% alphaCenter Align the label text with a 28 point font size. Change the text of the label to read Pizza Cam!!!.  Select the label,  and then  click the pin button pinMenuButton. Set the top  8 points,  left and right sides 0 points, but not the bottom, like this:

2016-06-28_06-24-32

Make  sure you select Update Frames: Items of New Constraints towards the bottom before clicking  Add 3 Constraints.

Drag out  a toolbar and place toward the bottom of the view. Using the pin menu pinMenuButton, Pin it to the bottom, left and right like this, again updating the frames:

2016-06-28_06-21-16

Add a bar button item and a flexible space bar item to the toolbar. In one of the two bar buttons label it Photo and the other Library. Your tool bar and layout should look like this:

2016-06-28_06-26-34

 

Select the iPhone 6s in the preview modes. It should look like this:

2016-06-28_06-27-22

If you did everything correctly, you should have no auto layout warnings. If you do, go to the resolver and click Update Frames on the bottom half of the menu that appears.  If things get messier after doing this, clear the constraints on the bottom of the resolver, and pin everything again.

Wire Up the Outlets

Your next step is to wire up all the outlets and actions. Open the assistant editor.  Control-drag from the pizza photo  image and make an outlet called myImageView. Control drag from the Library button and make an action called photoFromLibrary with a sender of type UIBarButtonItem. This is important for stuff we will do later. Do this again, but with the Photo button and named shootPhoto. Again, make the sender UIBarButtonItem.
You can clean up the code a bit if you like. When done you should have something like this:

import UIKit

class ViewController: UIViewController{
    @IBOutlet weak var myImageView: UIImageView!

    @IBAction func shootPhoto(_ sender: UIBarButtonItem){
}
    @IBAction func photofromLibrary(_ sender: UIBarButtonItem) {
   }

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.

    }
}

Add the UIImagePickerController and Delegate

The star of our show the UIImagePickerController is missing. We do that programmatically. Close the assistant editor, and open ViewController.swift. Add the following line under the outlet for myImageView:

let picker = UIImagePickerController()

The UIImagePicker does much of its works from a delegate. Add the following to the class description:

class ViewController: UIViewController, 
    UIImagePickerControllerDelegate,
    UINavigationControllerDelegate {

We actually have two delegates: UIImagePickerControllerDelegate and UINavigationControllerDelegate. The UINavigationControllerDelegate is required but we do nothing with it. In the viewDidLoad, add the following:

 override func viewDidLoad() {
        super.viewDidLoad()
        picker.delegate = self
    }

We have wired up the delegate. Unlike other delegates, there is no required methods. However, this will not work without implementing two methods. At the bottom of the class, add the following:

//MARK: - Delegates
func imagePickerController(_ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [String : AnyObject])
{
        
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        
}

These two methods handle our selections in the library and camera. We can either handle the cancel case with imagePickerControllerDidCancel or handle media with didFinishPickingMediaWithInfo

Getting a Photo from the Library

The UIImagePickerController is a view controller that gets presented modally. When we select or cancel the picker, it runs the delegate, where we handle the case and dismiss the modal. Let’s implement the photo library first, then the delegates. Add the following code to the photoFromLibrary method:

@IBAction func photoFromLibrary(_ sender: UIBarButtonItem) {
   picker.allowsEditing = false
   picker.sourceType = .photoLibrary
   picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
   present(picker, animated: true, completion: nil)
}

To get to the photo picker, it is three lines of code. We already initialized picker. In line two, we tell the picker we want a whole picture, not an edited version. In line three, we set the source type to the photo library. Line four we set the media types for all types in the photo library. Line five calls present to present the picker in a default full screen modal.

If you build and run, you could press the photoFromLibrary method, but then get stuck in the library. We need delegates to get out of the library. First let’s add the code for the imagePickerDidCancel delegate method:

func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
       dismiss(animated: true, completion: nil)
    }

This does nothing special: it dismisses the modal controller. If we do pick a photo, we want to do something with the photo. Add the following code to the didFinishPickingMediaWithInfo delegate method:

func imagePickerController(_ picker: UIImagePickerController,
    didFinishPickingMediaWithInfo info: [String : AnyObject])
{
    let chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
    myImageView.contentMode = .scaleAspectFit //3
    myImageView.image = chosenImage //4
    dismiss(animated:true, completion: nil) //5
}

One of the parameters of this method is info. It has a dictionary of various information about the selected media, including metadata,  a user edited image if the .allowsEditing property is true, and a NSURL location of the image. For our camera app, the important key is the UIImagePickerControllerOriginalImage. We take that key and put the image into a variable. We could make this a constant, but there is some future iterations of this project that will need this as a variable.

Camera images are usually bigger than a UIImageView on a device. To shrink them quickly to a visible size, the best way is to set the .contentView property of our UIImageView to .ScaleAspectFit as we do in line 3.

Security for the Photo Library

Run in the simulator as an iPhone 6s. You should get a screen like this:

2016-06-28_07-07-54

Tap the Library bar button. The app crashes:

2016-06-28_07-08-30

In the simulator, there’s no message about what happened, the system just ends the application. Apple requires any use of the camera, the photo library or any personal information for that matter asks the user to agree sharing information with the app on the first use. For the UIImagePicker, this is included in your first run of the app.  Starting with iOS10, there’s another layer of security on the photo library. The developer must make an entry in the info.plist describing why they want to use the photo. That description will show up in an alert when the user decides to allow access to the library.

There’s two ways to add this entry: in XML or in the property list. We’ll use the property list first.  In Xcode,  select Info.plist from the Navigator pane

2016-06-28_07-21-27

Right click on the Information Property List Row. In the menu that appears, select Add Row.

2016-06-28_07-22-27

You get a new row with a drop down menu. Select Privacy – Photo Library Usage… from the menu.

2016-06-28_07-28-07

With the row still highlighted, click the value column  and add why you want access to the photo library.  In this app we are setting a background image.

2016-06-28_07-31-16

If you wish to add this directly to XML, the key/value is this

<key>NSPhotoLibraryUsageDescription</key>
<string>Set the Background</string>

We’ll come back to this for the camera, and do it in XML.

Build and Run again. Click the Library button, and you will get the permissions alert with the description we placed in the property list:

2016-06-28_07-33-57

Tap OK.  We get our Image picker,

2016-06-28_07-41-08     2016-06-28_07-41-29

When we tap a photo, it appears on the app.

2016-06-28_08-00-41

Running as an iPhone and iPad app: Using Popovers

Stop the app and run as an iPad 2. After answering OK to the library privacy question you get this:

2016-06-28_08-04-30

Which looks fine, except Apple doesn’t like it. They want you to use a  popover. Popovers must access the photo library on iPads.   Fortunately, it is very easy to get the popover to work. Add the highlighted lines in photoFromLibrary

    @IBAction func photoFromLibrary(_ sender: UIBarButtonItem) {
        picker.allowsEditing = false
        picker.sourceType = .photoLibrary
        picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
        picker.modalPresentationStyle = .popover
        present(picker, animated: true, completion: nil)
        picker.popoverPresentationController?.barButtonItem = sender
    }  

Line 5 selects a presentation style of a popover. We then present the popover. Line 7 sets the reference point for the popover to the bar button item. As I mentioned in another post popovers need a reference rectangle to pop up from. In this case, we can use the UIBarButtonItem which happens to be sender . This is why it was so important to use UIBarButtonItem as the sender type.

Build and run with the iPad 2 simulator. Now we have a popover to pick a photo.

2016-06-28_08-14-44

 

Since present checks the class size, you will get the proper behavior on any phone .The iPhone 6s Plus in the simulator does this.

2016-06-28_08-20-16 2016-06-28_08-20-54

 

We save ourselves from hours of device fragmentation work this way.

Adding a Camera

Basic Camera code is almost the same as adding a photo library. Change the shootPhoto code to this:

@IBAction func shootPhoto(_ sender: UIBarButtonItem) {
    picker.allowsEditing = false
    picker.sourceType = UIImagePickerControllerSourceType.camera
    picker.cameraCaptureMode = .photo
    picker.modalPresentationStyle = .fullScreen
    present(picker,animated: true,completion: nil)
}

We changed the sourceType property to .camera and specified the cameraCaptureMode property to .photo. The camera, unlike the photo library, is required to be full screen, so we don’t make it a popover, but explicitly a .FullScreen.

Like the library, we have another info.plist entry to make. This time we’ll use XML. Right click the info.plist and in the menus select Open As> Source Code:

2016-06-28_08-48-01

Just below the tag <dict>add this:

<key>NSCameraUsageDescription</key>
<string>Set the background of the app with your beautiful photography</string>

We’re ready to run. However, if you build and run this in the simulator, you will crash. In order to test and use camera code you have to use a real connected device. I connected my iPad Pro and ran the code. When I pressed the photo button, I again get the message about allowing access.

IMG_0379

Tapping OK, I get the camera:

IMG_0381

Take a picture, and the app asks me if I want to keep it.

IMG_0382

The app returns with my photo.

IMG_0383

Preventing the Camera Crash

This all works, but we really want to prevent that crash if there is no camera. Add the highlighted lines to shootPhoto:

 @IBAction func shootPhoto(_ sender: UIBarButtonItem) {
    if UIImagePickerController.isSourceTypeAvailable(.camera) {
        picker.allowsEditing = false
        picker.sourceType = UIImagePickerControllerSourceType.camera
        picker.cameraCaptureMode = .photo
        picker.modalPresentationStyle = .fullScreen
        present(picker,animated: true,completion: nil)
    } else {
        noCamera()
    }
}

Line 2 uses the class method isSourceTypeAvailable to checks if we have a camera. If there is, run the camera. If not explain to the user the device does has noCamera. For that function, we’ll add an alert like this, using UIAlertController:

func noCamera(){
        let alertVC = UIAlertController(
            title: "No Camera",
            message: "Sorry, this device has no camera",
            preferredStyle: .alert)
        let okAction = UIAlertAction(
            title: "OK",
            style:.default,
            handler: nil)
        alertVC.addAction(okAction)
        present(
            alertVC,
            animated: true,
            completion: nil)
    }

Line 2 makes an alert with the proper message. We add an OK action button on lines 6 through 10, then present the alert as a modal in line 11 onwards. With this code, if you run in the simulator, you get this when you attempt to take a picture:

2016-06-28_09-29-39

The Basics, but Wait! There’s more!

Basic UIImagePickerController is relatively easy to implement, but there are a lot of issues it leaves hanging:

  • Hitting no for privacy settings for  accessing the camera or library
  • Dealing with more than one popover
  • Customizing and adding more controls for the camera
  • Adding and playing video
  • Using  and storing pictures
  • Using a UIScrollView to zoom and scroll around a picture.
  • Getting rid of  the memory warnings
  • Wanting more power over my camera controls
  • Sharing pictures with my friends

Many of these could be questions that can be answered in context with the camera app, or outside of that context. In other lessons I’ll be answering them both ways.

The Whole Code

//
//  ViewController.swift
//  SwiftPizzaCam
//
//  Created by Steven Lipton on 6/28/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController,
    UIImagePickerControllerDelegate,
    UINavigationControllerDelegate
{
    let picker = UIImagePickerController()
    @IBOutlet weak var myImageView: UIImageView!
    
    @IBAction func photoFromLibrary(_ sender: UIBarButtonItem) {
        picker.allowsEditing = false
        picker.sourceType = .photoLibrary
        picker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .photoLibrary)!
        picker.modalPresentationStyle = .popover
        present(picker, animated: true, completion: nil)
        picker.popoverPresentationController?.barButtonItem = sender
    }
    
    @IBAction func shootPhoto(_ sender: UIBarButtonItem) {
        if UIImagePickerController.isSourceTypeAvailable(.camera) {
            picker.allowsEditing = false
            picker.sourceType = UIImagePickerControllerSourceType.camera
            picker.cameraCaptureMode = .photo
            picker.modalPresentationStyle = .fullScreen
            present(picker,animated: true,completion: nil)
        } else {
            noCamera()
        }
    }
    func noCamera(){
        let alertVC = UIAlertController(
            title: "No Camera",
            message: "Sorry, this device has no camera",
            preferredStyle: .alert)
        let okAction = UIAlertAction(
            title: "OK",
            style:.default,
            handler: nil)
        alertVC.addAction(okAction)
        present(
            alertVC,
            animated: true,
            completion: nil)
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        picker.delegate = self
    }

    //MARK: - Delegates
    func imagePickerController(_ picker: UIImagePickerController,
        didFinishPickingMediaWithInfo info: [String : AnyObject])
    {
        var  chosenImage = UIImage()
        chosenImage = info[UIImagePickerControllerOriginalImage] as! UIImage //2
        myImageView.contentMode = .scaleAspectFit //3
        myImageView.image = chosenImage //4
        dismiss(animated:true, completion: nil) //5
    }
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
       dismiss(animated: true, completion: nil)
    }

}

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



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

Using Settings Bundles with Swift

Have you ever wondered how to put  user defined settings for your app into the settings app?  Xcode can create a special property list called a settings bundle which can append the  NSUserDefaults with more entries from Settings App.  You’ll find out in this lesson how to set up a settings bundle in your app for using the Settings App using XML. In the next lesson we’ll dive deep into the settings bundle using the property list editor.

I’ll assume you know how to use property lists and NSUserDefaults in this lesson. If you are not familiar with them you might want to check out my recent posts on Property Lists and NSUserDefaults

Create the  Settings Bundle

Start a new Single view application  project in Xcode called SettingsBundleDemo using Swift and a Universal device.

Right-click on the SettingsBundleDemo group folder and select New file. Select the  Resources  category in the template window. Select Settings Bundle and click Next.

2016-03-11_05-57-54

You should have name of settings. Keep the setting bundle in the SettingsBundleDemo Group  in the save window. Click Create.

The system will open up the settings bundle. which looks like a building block. In the Navigator open up the settings bundle by clicking on the arrow.

2016-03-11_06-00-28

Click on the Root.plist file. You’ll see a property list  like this:

2016-03-13_12-33-22

If closed, Open the Preference Items array. This is populated with demo data. Unlike other property lists,  this one does not directly store the value as a dictionary of values, but an array of dictionaries of controls. You pick the control you want in the Settings app by listing it in the property list. When we attach the settings bundle to the standard defaults of NSUserDefaults, the system will create key values in the NSUserDefaults.

The following table lists the type of controls that you can use:

2016-03-13_15-54-53

Each type of control has several attributes.   If not already open, Open the Group Control, the Item 0 entry,  in Root.plist.

2016-03-13_12-38-07

Groups are control used to group other controls together. They have only a title and a Type. Open the Item 3 (Slider).

2016-03-13_12-39-56

The slider has more required controls, the ones listed here.  Many controls will have a  poorly named attribute Default Value. This is the value shown in  the settings app, not a true value of the default. It will show the values after the first use, but it is for display purposes not value purposes.

Each control type has its own attributes, as described in the chart above.  Here’s a list of all the possible attributes for the property list.

2016-03-14_08-11-55

Making Your Own Settings

Delete the current entries. Property lists do not delete well in Root.plist.  The simplest way is to cut them out. Select Item 0 (Group). Right click on group, and select Cut in the menu. Then do the same for the Item 3( Slider)  and the rest of the controls. You should have a clean property list like this.

2016-03-13_12-43-40

Select Preference Items.  Make sure it is open. The arrow should be pointing down.  Press the Add (+) button on the entry. A new entry appears.

2016-03-13_12-48-19

There will also be a menu that opens. If you happen to click somewhere and lose the menu, click the down chevron(2016-03-13_12-50-18)  in the entry. You get a list of controls.

2016-03-13_12-51-21

Select Toggle Switch. This will give us a Bool value.  Open up the toggle switch control and you have the following attributes:

2016-03-13_12-53-55

Click the value for each blank attribute, and set Title to  Room for cream.  Set Identifier to coffee_cream.  The identifier is the key for the NSUserDefaults entry.  Set the Default Value to YES. Your attributes should look like this:

2016-03-13_12-57-07

Close the attributes for the switch, and select Item 0. Right click and select Add Row from the menu. Although it is the default, in the type menu, select Text Field.   Open up the Item 1(Text Field – ) to see the attributes.

2016-03-13_13-01-53

 

Set the Title to Beverage of choice.  Set the Identifier to coffee_type. Right click on Identifier and select Add Row. A new row appears asking for an appropriate attribute:

2016-03-13_13-08-55

Select the default Default Value. Set the Default Value to Coffee.

2016-03-13_13-08-56

Close the attributes for the Text menu. Select the  Preference items entry, right click  and select Add Row.  The new row pushed the other rows down becoming Item 0. Select Multi-Value. Multi-value uses two arrays to make a selection table. Open up the attributes of the multi-value.  Set the title to Size. Set the Default Value to 0. Set the Identifier to coffee_size.

2016-03-13_13-28-32

This control does not by default set up values. You must do that. Select the bottom attribute,  Right-click the entry, then choose Add Row.  Make the row type Values. Open Values‘ array, which should be empty. Using either the Add row or the + Icon, add four  sub rows to Values. Add the  number  0  to the first  row. Change the type of the row to Number.  Do the same for the other rows, making the values 1,  2 and 3.

2016-03-13_13-28-33

Close the Values array. Add another attribute to the multi-value control Titles. Open the attribute’s array and add four entries. These will be  the strings Small, Medium, Large, and Extra Large.

2016-03-13_13-31-04

We can look at our settings page now.  Just to make it easy to know when we can look at it, set the back ground in the Launchscreen.storyboard to Orange(#ff8000), and the Main.Storyboard to Yellow(#FFFF00). Build and run. When the Background turns yellow, go to the settings app.  If on a live device, hit the Home button. If on the simulator,  hit Command-Shift-H on your keyboard for the home button. Navigate to the settings app.

2016-03-13_13-42-05

Scroll down to the bottom of the app. You’ll find our app there.

2016-03-13_13-42-25

Click the item and we get a settings page.

2016-03-13_13-42-58

Setting up the Storyboard

Stop the app, then go to  Main.storyboard in Xcode. Add a switch, a label, a text field  and  a segmented control to the storyboard. Configure the controls to look like this:

2016-03-13_14-27-04

Select the switch. In the size inspector, set Compression Resistance and Content Hugging to 1000 horizontally and vertically. Switches should never change size, and this prevents that from happening.

2016-03-13_14-36-49

Click the stack view button2016-03-13_14-36-07 to make a horizontal stack view. Select everything on the storyboard, and make vertical stack view by pressing the stack view button2016-03-13_14-36-07 again.  Set the Stack View’s attributes to this:

2016-03-13_14-43-19

Pin the stack view 71 up, 5 left and 5 right. Update frames for items of new constraints.

2016-03-13_14-50-17

Open up the assistant editor and connect up the following outlets to their proper controls:

@IBOutlet weak var roomForCream: UISwitch!
@IBOutlet weak var drinkText: UITextField!
@IBOutlet weak var sizeSegment: UISegmentedControl!

Reading the Settings Bundle

The NSUserDefaults does not know we have a settings bundle. Our first task is to register the settings bundle with NSUserDefaults.

func registerSettingsBundle(){
   let appDefaults = [String:AnyObject]()
   NSUserDefaults.standardUserDefaults().registerDefaults(appDefaults)
}

This registers the settings bundle to the NSUserDefaults standard defaults. the registerDefaults method looks for property lists in the resources directory and changes the value:key entries to dictionaries of the form [String:AnyObject]. We only run this once in our code.

Make a new function updateDisplayFromDefaults and get our defaults

func updateDisplayFromDefaults(){
//Get the defaults
    let defaults = NSUserDefaults.standardUserDefaults()
}

Read the key:value pairs and assign them to our outlets. Add this to the function:

//Set the controls to the default values. 
    roomForCream.on = defaults.boolForKey("coffee_cream")
    if let drink = defaults.stringForKey("coffee_type"){
        drinkText.text = drink
    } else{
        drinkText.text = ""
    }
    sizeSegment.selectedSegmentIndex = defaults.integerForKey("coffee_size")

The stringForKey method returns a optional value. Our code checks for a nil value and reacts accordingly. Add the new function to viewDidload:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    updateDisplayFromDefaults()
 }

We want to start fresh when we load the app. Go to the simulator or your device. Delete the application by pressing and holding on the app until it shakes. Once shaking, click the X to delete it. You will be asked

2016-03-13_15-03-16

Click Delete and the app and the settings are now deleted. Build and run the application. We start with a blank preferences, since they haven’t been written to the app yet. Since the Default Value of the property list is a display value, it does not reflect here.

2016-03-13_15-14-09

Press Command-Shift-H, then navigate over to the settings. Set your settings to this:

2016-03-13_15-20-13

Press Command-Shift-HH to and select the demo app. Our app hasn’t changed.

2016-03-13_15-14-09

Close the app in Xcode. Re-run the app. Now we get our defaults.

2016-03-14_07-56-20

Updating Defaults with Observers

Updating our preferences on restart only  is not a good thing. The problem is we need to tell the app that there was a change. This needs to be done automatically.In both a settings bundle and for NSUserDefaults within an app, this will be a frequent problem. You change a setting and want everywhere that uses it to update. Such changes need notification observers. I think of fire watchers as an analogy to observers. You have a forest ranger sitting in a tower looking for fire in the forest below. If she finds one, then she sets off an alrm to fight the fire to everyone in the forest. An observer does the the same thing in code. It looks for a certain event to happen, then runs a target method if is sees it happening. These events can be system defined events known as notifications (not to be confused with push notifications that show up on your device) or you can make up your own. I’ll discuss how to make notifications in a future post. We’ll concentrate on system generated ones.

The NSUserDefaults class generates a notification NSUserDefaultsDidChangeNotification. We can set an observer for the notification, and then run some code to update the display. Change viewDidLoad to this:

override func viewDidLoad() {
    super.viewDidLoad()
    registerSettingsBundle()
    updateDisplayFromDefaults()
        
    NSNotificationCenter.defaultCenter().addObserver(self,
        selector: "defaultsChanged",
        name: NSUserDefaultsDidChangeNotification,
        object: nil)
    }

We call the default notification center which keeps track of our observers to register it. This center is NSNotificationCenter.defaultCenter(). We add the observer with name NSUserDefaultsDidChangeNotification and tell it to run the function defaultsChanged when it observes a notification.

As an aside, you might be tempted to add the code from registerSettingsBundle as part of your updateDisplayFromDefaults. Every execution of registerSettingsBundle changes the NSUserDefaults by adding more defaults. Each time we add a default we get a notification, causing a recursive death spiral until we run out of memory. Keep it separate.

We’ll need a small function defaultsChanged to run when we have a notification. Add this to your code:

func defaultsChanged(){
        updateDisplayFromDefaults()
    }

There’s also a bit of legacy code we have to add. Prior to iOS9, we need to remove the observer for good memory management. In iOS9 and later, ARC does that for us. For any iOS8 devices, add the following to remove the observer.

deinit { //Not needed for iOS9 and above. ARC deals with the observer.
NSNotificationCenter.defaultCenter().removeObserver(self)
}

The code uses the Swift class’ deinint function to remove the observer from NSNotificationCenter.defaultCenter().

A Bug in the Simulator

Build and run. Hit Command-Shift-H then switch over to the settings app. Select the settings for the SettingsBundleDemo and… It’s blank.

2016-03-13_15-19-17

This doesn’t happen on a device. Only the simulator. There is a workaround. It seems the simulator is still running your last iteration of your app. When you hit stop in Xcode it doesn’t stop the settings app. Click Command-Shift-HH to double-click the home button. Swipe up on the Settings app to kill the process. Start the Settings again, and settings refreshes itself. You’ll have a settings page again. Change the settings to this.

2016-03-14_07-33-44

Go back to the Settings Bundle Demo. Our results show up this time.

2016-03-14_07-34-16

That’s the basics of Settings Bundles. There are a few more advanced options such as child settings pages and coding the settings directly in XML. The charts above have the tags for XML and there is is the Root.plist code below in XML for your further exploration.

The Whole Code

//
//  ViewController.swift
//  SetttingsBundleDemo
//
//  Created by Steven Lipton on 3/11/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var roomForCream: UISwitch!
    @IBOutlet weak var drinkText: UITextField!
    @IBOutlet weak var sizeSegment: UISegmentedControl!
    
    deinit { //Not needed for iOS9 and above. ARC deals with the observer.
        NSNotificationCenter.defaultCenter().removeObserver(self)
    }
    
    func registerSettingsBundle(){
        let appDefaults = [String:AnyObject]()
        NSUserDefaults.standardUserDefaults().registerDefaults(appDefaults)
        //NSUserDefaults.standardUserDefaults().synchronize()
    }
    
    func updateDisplayFromDefaults(){
    
        
     
        //Get the defaults
        let defaults = NSUserDefaults.standardUserDefaults()

        //Set the controls to the default values. 
        roomForCream.on = defaults.boolForKey("coffee_cream")
        if let drink = defaults.stringForKey("coffee_type"){
            drinkText.text = drink
        } else{
            drinkText.text = ""
        }
        sizeSegment.selectedSegmentIndex = defaults.integerForKey("coffee_size")
    }

    func defaultsChanged(){
        updateDisplayFromDefaults()
    }
    @IBAction func updateDefaults(sender: AnyObject) {
        updateDisplayFromDefaults()
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        registerSettingsBundle()
        updateDisplayFromDefaults()
        NSNotificationCenter.defaultCenter().addObserver(self,
            selector: "defaultsChanged",
            name: NSUserDefaultsDidChangeNotification,
            object: nil)
        
    }

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


}


Root.plist

While we did not discuss them here, you can make entries in XML to your property list. Here’s the property list for the demo in XML:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>StringsTable</key>
	<string>Root</string>
	<key>PreferenceSpecifiers</key>
	<array>
		<dict>
			<key>Type</key>
			<string>PSMultiValueSpecifier</string>
			<key>Title</key>
			<string>Size</string>
			<key>Key</key>
			<string>coffee_size</string>
			<key>DefaultValue</key>
			<string>0</string>
			<key>Values</key>
			<array>
				<integer>0</integer>
				<integer>1</integer>
				<integer>2</integer>
				<integer>3</integer>
			</array>
			<key>Titles</key>
			<array>
				<string>Small</string>
				<string>Medium</string>
				<string>Large</string>
				<string>Extra Large</string>
			</array>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSToggleSwitchSpecifier</string>
			<key>Title</key>
			<string>Room for cream</string>
			<key>Key</key>
			<string>coffee_cream</string>
			<key>DefaultValue</key>
			<true/>
		</dict>
		<dict>
			<key>Type</key>
			<string>PSTextFieldSpecifier</string>
			<key>Title</key>
			<string>Beverage of Choice</string>
			<key>Key</key>
			<string>coffee_type</string>
			<key>DefaultValue</key>
			<string>Coffee</string>
		</dict>
	</array>
</dict>
</plist>

Using WatchOS2 Navigation in Swift

It’s rare to have a one controller application, even in something as small as the Apple watch. Multiple View Controllers, or Interface Controllers as they are called in WatchKit, need ways to move between controllers. WatchOS2 has a simplified version of the iOS navigation types. In this lesson we’ll explore these three types of navigation that are available for WatchOS: Page, Hierarchical, and Modal. You’ll find them summarized on this chart below

2016-03-04_12-03-25

In this tutorial we’ll show all three programmatically and on the storyboard.

Set Up the Project

Before starting the project, start the watch and phone simulators. If you do not have direct access to them, you might want to read the Before you Begin section in a earlier post.

Open Xcode if not already open. Create a new project and pick the iOS App with Watchkit App Watch project template.

2016-02-03_05-34-40

You’ll get a configuration window like this:

2016-03-02_06-25-53

Name the project WatchNavigationDemo using Swift as the language. Use a Universal device and uncheck Include Notification Scene as shown above. In the Navigator find these files:

2016-03-02_06-29-30

The Root Controller

Click on the Interface.storyboard in the WatchNavigationDemo WatchKit App group. You will get a blank watch scene with a watch controller and a notifications controller. Drag two buttons and a label to the controller. Set the background color to the system Dark Gray Color(#555555) for both buttons. In the attributes inspector, set one button’s alignment to Top Vertical Alignment, and the other button’s alignment to Bottom Vertical Alignment. Set the label’s text to Root and the font to Headline. Set the label’s vertical and horizontal alignment to Center.

2016-03-02_06-37-05

Click the view controller icon. At the top of the attribute inspector, add a Title and Identity of Root

2016-03-02_06-42-12

For each of our controllers we will add and identity and title. Title will set the title on the watch face. The Identifier names this controller programmatically. If you are only using segues, then you do not need it, but it is mandatory if you are programmatically calling your controllers. If you have neither a segue nor identifier you will get a warning error from Xcode that the controller is unreachable. With our controller done, it should look like this:

2016-03-01_06-11-01

The Page Controllers

Drag another interface controller to the storyboard. Change the background to Green(#AACC00). Add a button and a label. Set the vertical alignment for the button to Bottom. Set the background color to the system Dark Gray Color(#555555). Select the controller and change the Title and Identifier to Page 0. Set the label’s text to Page 0. Set the label’s vertical and horizontal alignment to Center.

Your controller should look like this:

2016-03-01_06-12-42

Select the interface controller icon, then press Command-C to copy the controller. Deselect the controller by clicking on the background of the storyboard. Press Command-V to paste a copy of the controller. This pastes the controller directly on top of the original. It looks like the original controller is selected again. Drag this controller and you will find it is a copy.

2016-03-02_07-09-40

Deselect, and press Command-V again. You now have three Page 0 controllers. Change the Identifier and Title of one copy to Page 1 and the other to Page 2. Arrange the controllers like this

2016-03-02_07-16-45

The Modal Controllers

Add two more controllers, which we’ll use as our modal controllers. Make one controller have a Red(#FF0000) background and the other a Blue(#0000FF) background. Set the Title and Identifier of the red modal to RedModal. Set the Title and Identifier of the blue modal to BlueModal.

2016-03-01_06-10-36

Arrange your controllers like this:

2016-03-02_07-32-44

Page Navigation Through Segues

Page navigation is similar to iOS page navigation  with pages sliding horizontally with a swipe. It is much simpler to set up than the iOS equivalent. All the glances on an apple watch are in a page sequence. If you don’t have a watch you can still see a quick example in the watch simulator. You can swipe up from the watch face to get to glances. Swipe left and right to see two glances.

Like most storyboard transitions between controllers we use segues. For page segues, We control-drag from one controller to another controller, then repeat to make the chain of controllers. Control-drag from the black background of Root to the green of Page 0. When you release the button, a Relationship Segue menu appears:

2016-03-01_06-18-24

Select Next Page. A segue appears.

2016-03-02_08-02-00

Control-drag from Page 0 to Page 1, repeating the process. Then do it again from Page 1 to Page 2. We have four pages connected together.

2016-03-02_08-06-51

Build and run. You have four pages to scroll back and forth through.

page navigation

Hierarchy Navigation by Segue

Like navigation controllers in iOS, hierarchy navigation pushes the next controller to view through a selection device, such as a button. As the name implies you can branch in as any direction you want, unlike the linear page navigation. The settings app on the Apple Watch is an example of a hierarchy navigation you can see on both the watch and the simulator.

Delete all the segues on the storyboard. Select the top button on the root controller. Control-drag from the top button to the Page 0 controller. When you release the mouse button, you will get this menu:

2016-03-01_06-13-40

Select Push. A segue appears between the controllers.

2016-03-03_05-31-22

The process is almost the same as the page segues. The difference is you start your drag from a control, usually a button but it may be a group or table. Make a segue between the bottom button of Root and Page 1. Control drag from the bottom button of Root to Page 1. Select Push for the Action Segue type.

Page and Hierarchy are Mutually Exclusive

Control-drag from Page 1 to Page 2 and make a page segue. Your storyboard should look like this.

2016-03-03_05-46-53

Xcode does not put any warnings up, but what we just did is wrong. You cannot combine page and hierarchy segues. You can only use one or the other on the storyboard. With one exception, once you use one type or segue or the other. You are stuck in the project using that type of navigation only. However, nothing tells you this except the documentation. Clean the project with a Shift-Command-K, then Build and Run. Tap the top button, and you get Page 0.

At the top left of the watch is a back indicator which you tap to go back to root. If you try going to page 1 with the bottom button, there is no page indicator on the Page 1 to get us to page 2. Swipes don’t work either. The system will ignore any illegal segues.

Stop the app and delete the page segue. Control-drag from the button on Page 1 to Page 2 and select the Push segue. Clean, Build and Run. You get all three pages.

hierarchy navigation

Adding Modal Controllers

The last type of controller does not care what type is the one before it. Modal controllers can be called by both page and hierarchy controllers. While the two we’ve learned so far transition horizontally, modal controllers transition vertically. In the watch or simulator, swipe up on the watch face and you have a modal to the glances. The glances, as we discussed earlier, happen to be page navigation. Here’s an exception to the mutual exclusion between hierarchy and pages. If you have a hierarchy then segue to a modal, your modal can segue to pages. However it does not work the other way around. From a page, you cannot call a modal and then a hierarchy.

You set a Modal segue the same way as a hierarchical segue: from control to controller. Stop the app. From the Page 0 button, control-drag to the RedModal controller. Select Modal in the Action Segue menu

2016-03-01_06-23-17

You get a segue between the two.

2016-03-03_06-43-33

Control-drag from the Page 2 button to the BlueModal controller. Select a Modal action segue. From the BlueModal, control-drag to the RedModal. Select the Page relationship segue. Your storyboard should look like this:

2016-03-03_06-44-38

Build and run. All the navigation works.

modal navigation

Programmatic Navigation

We’ve covered navigation on the storyboard. While the mutual exclusive part does provides some limits for building apps, there is enough flexibility to set up your entire app in the storyboard. It’s probably best to setup your applications in the story board, as many of the attributes of a controller are not accessible programmatically. However, many developers prefer programmatic navigation to segues. Sometimes the logic of the code dictates a more programmatic approach. There are three methods that call controllers directly, as shown in the chart above. We’ll discuss each as we code the controllers.

Press Command-N to make a new file. Make a new WatchOS Watchkit file named PageIntefaceController, subclassing WKInterfaceController. Save the file being careful to make sure the group is in the extension.

In the file that appears, add the following code.

    var whichModal = "RedModal"
    @IBOutlet var goModalOutlet: WKInterfaceButton!
    
    @IBAction func goModalAction() {
        presentControllerWithName(whichModal, context: nil)
    }

In the action goModalAction, the method presentViewControllerWithName:context: presents the modal controller with the identifier name from the storyboard. We’ve used a string variable whichModal, which we’ll use in a later iteration to switch between the blue and red modals. The second parameter context, sends data to the destination controller. We set this to nil to not use it. We’ll discuss this parameter in mmore detail below.

Select the InterfaceController.swift file. Add the follow code to the InterfaceController class

@IBAction func topButton() {
    pushControllerWithName("Page 0", context: nil)
}
@IBAction func bottomButton() {
    let names = ["Page 2","Page 1","Page 0"]
    presentControllerWithNames(names, contexts: nil)
 }

The bottomButton action, contains the presentControllerWithNames method. This presents the page views. It has similar parameters to presentControllerWithName, with one change: instead of a single value it uses arrays. The array order is the order of the pages displayed. Our array is in the constant names, counting down pages instead of up as we have been doing. The contexts array has the corresponding context to in the names array. We are not using it yet, so we left that nil.

The action topButton has the method for the hierarchical segue pushControllerWithName:context: Like a navigation controller, it pushes the new interface into view. The parameters are identical to its modal brother.

Connect up your outlets and actions. Go back to the story board, and select the Page 0 controller. In the identity inspector, change the class to PageInterfaceController. Repeat for Page 1 and Page 2. Close the right panel inspector and open the assistant editor. Click on the Root Controller in the storyboard. Drag from the topButton action to the upper button. Drag from the bottomButton action to the bottom button.

Select the Page 0 controller. Connect the outlet and action to the button. Do the same for Page 1 and Page 2.

Clean, build and run. From the root menu, click the top button.

2016-03-03_06-05-00

It goes to a hierarchical controller version of Page 0. Tap the button in Page 0 and we get the red modal.

Go back to the root, and tap the bottom button. You get a page controller starting with Page 2 and counts down pages to 0.

2016-03-03_06-05-30

Tap any button on a controller goes to the red modal.

programmatic navigation

We seem to have broken the mutual exclusive nature of the page and hierarchy controllers. Programmatically,  there’s one more wrinkle. The system does not know which navigation you use until your first use of it on the root. In our app, we set that by the button pressed.

Working with Contexts

Contexts pass data to a destination controller. In the modal and Hierarchical controllers it is a single AnyObject? For pages it is an optional array of AnyObject. Close the assistant editor, and go to the InterfaceController.swift file. Change the actions to this:

    @IBAction func topButton() {
      pushControllerWithName("Page 0", context: "BlueModal")
    }
    @IBAction func bottomButton() {
        let names = ["Page 2","Page 1","Page 0"]
        let contexts = ["RedModal","BlueModal","RedModal"]
        presentControllerWithNames(names, contexts: contexts)
    }

In topButton we pass a string as a context. In bottomButton we added a string array contexts that will pass the string RedModal for Page 0 and 2 and a BlueModal for page 1.

Go to the PageInterfaceController code. Change the awakeWithContext method to this:

override func awakeWithContext(context: AnyObject?) {
    super.awakeWithContext(context)
    if let newContext = context{
        whichModal = newContext as! String
        goModalOutlet.setTitle(whichModal)
    }
}

The awakeWithContext method is the equivalent to viewDidLoad in iOS, except it receives a context. We can use that context to set up the class. First optionally chain the context’s value. Then assign it, downcasting appropriately. I’m setting which modal we will use this way, and also setting the button’s title to tell you where you are going.

Build and run. Select the bottom button and you will see the contexts changing the page controllers.

contexts

What if we wanted to make a more elegant title for the button? A common way of passing more than one parameter in the context is to use a dictionary. Change InterfaceController’s actions to this:

@IBAction func topButton() {
    let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
    pushControllerWithName("Page 0", context: context)
}
@IBAction func bottomButton() {
    let names = ["Page 2","Page 1","Page 0"]
    let contexts:[[String:AnyObject]] = [
        ["Name":"RedModal","Title":"Red"],
        ["Name":"BlueModal","Title":"Blue"],
        ["Name":"RedModal","Title":"Red"]
    ]
    presentControllerWithNames(names, contexts: contexts)
}

Using a dictionary of type [String:AnyObject], I pass two strings to the array. I could use a [String:String], but I want to demonstrate the more generic case where you could use more than one type of data in the dictionary. Go to PageInterfaceController. Change awakeWithContext like this:

    override func awakeWithContext(context: AnyObject?) {
        super.awakeWithContext(context)
        if let newContext = context{
            let contextDictionary = newContext as! [String:AnyObject]
            whichModal = contextDictionary["Name"] as! String
            let title = contextDictionary["Title"] as! String
            goModalOutlet.setTitle(title)
        }
    }

We changed the code to read a dictionary instead of a single value. You could do the same with a class or struct as well, though dictionaries are nicely temporary in comparison. Build and run. Click the top button and you find the Blue titled button

2016-03-04_07-07-28

Go Back to Root, then click the bottom button, we have a Red button

2016-03-04_07-07-27

Contexts with Segues

Watchkit also has a equivalent to prepareForSegue for passing data when you use a storyboard segue. Like prepareForSegue, you override the method. For modals and hierarchy there is the method For pages there is the to accommodate the array of contexts. Go to the storyboard and add a push segue between Root and Page 0

2016-03-03_05-31-22

In the attributes inspector, set the Identifier for the segue to Page0. Go to InterfaceController. Comment out the code in topButton

@IBAction func topButton() {
    //let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
    //pushControllerWithName("Page 0", context: context)       
}

Add the following function to your code

override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
    
if segueIdentifier == "Page0"{
    let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
        return context
    } else {
        return nil
    }        
}

The contextForSegueWithIdentifier works similar to prepareForSegue. Take  segueIdentifier and do what you need to set up the destination controller for that specific segue. The difference is we return the context, instead of setting a properties of the destination controller directly. For pages, we do the same thing using arrays and contextsForSegueWithIdentifier

Clean, build and run. Tap the top button on Root You should see no difference between the last run and this one.

2016-03-04_07-07-28

That is most of what you need to know about navigation. There are two or three more thing you might want to know such as dismissals and delegates. We’ll cover those next time as we delve into using tables in WatchOS2.

The Whole Code

InterfaceController.swift

//
//  InterfaceController.swift
//  WatchNavigationDemo WatchKit Extension
//
//  Created by Steven Lipton on 3/1/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {
    
    @IBAction func topButton() {
        // code commented out for using segues and context
        // code for hierachy (push) segues
        //let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
        //pushControllerWithName("Page 0", context: context)
        
    }
    override func contextForSegueWithIdentifier(segueIdentifier: String) -> AnyObject? {
        // the prepareForsegue of WatchOS for modals and push
        // use contextsForSegueWithIdentifier for pages
        if segueIdentifier == "Page0"{
        let context:[String:AnyObject] = ["Name":"BlueModal","Title":"Blue"]
            return context
        } else {
            return nil
        }
        
    }
    @IBAction func bottomButton() {
        // A Page controller sequence with a dictionary as a context.
        let names = ["Page 2","Page 1","Page 0"]
        let contexts:[[String:AnyObject]] = [
            ["Name":"RedModal","Title":"Red"],
            ["Name":"BlueModal","Title":"Blue"],
            ["Name":"RedModal","Title":"Red"]
        ]
        presentControllerWithNames(names, contexts: contexts)
    }
   
}

PageInterfaceController.swift

//
//  PageInterfaceController.swift
//  WatchNavigationDemo
//
//  Created by Steven Lipton on 3/3/16.
//  Copyright © 2016 MakeAppPie.Com. All rights reserved.
//

import WatchKit
import Foundation


class PageInterfaceController: WKInterfaceController {
    var whichModal = "RedModal"
    @IBOutlet var goModalOutlet: WKInterfaceButton!
    
    @IBAction func goModalAction() {
        //present a modal controller
        presentControllerWithName(whichModal, context: nil)
    }
    override func awakeWithContext(context: AnyObject?) {
        // the viewDidLoad of WatchKit/WatchOS. 
        // has a parameter which has the context sent to the controller 
        // So you can set the controller up 
        // we use a dictionary  of [String:AnyObject]
        super.awakeWithContext(context)
        if let newContext = context{
            let contextDictionary = newContext as! [String:AnyObject]
            whichModal = contextDictionary["Name"] as! String
            let title = contextDictionary["Title"] as! String
            goModalOutlet.setTitle(title)
        }
    }
}