MKMapview overlay renderding performance issue on iOS16

Hi ,

I have following scenario where I feel performance issue.

Use-case:

I have multiple Overlays(MKOverlay) rendered on MapView, and overlay needs to refresh on point Drag(MKPinAnnotation). I have custom logic to handle drag behaviour of annotation, on annotation drag I do update the overlay. As point update, I create new overlay with updated coordinate and re-render it. iT slow down the performance after few overlay added.

Additional Notes: Performance was quite good on iOS16 but on iOS17, it lags the perforce on point drag. When I say it the performance, it point drag lags so it slow the overlay rendering.

I am using MKMapView inside SwiftUI.

I am sharing code-snippet where it re-render the overlay. Please help with issue in my code implementation.

func renderSegments(mapView: MKMapView, segmentPoint: FencePointAnnotation, renderNeeded: Bool = true) {
    mapViewModel.updateFencePointOrder()
    
    guard let activeLayer = mapViewModel.activeLayer else {
        debugPrint("Invalid active layer.")
        return
    }
    let segments = mapViewModel.activeFence.connectedSegmentsOf(vertex: segmentPoint)
    
    // Remove existing overlay.
    for overlay in mapView.overlays {
        if let overlay = overlay as? FenceOverlay {
            if overlay.layerId == activeLayer.layerId {
                mapView.removeOverlay(overlay)
            }
        } else if let overlay = overlay as? FenceSegmentPolyline {
            if overlay.layerId == activeLayer.layerId {
                for segment in segments.values where segment.identifier == overlay.identifier {
                    mapView.removeOverlay(overlay)
                }
            }
        }
    }
    
    // When vertex removed the no need to add segment
    if renderNeeded {
        if let segments = mapViewModel.updatedSegements(segment: segments.map({$0.key})) {
            let updatedSegments = mapView.updatedSegmentsWithOffset(segments: segments, layer: activeLayer)
            mapView.addOverlays(updatedSegments)
        }
    }
}

Attaching Timer Profiler Screenshot.

There's a lot going on here, so let's unpack a few different concepts for you to consider.

The first one relates to your type FenceSegmentPolyline. This sounds like it could be a good use for MKMultiPolyline and MKMultiPolylineRenderer as your rendering object, as I'm assuming there will be many fence segments that often use the same styling to render. This can produce a significant reduction in the number of renderer objects required to stroke the style for your polyline, and may give you a performance boost when there's a lot of identical rendering for the system to draw.

The second point is with the pattern of removing all overlays, and then putting them all back if that rendering flag is true. This can be pretty expensive, as you could be asking MapKit to re-evaluate a lot of overlays. Since overlays can have transparency, plus there is an order to them, this type of bulk removal just to then re-add them means that MapKit needs to effectively reevaluate all of the overlays multiple times, since it needs to redisplay the map with the overlays removed (and if there are overlays that remain, draw then again because the appearance may be different due to other overlays over or under considering transparency levels), and then do that evaluation again when things are re-added.

Depending on how common this operation is, sometimes doing a customized renderer is better, allowing you to more finely target regions of the overlay to redraw, which can shrink the amount of work required of the system to update the map. We have examples of this approach in sample code.

The third thing to watch out for is how you trigger these operations via your UIViewRepresentable with respect to integrating into SwiftUI. There's not anything in the code or trace screenshot for me to know if this is a problem for you or not. One thing that can be a surprise performance bottleneck is if you have significantly sized data objects with a lot of unrelated properties binding to SwiftUI views with ObservableObject. In such a scenario, one data change to any property may mean that the view needs to be reevaluated by SwiftUI, and if such data is changing with routine frequency, then you may wind up re-rendering the view with the MKMapView an unexpected number of times. The more recent Observable paradigm helps you avoid this, and there are also dedicated profiling tools for SwiftUI inside of Instruments that help you understand how many times various Views in your app are evaluated.

— Ed Ford,  DTS Engineer

MKMapview overlay renderding performance issue on iOS16
 
 
Q