Category Archives: GUI

Custom Table View Cells in Swift 3

2016-10-14_06-53-47In iOS you are limited to two labels and an image for one of the standard table view cell styles. You might want more custom cells than this. You are not alone with  other developers including, Instagram, Twitter, Feedly and even Apple often using more than the basic table view cell. You can customize table view cells and populate them with any number of views in any size. In this lesson, I’ll show you how to make a custom table view cell to display four labels. We’ll add some logic to the custom view to conditionally show two labels for specials and give a 10% discount.

I’ll modify the app I created in the Table Views in Subviews Lesson. You can go to that lesson to learn more or download the starter file pizzatable_customcell_start.zip.

Creating a Custom Cell

Go to the storyboard. In the document outline, select the cell

2016-10-14_07-13-44

In the attributes inspector, Change the style to Custom.

2016-10-14_07-16-24

The  height of the custom cell will be bigger than the standard 44 point height. Click the ruler to change from the attributes inspector to the size inspector.  You’ll find the row height at the top of the inspector. Click on Custom and change from the default 44 points to 75 points.

2016-10-14_07-20-29

Click back to the attributes inspector. Drag four labels into the table view cell. Change the labels to Menu Item DescRegular Price $99.99, Special, and $99.99

 2016-10-14_07-24-48

Drag the labels to the four corners of the cell. Use the blue guides to align them to the corners.

2016-10-14_07-28-28

Select the Menu Item Desc Label. In the attributes inspector, change the font to a custom font of Georgia 24 point. Set AutoShrink to Minimum Font Scale of 0.5. In case the description is too long, this will shrink the font to fit.

2016-10-14_07-30-46

Select Special. Change the font color to Red(#ff0000) and the Font to System Heavy 17.0. Right align the label. Select the $99.99   label. right align the label and set the font to Gerogia 22 point.

2016-10-14_07-37-36

I’ll use  auto layout to lay things out cleanly.  Select Menu Item Desc. Control-drag up and to the left until the content view highlights.

2016-10-14_07-38-58

Release the mouse button. A menu appears.  Shift select Leading Space to Container Margin, Top Space to Container Margin and Equal Widths.

2016-10-14_07-40-55

This pins the label to the upper right side, and sets the width of the label to the width of the cell. I want 70% of the cell to be the label. With the label selected, Click on the width constraint, which should have some number like -198 in it

2016-10-14_07-45-31

In the attributes inspector, change the multiplier to 0.7

2016-10-14_07-48-18

Select the Regular Price $99.99 label. Control-drag from this label down and to the left until the content view highlights. Release the mouse button.  Shift select Leading Space to Container Margin, Bottom Space to Container Margin, and Equal Widths.

2016-10-14_07-50-38

Click Add constraints.  Select the width constraint

2016-10-14_07-52-19

Change the width of this label to 60% of the cell. In the attributes inspector, change the Multiplier to 0.6.

2016-10-14_07-56-51

Select Special, then Control-drag Up and to the right. Shift-Select Trailing Space to Container margin and Top Space to Container margin. Click Add Constraints

2016-10-14_07-58-10

Select Special, then Control-drag up and to the right. When you release the mouse button, select Trailing Space to Container margin and Bottom Space to Container Margin.  Click Add Constraints.

2016-10-14_07-58-46

Click the resolverresolver button. In All views in Table View Cell, select Update Frames.

2016-10-14_08-00-13

The cell’s format looks like this, with each label constrained to the corner.

2016-10-14_08-01-04

Connecting Up the Cell

Table view cells need their own class for outlets and actions.  Press Command-N to create a new Cocoa Touch Class Named CustomTableViewCell, subclassing UITableViewCell. Create the file. Once created, return to the storyboard. Select the cell in the document outline.  In the Identity inspector, change the class of the cell to CustomTableViewCell.

2016-10-14_09-33-57

Hide the right ad left inspector panes to give ourself some room for the assistant editor.  Click the assistant editor. You see code for the table view controller. To get to the cell’s controller class is slightly tricky. Xcode does not yet know it is there. You have to manually select it.  In the Assistant editor,  click where it says Automatic. In the menu tree that appears select Manual>PizzaTable>PizzaTable>CustomTableViewCell.swift

2016-10-14_09-39-05

 Make  four outlets for the labels. Control drag from the Menu Item Desc to the code to make an outlet menuItemDescLabel. Control-drag from the Special label to make an outlet specialLabel. Control Drag from Regular price $99.99 to make an outlet regPriceLabel. Control drag the $99.99 label to make the outlet priceLabel. Close the assistant editor.

Functions For the Cell

Open the left inspector and in the navigator go to the CustomTableViewCell.swift file. In the class just under the outlets,  add two constants for color and a discount amount for specials.

let lightGreen = UIColor(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0)
let lightRed = UIColor(red: 1.0, green: 0.9, blue: 0.9, alpha: 1.0)
let discount = 0.1

The cell can do much of its own setup and formatting, leaving the table view controller a lot cleaner and free of code. Add a function to convert the price to a string in the cell, which also concatenates a string in front of the price.

func string(_ prefix:String, for price:Double) -> String{
    let priceString = String(format: "%2.2f", price)
    return prefix + priceString
}

When an item is on special, it has a 10% discount. I’ll show the discounted price, the regular price and print the word Special on the cell if it is a special. To attract even more attention, I’ll make the background light green.  If not a special, I’ll make the background light red, and show just the price.  Add all that as a function.

func show(isSpecial:Bool,for price:Double){
    if !isSpecial{ //normal
        regPriceLabel.text = ""
        specialLabel.text = ""
        priceLabel.text = string("$", for: price)
        contentView.backgroundColor = lightRed    
    } else { //special discount
        regPriceLabel.text = string("Regular price $", for: price)
        specialLabel.text = "Special"
        priceLabel.text = string("$", for: price * (1.0 - discount))
        contentView.backgroundColor = lightGreen
    }
}

Using the Cell

You’ve set up the custom cell and added a controller to it. Change the Table view controller to use the cell.  Go over the TableViewController.Swift file. Find the tableView(tableview:CellForRowAt index path) method. Comment it out or delete it. I’ll start this from scratch. Add the method in again

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
       
}

