fb-iso
Swift Tutorial: Build a Facebook Album Browser
July 9, 2014
Agile Software Development Workshop Invitation
SELISE School Workshop on Agile Software Development
August 31, 2014
Show all

Swift Tutorial – make your own iOS MapKit application

mapkit-feature

In today’s Swift tutorial, we will explore MapKit. MapKit is a nice little framework developed by Apple. We will learn to integrate this MapKit with the Google Map javascript api and thus develop a complete application using Swift, like the one you can see below:

App Description

The very first view will have a textbox. The user must first input their Area and City. Then the application will give the user a scope to select a category from which to search further. The categories can be things like restaurants, airports, hospitals, mosques, churches, atm’s, banks, and so on.

After a category has been chosen, the app will redirect the user to the mapview, where they can see the different markers/annotations of their individual results. Clicking each marker will open a custom flyout where he will see the name, the address and an icon of that place.

There are, of course, a lot of similar applications out there; many are natively available in Google play and the App Store.

To develop such an app by ourselves, we need to concentrate on the following things:

  • Configuring the Google App Console to use Google Map Javascript API with an API Key

  • Having a basic knowledge about MapKit

  • Google Map geolocation searcher API

  • If required, implementing custom flyout and annotation using MKMapViewDelegate

  • Having a basic knowledge of Swift

Configure Google App Console to use Google Map Javascript Api with a API Key

Go to Google Developers Console ; and create a project. Remember, you need to login first to use Google’s Console!

Once that is done, from your project, go to the Apps and Auth section and enable Google MAP related API.

Now you must create a key inside Credentials/Public API Access section. You are creating this API for the browser, and nothing else. After successfully completing this, you should see an API key. This API key is required to call different google APIs.

MapKit

The Mapkit framework makes it easy working with maps, and is highly customizable. You can pretty much do whatever you like on it. To enable the framework, drag a UKMapView to storyboard and link “MapKit framework” to your project

Start View/Search View

Start View/Search View of the app is very simple as well. I have put in a TextView where the user is supposed to input the area and the city. Upon pressing the return key, it will internally call Google Map API  to search with this area and city and retrieve a GeoLocation (Longitude, Latitude). If the Google API failed to retrieve a valid geolocation, then it is highly likely that the information put in were not valid, or were incorrect.

After obtaining the geolocation, the user will see a list from where they can pick a category, let’s say for example, Restaurants. What the app does after this is basically call another Google API and do a nearby search (5km) for all the places that fit the category, and then display them in a MapView.

Please note, that for displaying the categories, I have used TableView. The code of our SearchView are as follows:

