Centering a map annotation to the top quarter of the screen

so I'd like to go from image 1 to image 2 when an annotation is clicked (Mapbox):

https://i.stack.imgur.com/OFIFa.png

It's fairly easy to have the map center on the annotation, by calling mapView.setCenter() inside one of the Mapbox delegate functions as follows:

func mapView(_ mapView: MGLMapView, didSelect annotation: MGLAnnotation) {
    mapView.setCenter(annotation.coordinate, animated: true)
}

Obviously, this centers the annotation to the middle of the screen, but I need the annotation to be 'centered' to in the area above the view that pops up so it's still visible (ideally, it'll be equidistant from the top of 'View' and the top edge of the screen).

I was thinking of setting the zoomLevel in setCenter and then zooming to a specific distance south of the annotation, but the problem with this is various screen sizes on iOS devices will have the annotation centered differently.

I was also thinking maybe I could do some kind of coordinate conversion from the map to a CGPoint on the screen but am really unsure about how to implement this correctly (I've only ever used mapView.convert(CGPoint,toCoordinateFrom: mapView), and this won't be useful here). I'm not sure how to approach this problem. Any help would be much appreciated, whether it's just getting me started on a path or if you already have a solution that's even better. Thanks!


Solution 1:

I don't use MapBox but if you can access the region's span (horizontal and vertical distance of the map)

You can subtract the desired amount of the latitude's span from the annotation's coordinate and set that center for the map.

    ///Centers the map region by placing the passed annotation in the center of the I quadrant
    func centerTop(annotation: SpecialAnnot){
        //Find a 4th of the span (The horizontal and vertical span representing the amount of map to display.)
        let latCorrection = region.span.latitudeDelta/4 + region.span.latitudeDelta/8
        
        self.region = MKCoordinateRegion(center: .init(latitude: annotation.coordinate.latitude - latCorrection, longitude: annotation.coordinate.longitude), span: region.span)
    }

Here is some SwiftUI code.

import SwiftUI
import MapKit
class SpecialAnnot: NSObject, MKAnnotation, Identifiable{
    let id: UUID = .init()
    var coordinate: CLLocationCoordinate2D
    init(coordinate: CLLocationCoordinate2D) {
        self.coordinate = coordinate
        super.init()
    }
    
}
struct QuarterCenteredView: View {
    @State var region: MKCoordinateRegion = MKCoordinateRegion()
    
    let annotationCoordinate: [SpecialAnnot] = [SpecialAnnot(coordinate: .init(latitude: 40.748817, longitude: -73.985428))]
    var body: some View {
        VStack{
            Button("center quadrant I", action: {
                centerTop(annotation: annotationCoordinate.first!)
            })
            Map(coordinateRegion: $region, annotationItems: annotationCoordinate, annotationContent: { annot in
                MapAnnotation(coordinate: annot.coordinate, content: {
                    Image(systemName: "mappin").foregroundColor(.red)
                })
            })
                .onAppear(perform: {
                    DispatchQueue.main.async {
                        region = MKCoordinateRegion(center: annotationCoordinate.first!.coordinate, span: region.span)
                    }
                })
        }
    }
    ///Centers the map region by placing the passed annotation in the center of the I quadrant
    func centerTop(annotation: SpecialAnnot){
        //Find a 4th of the span (The horizontal and vertical span representing the amount of map to display.)
        let latCorrection = region.span.latitudeDelta/4 + region.span.latitudeDelta/8
        
        self.region = MKCoordinateRegion(center: .init(latitude: annotation.coordinate.latitude - latCorrection, longitude: annotation.coordinate.longitude), span: region.span)
    }
}

struct QuarterCenteredView_Previews: PreviewProvider {
    static var previews: some View {
        QuarterCenteredView()
    }
}

Solution 2:

You could resize the map frame to that square then center the coordinate. This would mean the view would no longer pop op over the mapview though it would appear to.

this can be done with:

(your MKMapView).frame = CGRect(x: , y: , width: , height: )

x and y are start coordinates in the superview (the view behind the mapview) coordinate system. Then the width and height parameters creates a rectangle with those dimensions starting from x and y. Be careful with the start point, it's sometimes in the lowerleft and sometimes in the upper left depending on the situation. Depending on your implementation you might have to modify the popup views frame too.

-if your map is always connected to the top of the screen you could use

(your MKMapView).frame = CGRect(x: self.view.origin.x, y: self.view.origin.y, width: self.view.frame.width, height: self.view.frame.height/2)