Hands On

Exploring HERE Mobile SDK UI Kit 2.0 for iOS

By Thomas Lucka | 24 January 2019

Have you seen Groundhog Day? Sometimes I feel like Phil Connors being caught in a time loop. Each day, before driving to HERE, I pull out my good old iPhone, lift it to the cup holder in my car, mumble the magic spell: “Hey, Siri, start HERE WeGo app.” and then – – – nothing more happens.

The app has started, but that’s it. Again, I look at the display and then I begin to type in the same sequence of commands each day, all over again. Finding a destination, calculating car routes, selecting the fastest route, starting navigation. Too much repetition. Saving some additional time in the morning would be great, or? What if the app already knew where I want to go? Why not build my own guide to HERE?

If you’ve read our last blog post, you may have heard already about the new HERE Mobile SDK UI Kit, v2.0 (MSDKUI). It is available for iOS and it looks like the perfect little helper to efficiently build user interfaces on top of the HERE Mobile SDK. User interfaces that look good on a variety of different devices and across different languages … sounds promising.  There is also an Android variant available with almost the same feature set.

Now we have an idea for a new app to save you, me and everybody else with the same destination any time in the morning – and we have a promising new UI framework from HERE at our hands. For our app idea, we’ll also need the help of the HERE Mobile SDK to get directions and to perform or carry out the navigation – moreover, the MSDKUI framework is specifically designed to work in hand with the HERE Mobile SDK, so both kind of make up a dream team.

If you are new to this dynamic duo, don’t worry. Following this blog post should inspire you into building your own time-saving apps in no matter of time. Let’s see how far we can get in a couple of minutes.

Getting Started

So, where to start? From a first quick look at the MSDKUI’s documentation, these three UI building blocks look promising:

  • GuidanceManeuverView: To show instructions for the upcoming maneuvers.
  • GuidanceSpeedView: To show the current speed and to indicate overspeeding.
  • GuidanceSpeedLimitView To show possible speed limits along the way.

Let’s keep it simple. Our app has just one purpose. It should guide the user to the HERE Berlin office (… yes, because this is where I work, but you can choose a different destination). As soon as the app opens, navigation begins. No user interaction, very Siri-friendly. Although this concept looks straightforward at first glance, there are some hurdles.

The HERE Mobile SDK is headless, it does not contain any maneuver icons. The information about a route is pure data. How many different maneuver icons and instruction sets exist to guide a user safely while driving? How to parse the raw route data and extract information from it? And how to combine all that into a user-friendly UI? This sounds time-costly and we really don’t want to go into details here. Fortunately, this is where the MSDKUI comes in.

We begin with an empty Xcode iOS project, using Xcode 10.1 and iOS 12. Xcode’s Single View App template is sufficient. As language, we select Swift. The next thing we want to do is to get the required dependencies into our project. CocoaPods is a great tool for that. In our case, we want to integrate the HERE Mobile SDK for iOS and its friendly tandem partner, the MSDKUI (HEREMapsUI) framework v2.0. Close Xcode and create a new Podfile with the following contents:

target 'GuideMeToHERE' do
    platform :ios, '12.0'
    pod 'HEREMapsUI', '2.0'
end

Then navigate from the Terminal to your Xcode project folder and execute:

pod install --repo-update

This will do the magic and integrate not only the MSDKUI 2.0 into our empty project, but also the HERE Mobile SDK. If all goes well, you should see the HERE Mobile SDK in the new Pods folder in your project, including all the example apps, all the necessary documentation, and more.

It’s time to go back to Xcode.

Keep in mind that CocoaPods creates a new workspace. Don’t double-click the *.xcodeproj to open your project – always use *.xcworkspace.

The last hurdle we have to take is to enter our HERE credentials. While the MSDKUI is free and open-source, it’s good news that the HERE Mobile SDK with the new Freemium plan is free too. It only requires registration at developer.here.com to get access to HERE’s excellent map data. Make sure to remember the bundle identifier you have used when registering your app. This bundle identifier should be set in the project’s Identity tab, right under the Display Name of your app. We have chosen: com.here.msdkui.example, but probably you want to change that.