For  the first line create a cell by dequeuing a reusable cell as you have for any other table view. However, downcast the cell to CustomTableViewCell. Add the code to return the cell

let cell = tableView.dequeueReusableCell(
     withIdentifier: "cell",
     for: indexPath) as! CustomTableViewCell
        
return cell

the rest of the code goes above the return. Add a constant for the row from the index path.

let row = indexPath.row

The model has three arrays for price of a menu item, names of menu items and a bool for specials.  To add the menu item name, just assign it to the outlet menuItemDescLabel's text property.

cell.menuItemDescLabel.text = menuItems.names[row]

That’s all you need for many controls: just passing values to its outlets. You  can use a function from the cell controller.  For the price,  there is the function show(isSpecial:,for price:) in the cell. I call that and it decides how to display the other three labels.

cell.show(
    isSpecial: menuItems.specials[row],
    for: menuItems.prices[row]
)

The final method is this:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
   let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
   let row = indexPath.row
   cell.menuItemDescLabel.text = menuItems.names[row]
   cell.show(isSpecial: menuItems.specials[row], for: menuItems.prices[row])
   return cell
}

Configuring the Cell Height

Once you change the cell height, you must tell the table view the row height using the rowHeight property.  In viewDidLoad add the following.

 tableView.rowHeight = 75

This is the same number from the storyboard. I went simple here and just took the 75 points from the storyboard. There are more sophisticated ways to find the height, but I often go this simple route.

Running the Application

Build and run,

2016-10-14_11-53-43

Usually you’ll see two labels with the description and price in each cell. For a special, the cell changes to include the extra two labels, with the discount reflected. If you select an item, the functionality almost works. For a regular item the numbers are correct.

2016-10-14_11-55-37

However a special gives the regular, not the discounted price.

2016-10-14_11-55-57

Stop the simulator.  Change the tableview(tableview:didSelectRowAtIndexPath:) method to this:

override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    let discount = 0.1
    let row = indexPath.row
    let name = menuItems.names[row]
    var price = menuItems.prices[row]
    if menuItems.specials[row]{price *= (1.0 - discount)} //adjust for discount
    delegate.didSelectItem(name: name , price: price)
}

This changes the price if the item is on special. Usually the price is the one given, if not, multiply off the discount. Try running again and the special discounts work.

2016-10-14_12-02-16

I kept the example to a few labels. What you can do in a table view cell is almost everything you can do in a view. You could add more advanced views, animations,  media players, and more as outlets. You can add buttons and switches to the cell, and set actions in CustomTableViewCell to change the cell or the app.

The Whole Code

You can download the completed project here: pizzatable_customcel.zip Below you will find a complete listing of the code.

CustomTableViewCell.swift

//
//  CustomTableViewCell.swift
//  PizzaTable
//
//  Created by Steven Lipton on 10/14/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class CustomTableViewCell: UITableViewCell {

    @IBOutlet weak var menuItemDescLabel: UILabel!
    @IBOutlet weak var specialLabel: UILabel!
    @IBOutlet weak var regPriceLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    
    let lightGreen = UIColor(red: 0.5, green: 1.0, blue: 0.5, alpha: 1.0)
    let lightRed = UIColor(red: 1.0, green: 0.9, blue: 0.9, alpha: 1.0)
    let discount = 0.1
    
    func string(_ prefix:String, for price:Double) -> String{
        let priceString = String(format: "%2.2f", price)
        return prefix + priceString
    }
    
    func show(isSpecial:Bool,for price:Double){
        if !isSpecial{
            regPriceLabel.text = ""
            specialLabel.text = ""
            priceLabel.text = string("$", for: price)
            contentView.backgroundColor = lightRed
            
        } else {
            regPriceLabel.text = string("Regular price $", for: price)
            specialLabel.text = "Special"
            priceLabel.text = string("$", for: price * (1.0 - discount))
            contentView.backgroundColor = lightGreen
        }
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        // Initialization code
    }

    override func setSelected(_ selected: Bool, animated: Bool) {
        super.setSelected(selected, animated: animated)

        // Configure the view for the selected state
    }

}

TableViewController.swift

//
//  TableViewController.swift
//  PizzaTable
//
//  Created by Steven Lipton on 10/2/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