//<br />
// ViewController.swift<br />
// AddressMap<br />
//<br />
// Created by md arifuzzaman on 7/15/14.<br />
// Copyright (c) 2014 md arifuzzaman. All rights reserved.<br />
//</p>
<p>import UIKit<br />
import MapKit</p>
<p>class ViewController: UIViewController,UITextFieldDelegate, UITableViewDataSource, UITableViewDelegate {</p>
<p>let apiKey = "" //here user your own api key<br />
let mapHelper:MapHelper = MapHelper();<br />
var autoCompleteTableView:UITableView;<br />
var autoCompleteDataSource:Array = [];<br />
var mapData:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0)<br />
var searchBy:String = ""</p>
<p>init(coder aDecoder: NSCoder!) {<br />
autoCompleteTableView = UITableView(frame: CGRectMake(13, 200, 295, 200), style: UITableViewStyle.Plain);<br />
super.init(coder: aDecoder);<br />
}</p>
<p>@IBOutlet var txtSearch: UITextField<br />
override func viewDidLoad() {<br />
super.viewDidLoad()<br />
txtSearch.delegate = self;<br />
NSNotificationCenter.defaultCenter()?.addObserver(self, selector: Selector("dataLoaded:"), name: "DataLoaded", object: nil);<br />
autoCompleteTableView.dataSource = self;<br />
autoCompleteTableView.delegate = self;<br />
autoCompleteTableView.backgroundColor = UIColor.clearColor();<br />
autoCompleteTableView.separatorColor = UIColor.clearColor();</p>
<p>autoCompleteDataSource.append("Restaurant");<br />
autoCompleteDataSource.append("Airport");<br />
autoCompleteDataSource.append("Atm");<br />
autoCompleteDataSource.append("Bank");<br />
autoCompleteDataSource.append("Curch");<br />
autoCompleteDataSource.append("Hospital");<br />
autoCompleteDataSource.append("Mosque");<br />
autoCompleteDataSource.append("Movie_theater");<br />
autoCompleteTableView.hidden = true;</p>
<p>self.view.addSubview(autoCompleteTableView);</p>
<p>}</p>
<p>override func viewWillAppear(animated: Bool) {<br />
super.viewWillAppear(animated);<br />
//autoCompleteTableView.hidden = true;<br />
}</p>
<p>func tableView(tableView: UITableView!, didSelectRowAtIndexPath indexPath: NSIndexPath!){</p>
<p>searchBy = self.autoCompleteDataSource[indexPath.row];<br />
let searchkey = self.autoCompleteDataSource[indexPath.row].lowercaseString;</p>
<p>let serviceHelper = ServiceHelper();<br />
let dynamicURL = "https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=(mapData.latitude),(mapData.longitude)&amp;radius=5000&amp;types=(searchkey)&amp;sensor=true&amp;key=(apiKey)"<br />
print(dynamicURL);<br />
serviceHelper.getServiceHandle(self.dataLoaded, url: dynamicURL);</p>
<p>}</p>
<p>func tableView(tableView: UITableView!, numberOfRowsInSection section: Int) -&gt; Int{<br />
return self.autoCompleteDataSource.count;<br />
}</p>
<p>// Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier:<br />
// Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls)</p>
<p>func tableView(tableView: UITableView!, cellForRowAtIndexPath indexPath: NSIndexPath!) -&gt; UITableViewCell!{<br />
var cell:UITableViewCell?;<br />
let cellString = "autocompletecell";<br />
if let cellToUse = cell{</p>
<p>}<br />
else{<br />
cell = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: cellString);<br />
}</p>
<p>let data = autoCompleteDataSource[indexPath.row];<br />
var img:UIImage?;</p>
<p>if(data == "Restaurant"){<br />
img = UIImage(named: "restaurent.jpg");</p>
<p>}<br />
else if(data == "Airport"){<br />
img = UIImage(named: "airport.png");<br />
}<br />
else if(data == "Hospital"){<br />
img = UIImage(named: "hospital.png");<br />
}<br />
else if(data == "Mosque"){<br />
img = UIImage(named: "mosque.png");<br />
}<br />
else if(data == "Curch"){<br />
img = UIImage(named: "church-icon.png");<br />
}<br />
else if(data == "Atm"){<br />
img = UIImage(named: "atm.png");<br />
}<br />
else if(data == "Bank"){<br />
img = UIImage(named: "bank.png");<br />
}<br />
else if(data == "Movie_theater"){<br />
img = UIImage(named: "cinema.png");<br />
}<br />
else{<br />
img = UIImage(named: "noimage.gif");<br />
}</p>
<p>cell!.imageView.image = img!;<br />
cell!.textLabel.text = data;<br />
cell!.backgroundColor = UIColor(red: 100, green: 100, blue: 190, alpha: 0.5);</p>
<p>return cell!;<br />
}</p>
<p>override func didReceiveMemoryWarning() {<br />
super.didReceiveMemoryWarning()<br />
// Dispose of any resources that can be recreated.<br />
}</p>
<p>func textFieldShouldReturn(textField: UITextField!) -&gt; Bool{<br />
txtSearch.resignFirstResponder();<br />
self.doSearch()<br />
return true;<br />
}</p>
<p>func doSearch(){</p>
<p>dispatch_async(dispatch_get_main_queue()){<br />
let searchText = self.txtSearch.text.stringByTrimmingCharactersInSet(NSCharacterSet(charactersInString: " "))<br />
if(searchText.isEmpty){<br />
return;<br />
}</p>
<p>self.mapData = self.mapHelper.geoCodeUsingAddress(searchText);<br />
if(self.mapData.latitude &lt;= 0.0 &amp;&amp; self.mapData.longitude &lt;= 0.0){<br />
var alert = UIAlertController(title: "Warning", message: "Unable to find any location", preferredStyle: UIAlertControllerStyle.Alert);<br />
alert.addAction(UIAlertAction(title: "Ok", style: UIAlertActionStyle.Default, handler: nil));<br />
self.presentViewController(alert, animated: true, completion: nil);<br />
self.autoCompleteTableView.hidden = true;<br />
return;<br />
}<br />
self.autoCompleteTableView.hidden = false;</p>
<p>}</p>
<p>}</p>
<p>func dataLoaded(userData:SearchModel[]){<br />
//print(userData);</p>
<p>let storyBoard = UIStoryboard(name: "Main", bundle: nil);<br />
let mapController:MapViewController = storyBoard.instantiateViewControllerWithIdentifier("mapViewController") as MapViewController;<br />
mapController.mapData = userData;<br />
mapController.searchBy = self.searchBy;<br />
self.navigationController.pushViewController(mapController, animated: true);</p>
<p>}</p>
<p>}