As for every iOS project, we have two Swift files: AppDelegate.swift and ViewController.swift. With the credentials at hand, open the AppDelegate and add the code to authenticate yourself. It should look like below:

func application(_ application: UIApplication,
                 didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Set credentials (based on bundle identifier).
    let appId = "YOUR_APP_ID"
    let appCode = "YOUR_APP_CODE"
    let licenseKey = "YOUR_LICENSE_KEY"
    let error = NMAApplicationContext.setAppId(appId, appCode: appCode, licenseKey: licenseKey)
    assert(error == NMAApplicationContextError.none, "Please make sure to set valid HERE credentials.")
    return true
}

Don’t forget to enter your own credentials and to add an import for the NMAKit at the top of the file.

User Interface Layout

Once done, it’s time to think about the app’s user interface and where to put our three shiny new MSDKUI v2.0 components. Since we want to use the app for our daily commuting to HERE, it should be easy to drop the phone upright to the car’s cup holder (if you’ve got one). And for that, it makes sense to choose Portrait in Xcode’s Deployment Info -> Device Orientation settings.

For building beautiful layouts, it is convenient that the MSDKUI’s UI components behave just like any other UIView, in fact they are descendants of UIView. This helps to quickly stitch together our main view in Xcode’s Interface Builder. If you are new to working with Interface Builder, you can find a good introduction on Apple’s developer sites.

This is how our layout looks like (of course, yours can be different):

layout

The layout in Xcode’s Interface Builder

 

We have not done anything fancy here, it’s just our three UI components from the MSDKUI v2.0 library and a map view. The GuidanceManeuverView sits at the top edge and the speed views are at the lower left and right screen corners: Current speed indication is left and and the speed limit view on the right.

Plus, there is not much MSDKUI specifics involved here. In Xcode’s Interface Builder, we open the library view, drag a UIView above the map view, and in Identity Inspector for Custom Class we type: GuidanceManeuverView in Class, and MSDKUI for Module. As a constraint, we set 128 px for height. Below, we set GuidanceSpeedView and GuidanceSpeedLimitView in a similar fashion. Note that you do not need to set the Module for the NMAMapView as it is part of the HERE Mobile SDK. In the screenshot above you can see the completed layout and the constraints we have used for each view (if you have sharp eyes, but feel free to use your own values).

Typically, you’ll want to reference your views from code. This is achieved by Control-dragging each view to a ViewController.

Open Main.storyboard to see the Interface Builder. In the upper right corner click the two circles to show the Assistant editor. This will allow you to see the layout and the ViewController code side by side. Now, on your keyboard hold down the Control-key and click with the left mouse button on one of your views. Then move the mouse to the ViewController and lift it. In the popup, you can enter a variable name of your choice.

Once done, it should look like this:

@IBOutlet weak var mapView: NMAMapView!
@IBOutlet weak var guidanceManeuverView: GuidanceManeuverView!
@IBOutlet weak var guidanceSpeedView: GuidanceSpeedView!
@IBOutlet weak var guidanceSpeedLimitView: GuidanceSpeedLimitView!

Xcode warns that it does not know these components, so we’ll add the required imports above the import for Apple’s UIKit:

import MSDKUI // This is needed for the HERE Mobile SDK UI Kit framework.
import NMAKit // This is needed for the HERE Mobile SDK for iOS.
import UIKit

Current Position

As always, there’s one more thing. We already know that our app needs the user’s current position. Since this is sensitive data, iOS will prompt the user for permission. This can be requested by adding location-services to the UIRequiredDeviceCapabilities key in the app’s Info.plist, along with a NSLocationWhenInUseUsageDescription. You can inspect the Xcode project on GitHub for an example.

