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):
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 theViewController
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 theViewController
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:
- When the application starts, we want to find out the current position.
- Once we have a valid position, we calculate a route from that position to HERE’s Berlin office.
- 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.
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
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.
Looks like accurate speed, or? When overspeeding, it should be white font on an alerting red background.
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.
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.