protocol TableViewControllerDelegate{
    func didSelectItem(name:String,price:Double)
}

class TableViewController: UITableViewController {
    var menuItems = MenuItems()
    var delegate:TableViewControllerDelegate! = nil
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return menuItems.names.count
    }
    
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! CustomTableViewCell
        let row = indexPath.row
        cell.menuItemDescLabel.text = menuItems.names[row]
        cell.show(isSpecial: menuItems.specials[row], for: menuItems.prices[row])
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        let discount = 0.1
        let row = indexPath.row
        let name = menuItems.names[row]
        var price = menuItems.prices[row]
        if menuItems.specials[row]{price *= (1.0 - discount)} //adjust for discount
        delegate.didSelectItem(name: name , price: price)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.rowHeight = 75

      }
}

ViewController.swift

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

import UIKit



class ViewController: UIViewController,TableViewControllerDelegate {

    @IBOutlet weak var pizzaOrderedLabel: UILabel!
    @IBOutlet weak var priceLabel: UILabel!
    
    func didSelectItem(name: String, price: Double) {
        pizzaOrderedLabel.text = name
        let priceText = String(format:"Price: %02.2f", price)
        priceLabel.text = priceText
    }
    

    // MARK: - Navigation
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        if segue.identifier == "table"{
            let vc = segue.destination as! TableViewController
            vc.delegate = self
        }
    }


}

MenuItems.swift

//
//  MenuItems.swift
//  SwiftTableViewDemo
//
//  Created by Steven Lipton on 10/1/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class MenuItems:NSObject{
    let names:[String] = [
        "Margherita Pizza","BBQ Chicken Pizza",
        "Pepperoni Pizza","Sausage Pizza",
        "Seafood Pizza","Sausage Deep Dish",
        "Meat Lover's Deep Dish","Veggie Lover's Deep Dish",
        "BBQ Chicken Deep Dish","Mushroom Deep Dish",
        "Tiramisu","Vanilla Ice Cream",
        "Apple Crostata","Hot Fudge Pizza",
        "Soft Drink","Coffee",
        "Espresso","Mineral Water"]
    let prices:[Double] = [
        7.95,11.49,
        8.45,8.45,
        12.75,10.65,
        12.35,10.00,
        16.60,11.25,
        6.50,2.25,6.50,
        9.75,1.25,
        1.25,3.50,3.75
    ]
    let specials:[Bool] = [
        false,true,
        false,false,
        false,false,
        true,false,
        false,true,
        false,false,
        false,true,
        false,false,
        true,false]
}

Introducing Table Views in Swift 3

2016-10-02_13-55-11Table views on any mobile platform are the workhorse of any application. To be a good developer you need  to make table views easily. Tables in iOS  use the UITableView class to do so. This is a very powerful class with many features to make the tables you see in applications like Facebook, Instagram and Apple Music.

Because of the power and flexibility you have available to build tables, they are not as easy to create as a UIButton or UILabel. In this lesson I’ll introduce you to dynamic tables, and show you how to display a dynamic table. We’ll put together a menu of pizzas displaying the name of the pizza and the price.

Setting Up the Application

We’ll need to set up an application. This requires pretty robust model full of data. to save you the trouble of typing all that in, you can download this starter file: pizzatable_start.zip  You can jump down to the Set up the Storyboard section, if you use the starter file. If you want to know what I did, or type it all in your self, I’ll very briefly tell you what’s in those files.

Create a new Single View project named PizzaTable. Use Swift as the language and Universal as the device. Once loaded, Press Command-N and make a new iOS cocoa touch class named MenuItems, subclassing NSObject. Create the class like this:

class MenuItems:NSObject{
let names:[String] = [
    "Margherita Pizza","BBQ Chicken Pizza",
    "Pepperoni Pizza","Sausage Pizza",
    "Seafood Pizza","Sausage Deep Dish",
    "Meat Lover's Deep Dish","Veggie Lover's Deep Dish",
    "BBQ Chicken Deep Dish","Mushroom Deep Dish",
     "Tiramisu","Vanilla Ice Cream",
     "Apple Crostata","Hot Fudge Pizza",
     "Soft Drink","Coffee",
     "Espresso","Mineral Water"]
let prices:[Double] = [
    7.95,11.49,
    8.45,8.45,
    12.75,10.65,
    12.35,10.00,
    16.60,11.25,
    6.50,2.25,
    6.50,9.75,
    1.25,1.25,
    3.50,3.75
]
let specials:[Bool] = [
        false,true,
        false,false,
        false,false,
        true,false,
        false,true,
        false,false,
        false,true,
        false,false,
        true,false
]
}

The class has three properties as arrays, the name of the menu Item, the price, and if it is a special or featured item.

We will not be using the ViewController.swift file. Select it, delete it and move it to the trash.

Set Up the Storyboard

In the storyboard,select the view controller scene and delete it. Find the Table View Controller in the object Library.

2016-10-02_12-29-17
Drag a table view controller on the story board. In the Attribute Inspector, click on Is Initial View Controller.
2016-10-02_12-30-49

In the document outline, select Table View Cell.
2016-10-02_12-32-48