So far, so good. That was all the preparation work. From now on we only have to write some Swift code and that’s gonna be pure coding fun, so lean back, take a deep breath and recap what we want to achieve:

  1. When the application starts, we want to find out the current position.
  2. Once we have a valid position, we calculate a route from that position to HERE’s Berlin office.
  3. If we have the route calculated, we start turn-by-turn navigation and expect the app to guide us safely to HERE.

Step one: How can we find the user’s position? Since we already included the HERE Mobile SDK, we can simply start the NMAPositioningManager of the SDK. This step does not include any MSDKUI v2.0 code, so we’ll keep it short here and just introduce a convenient method called findCurrentPosition() that will just do what it says. Along with that, we also do some little map preparation work, like enabling visualization of 3D buildings by setting mapView.landmarksVisible = true. I think it looks cool to see some of Berlin’s iconic attractions like the Brandenburger Gate in 3D while getting stuck in a traffic jam.

Note that all of this can be found in the accompanying example app, if you are interested. For example, if you’ll always want to start from the same location, then feel free to provide a hard-coded coordinate. If it’s your app, you can do with it what you want.

Calculate Route

Now, let’s go on to step two. We need to calculate the route we want to use for navigation. Let’s see how this could be implemented (as always there are many ways to Rome, errr …, HERE).

The NMACoreRouter, as its name implies, does the job to calculate the desired route for us:

private func calculateRoute(currentGeoCoordinates: NMAGeoCoordinates) {
    let startWaypoint = NMAWaypoint(geoCoordinates: currentGeoCoordinates)
    let destinationWaypoint = NMAWaypoint(geoCoordinates: geoCoordinatesOfHEREBerlin)
    let waypoints = [startWaypoint, destinationWaypoint]

    coreRouter = NMACoreRouter()
    coreRouter.calculateRoute(withStops: waypoints, routingMode: NMARoutingMode()) { routeResult, error in
        if error != NMARoutingError.none {
            print("Error: Routing failed. Maybe you are overseas?")
            return
        }

        guard let route = routeResult?.routes?.first else {
            print("Error: No route found.")
            return
        }

        // Use the route to set up UI and start guidance.
    }
}

As you may know, HERE Berlin is located at Invalidenstraße 116, 10115 Berlin in Germany. The exact location is 52.53102 (latitude) and 13.3848 (longitude), which we stored as geoCoordinatesOfHEREBerlin. The user’s current position will serve as starting point and the HERE office as destination. If you are not working at HERE, don’t feel sad, you can adapt the coordinates. By default, the NMACoreRouter will calculate only one route (routingMode.resultLimit = 1) and it’s using NMATransportMode.car as default transportMode. Once we have the route, there are two things to do, setting up our UI and – obviously – starting guidance. Before we look at the first one, let’s recap what we want to show on the screen.

First off, there’s the GuidanceManeuverView on top of the map, plus we have two indicators for current speed and speed limit. We already placed all three MSDKUI views on the screen. The main question left is how to get our data into the view. How does this all fit together?

Luckily, most MSDKUI components follow a simple pattern that consists of three elements: A monitor to get notified on new data, the data itself that can be inspected or altered and finally, the view where you can feed the data into.

Since a view serves a specific purpose, its data will be specific too. Therefore, the MSDKUI provides dedicated classes to monitor and to provide that data. In our case, we need to

  • provide maneuver data to show in the GuidanceManeuverView
  • provide speed data to show in the GuidanceSpeedView
  • provide speed limit data to show in the GuidanceSpeedLimitView

Since the data for GuidanceSpeedView and GuidanceSpeedLimitView is quite similar, the MSDKUI provides the same monitor for both. Therefore, we only need to declare two different monitors:

private var guidanceManeuverMonitor: GuidanceManeuverMonitor!
private var guidanceSpeedMonitor: GuidanceSpeedMonitor!

