In this tutorial we will show how to work with the route model returned from a directions service call. We will also show how you can utilize interaction between the route rendering on the map and textual instructions showed in another view.
In the route model there are some text properties that we will interpret as enum values, so start out by creating enums RouteContext describing whether we are outside or inside.
We will need some helper methods to make this example work. The helper methods will be added to our RouteSegmentView class. First create a method that can get us the previous step for later comparison.
Create a method getOutsideInsideInstructions that can get us instructions for walking inside or out of a building. This is determined by the routeContext property of an MPRouteStep
Create a method getElevationInstructions that can get us instructions for taking the stairs or elevator to another floor. This is determined by the highway and end_location.zLevel properties of a MPRouteStep.
Create a method getDefaultInstructions that can get us information about the default instructions in a route step. In some cases they are html formatted, so we need to pass it through an interpreter.
Create a method getDistanceInstructions that can get us information about the travelling distance. This is determined by the duration property of a MPRouteLeg. The distance is returned in meters so if you require imperial units, make a conversion.
fileprivatefuncgetDistanceInstructions(_distance:NSNumber?) ->String {let feet =Int((distance?.doubleValue ??0) *3.28)return"Continue for \(feet) feet"}
Suggested Logic for Generating Meaningful Instructions
Obviously it is up to your application to present some instructions to the end user, but here a suggestion. Add a method called updateViews that will fire whenever our models change. Initialize an array of textual instructions and check for existence of a current leg.
Create the Controller That Displays Generated Textual Instructions Segment by Segment
We use a collection view to do this but you can of course use whatever view that fits your use case best.
First we will define a protocol called RouteSegmentsControllerDelegate that will be used to handle the selection of each represented route segment. The method didSelectRouteSegment will be delegating the handling of route segment selections.
Create a controller class called RouteSegmentsController that inherits from UIViewController.
Add some properties to the controller
startingScrollingOffset We will do a side-ways scroll in the collection, so we will add a private point property to keep track of that
tableView the actual table view property.
delegate the delegate property.
classRouteSegmentsController:UIViewController {privatevar startingScrollingOffset = CGPoint.zeroprivatevar tableView:UITableView!var delegate:RouteSegmentsControllerDelegate?/*** Add a `route` property to the class ***/var route: MPRoute? {didSet { self.tableView.reloadData() } }/*** Add a `currentSegment` property to the class ***/var currentSegment:MPRouteSegmentPath =MPRouteSegmentPath() {didSet {if oldValue.legIndex != currentSegment.legIndex { self.tableView.reloadData() } } }/*** Implement `viewDidLoad` method, creating the horizontal collection view and assigning delegates to the collection view. Make sure that you register your own custom `RouteSegmentView` here.
***/overridefuncviewDidLoad() { self.tableView = UITableView.init(frame: view.frame) self.tableView.dataSource = self as UITableViewDataSource self.tableView.delegate = self as UITableViewDelegate self.tableView.register(RouteSegmentView.self, forCellReuseIdentifier:"TVC") self.tableView.bounces =true self.view= self.tableView }/*** Create a method 'updateRouteSegmentSelection' that notifies the delegate ***/funcupdateRouteSegmentSelection(segment: MPRouteSegmentPath) { delegate?.didSelectRouteSegment(segment: segment) currentSegment = segment }}
Create an extension of RouteSegmentsController that implements UITableViewDataSource protocol.
extensionRouteSegmentsController:UITableViewDataSource {/*** In the `collectionView numberOfItemsInSection` method, let the item count reflect the number of legs in the current route.
***/functableView(_tableView: UITableView, numberOfRowsInSectionsection: Int) ->Int {let leg = route?.legs[section]return leg?.steps.count??0 }/*** In the `collectionView cellForItemAt indexPath` method, create a segment based on the index paths row (leg) index. Dequeue a cell view and update the `route` and `segment` properties accordingly.
***/functableView(_tableView: UITableView, cellForRowAtindexPath: IndexPath) -> UITableViewCell {let segment =MPRouteSegmentPath(legIndex: indexPath.section, stepIndex: indexPath.row) let tvCell:RouteSegmentView = tableView.dequeueReusableCell(withIdentifier: "TVC", for: indexPath) as! RouteSegmentView
tvCell.renderRouteInstructions(route, for: segment)return tvCell }/*** In the `titleForHeaderInSection` method, return the number of legs in the current route. ***/funcnumberOfSections(intableView: UITableView) ->Int {return route?.legs.count??0 }/*** Implement the `heightForRowAtIndexPath` method. ***/functableView(_tableView: UITableView, heightForRowAtindexPath: IndexPath) -> CGFloat {return80 }/*** Optionally implement the `titleForHeaderInSection` method. ***/functableView(_tableView: UITableView, titleForHeaderInSectionsection: Int) ->String? {let leg = route?.legs[section]let meters = leg?.distance.intValue ??0return"\(meters) meters" }}
Create an extension of RouteSegmentsController that implements UITableViewDelegate protocol. In method didSelectRowAtIndexPath update the current route segment.
Create a Controller That Renders a Map and Utilizes Interaction Between a Route Rendered on the Map and the Selected Instructions
Start by creating a controller class AdvancedDirectionsController that inherits from UIViewController, MPMapControlDelegate and MPDirectionsRendererDelegate.
Create a setupRouteNav method that instantiates RouteSegmentsController and adds it as a child view controller. Assign this controller as its delegate.
Let's do a couple of extensions for the map interactions. First implement the RouteSegmentsControllerDelegate through an extension. In didSelectRouteSegment update the leg index for the directions renderer.