Select the menu in the attributes inspector for Style.You have several styles of menu.

2016-10-02_12-35-00
We’ll place the pizza name on the left and the price in a smaller font on the right. Select Right Detail which has this style.

Under the style, you’ll find the identifier. Your code will use this identifier to find the cell. Name it cell
2016-10-02_12-38-36

Press Command-N. Make a new iOS Cocoa Touch Class named TableViewController. There is a UITableViewClass, but it is easier to not use it. The template has so many methods, it’s confusing. Instead, subclass UIViewController, and save the file. When the code appears, change the The UIViewController subclass to UITableViewController.

class TableViewController: UITableViewController {

Delete everything else but viewDidLoad. The class should look like this:

class TableViewController: UITableViewController {

override func viewDidLoad() {
super.viewDidLoad()

}
}

Go to the storyboard. In the document outline, select the Table View Controller Scene. Select the identity inspector and set the class to TableViewController.
2016-10-02_13-00-33

Table View controllers don’t use outlets or actions if you set them up this way. Everything else is code.

Coding a Table View Controller

Coding a table view controller requires you to do three things: declare how many sections you have in your table, declare how many rows you have in each section, and populate each cell with data.

Before you do any of that, add the model. Go to the TableViewController.swift file. In the class add the following declaration:

var menuItems = MenuItems()

In many tables, there’s only one section. Our menu only has one section. For more on multiple sections, see my longer article Create Dynamic and Static Table View in Swift 3. We set the sections by overriding the numberOfsections(in tableView:) method. All we do in the code is return 1. Add this to the code:

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

To declare the number of rows for the sections, we override the tableView(_ tableView: NumberOfRowsInSection section:) method. The number of rows are equal to the number of menu items. Add this:

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

The last required method populates the cell. This one is a bit more involved. Add the override to the class:

override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        
    }

 

This method returns a cell with the proper data. The parameters are the table view and the location in the table, know as the indexPath. Type IndexPath has two important components: the section and the row in that section. We’ll  make a cell corresponding to the location indexPath in the table. Add this to the cellForRowAtIndexPath method:

let cell = tableView.dequeueReusableCell(
    withIdentifier: "cell",
    for: indexPath)

cell is of type UITableViewCell, which has several properties you can use. The two most important are the UILabels textLabel and detailTextLabel. They are labels which can get arranged differently depending on the style we use. You’ll take a value from the arrays in menuItem, and display it on these labels.
You’ll need an index for the arrays. We can get that from the index path. Since this app has only one section, you can consider only the row. Add this to the method’s code:

let row = indexPath.row

This give us the row number in the table, and the index for the arrays. To set titleLabel with the menu item, add this

cell.textLabel?.text = menuItems.names[row]

To set the price in the detail, add this:

let price = String(format:"%2.2f", menuItems.prices[row])
        cell.detailTextLabel?.text = price

The app will highlight specials in green by changing the background color of the cell. Add this:

if menuItems.specials[row]{
            cell.backgroundColor = UIColor.green
        } else {
            cell.backgroundColor = UIColor.white
        }

Finally return the cell

return cell

Build and run. You have a table you can scroll through.
2016-10-02_13-55-11

This is only the beginning of tables. There’s a lot more to explore. Once you got this small last of how to build a table you might want to check out a few more tutorials on tables. Next you should dive a little deeper into tables with this post.

The Whole Code

Here is the code for this lesson. You can also download the completed project here:pizza table.zip

MenuItems.swift

//
//  MenuItems.swift
//  SwiftTableViewDemo
//
//  Created by Steven Lipton on 10/1/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class MenuItems:NSObject{
    let names:[String] = [
        "Margherita Pizza","BBQ Chicken Pizza",
        "Pepperoni Pizza","Sausage Pizza",
        "Seafood Pizza","Sausage Deep Dish",
        "Meat Lover's Deep Dish","Veggie Lover's Deep Dish",
        "BBQ Chicken Deep Dish","Mushroom Deep Dish",
        "Tiramisu","Vanilla Ice Cream",
        "Apple Crostata","Hot Fudge Pizza",
        "Soft Drink","Coffee",
        "Espresso","Mineral Water"]
    let prices:[Double] = [
        7.95,11.49,
        8.45,8.45,
        12.75,10.65,
        12.35,10.00,
        16.60,11.25,
        6.50,2.25,6.50,
        9.75,1.25,
        1.25,3.50,3.75
    ]
    let specials:[Bool] = [
        false,true,
        false,false,
        false,false,
        true,false,
        false,true,
        false,false,
        false,true,
        false,false,
        true,false]
}

TableViewController.swift

//
//  TableViewController.swift
//  PizzaTable
//
//  Created by Steven Lipton on 10/2/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class TableViewController: UITableViewController {
    var menuItems = MenuItems()
    
    override func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }
    
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return menuItems.names.count
    }
    
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
        let row = indexPath.row
        cell.textLabel?.text = menuItems.names[row]
        let price = String(format:"%2.2f", menuItems.prices[row])
        cell.detailTextLabel?.text = price
        if menuItems.specials[row]{
            cell.backgroundColor = UIColor.green
        } else {
            cell.backgroundColor = UIColor.white
        }
        return cell
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()

      }
}