The guidanceManeuverMonitor notifies on new maneuver data. This data we can then pass to the GuidanceManeuverView. Similarly, the guidanceSpeedMonitor will deliver the speed information we can show in the GuidanceSpeedView and the GuidanceSpeedLimitView. Let’s begin with the GuidanceManeuverMonitor. At first, we need to create a new instance and pass the data object of interest: The route we have calculated in the previous step:

guidanceManeuverMonitor = GuidanceManeuverMonitor(route: route)
guidanceManeuverMonitor.delegate = self

We also set our ViewController as delegate to receive the desired notifications. By conforming to the GuidanceManeuverMonitorDelegate we can fulfill the contract. This protocol defines two methods we must implement. We do that in an extension to our ViewController, which helps to keep related code together.

extension ViewController: GuidanceManeuverMonitorDelegate {
    func guidanceManeuverMonitor(_ monitor: GuidanceManeuverMonitor,
                                 didUpdateData data: GuidanceManeuverData?) {
        guidanceManeuverView.data = data
    }

    func guidanceManeuverMonitorDidReachDestination(_ monitor: GuidanceManeuverMonitor) {
        guidanceManeuverView.highlightManeuver(textColor: .colorAccentLight)
    }
}

And that’s it. No more work is needed. The first method is called when new maneuver data arrives. Note that GuidanceManeuverData is an optional value, so in case of errors, or when there is no data available – for example – during a tunnel, the data object may be nil. Here we are satisfied with the default behavior of the component. We pass the data, regardless if it is nil or not: This will show a status message, indicating that the component is trying to get data.

The second callback informs us when the user has reached the destination. In this case, we customize the end maneuver with an accent color.

destination-1

Highlighted destination

 

There are multiple ways to adapt the components to your own needs. Similarly, we can change the default look of the guidanceManeuverView by reversing the white-on-black font:

guidanceManeuverView.foregroundColor = .colorForeground
guidanceManeuverView.backgroundColor = .colorBackgroundViewLight

The MSDKUI library already includes a set of predefined brand colors which we are using here.

Speed Components

Let’s move on and set up the speed components.

guidanceSpeedView and guidanceSpeedLimitView allow for a variety of customization options. In essence, we can define what to show based on the current speed value. Furthermore, for the guidanceSpeedView we would like to indicate its speed value on a rounded background. Since the guidanceSpeedView is a UIView, we can tweak it as much we like:

guidanceSpeedView.layer.cornerRadius = guidanceSpeedView.bounds.height / 2
guidanceSpeedView.textAlignment = .center

Since the speed view has equal width and height dimensions, we can round the view by setting the corner radius to half the height of the view. Secondly, we want to center the speed value and its unit within.

The same rounding strategy we apply to the guidanceSpeedLimitView. Additionally, we specify a border, to give it a look similar to a traffic sign:

guidanceSpeedLimitView.layer.cornerRadius = guidanceSpeedLimitView.bounds.height / 2
guidanceSpeedLimitView.layer.borderWidth = 4
guidanceSpeedLimitView.layer.borderColor =
    UIColor(red: 208 / 255, green: 0, blue: 13 / 255, alpha: 1.0).cgColor

speed_limit-1

Speed limit

 

The guidanceSpeedLimitView will always look the same, but with varying values. The guidanceSpeedView, in opposition, should have two possible states depending on whether we are driving with overspeed, or below the current speed limit. We can easily create two custom view appearances by changing the backgroundColor, the speedValueTextColor and the speedUnitTextColor properties of the guidanceSpeedView.

private func setDefaultSpeedColors() {
    guidanceSpeedView.backgroundColor = .colorBackgroundViewLight
    guidanceSpeedView.speedValueTextColor = .colorForeground
    guidanceSpeedView.speedUnitTextColor = .colorForeground
}

private func setOverspeedColors() {
    guidanceSpeedView.backgroundColor = .colorNegative
    guidanceSpeedView.speedValueTextColor = .colorBackgroundViewLight
    guidanceSpeedView.speedUnitTextColor = .colorBackgroundViewLight
}

By default, when we are driving according to the rules, it should be black font on white background.

