From Apple’s Mapkit to GoogleMaps

The other day started just like any other: the sun rose, the clouds parted, and the morning was filled with promise. But as I sat down to my computer, I noticed an irregular message in my inbox… It was from Google of all people. Had I finally gotten noticed!? Yes, but for the wrong reasons:

For any developers out there interested in using any of Google’s products, know that once you implement a single feature, you must follow through for the entire app. We used StreetView on a page entirely separate from the AppleMap on the home screen. However since both appeared within the same app, Google apparently views this clash of products as a potential threat to its brand.

In all fairness they were pretty polite about the situation, but terms were set: update the app for it will be removed from the store. So I set out to fix our app, hoping that with surgical precision, I might be able to swap in a new GoogleMap in place of the AppleMap. Hence this blog will cover how to implement this, as well as a comparison between the delegate methods of GoogleMaps and Mapkit.

The first step (after downloading the proper CocoaPods for GoogleMaps) was simply to find the file with the instance of MKMapView(). In this file, we will now import GoogleMaps and substitute MKMapView() for GMSMapView(). And that’s when the fun starts!

Step two: take a deep breath and remind yourself, it’s not as bad as it looks! With a few minor changes, the app will be up and running again in no time.

In order to have a GMSMapView(), we’ll have to properly initialize it. Essentially this just means giving it a frame to work within, and a camera to know where to focus on the map. This is very similar to MapKit’s map.setRegion(region: MKCoordinateRegion, animated: Bool), but instead of initializing a MKCoordinateRegion, you will create a CLLocationDegrees, longitude: CLLocationDegrees, zoom: Float)

MapKit vs GoogleMaps initialization. ALSO be sure to set the delegates in the proper file!

So at this stage if we comment out all the other errors, the app should run with a GoogleMap loading on the screen.

Next we still need our locations appear on the map, but small caveat — GoogleMaps does not use “annotations” but rather “markers.” So the exceptionally useful method map.addAnotations(annotations: [MKAnnotation]) is no longer available. In order accommodate this change, we’ll have to convert all the MKAnnotation to GMSMarker. This actually isn’t a huge inconvenience, since both need CLLocationCoordinate2D to plot. In our case we simply changed our CustomAnnotation class to CustomMarker, and had it inherit from GMSMarker instead of MKAnnotation. Hint: when changing class names, it helps to do a Find+Replace throughout the entire project using the helpful Command+Shift+F combination so that every instance in every file is updated.

Additionally, markers are added to the map slightly differently than the annotations in Mapkit, however in some ways it grants you more control. Rather than giving an array of annotations to a map, each marker is assigned to a map individually. The most straight-forward method I chose to do this was via for-in loop, where I could also give each marker its proper qualities:

Notice how the marker.position still needs a coordinate. In our project, we have a class called DogRun that possess a name, address, coordinates, etc. The CustomMarker class has a computed variable that assigns it a coordinate based on the DogRun location it represents; in hindsight, with GoogleMaps this could just as easily have been accomplished without the computed property. Either way, all a marker needs to show up on a map is a position (CLLocationCoordinate2D { get set } ) and a map to which it will be placed.

Pounding out that code!

Alright, getting somewhere! With markers showing up, the final piece to the problem is adding some functionality. Probably the biggest difference is the delegate methods, and to be honest, they’re not all that different! Let’s take a look at the major methods for comparison’s sake:

func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? { }
func mapView(_ mapView: GMSMapView, markerInfoWindow marker: GMSMarker) -> UIView? { }

Here we have the methods that provide each marker or annotation with a corresponding window or view. They are essentially identical, except that Mapkit requires you to return a MKAnnotationView whereas GoogleMaps allows you to return a UIView. This may seem like a nuisance to some, so I think Google has the edge here with allowing the developer to simply implement a UIView as opposed to creating an MKAnnotationView class.

Next we have the methods that handle user interaction:

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) { }
func mapView(_ mapView: MKMapView, didDeselect view: MKAnnotationView) { }
func mapView(_ mapView: GMSMapView, didTapInfoWindowOf marker: GMSMarker) { }
func mapView(_ mapView: GMSMapView, didCloseInfoWindowOf marker: GMSMarker) { }

These delegates seem to operate similarly on the surface. But not exactly, and to explain why let me start with describing the flow of our app.

Once you tapped an annotation, a window would appear; if you tap that window or anywhere else on the map, the window would disappear. If you wished to segue to that location’s profile, a button would appear at the bottom of the screen.

In this way, didTapInfoWindowOf marker: and didCloseInfoWindowOf marker: actually operated in the same way. But how to the get goToLocationButton to appear at the bottom of the screen? Easy! By placing that appearance animation within the markerInfoWindowFor marker: method. In this way, whenever a window was present, so was the goToLocationButton.

In regards to the didTapWindow method, I should note that from past attempts, it is surprisingly difficult to implement buttons within the UIView window. This a subject for another blog post, but essentially the entire window itself behaves as a surface for taps and gestures. The didTapWindow could still take you to another screen, but I would rather consult with the team before changing out app’s flow 😸

As you can see despite the initial headache, swapping out MapKit for GoogleMaps is actually quite a painless process! Personally I like using UIViews over the MKAnnotationView, but on the flip-side Apple’s hybrid map is a more vivid and rich color than Google’s in my own opinion. At any rate, I hope you are as satisfied with this blog post as Google is that we decided to update our app and resubmit it to the app store!

Happy coding and may the wind be ever in your wings!