xkam1x
xkam1x

Reputation: 11

SwiftUI MapKit Annotation Optimisation

I am working on a project where SwiftUI and MapKit is used to draw annotations on a Map. The solution I have is able to draw about 1k annotations, but I need a solution that can handle at least 10k without lag or performance issues.
I have created a sample code that draws a red circle on a Map. You can add annotation by pressing the 'Add' button. There is also a tracker class that tracks number of times annotation is rendered.

import SwiftUI
import MapKit

struct MapAnnotation {
    var id: Int
    var location: CLLocationCoordinate2D
}

class Tracker {
    var done = 0
    static let shared = Tracker()
}

struct ContentView: View {
    @State private var mapAnnotations: [MapAnnotation] = .init()
    var body: some View {
        ZStack {
            Map() {
                ForEach(mapAnnotations, id: \.id) { intensity in
                    Annotation("", coordinate: intensity.location, content: {
                        Circle()
                            .foregroundStyle(Color.red)
                            .frame(width: getWidth(intensity: intensity))
                    })
                }
            }
            
            Button(action: {
                print("Adding")
                Tracker.shared.done = 0
                mapAnnotations.append(.init(id: .random(in: 0 ... Int.max), location: .init(latitude: .random(in: -5 ... 5), longitude: .random(in: -5 ... 5))))
            }, label: {
                Text("Add")
                    .font(.title)
                    .foregroundStyle(Color.red)
            })
        }
    }
    
    private func getWidth(intensity: MapAnnotation) -> CGFloat {
        Tracker.shared.done += 1
        print(Tracker.shared.done)
        return 10
    }
}

#Preview {
    ContentView()
}

Running the code (Xcode 15.1, iOS 17.2 SDK) above prints the following:

Adding
1
2
Adding
1
2
3
Adding
1
2
3
4

This indicates that whenever a new annotation is introduced, the system re-renders all previously existing annotations. Consequently, when handling a dataset of 5000 annotations and introducing an additional one, the entire set of 5001 annotations undergoes rendering, leading to lag and performance issues.
Is there any way to optimise annotation rendering to avoid performance issues?

Update:
Changed the code to add annotations in bulk:

import SwiftUI
import MapKit

struct MapAnnotation {
    var id: Int
    var location: CLLocationCoordinate2D
    // Other Data
}

struct ContentView: View {
    @State private var mapAnnotations: [MapAnnotation] = .init()
    var body: some View {
        ZStack {
            Map() {
                ForEach(mapAnnotations, id: \.id) { mapAnnotation in
                    Annotation("", coordinate: mapAnnotation.location, content: {
                        Circle()
                            .foregroundStyle(Color.red)
                            .frame(width: 10)
                    })
                }
            }
            
            VStack {
                Button(action: {
                    mapAnnotations.append(.init(id: .random(in: 0 ... Int.max), location: .init(latitude: .random(in: -5 ... 5), longitude: .random(in: -5 ... 5))))
                }, label: {
                    Text("Add One")
                        .font(.title)
                        .foregroundStyle(Color.red)
                })
                
                Button(action: {
                    for _ in 0 ... 999 {
                        mapAnnotations.append(.init(id: .random(in: 0 ... Int.max), location: .init(latitude: .random(in: -5 ... 5), longitude: .random(in: -5 ... 5))))
                    }
                }, label: {
                    Text("Add Many")
                        .font(.title)
                        .foregroundStyle(Color.red)
                })
            }
            .background(Color.black)
        }
    }
}

#Preview {
    ContentView()
}

I have profiled the new code in instruments and have found that following trend:

image

It shows that when 5k annotation are present, adding one more is taking about 27 seconds on main thread causing unresponsiveness.
Most of the time is spent inside MapKit [MKAnnotationView _metricsDidChange] and I doubt I can optimise that.

Any suggestions please?

Upvotes: 1

Views: 605

Answers (1)

malhal
malhal

Reputation: 30549

Cluster the annotations or draw a custom overlay.

ForEach looping all the items is normal and not intensive. Only the diff result is used to init new objects like a MKMapAnnotation if it is initing duplicate annotations then there could be an issue with the annotation identities.

FYI SwiftUI “does not render views provided by native platform frameworks (AppKit and UIKit) such as web views, media players, and some controls.” https://developer.apple.com/documentation/swiftui/imagerenderer

Upvotes: 0

Related Questions