Make a WatchOS 3 Haptic Catalog with a Picker

How does the Apple Watch communicate to the user when they are not looking at the watch face? That is done with haptics. Haptics are sounds and taps letting the user know something is happening. In this lesson, I’ll explain to you how to use haptics in Watch OS applications by creating a catalog of all the haptics using a WatchOS picker.

The picker control is an object for selection of multiple items. You can use the picker to display either full-screen images or text for selection. In this lesson, I’ll keep it simple with a text-based picker.

Make a New Project

Create a new WatchOS project called HapticPickerDemo. Use Swift as the language, uncheck Notifications, and keep the device Universal.  Open the interface.storyboard file in the WatchKit App group.

Find the picker in the object library. Drag a picker to the interface.

2016-08-27_07-01-54

With the picker selected you’ll see four attributes of a
picker.  The Focus Style and Indicator give you visual elements to highlight the picker. The most important attributes are Style and of course Enabled. You have three style choices: List, Stack and Sequence. List is a text-based picker. Stack and Sequence is an animated and non-animated image picker.

2016-08-27_07-09-18

Set your attributes to the illustration above.  Set the picker’s Style  to List. Set the Focus Style to  Outline with Caption. This will show an outline around the picker with a caption at the top. This caption is context sensitive to the picker selection. If you’ve set a complication on a watch, you are familiar with this focus style, such as  the black on green lettering  for weather in this setting:

2016-08-27_07-02-50

From the Object Library, add  a button under the picker. Title the button Play Haptic.

2016-08-27_07-04-29

Close the attributes and navigation panels. Open the assistant editor.

Make an outlet for the picker by control dragging from the picker to the InterfaceController class

@IBOutlet var picker: WKInterfacePicker!

Make an action for the picker’s action,changing Outlet to Action in the popup after you control drag.

 func pickerDidChange(_ value: Int) {
    }

Make an action for the button’s action, changing Outlet to Action in the popup after you control-drag.

 func playHaptic(_ value: Int) {
    }

Playing a Haptic

There’s two parts to a haptic you need to know. There is a play function [in WatchOS 2 playHaptic()]  you call on the current device object called by
WKInterfacedevice.current()object [in WatchOS 2 WKInterfaceDevice.currentdevice() ]. The play method has one parameter of type WKHapticType, which is an enumeration of haptic types.

  • notification tells the user there is a notification,
  • directionUp indicates an upward value,
  • directionDown indicates a downward value,
  • success indicates the successful completion of a task,
  • failure a failed task,
  • retry tells the user to retry,
  • start the beginning of an action,
  • stop the end of an action,
  • click is a very slight click, which you probably won’t hear but probably feel on a watch.

Close the Assistant editor.  Go to InterfaceController.swift in the App extension group. To make the button play a .success haptic change the playHaptic action to this:

 
func playHaptic(_ value: Int) {
    WKInterfaceDevice.current().play(.success)
}

Build and run using the 38mm simulator. Press the Play Haptic Button and  you get a sound.

In the simulator, you don’t get the taps you’ll get if you were using the watch. If you have a watch, run this on your watch and you’ll feel the tap.

Implementing the Picker

There is no documentation on what the taps feel like. It’s hard to describe in words,you have to feel it. In the rest of this lesson we’ll use WatchKit’s WKInterfacePicker to make a selectable catalog of haptics you can run on an Apple Watch.

Pickers have one parameter. Since pickers hold the list in a sequence, the parameter value is the index of that sequence. Delete all life cycle methods but willActivate. Add the list of haptic types exactly in the order above for the picker as an array.

let titles = [
    "notification","directionUp",
    "directionDown","success",
    "failure","retry",
    "start","stop","click"
]

Pickers will not use these arrays directly. Pickers get their selections from a WKPickerItem object. Code will create an array of WKPickerItem, and set that as the picker’s items. WKPickerItem has several properties to make the picker flexible.

  • title – A String? to use in a list
  • caption – A String? used as a caption for item in a list
  • accessoryImage – A Small WKImage? to display next to title in a
    list or as an alternate to text as in small complication setting.
  • contentImage – In a Stacked or Image Sequence style, a
    WKImage?

The picker has a setItems method which takes the array of picker
items to make the list, stack, or sequence of images, depending on the display style. For every picker you create, you build a function that iterates through the arrays, adding the elementsts as pickerItems. Add a function refreshPickerItems:

 func refreshPickerItems(){
 }

Add an empty array of picker items to the function.

 
func refreshPickerItems(){
    var pickerItems:[WKPickerItem] = []
}

Add a loop iterating through the titles.

 
func refreshPickerItems(){
    var pickerItems:[WKPickerItem] = []
    for item in titles{
    }
}

In the loop creates an instance of WKPickerItem. Add to pickerItem the title  from the corresponding element of the arrays. Add the literal caption Haptic. (If you want to experiment, with captions, try coding it as "Haptic-" + item ) Add the picker item to the pickerItems array.

func refreshPickerItems(){ 
    var pickerItems:[WKPickerItem] = [] 
    for item in titles{ 
        let pickerItem = WKPickerItem()
        pickerItem.title = item
        pickerItem.caption = "Haptic"
        pickerItems += [pickerItem]
    }
}