That’s it, very simple!

Two very important Google APIs that have been used here are:

1. http://maps.google.com/maps/api/geocode/json?sensor=false&address=[enter your address here]

2. https://maps.googleapis.com/maps/api/place/nearbysearch/json?location=[lat,lon]&radius=5000&types=[category]&sensor=true&key=[enter your API key here]

 MapViewController

MapViewController is responsible for displaying the MapData as returned by Google. I would like to point out an interesting aspect of this controller. As you can see, we implemented MKMapViewDelegate. The reason behind this is, when we click a marker, the app will produce a popup with a custom view with different useful information. I implemented didSelectAnnotationView which is supposed to fire on each click.  The details is given below:

 

//<br />
// MapViewController.swift<br />
// AddressMap<br />
//<br />
// Created by md arifuzzaman on 7/15/14.<br />
// Copyright (c) 2014 md arifuzzaman. All rights reserved.<br />
//</p>
<p>import UIKit<br />
import MapKit;</p>
<p>class MapViewController: UIViewController, MKMapViewDelegate {</p>
<p>@IBOutlet var _mapCtl: MKMapView</p>
<p>var searchBy:String = "";<br />
var mapData:SearchModel[]?;<br />
var customAnotations:CustomAnotation[] = CustomAnotation[]();</p>
<p>init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: NSBundle?) {<br />
super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)<br />
// Custom initialization<br />
}</p>
<p>init(coder aDecoder: NSCoder!) {<br />
super.init(coder: aDecoder);<br />
}</p>
<p>func mapView(mapView: MKMapView!, didDeselectAnnotationView view: MKAnnotationView!)<br />
{<br />
for childView:AnyObject in view.subviews{<br />
childView.removeFromSuperview();<br />
}<br />
}</p>
<p>func mapView(mapView: MKMapView!, didSelectAnnotationView view: MKAnnotationView!){<br />
if(!view.annotation.isKindOfClass(MKUserLocation)){<br />
let flyOutView:CustomFlyout = (NSBundle.mainBundle().loadNibNamed("CustomFlyout", owner: self, options: nil))[0] as CustomFlyout;<br />
var calloutViewFrame = flyOutView.frame;<br />
calloutViewFrame.origin = CGPointMake(-calloutViewFrame.size.width/2 + 15, -calloutViewFrame.size.height);<br />
flyOutView.frame = calloutViewFrame;</p>
<p>let customAnotation = view.annotation as CustomAnotation;<br />
let model = customAnotation.searchModel;</p>
<p>flyOutView.lblTitle.text = model!.name;<br />
let url = NSURL(string: model!.icon);<br />
let urlData = NSData(contentsOfURL: url);<br />
let img = UIImage(data: urlData);<br />
flyOutView.lblIcon.image = img;<br />
flyOutView.lblPosition.text = model!.address; //"Longitude: (model!.lon) &amp; Latitude: (model!.lat)";<br />
view.addSubview(flyOutView);<br />
}<br />
}</p>
<p>override func viewDidLoad() {<br />
super.viewDidLoad()<br />
self.navigationItem.title = "Search By (searchBy)";<br />
self._mapCtl.mapType = .Standard;<br />
self._mapCtl.delegate = self;<br />
self.customAnotations.removeAll(keepCapacity: false);<br />
for searchModel in self.mapData!{<br />
let customAnotation = CustomAnotation()<br />
customAnotation.coordinate = CLLocationCoordinate2D(latitude: searchModel.lat, longitude: searchModel.lon);<br />
customAnotation.title = "";<br />
customAnotation.subtitle = ""<br />
customAnotation.searchModel = searchModel;<br />
self.customAnotations.append(customAnotation)<br />
}</p>
<p>self.zoomToFitMapAnnotations();</p>
<p>}</p>
<p>func zoomToFitMapAnnotations(){<br />
var topLeftCoord:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0);<br />
topLeftCoord.latitude = -90;<br />
topLeftCoord.longitude = 180;</p>
<p>var bottomRightCoord:CLLocationCoordinate2D = CLLocationCoordinate2D(latitude: 0, longitude: 0);<br />
bottomRightCoord.latitude = 90;<br />
bottomRightCoord.longitude = -180;</p>
<p>var foundAnotation = false;</p>
<p>for anotation in self.customAnotations{<br />
topLeftCoord.longitude = fmin(topLeftCoord.longitude, anotation.coordinate.longitude);<br />
topLeftCoord.latitude = fmax(topLeftCoord.latitude, anotation.coordinate.latitude);</p>
<p>bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, anotation.coordinate.longitude);<br />
bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, anotation.coordinate.latitude);</p>
<p>self._mapCtl.addAnnotation(anotation);<br />
foundAnotation = true;<br />
}</p>
<p>if(!foundAnotation){<br />
return;<br />
}</p>
<p>var region:MKCoordinateRegion = MKCoordinateRegion(center: topLeftCoord, span:MKCoordinateSpan(latitudeDelta: 0, longitudeDelta: 0));<br />
region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;<br />
region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;<br />
region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;<br />
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1;</p>
<p>self._mapCtl.regionThatFits(region);<br />
self._mapCtl.setRegion(region, animated: true);</p>
<p>}</p>
<p>override func didReceiveMemoryWarning() {<br />
super.didReceiveMemoryWarning()<br />
// Dispose of any resources that can be recreated.<br />
}</p>
<p>}<br />

