Patrick McConnell
About Archive Also on Micro.blog
  • Swift Subscripts

    Recently, I’ve added Swift Subscripts to my vocabulary. In many cases they feel like a reasonable amount of syntactic sugar, making my code more concise and I hope more legible. I can see where they could be overused so I’m going slow.

    If you are not aware Swift allows you to declare your own subscripts to access collection members. You can always use filter, first, etc on your collection but with subscript your code may read better. 

    Let’s say you have a Observable DataService with a property of Locations containing an array of places to display on a map.

    @Observable class DataService {
        var locations: [Location] = []

    Without subscripts you might add a method to get a specific location by searching for it’s ID:

    func location(forID id: String) -> Location? {}

    Then elsewhere in your code you can call that method and work with the location.

    let loc = service.location(forID: id)

    Subscripts provide the same function but you may prefer the simple format when using them:

    let loc = service.locations[id]

    It’s virtually the same thing as calling your method but I think it reads well and makes the code easier to reason with.

    Assuming you want to call the subscript on the location array, the subscript for that usage might look something like this:

    extension Array where Element == Location {
      subscript(id: String) -> Location? {
          self.first(where: {$0.id == id})
      }
    }

    Notice we placed the subscript in an extension on an array. If you define the subscript right in your DataService you would need to call it as service[id] and that may not be as clear as service.locations[id].

    I try to go slowly if and when I adopt new Swift features. The curmedgeon in me thinks that so much of it just adds unnecessary complexity and often I find myself looking at recent Swift code “improvements" and thinking “thats just looks confusing” Simple, legible code is the easiest to reason with. It’s not a cleverness competition. I think judicious use of subscripts can be a “just right” amount of improved code syntax.

    #iOSDev #Swift

     

    → 11:31 AM, Oct 30
  • SwiftUI Map Marker Clustering

    So as not to be one of those recipe web sites where all you want is the recipe, the code for this sample project is here.

    I have a few app projects that make use of maps to convey location information. I prefer to work in SwiftUI when possible and this sometimes requires setting lower expectations of what you can achieve. While MapKit was recently improved for SwiftUI use, it still lacks features that are present in UIKit/AppKit. One of those missing features is support for “clustering” markers on the map. Clustering is when a group of map markers are joined into one where their display would otherwise overlap.

    In older, pre-SwiftUI, frameworks Apple provides robust support for clustering markers with just a few lines of code. Alas with SwiftUI, we don’t have that luxury and with the yearly release cycles we are unlikely to have this ability show up any time soon.

    I wanted to see if it was possible to do this myself so I did some research into how clustering is typically done. I came across many articles describing various methods and algorithms used to implement clustering and calculating the distance between map coordinates on the screen. You need both of these pieces to implement any solution. It’s not enough to say this two locations are 100 meters apart so their markers should be clustered. 100 meters means different things at different map scales. You would also like this clustering to happen any time the map is redrawn so it needs to be fast.

    There seems to be consensus around using the DBSCAN algorithm to create clusters of locations on a map. I try to avoid math in order to avoid headaches but the basic concepts of the article linked above make sense and are worth browsing.  An implementation of the DBSCAN algorithm will take a group of items with coordinates, a distance to check for separation and a function to be used to determine the actual distance between any of those items.

    There are a few DBSCAN implementations available for Swift and I picked one and added it to my projects. A version is included in the repo for this sample project. I don’t recall where I found this implementation but I am certain it was free to use/distribute. If you are or know the author let me know and I’ll give credit.

    Give the DBSCAN algorithm we only need our location items and that distance function to determine how far apart any markers would be rendered on the map view.  More research reveals that it’s nice to be able to use your GPU to quickly process a bulk of point data to determine these distances. As luck would have it Apple has created a Single Instruction, Multiple Data (SIMD) library that contains methods to do this distance calculation very quickly. If you want to read more about SIMD head on over to Wikipedia. All that you need to know to make use of Apples code is to import simd into your project and call it accordingly.

    Now we get to the actual recipe of how to make use of all this math.

    For this sample project we will render a few peaks in the Catskill mountains. We also provide a static function to give us a few peaks.

     

    You can easily render those sample peaks on a map in SwiftUI by iterating over the samplePeaks array and creating a Marker for each one. Doing so would show the overlapping problem we hope to clear up by implementing clustering. Notice in the upper right and lower left of the map a few peaks overlap.

     

    So we need to render something other than these individual peaks.  What we will render is a marker for an array of peaks. That array may contain only one peak when there is no overlap but in instances where peak markers would overlap the array will contain more than one peak rendered as a single marker. Here is a simple implementation of a PeakCluster item that can be used to create these arrays.

    Should make sense so far but how do we determine which peak markers would overlap. As I mentioned in the intro above, it’s not just a matter of how far apart the peaks are in real world distances. Real world distances are rendered differently depending on how far in or out we are zoomed in the map. We will need a way to convert real world distances to map unit distances. This is where a MapReader and its provided proxy will come to our rescue. Next we will need a way to calculate these clusters as the user zooms in and out of the map. Thankfully SwiftUI makes this easy. There is a modifier on Map called .onMapCameraChange. This modifier will be invoked any time the camera changes (i.e. the user pans or zooms the map.) All we need to do is call our clustering algorithm to update our clusters each time.

    The implementation of this will look like this:

    Notice the formerly overlapping peaks are now joined into one cluster. Clustered markers are rendered with the number of peaks in the cluster and color is used in this example to highlight the clusters.

    Simple, right? That “doClustering” method is doing some heavy lifting so let’s see how that magic happens.

    doClustering requires two things to do it’s job. The first is the mapProxy so we can convert the peak latitude/longitude to map view units and the second thing is the actual peak items.

    First we define the method to be used to determine the distance between our items. In our case the items are Peaks and they have coordinate information. Here we call into SIMD2 (for 2d point calculations, other implemetations exist for 3d, etc) This dist function will be provided to our DBSCAN function to do the clustering.

    We instantiate a dbscan object with our peaks to be clustered then call its method to output the clusters and outliers. Clusters are groups of overlapping peaks and outliers are peaks that are to be rendered individually. I won’t cover how the dbscan implementation works here. Feel free to browse the code.

    Then we iterate over both the clusters and the outliers creating PeakClusters for each item. In this example we just use the coordinate of the fist peak in a closter as the coordinate for the cluster. You may want to use a different method, perhaps the center of the region containing all the peaks in the cluster. For this example this simplification will work. Clusters are given a name denoting how may peaks are contained therein. The size is set to the number of peaks in the cluster. This allows us to quickly determine the marker to render back on our map.

    Outliers are processed similarly. The coordinate for the outlier cluster is correctly set to the peaks coordinate as is the name and the size of one.

    We then return our markers (PeakClusters) to the calling method. 

    This is only a very simple example to show the clustering. For an actual map app you probably need to do a bunch more. Several of the structs defined here are just barebones implementations and you might flesh out your Peak or PeakCluster objects to provide more needed data.

    This implementation feels ok. It’s not perfect. It doesn’t animate the changes from cluster to unclustered markers, for example. I would love to have that but I’m not sure it’s achievable here.

    One caveat is that while this code works well for iOS 18 projects, my original implementation was for iOS 17. Under iOS 17 I noticed that onMapCameraChange was called more than I would expect and in some cases the rendering of the markers before the map completed drawing would cause an infinite loop of redrawing the map. I was able to work around that with some hacks to check if I really wanted to be redrawing the map at those times. It was not an ideal solution and with iOS 18 things seem improved to the point that outside the math/algorithms to do the clustering the View code is pretty concise.

    → 12:00 PM, Oct 24
  • Existential WWDC 2024 Dread

    The original version of this post was almost 1,000 words. It just felt like a lot of ranting with no end so I will sum up my feelings regarding the near future of the Apple platforms/frameworks as follows:

    If you were building a new house and patiently waiting for the builder to finish the roof so the house didn’t leak, how excited would you be when he tells you he spent the past six months working on the addition for the sauna?

    → 2:41 PM, Apr 9
  • No one cares about my opinion but...

    This DOJ vs Apple thing is an annoying distraction to my social media timelines and surely soon to expand to fill all my podcast listening time.

    It is my novice opinion that while Apple is frequently poorly behaved, to put it mildly, most of this stuff doesn’t rise to the level of monopoly abuse. The problems are purely Apple’s making and are almost all caused by simple greed. Isn’t it always?

    It is convenient for them that things like privacy provide cover for some behaviors. That doesn’t make things like privacy unimportant and certainly not worth compromising too much in these areas.

    Another interesting thing is seeing all the Apple pundits choosing very clear sides on this. I think any rational thinking Apple observer can point out dozens of places where Apple could do better by consumers or developers but stubbornly sticks to their ways. Blindly defending Apple is a bad look. They are not without many faults here. At the same time, bashing Apple blindly with “I told you so…” is equally poor form.

    I believe this is yet another one of those situations where nuance is needed. Yes, Apple often behaves badly. At the same time It’s possible that these things don’t rise to the level of monopoly abuse.  If we, as a supposed democracy, want to curtail some of these behaviors we need better laws. I don’t think the current laws address this adequately. Nor do I believe the people in power are knowledgable enough to deal with these sorts of technical things in the ways that are required.

    Along with “AI”** I fear this sort of “distraction” will just slow improvements to the development tools we use every day. It already takes Apple years to improve on some frameworks. I would  prefer they improve MapKit for SwiftUI and 100 other things rather than waste time dealing with this nonsense.

    ** I realize some level of this so called “AI” is needed. I don’t want it to absorb all the oxygen in the room. Much like I know they spent years on the Vision Pro but right now I don’t care. I have work to do on the other platforms likely to be shortchanged by focus in these other areas for the foreseeable future.

    → 11:34 AM, Mar 22
  • RSS
  • JSON Feed
  • Surprise me!