Once the pickerItems array is complete, set the items in the picker.

func refreshPickerItems(){ 
    var pickerItems:[WKPickerItem] = [] 
    for item in titles{ 
        let pickerItem = WKPickerItem()
        pickerItem.title = item
        pickerItems += [pickerItem]
    }
    picker.setItems(pickerItems)
}

In willActivate, call this function to refresh the list.

override funcwillActivate() {
    super.willActivate()
    refreshPickerItems()
}

Selecting a Picker Entry

If you were to run the project now, you would see the picker displayed correctly. You could not select anything though. That happens in the action. The pickerDidChange action has a Int parameter value, which is the current index on the picker.

I told you earlier to be careful getting the order correct in the titles  array.  This is why. Since WKHapticType is a enum you can select the type by the rawValue of the enum. That raw value matches the value parameter. Before we try this, add a property hapticType to store the type.

varhapticType:WKHapticType = .notification

I’ve initially set this to .notification. In the pickerDidChange action set the hapticType property with the raw
value we can get the value parameter.

@IBAction func pickerDidChange(_ value: Int) { 
    hapticType = WKHapticType(rawValue:value)!
}

Playing the Haptic

The last step is to play the haptics. To the action  pickerDidChange  add a .click haptic

WKInterfaceDevice.current().play(.click)

This demonstrates one use for haptics, feedback to the user.  In the action playHaptic, change the .success haptic to the property hapticType

 @IBAction fund playHaptic() {
        WKInterfaceDevice.current().play(hapticType)
    }

Run in the 38mm simulator. Select a haptic and press Play Haptic. You’ll hear the chime for it.

If you have a watch, load it on your watch, and try the application. When you tap Play haptic on the watch you will get the watch tap as well.

Don’t over use Haptics. Unless your app is an interval timer, don’t  have one go off every second or minute. That just gets annoying. Use haptics at the right times to attract a user’s attention to their Apple watch or give instant feedback.

The Whole Code

//
//  InterfaceController.swift
//  HapticPickerDemo WatchKit Extension
//
//  Created by Steven Lipton on 8/26/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import WatchKit
import Foundation


class InterfaceController: WKInterfaceController {

    @IBOutlet var picker: WKInterfacePicker!
    
    //List of WKHapticType in rawValue order
    let titles:[String] = [
        "notification","directionUp",
        "directionDown","success",
        "failure","retry",
        "start","stop","click"]
    
    var hapticType:WKHapticType = .notification
    
    //Use the index from the picker as the rawValue for the WKHapticType
    @IBAction func pickerDidChange(_ value: Int) {
        hapticType = WKHapticType(rawValue: value)!
        WKInterfaceDevice.current().play(.click)
    }
    
    
    @IBAction func playHaptic(){
        WKInterfaceDevice.current().play(hapticType)
    }
    
    
    func refreshPickerItems(){
        var pickerItems:[WKPickerItem] = []
        for item in titles{
            let pickerItem = WKPickerItem()
            pickerItem.title = item
            pickerItem.caption = "Haptic"
            pickerItems += [pickerItem]
        }
        picker.setItems(pickerItems)
    }
    
    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()
        refreshPickerItems()
       
        
    }

}

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

}

Using The Navigation Bar Title and Back Button in Swift 3.0

In writing the Swift Swift View Controllers book, it came to my attention many people don’t understand the functionality of the navigation toolbar’s title and Back button. In an early part of writing, I planned to skip the topic as a minor detail  so I could get the book done and published. However, the built-in features of the navigation toolbar make it a powerful and useful feature that can’t be missed.

Setting Up the Storyboard