That is pretty much all the major parts behind this app. I hope this will help you develop your very own Map Centric iOS app. If anyone is interested to get the source code, please download from here.


Md. Arifuzzaman Md. Arifuzzaman is a Senior Software Engineer at SELISE rockin’ software. He has extensive experience in diverse facets of C#, .NET, BI development, and mobile app development (Objective-C, Android).

21 Comments

  1. Anthony says:

    Hi,

    The Code is very good, I am new to swift and I am learning my way thru. Would you be able to send me the source please?

    Thanks, have a gr8 day
    Anthony

  2. Miriam says:

    Good work!
    It´s helping me a lot with my app. Please, I would like to have the source code.

    Thanks a lot. Miriam.

  3. Keevin Mitchell says:

    Hi Sir,

    Thanks you very much for the tutorial. However Im still a little confused. May I please have the source code?

    Thanks again

    Keevin Mitchell

  4. Therk says:

    Where do you declare the CustomAnotation()? Can you share the source code?

  5. Thanks for your valuable comments. Please find the source here

  6. Naga says:

    Thanks Arif for this tutorial , it is very clear. Could you please post usage of JSON, Core Data, UI view

  7. Frank says:

    Thank you for creating this demo. I’ve been learning iOS programming and only got used to objective-c not long ago. This tutorial can really help me transition to Swift. Can you please kindly send me the source code?

  8. Shin says:

    Hi! Nice work!
    I would appreciate if you could send me the source code too.
    Thank you!

  9. TC says:

    Hi Arif,

    I just downloaded the source from your OneDrive and tried to build in Xcode6 Beta6. I got 19 compilation errors and 3 warnings. Any idea as to what the problem may be?

    Cheers,

    Tom

  10. […] SWIFT Tutorial – make your own iOS MapKit application […]

  11. […] selise.ch you will explore MapKit. MapKit is a nice little framework developed by Apple. We will learn to integrate this MapKit with the Google Map javascript api and thus develop a complete application using Swift, like the one you can see below: […]

  12. Mavani Ravi says:

    Hi Arif,
    Thanks for this Tutorial.

    I Create a Demo from your Tutorial and it work perfectly for me.
    also i Configure Google App Console to use Google Map JavaScript API with a API Key form your tutorial suggestion but it not work for me please list out all the needed API that i should enable.

    Thanks in Advance.

  13. SA says:

    Hello,

    Could you please post another link to download the source code ? The page is no longer available.

    Thank you very much,

    S.

  14. soleilcicada says:

    I fixed most error exceptone

    Swift Compiler Error
    Type “ViewController” does not conform to protocol “UITableViewDataSource”

    This is on ViewController.swift line 12

  15. Arif says:

    Guys.

    About source code:

    Please visit the below link:
    https://onedrive.live.com/?cid=9E484CB5D84D17A1&id=9E484CB5D84D17A1%21127&authkey=%21AE2_FYrJa9LAKRw

    There you will find a zip file named: “AddressMap”. You will find the sources inside there.

  16. Stewart Lynch says:

    Your source code no longer works. Many errors. I suspect due to changes in Swift.
    Can you update it?

  17. Şeref Bülbül says:

    Hi,

    I updated the codes for the changes in Swift. You can download it from my GitHub.
    Here is the link:

    https://github.com/serefbulbul/AddressMap

    Btw thanks for the tutorial Arif.

Leave a Reply

Your email address will not be published. Required fields are marked *

'