source 'https://github.com/baato/BaatoPodSpec.git'
source 'https://github.com/CocoaPods/Specs.git'
target '${YourApp}' do
use_frameworks!
# Pods for ${YourApp}
pod 'BaatoSwift', '~> ${LatestVersion}'
# You can use latest mapbox-ios-sdk and mapboxnavigation
pod 'Mapbox-iOS-SDK', '~> 6.2.1'
pod 'MapboxNavigation', '~> 1.1'
end
Usage examples
Go to project target -> Signing & Capabilities, add the following Background Modes from Capability- Audio, Airplay, and Picture in Picture
- Location updates
Map and navigation delegates
Add a delegate method MGLMapViewDelegate, NavigationMapViewDelegate and NavigationViewControllerDelegate. And initialize router, service and mapstyle.class ViewController: UIViewController, MGLMapViewDelegate, NavigationViewControllerDelegate, NavigationMapViewDelegate {
// Initializing parameter for navigation
let mytStyle = DayStyle()
var navigationViewController: NavigationViewController?
var navigationService: NavigationService!
var route: Route!
var router: LegacyRouteController!
var startPoint = MGLPointAnnotation()
var endPoint = MGLPointAnnotation()
}
Requesting a baato route
We have to parse directionRoute for mapbox navigation with the help of ResType and RouteResDecoder class. Create a copy of the following classesResType.swift
import Foundation
// MARK: - Welcome
public class ResType: NSObject, NSCoding, Codable {
let timestamp: String
let status: Int
let message: String
var data: RouteResDecoder
// coding keys for Decodable
enum CodingKeys: String, CodingKey {
case timestamp
case status
case message
case data
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(timestamp, forKey: .timestamp)
try container.encode(status, forKey: .status)
try container.encode(message, forKey: .message)
}
// MARK: Decodable
required public init(from decoder: Decoder) throws {
// get container
let container = try decoder.container(keyedBy: CodingKeys.self)
// get properties
timestamp = try container.decode(String.self, forKey: .timestamp)
status = try container.decode(Int.self, forKey: .status)
message = try container.decode(String.self, forKey: .message)
data = try container.decode(RouteResDecoder.self, forKey: .data)
}
public func encode(with aCoder: NSCoder) {
aCoder.encode(timestamp, forKey: CodingKeys.timestamp.rawValue)
aCoder.encode(status, forKey: CodingKeys.status.rawValue)
aCoder.encode(message, forKey: CodingKeys.message.rawValue)
aCoder.encode(data, forKey: CodingKeys.data.rawValue)
}
init(timestamp: String, status: Int, message: String, data: RouteResDecoder) {
self.timestamp = timestamp
self.status = status
self.message = message
self.data = data
}
required public init?(coder aDecoder: NSCoder) {
timestamp = aDecoder.decodeObject(forKey: CodingKeys.timestamp.rawValue) as! String
status = aDecoder.decodeInteger(forKey: CodingKeys.status.rawValue)
message = aDecoder.decodeObject(forKey: CodingKeys.message.rawValue) as! String
data = aDecoder.decodeObject(forKey: CodingKeys.data.rawValue) as! RouteResDecoder
}
}
RouteResDecoder.swift
import Foundation
import MapboxDirections
import MapboxCoreNavigation
public struct RouteResDecoder {
public let httpResponse: HTTPURLResponse?
public let identifier: String?
public var routes: [Route]?
public let waypoints: [Waypoint]?
/**
The time when this `RouteResponse` object was created, which is immediately upon recieving the raw URL response.
If you manually start fetching a task returned by `Directions.url(forCalculating:)`, this property is set to `nil`; use the `URLSessionTaskTransactionMetrics.responseEndDate` property instead. This property may also be set to `nil` if you create this result from a JSON object or encoded object.
This property does not persist after encoding and decoding.
*/
public var created: Date = Date()
}
extension RouteResDecoder: Codable {
enum CodingKeys: String, CodingKey {
case code
case message
case error
case identifier = "uuid"
case routes
case waypoints
}
public init(httpResponse: HTTPURLResponse?, identifier: String? = nil, routes: [Route]? = nil, waypoints: [Waypoint]? = nil) {
self.httpResponse = httpResponse
self.identifier = identifier
self.routes = routes
self.waypoints = waypoints
}
public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
self.httpResponse = decoder.userInfo[.httpResponse] as? HTTPURLResponse
self.identifier = try container.decodeIfPresent(String.self, forKey: .identifier)
// Decode waypoints from the response and update their names according to the waypoints from DirectionsOptions.waypoints.
let decodedWaypoints = try container.decodeIfPresent([Waypoint?].self, forKey: .waypoints)?.compactMap{ $0 }
var optionsWaypoints: [Waypoint] = []
if let decodedWaypoints = decodedWaypoints {
// The response lists the same number of tracepoints as the waypoints in the request, whether or not a given waypoint is leg-separating.
waypoints = zip(decodedWaypoints, optionsWaypoints).map { (pair) -> Waypoint in
let (decodedWaypoint, waypointInOptions) = pair
let waypoint = Waypoint(coordinate: decodedWaypoint.coordinate,
coordinateAccuracy: waypointInOptions.coordinateAccuracy,
name: waypointInOptions.name?.nonEmptyString ?? decodedWaypoint.name)
waypoint.targetCoordinate = waypointInOptions.targetCoordinate
waypoint.heading = waypointInOptions.heading
waypoint.headingAccuracy = waypointInOptions.headingAccuracy
waypoint.separatesLegs = waypointInOptions.separatesLegs
waypoint.allowsArrivingOnOppositeSide = waypointInOptions.allowsArrivingOnOppositeSide
return waypoint
}
waypoints?.first?.separatesLegs = true
waypoints?.last?.separatesLegs = true
} else {
waypoints = decodedWaypoints
}
if let routes = try container.decodeIfPresent([Route].self, forKey: .routes) {
// Postprocess each route.
for route in routes {
route.routeIdentifier = identifier
// Imbue each route’s legs with the waypoints refined above.
// if let waypoints = waypoints {
// route.legSeparators = waypoints.filter { $0.separatesLegs }
// }
}
self.routes = routes
} else {
routes = nil
}
}
public func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encodeIfPresent(identifier, forKey: .identifier)
try container.encodeIfPresent(routes, forKey: .routes)
try container.encodeIfPresent(waypoints, forKey: .waypoints)
}
}
// Initialize Baato with your token
let apis = BaatoSwift.API.init(token: "YOUR_BAATO_ACCESS_TOKEN")
//startPoint and endPoint are MGLPointAnnotation()
apis.startLat = startPoint.coordinate.latitude
apis.startLon = startPoint.coordinate.longitude
apis.destLat = endPoint.coordinate.latitude
apis.destLon = endPoint.coordinate.longitude
apis.navMode = navMode
apis.navInstructions = true
apis.getMapboxDirections{(result) in
self.manager.dismissAndCallCompletionBlocks(withCategory: .none)
switch result {
case .success (let data):
guard let data = data else {
return
}
let origin = Waypoint(coordinate: self.startPoint.coordinate, name: "Origin")
let destination = Waypoint(coordinate: self.endPoint.coordinate, name: "Destination")
let routeOptions = NavigationRouteOptions(waypoints: [origin, destination])
routeOptions.distanceMeasurementSystem = .metric
let decoder = JSONDecoder()
decoder.userInfo = [.options: routeOptions]
guard let value = try? decoder.decode(ResType.self, from: data) else {
print("Error parsing JSON")
return
}
//save a route object so that it can be used while rerouting
self.route = value.data.routes![0]
//self.drawRoute(route: (value.data.routes![0].shape)!)
//self.bottomRouteInfo()
self.startNavigation(startPoint: startPoint, endPoint: endPoint)
case .failure (let error) :
print(error.localizedDescription)
self.mapError(error: error)
self.handleRouteEvent()
}
}
Starting navigation
Create and assign parameter to navigationviewcontroller private func startNav(startPoint: MGLPointAnnotation, endPoint: MGLPointAnnotation){
let origin = Waypoint(coordinate: startPoint.coordinate, name: "My Location")
let destination = Waypoint(coordinate: endPoint.coordinate, name: "Destination")
routeOptions = NavigationRouteOptions(waypoints: [origin, destination])
routeOptions!.distanceMeasurementSystem = .metric
navigationService = MapboxNavigationService(route: self.route, routeIndex: 0, routeOptions: routeOptions!, simulating: .onPoorGPS, routerType: LegacyRouteController.self)
router = LegacyRouteController(along: self.route, routeIndex: 0, options: routeOptions!, dataSource: navigationService)
let navigationOptions = NavigationOptions(styles: [mytStyle], navigationService: navigationService)
self.navigationViewController = NavigationViewController(for: self.route, routeIndex: 0, routeOptions: routeOptions!, navigationOptions: navigationOptions)
self.navigationViewController?.modalPresentationStyle = .fullScreen
self.navigationViewController?.delegate = self
// self.navigationService?.delegate = self
self.navigationViewController?.routeLineTracksTraversal = false
self.navigationViewController?.showsReportFeedback = false
self.navigationViewController?.mapView?.logoView.image = UIImage(named:"baatologo")
self.navigationViewController?.mapView?.attributionButton.isHidden = true
self.present(self.navigationViewController!, animated: false, completion: nil)
}
public init(accessToken token: String? = nil, host: URL? = nil) {
self.accessToken = "pk.xxx"
self.host = URL(string: "https://api.baato.io")!
}
var usesMetric: Bool {
get {
return true
}
}
Re-routing
Override shouldReroute from NavigationViewControllerDelegateextension ViewController: NavigationViewControllerDelegate {
func navigationViewController(_ navigationViewController: NavigationViewController, didRerouteAlong route: Route) {
print("reroute captured")
}
// Never reroute internally. Instead,
// 1. Fetch a route from your server
// 2. Map Match the coordinates from your server
// 3. Set the route on your server
func navigationViewController(_ navigationViewController: NavigationViewController, shouldRerouteFrom location: CLLocation) -> Bool {
let routeOptions = NavigationRouteOptions(waypoints: [Waypoint(location: location), self.routeOptions!.waypoints.last!])
let startPoint = MGLPointAnnotation()
guard let userLocation = self.mView.userLocation else {
return false
}
startPoint.coordinate = userLocation.coordinate
startPoint.title = "Origin"
apis.startLat = startPoint.coordinate.latitude
apis.startLon = startPoint.coordinate.longitude
apis.destLat = endPoint.coordinate.latitude
apis.destLon = endPoint.coordinate.longitude
apis.getMapboxDirections{(result) in
self.manager.dismissAndCallCompletionBlocks(withCategory: .none)
switch result {
case .success (let data):
guard let data = data else {
return
}
let origin = Waypoint(coordinate: self.startPoint.coordinate, name: "Origin")
let destination = Waypoint(coordinate: self.endPoint.coordinate, name: "Destination")
let routeOptions = NavigationRouteOptions(waypoints: [origin, destination])
let decoder = JSONDecoder()
routeOptions.distanceMeasurementSystem = .metric
decoder.userInfo = [.options: routeOptions]
guard let value = try? decoder.decode(ResType.self, from: data) else {
print("Error parsing JSON")
return
}
let route = value.data.routes![0]
let router = self.navigationService.router! as! LegacyRouteController
self.router = router
let routeProgress = RouteProgress(route: route, routeIndex: 0, options: routeOptions)
self.router.routeProgress = routeProgress
case .failure (let error) :
print(error.localizedDescription)
}
}
return false
}
func navigationViewControllerDidDismiss(_ navigationViewController: NavigationViewController, byCanceling canceled: Bool) {
navigationService.stop()
dismiss(animated: true, completion: nil)
}
}