Search code examples
swiftuiuikit

Clustering annotation with swiftUI


My goal is clustering annotation on map with show number of items in cluster, I have no experience in UIKit and try to avoid it. Is it possible to do it using swiftUI only? If not how to reduce intervention of UIKit? This is how it should look like

import SwiftUI
import MapKit

struct ContentView: View {

@State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 43.64422936785126, longitude: 142.39329541313924),
    span: MKCoordinateSpan(latitudeDelta: 1.5, longitudeDelta: 2)
)

   var body: some View {
    Map(coordinateRegion: $region, annotationItems: data) { annotation in
        MapAnnotation(coordinate: annotation.coordinate) {
            Image(systemName: "person.circle.fill")
                .resizable()
                .frame(width: 20, height: 20)
                .foregroundColor(Color.purple)
        }
    }
    .edgesIgnoringSafeArea(.all)
   }
}

struct SampleData: Identifiable {
var id = UUID()
var latitude: Double
var longitude: Double
var coordinate: CLLocationCoordinate2D {
    CLLocationCoordinate2D(
        latitude: latitude,
        longitude: longitude)
 }
}

var data = [
SampleData(latitude: 43.70564024126748, longitude: 142.37968945214223),
SampleData(latitude: 43.81257464206404, longitude: 142.82112322464369),
SampleData(latitude: 43.38416585162576, longitude: 141.7252598737476),
SampleData(latitude: 45.29168643283501, longitude: 141.95286751470724),
SampleData(latitude: 45.49261392585982, longitude: 141.9343973160499),
SampleData(latitude: 44.69825427301145, longitude: 141.91227845284203)
]

struct ContentView_Previews: PreviewProvider {
 static var previews: some View {
    ContentView()
 }
}

Solution

  • I find the way to cluster annotations with MapKit, but reuse map like a view for easy swiftUI. Looks like that https://i.sstatic.net/u3hKR.jpg

    import SwiftUI
    import MapKit
    
    struct MapView: UIViewRepresentable {
    
    
    var forDisplay = data
    @State private var region = MKCoordinateRegion(center: CLLocationCoordinate2D(latitude: 43.64422936785126, longitude: 142.39329541313924),
        span: MKCoordinateSpan(latitudeDelta: 1.5, longitudeDelta: 2)
    )
    
    
    class Coordinator: NSObject, MKMapViewDelegate {
        
        var parent: MapView
    
        init(_ parent: MapView) {
            self.parent = parent
        }
        
    /// showing annotation on the map
        func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
            guard let annotation = annotation as? LandmarkAnnotation else { return nil }
            return AnnotationView(annotation: annotation, reuseIdentifier: AnnotationView.ReuseID)
        }
    
    }
    
    
    
    func makeCoordinator() -> Coordinator {
        MapView.Coordinator(self)
    }
    
    
    func makeUIView(context: Context) -> MKMapView {
        ///  creating a map
        let view = MKMapView()
        /// connecting delegate with the map
        view.delegate = context.coordinator
        view.setRegion(region, animated: false)
        view.mapType = .standard
        
        for points in forDisplay {
            let annotation = LandmarkAnnotation(coordinate: points.coordinate)
            view.addAnnotation(annotation)
        }
        
    
        return view
        
    }
    
    func updateUIView(_ uiView: MKMapView, context: Context) {
        
    }
    }
    
    struct SampleData: Identifiable {
    var id = UUID()
    var latitude: Double
    var longitude: Double
    var coordinate: CLLocationCoordinate2D {
    CLLocationCoordinate2D(
        latitude: latitude,
        longitude: longitude)
     }
    }
    
    var data = [
    SampleData(latitude: 43.70564024126748, longitude: 142.37968945214223),
    SampleData(latitude: 43.81257464206404, longitude: 142.82112322464369),
    SampleData(latitude: 43.38416585162576, longitude: 141.7252598737476),
    SampleData(latitude: 45.29168643283501, longitude: 141.95286751470724),
    SampleData(latitude: 45.49261392585982, longitude: 141.9343973160499),
    SampleData(latitude: 44.69825427301145, longitude: 141.91227845284203)
    ]
    
    
    class LandmarkAnnotation: NSObject, MKAnnotation {
    let coordinate: CLLocationCoordinate2D
    init(
         coordinate: CLLocationCoordinate2D
    ) {
        self.coordinate = coordinate
        super.init()
    }
    }
    
    
    /// here posible to customize annotation view
    let clusterID = "clustering"
    
    class AnnotationView: MKMarkerAnnotationView {
    
    static let ReuseID = "cultureAnnotation"
    
    /// setting the key for clustering annotations
    override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
        super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
        clusteringIdentifier = clusterID
    }
    
    
    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    override func prepareForDisplay() {
        super.prepareForDisplay()
        displayPriority = .defaultLow
     }
    }
    

    And use that map like a default view

    import SwiftUI
    
    struct ContentView: View {
    
    
    var body: some View {
    MapView()
        .edgesIgnoringSafeArea(.all)
     }
    }
    

    For solving a problem I used next resources:

    https://www.hackingwithswift.com/books/ios-swiftui/communicating-with-a-mapkit-coordinator

    https://www.hackingwithswift.com/books/ios-swiftui/advanced-mkmapview-with-swiftui

    https://developer.apple.com/videos/play/wwdc2017/237/

    https://www.youtube.com/watch?v=QuYA7gQjTt4