Skip to main content

iOS MapKit Tutorial: Getting Started Search Location and Directions on Mapkit



MapKit is a really useful API available on iOS devices that makes it easy to display maps, user locations with locality, and even draw routes your location to your destination.
iOS MapKit Tutorial

Getting Started User Location


Create a new project single view appplication in Swift. - MKLocationMap


Open Info.plist from the Project navigator and configure the location permission prompt message NSLocationWhenInUseUsageDescription

Add "NSLocationAlwaysAndWhenInUseUsageDescription" instead of "Privacy - Location Always and When in use usage description".


ViewController.swift:
import UIKit
import MapKit

protocol HandleMapSearch {
    func dropPinZoomIn(placemark:MKPlacemark)
}
class ViewController: UIViewController{
    let locationManager = CLLocationManager()
    var resultSearchController:UISearchController!
    var selectedPin:MKPlacemark?
  
    @IBOutlet var mapView: MKMapView!
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view, typically from a nib.
        locationManager.delegate = self
        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.requestLocation()
        let locationSearchTable = storyboard!.instantiateViewController(withIdentifier: "LocationSearchTable") as! LocationSearchTable
        locationSearchTable.mapView = mapView
        resultSearchController = UISearchController(searchResultsController: locationSearchTable)
        resultSearchController?.searchResultsUpdater = locationSearchTable
        locationSearchTable.handleMapSearchDelegate = self
        let searchBar = resultSearchController!.searchBar
        searchBar.sizeToFit()
        searchBar.placeholder = "Search for places"
        navigationItem.titleView = resultSearchController?.searchBar
        resultSearchController?.hidesNavigationBarDuringPresentation = false
        resultSearchController?.dimsBackgroundDuringPresentation = true
        definesPresentationContext = true
        locationSearchTable.mapView = mapView
        locationSearchTable.handleMapSearchDelegate = self
    }
    @objc func getDirections(){
        guard let selectedPin = selectedPin else {
            return
        }
        let mapItem = MKMapItem(placemark: selectedPin)
        let launchOptions = [MKLaunchOptionsDirectionsModeKey: MKLaunchOptionsDirectionsModeDriving]
        mapItem.openInMaps(launchOptions: launchOptions)
        print ("GET DIRECTION")
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }
}
extension ViewController : CLLocationManagerDelegate {
    func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) {
        print("error:: \(error.localizedDescription)")
    }
 func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
        if status == .authorizedWhenInUse {
            locationManager.requestLocation()
        }
    }
  func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) {
        if let location = locations.first {
            let span = MKCoordinateSpanMake(0.05, 0.05)
            let region = MKCoordinateRegion(center: location.coordinate, span: span)
            mapView.setRegion(region, animated: true)
        }
    }
}
extension ViewController: HandleMapSearch {
    func dropPinZoomIn(placemark:MKPlacemark){
        // cache the pin
        selectedPin = placemark
        // clear existing pins
        mapView.removeAnnotations(mapView.annotations)
        let annotation = MKPointAnnotation()
        annotation.coordinate = placemark.coordinate
        annotation.title = placemark.name
        if let city = placemark.locality,
           let state = placemark.administrativeArea {
            annotation.subtitle = "\(city) \(state)"
        }
        mapView.addAnnotation(annotation)
        let span = MKCoordinateSpanMake(0.05, 0.05)
        let region = MKCoordinateRegionMake(placemark.coordinate, span)
        mapView.setRegion(region, animated: true)
    }
}

In the Project Navigator, open Assets.xcassets and create a add image icon like used car.png files of various sizes (30×30, 60×60, and 90×90) to fill up the 1x, 2x, and 3x




extension ViewController : MKMapViewDelegate {
    
    func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?{
        if annotation is MKUserLocation {
            //return nil so map view draws "blue dot" for standard user location
            return nil
        }
        let reuseId = "pin"
        var pinView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId) as? MKPinAnnotationView
        if pinView == nil {
        pinView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: reuseId)
        }
        pinView?.pinTintColor = UIColor.orange
        pinView?.canShowCallout = true
        let smallSquare = CGSize(width: 30, height: 30)
        let button = UIButton(frame: CGRect(origin: CGPoint.zero, size: smallSquare))
        button.setBackgroundImage(UIImage(named: "car"), for: .normal)
        button.addTarget(self, action: #selector(ViewController.getDirections), for: .touchUpInside)
        pinView?.leftCalloutAccessoryView = button
        return pinView

    }
}


LocationSearchTable.swift:

import UIKit
import MapKit

class LocationSearchTable: UITableViewController{
    
