6

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()
 }
}
Nizami
  • 728
  • 1
  • 6
  • 24
  • 1
    You would have to do some [grouping](https://stackoverflow.com/questions/31220002/how-to-group-by-the-elements-of-an-array-in-swift) in relation to the size of the region `CLLocation` has a [`distance` function](https://developer.apple.com/documentation/corelocation/cllocation/1423689-distance) – lorem ipsum Jun 13 '21 at 15:15
  • I think you may have to reply on a UIKit Component to achieve that – cedricbahirwe Jun 13 '21 at 15:19
  • @loremipsum can you please describe in more detail how to do that? – Nizami Jun 16 '21 at 15:38
  • You would have to come up with one on your own. I don't think there is something out there for that. Something where you use the current region span to decide how close the location has to be to cluster it. It would take me some time to create something to do it and SO is not a code writing service. Give it a try and we can help fine tune. Start by writing down a possible process. – lorem ipsum Jun 16 '21 at 16:25

1 Answers1

20

I find the way to cluster annotations with MapKit, but reuse map like a view for easy swiftUI. Looks like that https://i.stack.imgur.com/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

Nizami
  • 728
  • 1
  • 6
  • 24
  • 1
    Great answer! No idea why 0 votes. – M1X Nov 17 '21 at 01:01
  • Great answer! If forDisplay was a banded var, how do I update the map once the data changes? – Gianni Mar 24 '23 at 12:34
  • @Gianni Try to add var array with data that you gonna show on MapView. Create publisher with same data array in your ViewModel and pass it to MapView in SwiftUI hierarchy. May be need to add something to updateUIView func in MapView, not sure but would try this way first – Nizami Mar 24 '23 at 16:09