Hello there, i've came here after searching por a while a solution to the problem i have but i couldn't find anything helpfull.
I'm displaying a map in iOS16 using only swiftUI and i'm wondering if its any way to know if the region of the map it's displaying has any annotations or not. I want to display a list in the bottom of the map of annotations that are visible in that map area, but if the user drag to another position without annotations i want to hide that list.
Since you already have the visible region shown on the map and all annotations, with their coordinates, you can work out which annotations are located in that region.
MKMapRect
has a useful method contains(_:)
which indicates whether the specified map point lies within the rectangle. There are two (small?) problems though:
Map
expects annotations to have location coordinates of typeCLLocationCoordinate2D
butMKMapRect
usesMKMapPoint
. Luckily, anMKMapPoint
can be formed from aCLLocationCoordinate2D
.- I'm assuming you are storing your map's region as an
MKCoordinationRegion
object inside of an@State
variable. If so, this needs to be converted to anMKMapRect
object and that isn't an easy process. If you don't require usingMKCoordinationRegion
, then store anMKMapRect
instead which will make things easier. If you do need it, you will need to manually convert (a quick search online will yield some results), and don't forget about the 180th meridian.
[Note: iOS 17 doesn't make Problem 2 an issue, with the new APIs handling things differently.]
Here's a demo app I made to showcase how you can achieve this:
// A single map annotation as an object
struct AnnotationItem: Identifiable {
let id = UUID()
let title: String
let coordinates: CLLocationCoordinate2D
}
struct MapAnnotations: View {
// Store the map's currently visible rect
@State private var visibleRect = MKMapRect(x: 125_000_000, y: 75_000_000, width: 15_000_000, height: 25_000_000)
// Dummy data
let items: [AnnotationItem] = [
.init(title: "San Francisco", coordinates: .init(latitude: 37.77938, longitude: -122.41843)),
.init(title: "New York", coordinates: .init(latitude: 40.71298, longitude: -74.00720)),
.init(title: "São Paulo", coordinates: .init(latitude: -23.57964, longitude: -46.65506)),
.init(title: "London", coordinates: .init(latitude: 51.50335, longitude: -0.07940)),
.init(title: "Rome", coordinates: .init(latitude: 41.88929, longitude: 12.49355)),
.init(title: "Johannesburg", coordinates: .init(latitude: -26.20227, longitude: 28.04363)),
.init(title: "Mumbai", coordinates: .init(latitude: 18.94010, longitude: 72.83466)),
.init(title: "Tokyo", coordinates: .init(latitude: 35.68951, longitude: 139.69170)),
.init(title: "Melbourne", coordinates: .init(latitude: -37.81503, longitude: 144.96634))
]
var body: some View {
Map(mapRect: $visibleRect, annotationItems: items) { item in
MapMarker(coordinate: item.coordinates)
}
.ignoresSafeArea()
.overlay(alignment: .bottom) {
let annotations = visibleAnnotations()
// Show the list of visible annotations if there are any
if !annotations.isEmpty {
VStack {
ForEach(annotations) { item in
Text(item.title)
.font(.title3.bold())
}
}
.padding(10)
.background(.thinMaterial, in: RoundedRectangle(cornerRadius: 10))
.padding()
}
}
}
func visibleAnnotations() -> [AnnotationItem] {
items.filter { item in
// Check if the item's location is in the map's currently visible rect
visibleRect.contains(.init(item.coordinates))
}
}
}
Any questions about this please let me know and I'll be happy to answer.