Using iOS MapKit and CoreLocation with Delphi Prism and MonoTouch
The iPhone Maps application is one of the most useful app for me, we always used this app to track a direction, check the traffic, search for a restaurant, gas station, etc. In this post I will show how to use the MapKit and CoreLocation framework from iOS SDK, both enable mapping related functionality within you application.
The MapKit provide a visual presentation of geographic information using Google Maps data and image, using the MKMapView control, the same use in the iPhone Maps app. The MapKit framework is exposed on MonoTouch via the MonoTouch.MapKit namespace.
CoreLocation works on a number of iOS devices using a variety of technologies to determine position, and using a built-in compass of newer devices provides the heading the device is pointing to. Using this information you can determine where users are and what direction their device is facing, and by monitoring changes to that data how fast they are moving and how far they have traveled. The CoreLocation framework is exposed on MonoTouch via the MonoTouch.CoreLocation namespace, and deal with two types of information: Location and Heading.
I’m assuming you already watch my introductory presentation about iPhone which I explain how to create a simple Web browser using Mono IDE and Interface Builder, on this post I will just go through the implementation.
In order to use MKMapView you just need to drag and drop the control in your window using Interface Builder, also the Segmented Control which will allow you to change the Map type (Standard, Hybrid, Satellite).
Based on the address informed I will add a PIN on the correspondent location, to do add PIN markers to the map requires creating and adding MKAnnotations to the MKMapView. MKAnnotation is a abstract class and need to be implemented in your application, like the code bellow.
MyAnnotation nested in AppDelegate = public class(MKAnnotation) private _coordinate: CLLocationCoordinate2D; _title: System.String; _subtitle: System.String; public property Coordinate : CLLocationCoordinate2D read _coordinate write _coordinate;override; property Title: System.String read _title; override; property Subtitle: System.String read _subtitle; override; // The custom constructor is required to pass the values to this class, // because in the MKAnnotation base the properties are read-only constructor (l: CLLocationCoordinate2D; t: System.String; s: System.String); end;
Now I had to implement the Search Bar delegate to handle events that it generates, it’s not a full implementation of the UISearchDelegate but it implement what is necessary to implement the search behavior.
The Search button will retrieve the latitude/longitude from Google’s web service and add a pin to the map using the MyAnnotation class. Remember you need to sign up a Google Maps key at http://code.google.com/apis/maps/signup.html to use this application, assigned the Google Maps Key to the variable GoogleMapsKey.
MySearchBarDelegate nested in AppDelegate = public class(UISearchBarDelegate) private app: AppDelegate; lastResult: MyAnnotation; GoogleMapsKey:String := String.Empty; // Define you google maps key here. // Keep a reference to the application so we can access the UIControls. // This needs to be an internal class to access those (private) controls. public constructor (a: AppDelegate); // Method for the searchbar to call when the user wants to search, // where we create and call the Geocoder class. Note that we are // calling it synchronously which is undesirable in a "real" // application. method SearchButtonClicked(searchBar: UISearchBar); override; end; method AppDelegate.MySearchBarDelegate.SearchButtonClicked(searchBar: UISearchBar); begin var g := new Geocoder(GoogleMapsKey); var location: CLLocationCoordinate2D; searchBar.ResignFirstResponder; UIApplication.SharedApplication.NetworkActivityIndicatorVisible := true; // synchronous webservice call if g.Locate(searchBar.Text, out location) then begin if lastResult <> nil then begin // if there is already a pin on the map, remove it app.Map.RemoveAnnotation(lastResult) end; app.Map.SetCenterCoordinate(location, true); var pin := new MyAnnotation(location, searchBar.Text, location.Latitude.ToString + ',' + location.Longitude.ToString); app.Map.AddAnnotationObject(pin); lastResult := pin; end else begin // display a message that the webservice call didn't work using alert := new UIAlertView('Not found', 'No match found for ' + searchBar.Text, nil, 'OK', nil) do begin alert.Show() end end; UIApplication.SharedApplication.NetworkActivityIndicatorVisible := false; end;
To finalized here the implementation of the Geo code using Google web services, just passing the address and Google will retrieve the latitude/longitude.
interface uses System, System.Xml, System.IO, System.Net, MonoTouch.CoreLocation; // Documentation for the service http://code.google.com/apis/maps/documentation/geocoding/ type Geocoder = public class public constructor (key: System.String); // Sign up for a Google Maps key http://code.google.com/apis/maps/signup.html private var _GoogleMapsKey: System.String; var xmlString: System.String := ''; public method Locate(query: System.String; out return: CLLocationCoordinate2D): System.Boolean; private // Retrieve a Url via WebClient class method GetUrl(url: System.String): System.String; end; implementation constructor Geocoder(key: System.String); begin _GoogleMapsKey := key; end; method Geocoder.Locate(query: System.String; out return: CLLocationCoordinate2D): System.Boolean; var url: System.String := 'http://maps.google.com/maps/geo?q={0}&output=xml&key=' + _GoogleMapsKey; coords: XmlNode := nil; xd: XmlDocument; xnm: XmlNamespaceManager; gl: System.String; coordinateArray: array of System.String; begin url := String.Format(url, query); return := new CLLocationCoordinate2D(0, 0); try xmlString := GetUrl(url); xd := new XmlDocument(); xd.LoadXml(xmlString); xnm := new XmlNamespaceManager(xd.NameTable); coords := xd.GetElementsByTagName('coordinates')[0] except end; if coords <> nil then begin coordinateArray := coords.InnerText.Split(','); if coordinateArray.Length >= 2 then begin gl := Convert.ToDouble(coordinateArray[1].ToString()).ToString + ',' + Convert.ToDouble(coordinateArray[0].ToString()); return := new CLLocationCoordinate2D(Convert.ToDouble(coordinateArray[1].ToString()), Convert.ToDouble(coordinateArray[0].ToString())); exit true end end; exit false end; class method Geocoder.GetUrl(url: System.String): System.String; var return: System.String := System.String.Empty; client: System.Net.WebClient := new WebClient(); begin using strm: Stream := client.OpenRead(url) do begin var sr: StreamReader := new StreamReader(strm); return := sr.ReadToEnd() end; exit return; end; end.
When we run the application we can have three different views of the Map, the pin (in red) is the location of our office here in Scotts Valley.
The source code is available at CodeCentral.
I’m new to Mac programming. When I try to run your example I get the error
SDK version 4.1 is not installed.
How can I change the needed SDK version?
Regards from Germany
Franz-Leo
Hi Franz,
In the project options, option build you have to change the iOS version, you probably are using a old version 4 or 3.x
datasets actualicen bien, tanto el maestro como el detalle y no mande el mensaje “columna desconocida” para los campos del detalle (hay alguna forma de indicarle el nombre de la base de datos que debe actualizar el detalle )?
(de ser posible favor de e)nviar un ejemplo
Roberto,
El DataSetProvider trae un evento llamado OnGetTableName donde el parámetro TableName representa el nombre de la tabla a ser actualizada, si quieres cambiar solamente tienes que cambiar el valor desta variable
Great. One question, i don’t found the solution. Do you know how trace route between two points with mapkit?
Thaks,
Hi Felipe,
I never tried, need to look Apple’s documentation or google 🙂