    var matchingItems:[MKMapItem] = []
    var mapView: MKMapView?
    var handleMapSearchDelegate: HandleMapSearch?
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    func parseAddress(selectedItem:MKPlacemark) -> String {
        // put a space between "4" and "Melrose Place"
        let firstSpace = (selectedItem.subThoroughfare != nil && selectedItem.thoroughfare != nil) ? " " : ""
        // put a comma between street and city/state
        let comma = (selectedItem.subThoroughfare != nil || selectedItem.thoroughfare != nil) && (selectedItem.subAdministrativeArea != nil || selectedItem.administrativeArea != nil) ? ", " : ""
        // put a space between "Washington" and "DC"
        let secondSpace = (selectedItem.subAdministrativeArea != nil && selectedItem.administrativeArea != nil) ? " " : ""
        let addressLine = String(
            format:"%@%@%@%@%@%@%@",
            // street number
            selectedItem.subThoroughfare ?? "",
            firstSpace,
            // street name
            selectedItem.thoroughfare ?? "",
            comma,
            // city
            selectedItem.locality ?? "",
            secondSpace,
            // state
            selectedItem.administrativeArea ?? ""
        )
        return addressLine
    }
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
        // Dispose of any resources that can be recreated.
    }

}

extension LocationSearchTable : UISearchResultsUpdating {
    
    func updateSearchResults(for searchController: UISearchController) {
        guard let mapView = mapView,
        let searchBarText = searchController.searchBar.text else { return }
        let request = MKLocalSearchRequest()
        request.naturalLanguageQuery = searchBarText
        request.region = mapView.region
        let search = MKLocalSearch(request: request)
        search.start { response, _ in
            guard let response = response else {
                return
            }
            self.matchingItems = response.mapItems
            self.tableView.reloadData()
        }
    }
}
extension LocationSearchTable {
    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return matchingItems.count
    }
    override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
                let cell = tableView.dequeueReusableCell(withIdentifier: "cell")!
                let selectedItem = matchingItems[indexPath.row].placemark
                cell.textLabel?.text = selectedItem.name
        cell.detailTextLabel?.text = parseAddress(selectedItem: selectedItem)
                return cell
            }
}
extension LocationSearchTable {
    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        
        let selectedItem = matchingItems[indexPath.row].placemark
        handleMapSearchDelegate?.dropPinZoomIn(placemark: selectedItem)
        dismiss(animated: true, completion: nil)
    }
}


Comments

  1. I do trust all of the concepts you’ve presented on your post. They’re really convincing and will definitely work. Still, the posts are too brief for newbies. May you please extend them a little from subsequent time?Also, I’ve shared your website in my social networks.
    Mobile App Development Company in Dubai
    Android App Development Company in Dubai

    ReplyDelete
  2. Thanks for sharing Information to us. If someone wants to know about,I think this is the right place for you!

    mobile app development in coimbatore
    mobile app development company in atlanta
    3d animation company

    ReplyDelete
  3. You just copied someone else's tutorial and added directions to it, you should at least give him some credit.
    Also the location autocomplete function here works, but there are newer functions that give more precise results.

    ReplyDelete

Post a Comment

Popular posts from this blog

Add a Scene Delegate to your existing project with Storyboard in Swift

To add a scene delegate, first, create a new Swift file that you’ll call "SceneDelegate" containing a subclass of UIResponder, just like the AppDelegate, and that conforms to UIWindowSceneDelegate.  As your app might supports other versions than iOS 13, make this class only available for iOS 13. This is what you should have : If you are working a project that is storyboard based, please set storyboard  initial view controller SceneDelegate.swift import UIKit @available ( iOS 13.0 , *) class SceneDelegate : UIResponder , UIWindowSceneDelegate {     var window : UIWindow ?     func scene ( _ scene: UIScene , willConnectTo session: UISceneSession , options connectionOptions: UIScene . ConnectionOptions ) {                  let storyboard = UIStoryboard (name: "Main" , bundle: nil )         let initialViewController = storyboard. instantiateViewController (withIdentifier: "ViewController" )         let mainNavigationController = UINavigationControlle

How Create Animated Circle Progress Bar iOS 11 Swift 4

  Animated Circle Progress Bar iOS 11 Swift 4 With MBCircularProgressBa r - https://github.com/MatiBot/MBCircularProgressBar A circular, animatable & highly customizable progress bar from the Interface Builder Swift, Using pod fite MBCircularProgressBar Installation Cocoapods terminal. pod "MBCircularProgressBar" That - A Simple Steps to installed pod file -        Open terminal        Command on terminal go to project folder Cd path        set your project path on terminal.        command : pod init        open pod file - open -e podfile        added in pod file with in : pod "MBCircularProgressBar"        Command : Pod install        Close project of Xcode        open your Project from terminals        Command : open PodDemos.xcworkspace After opern StoryBoard and Now drag a UIView over the viewController in storyboard Or set UIView Constraint width, height or verticle or horzentail space and set a class MBCircul

How to Use Multiple Sections in UITableView iOS Swift !

Multiple sections in UITableView iOS Swift. UITableView is very important part of iOS ecosystem. So we split tableviews in sections. Then its easier to find right information.  1. First let’s create a project as usual. Create a new single view application X code project. Set project name to UIViewController.  2. Go to main storyboard & select view controller & use UITableView 3. Select tableview & make it initial view controller  4 Create a custom Sections Class like Name => TableSections, create register cell static return “ getCellNibs ” method. Then create  4 section enum “TableItems” then after append all sections to an array model. import UIKit struct CellNib {      static func getCellNibs () -> [ String ] {          return [ "Cell1" , "Cell2" , "Cell3" , "Cell4" ]     } } enum TableItems : Int {      case TableSections1      case TableSections2      case TableSections3      case TableSections4 } class TableSec