Open up a new single view project named NavBarDemo using Swift as the language and Universal device. Go to the storyboard and click the view controller icon in the scene. In the drop down menu, select Editor>Embed in>Navigation controller. Drag two buttons, one labeled Pizza and the other labeled Pasta out to the scene. Set the font size on both to 26 Point. Make the Pizza button White(#FFFFFF) text on a Red(#FF0000) background. Make the Pasta button White(#FFFFFF) text on a Blue(#0000FF) background. Arrange them like this on the storyboard:

2016-06-22_06-17-09

Select the Pizza button. Click the  auto layout pin button pinMenuButton. Pin the Pizza button 0 points up, 0 left, 0 right, and 0 down like this:

2016-06-22_06-22-30

Add the four constraints without updating. Select the Pasta button. Click the pin button pinMenuButtonand repeat the constraints of   0 up, 0 left, 0 right, and 0 down. Add the four constraints. Now Control-drag from the Pizza button to the Pasta button. Select Equal Widths in the menu that appears.

2016-06-22_06-30-19

On the auto layout resolver menu resolver button, select Update Frame in the All Frames in View section.

2016-06-22_06-31-40

Your controller should look like this:

2016-06-22_06-34-58

Drag two more view controllers on to the storyboard. Make the background of one Blue(#0000FF) and the background of the other Red(#FF0000).

Control drag from the Pizza button to the red scene. Select a show segue.

2016-06-22_06-38-52

In the attributes inspector, set the segue identifier to pizza. Control-drag from the Pasta button to the blue scene. Select a show segue. In the properties inspector, set the segue identifier to pasta. Your storyboard should look like this:

2016-06-22_06-41-09

In this lesson we will do all the coding in ViewController, so there is no need of code in the two new controllers.

Setting the Navigation Title Bar

There is a property on UIViewController called navigationItem. When used with navigation controllers, this controls the navigation bar at the top of the view. The navigationItem property is an instance of UINavigationItem, which has four major properties: a title, a left bar button, a right bar button and a prompt. To set the title on the toolbar , you set the string for the title property. For example add this to the ViewController class

override func viewWillAppear(_ animated: Bool) {
        navigationItem.title = "One"
    }

Build and run. The root view now has One as a title.

2016-06-22_06-59-59

We used viewWillAppear and not viewDidLoad. ViewController is the root view controller in the navigation stack. It only loads once and stays active in the background. It will execute viewDidload only once. To make sure we update, we use viewWillAppear instead.
The title is dynamic. As a simple example, We’ll place a count in the title of the navigation bar. Add the following code:

var vcCount:Int = 0{
    didSet{
      navigationItem.title = "Count: \(vcCount)"
    }
  }

We used the  didSet property observer feature of Swift. Any time vcCount changes, the title changes with it. We change the count on any segue so we can increment in prepare for segue:. Add this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount+=1
    }

It’s rare to not have a statement within an if clause in a prepare for segue:. We want any segue to increment the count, so we don’t need the if.

We want to show the count when we load. Change the viewWillAppear to this:

 override func viewWillAppear(_ animated: Bool) {
        // navigationItem.title = "One"
        navigationItem.title = "Count: \(vcCount)"
    }

Build and run. Go back and forth in the views. The title changes every time you come back to it.

2016-06-22_07-04-28

Programming the Back Button

You’ll notice once you have a title, the navigation Back button disappears, to be replaced by the previous view’s title.

2016-06-22_07-08-42

The Back button reads the controller underneath the current controller for its title. You cannot set the back button title directly. Instead, you set the title of the current controller before you leave for the destination controller. If the previous view controller’s title is nil, the button titles itself Back. Change the prepare for segue: to this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil
    }

Build and run.
You have the count on the root controller

2016-06-22_07-14-16

You have a Back button on the child controllers

2016-06-22_07-14-26

If you wanted to add your own text to the Back button, you have to change the title of the controller directly under it on the navigation stack. The simplest way is change the title just before you segue to the new controller. Change prepare for segue: to this:

override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil

        if segue.identifier == "pizza"{
            navigationItem.title ="Pizza to One"
        }
        if segue.identifier == "pasta"{
            navigationItem.title = "Pasta to One"
        }
    }

Before we segue to the pizza and pasta controller, we change the title of the current controller’s navigationItem. The Back button reads the title of navigationItem and sets the button’s title accordingly. The viewWillAppear method will reset the title to the count in the first view controller when we pop the Pizza or Pasta view controller. Build and Run. Select to the Pizza and Pasta buttons:

2016-06-22_07-30-59

2016-06-22_07-30-05

To see this happening, you can comment out this in viewWillAppear.

//navigationItem.title = "Count: \(vcCount)"

Build and Run. When you go back, the title remains:

2016-06-22_07-33-30

Uncomment the line before you go on.

The Size Sensitive Back Button

The Back button is sensitive to the space around it. The button’s title responds to not having enough space to place itself. Change prepare for segue: to this:

    override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil
        if segue.identifier == "pizza"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Pizza One"
            navigationItem.title = "Pizza to One"
        }
        if segue.identifier == "pasta"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Linguine all’arrabbiata"
            navigationItem.title = "Pasta to One"
        }
    }

We get the destination view controller and assign it to vc. We then send the title for the destination view controller  to vc.navigationItem.title. We’ve picked some long labels to test size restrictions.

Set the simulator to iPad Air 2. Build and run. Select the Pizza button.

2016-06-22_07-58-45

Go back to select the Pasta button.

2016-06-22_07-45-03

That works as we expected. Now try an iPhone 6 in the simulator. Run the demo, select the Pasta controller and you get this:

2016-06-22_07-57-14

The navigation controller title gets replaced with Back. Go back and try the Pasta Button.

2016-06-22_07-51-03

There is only the  2016-06-22_08-10-17 icon and no text for the back button. Rotate the device by pressing Command-Left Arrow:

2016-06-22_08-08-46

The text re-appears with more space to place the text.

The back button is intelligent. If it has enough space, it displays the title of the previous controller. If there is not enough space, it displays Back. If there is not enough space for the word Back, it displays only the 2016-06-22_08-10-17 icon.
There is an important design rule in play here that I am intentionally breaking to make the point: Keep your titles short in a navigation bar. If you keep titles short, you will have the full functionality of the back button.

The Whole Code

ViewController.swift

