As we’ve been developing the iPhone version of ribl, we’ve learned a lot about how different iOS and Android are.
In addition to different user interface guidelines and test processes, one of the major variations we found was how Google Maps and Apple Maps handle clustering of pins.
One of the key features of ribl is the Explore feature that allows you to view stories from locations other than your current one. The interface for this feature is a map, and you can move around and zoom in to particular areas to view the stories created there.
The issue arises when there are many stories in a certain area and it becomes difficult to tap a single pin to pull up the associated story.
Google Maps has a native library that provided this map clustering, so we didn’t have to write any additional code for this feature. Apple Maps, on the other hand, didn’t have this library, so we needed to do a bit more work to achieve the map clustering effect.
Here’s how we did it.
With a lot of pins, it’s hard to tap just one
MapKit is a powerful API built into iOS that allows you to drop pins on a native Apple Map. These pins can be tapped to reveal a callout, with options for further action. But for a map-centric app like ribl, it becomes difficult to precisely tap a single pin in a story-dense area.
Unfortunately, clustering is not built into MapKit. The third-party map clustering libraries available are written in Objective-C, and we built ribl in Swift, so we needed to use a bridging header to integrate a clustering library into our app.
Maps can be fairly resource-intensive. And as with any other location-based app, maps are a core feature of ribl. For performance and extensibility, it made sense to translate one of these Objective-C libraries to Swift.
How map clustering works
Map clustering relies on something called QuadTree, which is used to subdivide information.
Imagine you have a dresser with four drawers. In the top drawer, you divvy up the left side with skubb boxes from Ikea for your socks and mittens. On the right, maybe you use a Beckis for cuff links and collar stays.
QuadTree works similarly – it repeatedly divides boxes by four until each box contains just a handful of pins. This organization technique is capable of processing thousands of pins with minimal lag.
There are a few Objective-C cluster libraries that use QuadTree. I ultimately decided on translating FBAnnotationClustering (FB stands for Filip Bec, not Facebook) into Swift.
We knew the code base was quality, as it garnered 423 stars and had only 5 open issues on GitHub. In addition, the code base was small, so it would be easier to translate. Win-win.
Translating someone else’s Objective-C code to Swift is painstaking, but it’s a great way to understand the internals of the library. It got tricky when a method call was intertwined with a lot of others, and I often had to stub out dummy methods just to suppress Xcode errors. It reminded me of tracing ethernet cables in my sysadmin days.
One thing I need to remember for next time is to stick to a 1-to-1 translation. Any bright ideas to refactor the code usually ended up in serious regressions. Note to self: the original library is tested code, and every line is there for a good reason.
The ribl Swift clustering library
The final result is this Swift clustering library on our ribl GitHub page. It’s a working example that randomly generates 1000 pins, represented by clusters. Keep zooming in, and the clusters eventually separate out into individual pins.
Instructions for installation and integration into your own project are in the README file. Just copy a few Swift files into a subgroup. Then copy and paste a few snippets of code into your ViewController. If all goes well, you should have two pins in Kentucky that turn into a cluster, depending on your zoom level.
Note that you will need to copy over the image files into your Images.xcassets folder, or the app will crash. It’s a hassle managing image assets, but they do perform better because they’re already rendered. Font Awesome to PNG is a great resource for generating PNGs to fit your project’s color scheme. The icon name is fa-circle, and use 30/60/90, 40/80/120, and 50/100/150px for small, medium, and large clusters, respectively.
Check out this animation of ribl’s map clustering in action:
We hope that this tutorial was helpful! Feel free to use FBAnnotationClusteringSwift in your own project.
To see the end product in the flesh, sign up for our iOS ribl beta and we’ll send you a build via TestFlight. Or you can see how Google Maps handles clustering by downloading our Android version, which is live on Google Play.
Please let us know in the comments what you think about how we approached the iOS map clustering problem, and how you might implement this solution in your app.
If you liked this article, please share it!