Although not as popular as Google, Mapbox provides a very useful Places API which many of you may prefer since Google APIs introduced billing on their services. Mapbox doesn’t provide an iOS SDK for Places, so we will be using the endpoints based on their Geocoder API, to make our requests and fetch places.
Getting Started
Before you begin, Sign up for an account at mapbox.com/signup. Find your access token on your account page.
Open info.plist file and the following key, with value as the access token you received from MapBox in the first step.
<key>MGLMapboxAccessToken</key> <string>YOUR_TOKEN</string>
Assuming you have created your XCode project, add these pods to your Podfile.
pod 'MapboxGeocoder.swift', '~> 0.10' pod 'Alamofire' pod 'Alamofire-SwiftyJSON'
We will be using Alamofire for our requests to Mapbox API endpoints, and SwiftyJSON for parsing the responses.
In your terminal, run ‘pod install’.
Setting up the UI
Assuming you have created your Xcode project, add a UIViewController, make sure it is embedded inside a UINavigationController, since we will be adding our UISearchBar to the Navigation Bar.
Add a UITableView to completely fill the view, and create an outlet for it in your ViewController Swift class file.
Import the necessary modules
import UIKit import Alamofire import SwiftyJSON import Alamofire_SwiftyJSON
Implement the delegates
class PlacesSearchVC: UIViewController, UITableViewDelegate, UITableViewDataSource, UISearchBarDelegate { override func viewDidLoad() { super.viewDidLoad() } }
Inside the class, declare the objects
@IBOutlet var tableView: UITableView! //outlet to the tableview you created in storyboard var searchActive : Bool = false //for controlling search states var searchBar:UISearchBar? //the searchbar to be added in navigation bar var searchedPlaces: NSMutableArray = [] //array to store the places returned in response let decoder = JSONDecoder() //for decoding data returned by API
Add searchBar to the navigation bar in viewDidLoad()
if self.searchBar == nil { self.searchBar = UISearchBar() self.searchBar!.searchBarStyle = UISearchBarStyle.prominent self.searchBar!.tintColor = Helper.UIColorFromRGB(rgbValue: 0x000000) self.searchBar!.barTintColor = Helper.UIColorFromRGB(rgbValue: 0xffffff) self.searchBar!.delegate = self self.searchBar!.placeholder = "Search for place"; } self.navigationItem.titleView = searchBar
Create delegate functions of the UISearchBar to handle the events
func searchBarCancelButtonClicked(_ searchBar: UISearchBar) { self .cancelSearching() searchActive = false; } func searchBarSearchButtonClicked(_ searchBar: UISearchBar) { self.view.endEditing(true) } func searchBarTextDidBeginEditing(_ searchBar: UISearchBar) { self.searchBar!.setShowsCancelButton(true, animated: true) } func searchBarTextDidEndEditing(_ searchBar: UISearchBar) { self.searchBar!.setShowsCancelButton(false, animated: false) } func cancelSearching(){ searchActive = false; self.searchBar!.resignFirstResponder() self.searchBar!.text = "" }
Now let’s add the main method that will call the search function. We want to wait a moment until the user pauses typing, and then fire the method, and in doing so, cancel all previous requests that may have been triggered.
func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) { NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(self.searchMe), object: nil) self.perform(#selector(self.searchMe), with: nil, afterDelay: 0.5) if(searchBar.text!.isEmpty){ searchActive = false; } else { searchActive = true; } } @objc func searchMe() { if(searchBar?.text!.isEmpty)!{ } else { self.searchPlaces(query: (searchBar?.text)!) } }
Searching
For searching, create the API URL first. Define the mapbox endpoint and the access token as strings
static var mapbox_api = "https://api.mapbox.com/geocoding/v5/mapbox.places/" static var mapbox_access_token = "YOUR_ACCESS_TOKEN_HERE"
Create a new class Feature.swift and define the structure of the response, so that we are able to parse the response directly.
import UIKit struct Feature: Codable { var id: String! var type: String? var matching_place_name: String? var place_name: String? var geometry: Geometry var center: [Double] var properties: Properties } struct Geometry: Codable { var type: String? var coordinates: [Double] } struct Properties: Codable { var address: String? }
Now create the search function.
@objc func searchPlaces(query: String) { let urlStr = "\(mapbox_api)\(query).json?access_token=\(mapbox_access_token)" Alamofire.request(urlStr, method: .get, parameters: nil, encoding: URLEncoding.default, headers: nil).responseSwiftyJSON { (dataResponse) in if dataResponse.result.isSuccess { let resJson = JSON(dataResponse.result.value!) if let myjson = resJson["features"].array { for itemobj in myjson ?? [] { try? print(itemobj.rawData()) do { let place = try self.decoder.decode(Feature.self, from: itemobj.rawData()) self.searchedPlaces.add(place) self.tableView.reloadData() } catch let error { if let error = error as? DecodingError { print(error.errorDescription) } } } } } if dataResponse.result.isFailure { let error : Error = dataResponse.result.error! } } }
That’s it! We have fetched the data and parsed it and saved it into the array. Now let’s write the code to display it into the table.
Displaying the data in the table
Write the UITableView delegate methods
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { let cell = UITableViewCell.init(style: .subtitle, reuseIdentifier: "cell") cell.detailTextLabel?.textColor = UIColor.darkGray let pred = self.searchedPlaces.object(at: indexPath.row) as! Feature cell.textLabel?.text = pred.place_name! if let add = pred.properties.address { cell.detailTextLabel?.text = add } else { } cell.imageView?.image = UIImage.init(icon: .fontAwesome(.mapMarker), size: CGSize(width:30,height:30)) return cell } func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { return 60.0 }
If you want to get coordinates from this Feature object, here’s how you do it
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { let cell = tableView.cellForRow(at: indexPath) let pred = self.searchedPlaces.object(at: indexPath.row) as! Feature let coord = CLLocationCoordinate2D.init(latitude: pred.geometry.coordinates[1], longitude: pred.geometry.coordinates[0]) }
…
Leave A Comment