MapKit for iOS 9: Flyover, Transit and Customization
During the WWDC 2015, Apple introduced new iOS 9 features in MapKit. The development community has been requesting these feature for a long time and finally, here they are. Let’s check them in detail.
Pin Color Customization
Since the launch of iOS 3, MapKit has offered very limited options for the color of a pin (an instance of MKPinAnnotationView
). Basically, the only available option was to select the pin color among red, green and purple. In iOS 9, we can now customize the pin color. A new property pinTintColor
of type UIColor
allows you to assign any color to the pin. The old property pinColor
has been deprecated.
Callout Customization
A callout is the view that pops up when tapping on an annotation view. By default, it has 4 components: title, subtitle, right accessory view and left accessory view. These callout components are good enough for very limited customization. If you need more, you can use the new property detailCalloutAccessoryView
of type UIView?
. This property allows you to add to the callout any view. You could use the new UIStackView
. If you want to know more about UIStackView
, please check out this tutorial: UIStackView, Auto Layout and Core Animation. Being a UIView
, the detailCalloutAccessoryView
supports auto layout, left to right languages, and everything that you can do with any UIView
.
To create a custom callout, you just have to set the canShowCallout
property to true
, so the callout is visible and then, pass any UIView
you want to detailCalloutAccessoryView
. Here’s an example:
1 2 3 |
view.canShowCallout = true view.detailCalloutAccessoryView = UIImage(image:UIImage(named:"YourImageName")) |
Map Customization
With map customizations, we can control what to render on the Map View. There are 3 new possibilities offered to developers and accessible through the following properties: showsTraffic
, showsScale
and showsCompass
. These 3 properties are booleans. Setting them to true
will show traffic, scale and compass on the map.
Time Zone Support
Time zone support is the property to get the associated time zone. It can be used in combination with CLGeocoder
to find time zone associated with a particular coordinate or with MKLocalSearch
to find the time zone associated to a point as a result of a query.
Transit
MapKit allows transit ETA (Estimated Time Arrival) requests. Until now, only 3 types of transits were available to developers: Automobile, Walking and Any. But iOS 9 introduces a new type: Transit. So, in iOS 9 the MKDirectionsTransportType
looks like this:
1 2 3 4 5 6 7 |
struct MKDirectionsTransportType: OptionSetType { init(rawValue rawValue:UInt) static var Automobile: MKDirectionsTransportType { get } static var Walking: MKDirectionsTransportType { get } static var Transit: MKDirectionsTransportType { get } static var Any: MKDirectionsTransportType { get } } |
In addition to the ETA, you will get the expected arrival and departure time. Now, let’s see how to request a transit ETA:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
func getTransitETA() { let request = MKDirectionRequest() // 3 let source = MKMapItem(placemark: MKPlacemark(coordinate:CLLocationCoordinate2D(latitude: 37.78257, longitude: -122.41115), addressDictionary: nil)) // 4 source.name = "iNVASIVECODE HQ" request.source = source //5 let destination = MKMapItem(placemark: MKPlacemark(coordinate:CLLocationCoordinate2D(latitude: 37.79467, longitude: -122.39302), addressDictionary: nil)) // 6 destination.name = "Ferry Building" request.destination = destination // 7 request.transportType = MKDirectionsTransportType.Transit // 8 let directions = MKDirections(request: request) // 9 directions.calculateETAWithCompletionHandler { response, error in // 10 if error == nil { if let r = response { print(r.expectedTravelTime) // 11 } } } } |
In line 3, I create a MKDirectionRequest
. Then, I create a MKMapItem
with the coordinates of the iNVASIVECODE HQ (line 4) and I assign it as the starting point for routing directions (line 5). I do the same for the destination: I create a MKMapItem
(line 6) and I assign it as the end point for routing directions (line 7). Then, I set transportType
to Transit
(line 8). Finally, I create a MKDirections
object (line 9) with the request from line 3 and I begin calculating the requested travel-time information (line 10). I finally print the expected travel time in seconds (line 11).
After receiving the response from the Apple servers, you could launch Maps in driving, walking or starting from iOS 9, in transit mode. Here an example on how to launch the application Maps from your app:
1 2 3 4 5 6 |
func openInMapsTransit(coord:CLLocationCoordinate2D) { var placemark = MKPlacemark(coordinate:coord, addressDictionary: nil) var mapItem = MKMapItem(placemark: placemark) // 12 let launchOptions = [MKLaunchOptionsDirectionsModeKey:MKLaunchOptionsDirectionsModeTransit] // 13 mapItem.openInMapsWithLaunchOptions(launchOptions) // 14 } |
In the previous snippet of code, I create a MKMapItem
(line 12). Then, I specify the launch options when opening the Maps app (line 13). You should pass MKLaunchOptionsDirectionsModeDriving
to the openInMapsWithLaunchOptions:
method to tell the Maps app to display driving directions between the start and end points; pass MKLaunchOptionsDirectionsModeWalking
to tell the Maps app to display walking directions or pass MKLaunchOptionsDirectionsModeTransit
to tell the Maps app to display transit directions. In my case, I’m passing MKLaunchOptionsDirectionsModeTransit
. Finally, I open the Maps app and display the map item (line 14).
Flyover
Flyover is a photorealistic 3D model of cities and landmarks. It has been available in Maps since iOS 6.0, but until now it was not available in the MapKit framework. In iOS 9, flyover is also offered to developers. Flyover is available in many places around the world, but not everywhere. Where flyover is not available, flat satellite imagery will be displayed on top of a 3D terrain map (height model).
The mapType
property from the MKMapView
class allows you to define the type of data displayed by the map view. Since iOS 3, there have been three types available: Standard
, Satellite
and Hybrid
. And iOS 9 brings two new types: SatelliteFlyover
(three-dimensional model of terrain and buildings) and HybridFlyover
(the same as SatelliteFlyover with additional elements displayed on top like roads and labels).
Flyover and satellite imagery are displayed on a globe, which is a three-dimensional scale model of the Earth. So, when using flyover, there are some things you have to consider when defining the regions you want to visualize.
Size Limits
If you define a very large region of the earth, some areas might not be visible because the sides of the region are on the curvature of the globe. Those areas will be hidden from the point of view. There is nothing you can do here, the earth has a round shape and so it is impossible to see every single regions from a single view point.
Distortion
To see the facades of the buildings, you must use a tilted view of the map. Instead of positioning the camera looking top-down, you can angle the camera to get a tilted view of a region. In this case, the visible area you actually see is not rectangular, but trapezoidal. However, the region defined with MKCoordinateRegion
and MKMapRect
is a rectangle. In these cases, the rectangle defined will be the enclosing rectangle of the visible area.
To handle this problem, Apple recommends to set the visible region of your map using MKMapCamera
. You can check a post I wrote in 2013 about MapKit in iOS to learn more about MKMapCamera
and a new initializer for MKMapCamera
, available in iOS 9:
1 2 3 4 |
convenience init(lookingAtCenterCoordinate centerCoordinate: CLLocationCoordinate2D, // 15 fromDistance distance:CLLocationDistance, // 16 pitch:CGFloat, // 17 heading: CLLocationDirection) // 18 |
In this initializer, you can fix the latitude and longitude location of the camera on the terrain (line 15). Then a distance in meters defining how far away you want the camera to be positioned (line 16). The pitch (line 17) specifies the angle of the camera view measured in degrees. A value of 0 degrees results in a camera pointing straight down at the map. Angles greater than 0 degrees result in a camera that is pitched toward the horizon by the specified number of degrees. Finally, the heading (line 18) defines the direction the camera should be looking at (measured in degrees) relative to true north.
Once you have created the camera and set its properties, you can assign the camera to the map view using the camera:
property or the setCamera:animated:
method.
Conclusions
iOS 9 is finally bringing some long-time expected improvements to MapKit. Personally I find the callout customization quite useful. Also, there are a couple of new features, transit and flyover. Transit is definitely the most expected new feature and I’m looking forward to using it and confident I will see amazing results.
Keep innovating, swiftly!
Eva
Eva Diaz-Santana (@evdiasan) is cofounder of InvasiveCode. She has developed iOS applications and taught iOS development since 2008. She also worked at Apple as Cocoa Architect and UX designer.