//
//  ViewController.swift
//  NavBarDemo for Swift 3.0
//
//  Created by Steven Lipton on 6/22/16.
//  Copyright © 2016 Steven Lipton. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
    var vcCount:Int = 0{
        didSet{
            navigationItem.title = "Count: \(vcCount)"
        }
    }
    
    override func viewWillAppear(_ animated: Bool) {
        //navigationItem.title = "One"
        navigationItem.title = "Count: \(vcCount)"
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
    }
    
    override func prepare(for segue: UIStoryboardSegue, sender: AnyObject?) {
        vcCount += 1
        navigationItem.title = nil
        if segue.identifier == "pizza"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Pizza One"
            navigationItem.title = "Pizza to One"
        }
        if segue.identifier == "pasta"{
            let vc = segue.destinationViewController as UIViewController
            vc.navigationItem.title = "View Controller Linguine all’arrabbiata"
            navigationItem.title = "Pasta to One"
        }
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }


}

Adding Annotations and Overlays to Maps

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

Make a New Project

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

2016-05-04_05-42-52

Turn on maps and the menu opens.

2016-05-04_05-42-53

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

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

2016-05-04_05-47-46

Drag it somewhere on the story board.

2016-05-04_05-48-39

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

2016-05-04_05-49-37

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

2016-05-04_05-51-02

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

2016-05-04_05-59-11

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

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

import UIKit
import MapKit

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

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

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

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

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

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

Add the region to viewDidLoad:

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

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

2016-05-12_15-56-16

Adding a Single Annotation Pin

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

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

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

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

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

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

2016-05-12_15-57-27

Tap the pin and you get a title.

2016-05-12_15-57-36

Using More Than One Pin

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

A New Data Set

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

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

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

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

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

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

Adding an Array of Annotations to the Map

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

let restaurants = PizzaLocationList().restaurant

Change viewDidLoad to this:

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

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

Build and run. You get a series of pins.

2016-05-15_09-47-11

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

2016-05-15_09-47-12

Using the Map Kit Delegate for Annotations

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

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

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

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

mapkit.delegate = self

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

2016-05-15_09-47-11

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

Changing Pin Color

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

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

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

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

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

2016-05-15_10-14-56

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

Multiple Classes of Annotations

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

2016-05-13_06-56-39

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

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

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

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

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

 2016-05-15_09-47-14

Customizing the Annotation

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

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

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

2016-05-13_15-07-42

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

pizza pin                   crosshairs

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

2016-05-15_09-47-15

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

2016-05-15_09-47-16

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

2016-05-15_09-47-17

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

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

Change the code for mapView:viewForAnnotation: to this

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

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

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

2016-05-15_11-43-44

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

2016-05-15_09-47-18

Working with Overlays

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

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

Circle Overlays

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

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

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

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

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

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

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

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

2016-05-15_11-57-33

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

2016-05-15_11-57-35

Rendering Polylines in Overlays

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

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

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

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

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

Next we make a few additions to the delegate method.

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

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

UnoDirections()

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

2016-05-15_09-47-19

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

2016-05-15_09-47-20

Where to Go from Here

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

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

The Whole Code

ViewController.swift

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

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

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


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

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

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

}

PizzaLocation.swift

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

import UIKit
import MapKit

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


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


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

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

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

Make a New Project

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

2016-05-04_05-37-49

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

2016-05-04_05-42-52

Turn on maps and the menu opens.

2016-05-04_05-42-53

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

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

2016-05-04_05-47-46

Drag it somewhere on the story board.

2016-05-04_05-48-39

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

2016-05-04_05-49-37

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

2016-05-04_05-51-02

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

2016-05-04_05-59-11

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

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

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

2016-05-04_06-17-48

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

2016-05-04_06-19-16

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

2016-05-04_06-28-34

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

2016-05-04_06-32-26

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

2016-05-04_06-47-17

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

2016-05-04_06-47-17_01

Add the constraints.

2016-05-04_06-49-14

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

2016-05-04_06-59-14

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

2016-05-04_07-08-54

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

import UIKit
import MapKit

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

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

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

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

2016-05-04_07-29-57

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

2016-05-04_07-30-54

Getting Sample Location Data From Google Maps

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

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

 

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

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

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

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

/@41.9472901,-87.6565357,21z

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

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

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

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

Core Location Data Types

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

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

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

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

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

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

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

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

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

Setting the Map Region

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

maps regions

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

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

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

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

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

2016-05-05_06-58-30

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

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

Using Cameras

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

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

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

2016-05-06_05-52-55

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

2016-05-06_06-11-53

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

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

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

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

2016-05-06_06-33-55

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

2016-05-06_06-32-43

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

2016-05-06_06-18-13

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

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

2D Views Revisited

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

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

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

2016-05-06_07-02-59

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

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

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

2016-05-06_13-54-50

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

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

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

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

2016-05-06_14-00-29

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

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

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

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

2016-05-06_14-09-20

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

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

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

2016-05-07_07-31-47

Using Satellite Imagery

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

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

mapView.mapType = .Satellite

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

2016-05-07_07-47-17

Change .Satellite to .Hybrid and you get this

2016-05-07_07-51-07

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

Satellite and Flyover

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

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

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

2016-05-07_13-58-26

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

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

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

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

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

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

Toggling features

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

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

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

2016-05-07_13-54-50

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

2016-05-07_13-54-48

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

2016-05-07_14-11-43

Tap Flyover and we get the 3d view with buildings

2016-05-07_13-54-52

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

2016-05-07_13-54-51

 

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

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

The Whole Code

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

import UIKit
import MapKit

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