Make App Pie

Training for Developers and Artists

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



11 responses to “How to Use MapKit for 2D and 3D Map Views.”

  1. An excellent app along with a presentation of the use of MapKit. I look forward to the next session.😀

  2. Steven. Thanks. Need some help with app. jfa2atbellsouth net

  3. Great tutorial. Can you explain how to zoom out to see the entire globe (not just a large map of the world, but a spherical globe?)

    1. Zoom your camera out 30,000 meters. The number doesn’t really matter, just a really big one. . You’ll find example code on Github here as a swift playground: https://github.com/MakeAppPiePublishing/Tips_03_iPadMapPlaygrounds_Extra

      1. No luck I’m afraid. Both the playground code and in my app are just a large map. It’s close, but I want to zoom out even farther and see the entire round planet so it looks like a marble. Here’s a screenshot of the globe view. https://www.applegazette.com/ios/my-favorite-hidden-features-in-ios-7/attachment/apple-maps-globe-view/
        Thanks for trying to help me!

  4. Just to be clear, the playground code from GitHub did work for me. It just didn’t zoom out to the globe view.

    1. I didn’t give you a distance factor high enough. try 100,000,000 meters for distance.

      worked fine for me with this code:

      import UIKit
      import PlaygroundSupport
      import MapKit
      
      class MapViewController:UIViewController {
          let brandi = CLLocationCoordinate2DMake(40.836724, 14.2468766)
          let mapView = MKMapView()
          override func viewDidLoad() {
              super.viewDidLoad()
              mapView.frame = view.frame
              view.addSubview(mapView)
              mapView.mapType = .satelliteFlyover
              let camera = MKMapCamera(lookingAtCenter: brandi, fromDistance:100000000, pitch: 30, heading: 262)
              mapView.camera = camera
              let annotation = MKPointAnnotation()
              annotation.coordinate = brandi
              mapView.addAnnotation(annotation)
              
          }
      }
      PlaygroundPage.current.liveView = MapViewController()
      
      1. Hmm, I’m not sure why it isn’t working for me. Copy/Pasting this exact code gives me a sideways map (not a globe) that is fully zoomed out. Could I be doing something wrong with the playground or is could my Xcode be out of date? Is there a good way to get you a screenshot?

      2. You said the magic word!!!! Xcode!!! Maps works like crap in the simulators. You need a live device to do this. If you have an iPad try it on Swift Playgrounds for iPad, or make a quick project to launch the code on a device.

      3. Steve, you did it! I loaded it on my iPhone and your code worked perfectly. Thanks so much, I’ve been struggling with this for awhile!!

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: