I have MapAnnotationView class:
final class MapAnnotationView: MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
frame = CGRect(x: 0, y: 0, width: 50, height: 50)
centerOffset = CGPoint(x: 0, y: -frame.size.height / 2)
}
@available(*, unavailable)
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setupUI(with annotation: PlaceAnnotation) {
backgroundColor = .clear
let vc = UIHostingController(rootView: MapPin(color: Color(hex: annotation.color ?? 0),
imageSystemName: annotation.icon ?? ""))
vc.view.backgroundColor = .clear
addSubview(vc.view)
vc.view.frame = bounds
}
}
MapPin used as HostingController:
struct MapPinShape: Shape {
func path(in rect: CGRect) -> Path {
let radius = rect.width / 2
var path = Path()
path.addArc(center: CGPoint(x: rect.midX, y: rect.minY + radius), radius: radius, startAngle: .degrees(70), endAngle: .degrees(110), clockwise: true)
path.addLine(to: CGPoint(x: rect.midX, y: radius * 2 + 0.2 * radius))
return path
}
}
struct MapPin: View {
var color: Color
var imageSystemName: String
var body: some View {
ZStack {
MapPinShape()
.fill(color.darker())
GeometryReader { geo in
Circle().fill(color)
.frame(width: geo.size.width * 0.90)
.frame(width: geo.size.width, height: geo.size.height, alignment: .center)
Image(systemName: imageSystemName)
.resizable()
.scaledToFit()
.foregroundColor(.white)
.frame(width: geo.size.width / 3)
.frame(width: geo.size.width, height: geo.size.height, alignment: .center)
}
}
}
}
It is used in MKMap that is in UIViewRepresentable:
struct MapView: UIViewRepresentable {
var annotations: [PlaceAnnotation]
var trackingMode: MKUserTrackingMode
var onLongPress: (_ location: MapLocation) -> Void
var onAnnotationTap: (_ annotation: PlaceAnnotation) -> Void
let mapView = MKMapView()
let locationManager = CLLocationManager()
func makeUIView(context: Context) -> MKMapView {
locationManager.delegate = context.coordinator
mapView.delegate = context.coordinator
mapView.showsUserLocation = true
mapView.register(MapAnnotationView.self, forAnnotationViewWithReuseIdentifier: "AnnotationView")
mapView.register(MapClusterAnnotationView.self, forAnnotationViewWithReuseIdentifier: "ClusterAnnotationView")
let longPressRecognizer = UILongPressGestureRecognizer(target: context.coordinator,
action: #selector(Coordinator.longPressed(_:)))
mapView.addGestureRecognizer(longPressRecognizer)
configureLocationServices()
return mapView
}
func updateUIView(_ mapView: MKMapView, context: Context) {
let existing = mapView.annotations.compactMap { $0 as? EntMap.PlaceAnnotation }
let diff = annotations.difference(from: existing) { $0 === $1 }
for change in diff {
switch change {
case .insert(_, let element, _): mapView.addAnnotation(element)
case .remove(_, let element, _): mapView.removeAnnotation(element)
}
}
}
func makeCoordinator() -> MapViewCoordinator {
MapViewCoordinator(parent: self)
}
func centerMapOnUserLocation() {
guard let coordinate = locationManager.location?.coordinate else {return}
let coordinateRegion = MKCoordinateRegion(center: coordinate, latitudinalMeters: 1000, longitudinalMeters: 1000)
mapView.setRegion(coordinateRegion, animated: true)
}
private func configureLocationServices() {
if locationManager.authorizationStatus == .notDetermined {
locationManager.requestAlwaysAuthorization()
} else {
return
}
}
}
class MapViewCoordinator: NSObject, MKMapViewDelegate, CLLocationManagerDelegate {
let parent: MapView
init(parent: MapView) {
self.parent = parent
}
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? EntMap.PlaceAnnotation {
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "AnnotationView", for: annotation) as? MapAnnotationView {
let mapAnnotation = annotation as EntMap.PlaceAnnotation
annotationView.clusteringIdentifier = "ClusterAnnotationView"
annotationView.setupUI(with: mapAnnotation)
return annotationView
}
}
if let annotation = annotation as? MKClusterAnnotation {
if let annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: "ClusterAnnotationView") as? MapClusterAnnotationView {
annotationView.annotation = annotation
let count = annotation.memberAnnotations.count < 100 ? "\(annotation.memberAnnotations.count)" : "99+"
annotationView.setupUI(count: count)
return annotationView
}
}
return nil
}
func mapView(_ mapView: MKMapView, didAdd views: [MKAnnotationView]) {
for view in views {
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(annotationTapped))
view.addGestureRecognizer(tapGesture)
}
}
func locationManager(_ manager: CLLocationManager, didChangeAuthorization status: CLAuthorizationStatus) {
parent.centerMapOnUserLocation()
}
@objc func annotationTapped(_ gestureRecognizer: UITapGestureRecognizer) {
if let view = gestureRecognizer.view as? MKAnnotationView {
if let annotation = view.annotation as? MKClusterAnnotation {
parent.mapView.showAnnotations(annotation.memberAnnotations, animated: true)
}
if let annotation = view.annotation as? EntMap.PlaceAnnotation {
parent.onAnnotationTap(annotation)
}
}
}
@objc func longPressed(_ gestureRecognizer: UIGestureRecognizer) {
if gestureRecognizer.state == .ended {
if let map = gestureRecognizer.view as? MKMapView {
let tapPoint = gestureRecognizer.location(in: map)
let tapCoordinate = map.convert(tapPoint, toCoordinateFrom: map)
parent.onLongPress(MapLocation(coordinates: tapCoordinate))
}
}
}
}
I have a problem that annotation view is breaking when it must render on the edge of map. The view is being fixed when it is closer to center. I believe the problem is in the MapPin view or in the UIHostingView in setupUI
method but I can't figure it out why exactly.
I have tried to change the SwiftUI with UIKit with simpler custom view and I have not noticed the bug.