This is the third part of our Google maps SDK series. In the first tutorial, you Learned how to use the Google maps SDK to deliver search suggestions, autocompleted to the user and based on what he types in. The second tutorial is also as much important as the first, where it shows you how to implement the powerful nearby API in your app to overcome Apple limits and make your app special.
In this quick tutorial, you will customise the marker icons as well as the info window view on your map.
Without further ado, download the starter project and let’s get this done!
Make sure to open the file named CustomInfoWindow.xcworkspace since cocoapods is now integrated with the project and the Google Maps SDK for iOS is installed.
If you take a look at the Project navigator view, I added two files called CustomInfoWindow.xib and CustomInfoWindow.swift files. This two files will manage the custom info window layout and outlets. Also, few assets for the custom markers icons and info window are already added to the Assets.xcassets catalog.
Note 1: Before you continue, make sure to generate a Google API Key. If you don’t have one already, head over here to make your own (you may need to follow only steps 4 and 5 of the guide).
Note 2: Also, make sure that the bundle identifier of the app (in this case: com.medigarage.CustomInfoWindow) is registered in order to be able to use the API Key. Head over here to register the bundle identifier with the API Key if you didn’t already.
Good, now you are all set to go!
First thing to do is to provide the API Key in the app, select the AppDelegate.swift file from the Project navigator view and make the following changes:
// At the top of the file import GoogleMaps // In the didFinishLaunchingWithOptions method GMSServices.provideAPIKey("YOUR_API_KEY_HERE")
Change the argument in the provideAPIKey method with the API Key you just generated in the Google APIs Console.
Next, select the ViewController.swift file from the Project navigator view and import the Google Maps SDK at the top of the file:
import GoogleMaps
We will need some properties for the maps and its data, copy the following declarations just after the class name:
var map:GMSMapView! var longitudes:[Double]! var latitudes:[Double]! var architectNames:[String]! var completedYear:[String]!
Apart the map property which works as a reference for the map view, all the properties declared above are arrays to use to provide the markers with coordinates and the info window views with informations.
Let’s initialise the arrays with some data, place the following code inside the viewDidLoad method:
latitudes = [48.8566667,41.8954656,51.5001524] longitudes = [2.3509871,12.4823243,-0.1262362] architectNames = ["Stephen Sauvestre","Bonanno Pisano","Augustus Pugin"] completedYear = ["1889","1372","1859"]
The app you are about to build will show three historical monuments, in Paris, London and Rome. The arrays above are initialised with the coordinates, architects names and years of construction completion.
Next, you will load the map and make it take the whole screen frame. Always in the viewDidLoad method, implement the following code:
self.map = GMSMapView(frame: self.view.frame) self.view.addSubview(self.map) self.map.delegate = self
Setting the current controller as the delegate of the map view protocol requires making the class itself to adopt to the GMSMapViewDelegate protocol. To do so, just change the class declaration as below:
class ViewController: UIViewController, GMSMapViewDelegate {
Cool, time to add some markers to the map 🙂
Implement the code below in the viewDidLoad method:
for i in 0...2 { let coordinates = CLLocationCoordinate2D(latitude: latitudes[i], longitude: longitudes[i]) let marker = GMSMarker(position: coordinates) marker.map = self.map marker.icon = UIImage(named: "\(i)") marker.infoWindowAnchor = CGPointMake(0.5, 0.2) marker.accessibilityLabel = "\(i)" }
The code above will loop 3 times, the number of assets we got. It will use the lon/lat coordinates from the arrays to place the markers on the map. It will also change the default red icon with a custom image and modify the anchor of the info window slightly so it appears just above the icon. Finally the accessibilityLabel is set with the index of the icon in order to tag it with a reference for later use.
To finish up, let’s implement the mapView:markerInfoWindow: delegate method to allow the app to use the custom info window you just have in the CustomInfoWindow.xib file.
Place the following code before the class closing bracket:
func mapView(mapView: GMSMapView!, markerInfoWindow marker: GMSMarker!) -> UIView! { // 1 let index:Int! = Int(marker.accessibilityLabel!) // 2 let customInfoWindow = NSBundle.mainBundle().loadNibNamed("CustomInfoWindow", owner: self, options: nil)[0] as! CustomInfoWindow customInfoWindow.architectLbl.text = architectNames[index] customInfoWindow.completedYearLbl.text = completedYear[index] return customInfoWindow }
The protocol method above will be called each time you select a marker, and it’s intended to return a custom view for each marker, so this is the right place where to customise the info window 🙂
First, the code will get a reference for the selected marker, this become easy since you use the accessibilityLabel property you set it earlier. second, you load the custom info window file and cast it to the CustomInfoWindow class in order to be able to use the properties hooked up in the view.
That’s it! Run the app and enjoy your work 🙂
As usual, here is the final project, feel free to leave your comment below. I am always open to your suggestions!
Hey Malek. Again, thanks for the tutorial. Very helpful as always.
Quick question: do you know the best way to add a user editable UITextView to the custom info window?
I tried by adding a text view object to the xib and creating a UITextView outlet in the CustomInfoWindow class. The text view box shows up in the simulator but isn’t editable. Any thoughts?
Thanks man
Hi Tristan 🙂
I would suggest putting the UITextView outside the map, ’cause placing it on the map view will drop the user experience. For example:
1- When click on the marker view, an alert view shows up with a UITextField inside
2- User enters his input and click OK
3- When alert’s OK is clicked, the marker content is updated accordingly.
Hope this helps.
Hey Malek.
Thanks for very helpful tutorial.
A question: How can I load an asynchronies Image to my InfoWindow?
When I load the image synchronize everything works fine but when I load the image asynchronies it doesn’t work for me.
func mapView(mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? {
guard let piece = marker.userData as? Piece else {
return nil
}
let view = PieceMapAnnotationView(frame: CGRect(x: 0, y: 0, width: 320, height: 120))
view.descriptionLabel.text = piece.name
piece.loadPhoto { (photo) -> () in
NSOperationQueue.mainQueue().addOperationWithBlock {
view.imageView.image = photo
view.layoutIfNeeded()
}
}
return view
}
Thanks,
Guy