Skip to content

Commit

Permalink
Separate custom UI example (#301)
Browse files Browse the repository at this point in the history
* Seperate custom UI example

* remove

* use toolbar

* Reorganize examples

* Tweaks

* Weak self

* Set default camera to route origin + bearing

* defaultCamera -> tiltedCamera
  • Loading branch information
Bobby Sudekum authored and ericrwolfe committed Jun 23, 2017
1 parent dcb5b48 commit 2c6ec67
Show file tree
Hide file tree
Showing 6 changed files with 623 additions and 242 deletions.
273 changes: 215 additions & 58 deletions Examples/Swift/Base.lproj/Main.storyboard

Large diffs are not rendered by default.

198 changes: 198 additions & 0 deletions Examples/Swift/CustomViewController.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import UIKit
import MapboxCoreNavigation
import Mapbox
import CoreLocation
import AVFoundation
import MapboxDirections

class CustomViewController: UIViewController, MGLMapViewDelegate, AVSpeechSynthesizerDelegate {

var destination: MGLPointAnnotation!
let directions = Directions.shared
var routeController: RouteController!

let distanceFormatter = MGLDistanceFormatter()
lazy var speechSynth = AVSpeechSynthesizer()
var userRoute: Route?

@IBOutlet var mapView: MGLMapView!
@IBOutlet weak var arrowView: UILabel!
@IBOutlet weak var instructionLabel: UILabel!
@IBOutlet weak var distanceLabel: UILabel!
@IBOutlet weak var cancelButton: UIButton!

override func viewDidLoad() {
super.viewDidLoad()

mapView.delegate = self

distanceFormatter.unitStyle = .long
distanceFormatter.numberFormatter.maximumFractionDigits = 0

routeController = RouteController(along: userRoute!, directions: directions)
routeController.snapsUserLocationAnnotationToRoute = true

mapView.userLocationVerticalAlignment = .center
mapView.userTrackingMode = .followWithCourse

resumeNotifications()

// Start navigation
routeController.resume()
}

deinit {
suspendNotifications()
routeController.suspendLocationUpdates()
}

func resumeNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(alertLevelDidChange(_ :)), name: RouteControllerAlertLevelDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(progressDidChange(_ :)), name: RouteControllerProgressDidChange, object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(rerouted(_:)), name: RouteControllerWillReroute, object: nil)
}

func suspendNotifications() {
NotificationCenter.default.removeObserver(self, name: RouteControllerAlertLevelDidChange, object: nil)
NotificationCenter.default.removeObserver(self, name: RouteControllerProgressDidChange, object: nil)
NotificationCenter.default.removeObserver(self, name: RouteControllerWillReroute, object: nil)
}

func mapView(_ mapView: MGLMapView, didFinishLoading style: MGLStyle) {
addRouteToMap()
}

// When the alert level changes, this signals the user is ready for a voice announcement
func alertLevelDidChange(_ notification: NSNotification) {
let routeProgress = notification.userInfo![RouteControllerAlertLevelDidChangeNotificationRouteProgressKey] as! RouteProgress
let alertLevel = routeProgress.currentLegProgress.alertUserLevel
var text: String

let distance = routeProgress.currentLegProgress.currentStepProgress.distanceRemaining
let formattedDistance = distanceFormatter.string(fromDistance: distance)
if let upComingStep = routeProgress.currentLegProgress.upComingStep {
// Don't give full instruction with distance if the alert type is high
if alertLevel == .high {
text = upComingStep.instructions
} else {
text = "In \(formattedDistance) \(upComingStep.instructions)"
}
} else {
text = "In \(formattedDistance) \(routeProgress.currentLegProgress.currentStep.instructions)"
}

let utterance = AVSpeechUtterance(string: text)
speechSynth.delegate = self
speechSynth.speak(utterance)
}

// Notifications sent on all location updates
func progressDidChange(_ notification: NSNotification) {
let routeProgress = notification.userInfo![RouteControllerAlertLevelDidChangeNotificationRouteProgressKey] as! RouteProgress
updateRouteProgress(routeProgress: routeProgress)
}

// Updates the turn banner with information about the next turn
func updateRouteProgress(routeProgress: RouteProgress) {
guard let step = routeProgress.currentLegProgress.upComingStep else { return }

if let direction = step.maneuverDirection {
switch direction {
case .slightRight:
self.arrowView.text = "↗️"
case .sharpRight, .right:
self.arrowView.text = "➡️"
case .slightLeft:
self.arrowView.text = "↖️"
case .sharpLeft, .left:
self.arrowView.text = "⬅️"
case .uTurn:
self.arrowView.text = "⤵️"
default:
self.arrowView.text = "⬆️"
}
}
self.instructionLabel.text = step.destinationCodes?.first ?? step.destinations?.first ?? step.names?.first ?? step.instructions
let distance = routeProgress.currentLegProgress.currentStepProgress.distanceRemaining
self.distanceLabel.text = distanceFormatter.string(fromMeters: distance)
}

// Fired when the user is no longer on the route.
// A new route should be fetched at this time.
func rerouted(_ notification: NSNotification) {
speechSynth.stopSpeaking(at: .word)

getRoute {
/*
**IMPORTANT**
When rerouting, you need to give the RouteController a new route.
Otherwise, it will continue to compare the user to the old route and continually reroute the user.
*/
let routeProgress = RouteProgress(route: self.userRoute!)
self.routeController.routeProgress = routeProgress
self.updateRouteProgress(routeProgress: routeProgress)
}
}

func getRoute(completion: (()->())? = nil) {
let options = RouteOptions(coordinates: [mapView.userLocation!.coordinate, destination.coordinate])
options.includesSteps = true
options.routeShapeResolution = .full
options.profileIdentifier = .automobileAvoidingTraffic

_ = directions.calculate(options) { [weak self] (waypoints, routes, error) in
guard error == nil else {
print(error!)
return
}

guard let route = routes?.first else {
return
}

self?.userRoute = route

completion?()
self?.addRouteToMap()
}
}

func addRouteToMap() {
guard let style = mapView.style else { return }
guard let userRoute = userRoute else { return }

if let annotations = mapView.annotations {
mapView.removeAnnotations(annotations)
}
mapView.addAnnotation(destination)

let polyline = MGLPolylineFeature(coordinates: userRoute.coordinates!, count: userRoute.coordinateCount)
let lineSource = MGLShapeSource(identifier: sourceIdentifier, shape: polyline, options: nil)

if let source = style.source(withIdentifier: sourceIdentifier) as? MGLShapeSource {
source.shape = polyline
} else {
let line = MGLLineStyleLayer(identifier: layerIdentifier, source: lineSource)

// Style the line
line.lineColor = MGLStyleValue(rawValue: UIColor(red:0.00, green:0.45, blue:0.74, alpha:0.9))
line.lineWidth = MGLStyleValue(rawValue: 5)
line.lineCap = MGLStyleValue(rawValue: NSValue(mglLineCap: .round))
line.lineJoin = MGLStyleValue(rawValue: NSValue(mglLineJoin: .round))

// Add source and layer
style.addSource(lineSource)
for layer in style.layers.reversed() {
if !(layer is MGLSymbolStyleLayer) {
style.insertLayer(line, above: layer)
break
}
}
}
}

@IBAction func cancelButtonPressed(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
}
Loading

0 comments on commit 2c6ec67

Please sign in to comment.