current_speed-1

Looks like accurate speed, or? When overspeeding, it should be white font on an alerting red background.

overspeed-1

Oh no, are we too fast?

Okay, these visuals seem to work. However, we still need to wire up the views with the incoming speed data. As a helping hand, the GuidanceSpeedMonitor delivers the required information. You may remember, we already declared the monitor above. Its constructor is empty as there is no need to provide a route.

guidanceSpeedMonitor = GuidanceSpeedMonitor()
guidanceSpeedMonitor.delegate = self

Since no route is needed, we could use the GuidanceSpeedMonitor in tracking mode, too. For example, without following any route – just getting warned about speed limits while driving around … but hey, that may be a plan for another app.

Again, we set the ViewController as delegate. This time we need to conform to the GuidanceSpeedMonitorDelegate protocol, which requires one method to be implemented:

extension ViewController: GuidanceSpeedMonitorDelegate {
    func guidanceSpeedMonitor(_ monitor: GuidanceSpeedMonitor,
                              didUpdateCurrentSpeed currentSpeed: Measurement<UnitSpeed>,
                              isSpeeding: Bool,
                              speedLimit: Measurement<UnitSpeed>?) {

        // Update the current speed view.
        guidanceSpeedView.speed = currentSpeed
        isSpeeding ? setOverspeedColors() : setDefaultSpeedColors()

        // Update the speed limit view.
        guidanceSpeedLimitView.speedLimit = speedLimit
        guidanceSpeedLimitView.isHidden = speedLimit == nil
    }
}

To update the current speed view, we check isSpeeding and call the respective methods to update the current speed view. Quite simple, isn’t it? For the speed limit view, it is even simpler as we just have to set the current speed limit. Since speedLimit is an optional value, we may want to check for nil to update the view’s visibility based on that. A nil value indicates that no information about speed limits is available.

Start Navigation Guidance

Now, our little app is almost complete. The one thing that is left is to start the trip (step three):

private func startGuidance(route: NMARoute) {
    mapView.add(mapObject: NMAMapRoute(route)!)
    NMANavigationManager.sharedInstance().map = mapView
    NMANavigationManager.sharedInstance().startTurnByTurnNavigation(route)
}

What is going on here? As we want to see our current route on the map, we simply have to add the route object to the mapView. Then we tell the NMANavigationManager which map to use and finally, we call startTurnByTurnNavigation(). This will not only start navigation, but also enable voice guidance (using the default language) and the obligatory beep sound when driving too fast. Both can be adapted, or even turned off, if desired. You can find out more about that in the documentation for the HERE Mobile SDK.

guidance3-1

On our way to HERE!

 

During development of the app it may be useful to start guidance on a simulator (see the screenshot above – by the way, that’s also a safe way to test overspeeding without risking tickets). You can start simulated navigation by setting a custom data source for positioning. By default, it’s NMADevicePositionSource, but it can be changed to use a NMARoutePositionSource, so that the position follows the route with a custom driving speed. You can find an example for that in the example’s source code.

Wrapping Up

Now, all that is left is to get the app deployed onto a device and take the first ride to HERE.

I hope this blog post was giving you enough confidence to start your own experiments with the new MSDKUI v2.0 framework. If for some reason, you got stuck along the way, you can find the complete Xcode project on GitHub.

What’s next? For sure, there is plenty of room to improve our little app. For example, we could turn off that louche overspeeding beep to play our own funky sound instead – or we could enhance the driving experience by adding dynamic re-routing – or add additional navigation components, for example, a guidance dashboard showing the estimated arrival time.

Whatever you may decide, there are a lot more MSDKUI building blocks waiting to be explored. And if that is not enough, the full MSDKUI’s source code is open to your contribution. It’s great to see that HERE gives so much trust into the open-source community. Millions of developers can’t be wrong. It’s in your hands. And for your next projects, you’ll never have to miss that compelling “You’ve reached your destination!” voice again – when driving to HERE or wherever you want to go.