William B Travis
William B Travis

Reputation: 1

Swift - Making MapView callout annotation visible when pin annotation clicked by User

Please observe the linked image (bottom, below)

When a user moves the map screen to where the pin ends up near the edge of the screen and then selects that pin annotation, an Apple method then moves the map screen slightly toward the center of the screen so that the pin annotation callout is visible.

My question is twofold:

I. What Apple method is called to make that screen adjustment?

II. What would be the smartest way to implement that same functionality within the mapView didSelect view method if the MKMapViewDelegate extension were implemented?

Mapview screen adjustment sequence:

Upvotes: -1

Views: 1175

Answers (2)

William B Travis
William B Travis

Reputation: 1

What I came up with is the following:

 func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {

    // Get Coordinate for Selected Map Pin
    let selectedPinCoordinate = (view.annotation?.coordinate)!

    // Determine (X,Y) coordinates for Selected Map Pin on view (mapView)
    let SelectedPinMapPoint = mapView.convert(selectedPinCoordinate, toPointTo: mapView)

    // Determine (X,Y) coordinate for center point of view (mapView)
    let mapCenterMapPoint = mapView.convert(mapView.centerCoordinate, toPointTo: mapView)

    // Define an inner view within the existing view frame.  Any Selected Map Pin outside
    // bounds of inner view is deemed to be too close to the edge of the map for the callout
    // to be fully viewable by the user
    let innerRectFrameMinX = mapView.frame.maxX * 0.15
    let innerRectFrameMaxX = mapView.frame.maxX * 0.85
    let innerRectFrameMinY = mapView.frame.maxY * 0.30
    let innerRectFrameMaxY = mapView.frame.maxY * 0.85

    // Create a variable which will serve as the new center (X,Y) coordinate
    var newCenter = CGPoint()

    // Default new center (X,Y) coordinate to current view (X,Y) coordinate
    newCenter.x = mapCenterMapPoint.x
    newCenter.y = mapCenterMapPoint.y


    // Determine if x coordinate of Selected Map Pin is outside bounds. If so,
    // set a new center x coordinate so callout can be clearly seen by the user
    switch (SelectedPinMapPoint.x) {

    case _ where (SelectedPinMapPoint.x < innerRectFrameMinX):
        newCenter.x = mapView.frame.midX - (innerRectFrameMinX - SelectedPinMapPoint.x)

    case _ where (SelectedPinMapPoint.x > innerRectFrameMaxX):
        newCenter.x = mapView.frame.midX + (SelectedPinMapPoint.x - innerRectFrameMaxX)

    default: break

    }

    // Determine if y coordinate of Selected Map Pin is outside bounds. If so,
    // set a new center y coordinate so callout can be clearly seen by the user
    switch (SelectedPinMapPoint.y) {

    case _ where (SelectedPinMapPoint.y < innerRectFrameMinY):
        newCenter.y = mapView.frame.midY - (innerRectFrameMinY - SelectedPinMapPoint.y)

    case _ where (SelectedPinMapPoint.y > innerRectFrameMaxY):
        newCenter.y = mapView.frame.midY + (SelectedPinMapPoint.y - innerRectFrameMaxY)

    default: break

    }

   // Convert new map Center (X,Y) coordinate to map coordinate
   let newCenterCoordinate = mapView.convert(newCenter, toCoordinateFrom: nil)

   // Set new center as center for map view
   mapView.setCenter(newCenterCoordinate, animated: true)

}

I've linked a rough graphical sketch of my conception for whatever benefit it may provide.Rough Graphical Sketch

Upvotes: 0

Code Different
Code Different

Reputation: 93191

Theoretically, you can get the frame of the annotation view and check if it's too close to the edges of the map view:

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    // if view.frame is too close to the edges of the MKMapView, triggered your desired method
}

In practice this is unreliable because it's also depends on the callout bubble of the annotation view. We need a diffrent threshold for the "too close to the edges" criteria depending on the size of the callout bubble, for which there's no easy way to do.

This is a little hacky: when the screen adjusts, the regionWillChangeAnimated method will be called. If it is triggered within a split second of tapping on an annotation, there's a good chance that it was caused by user tapping on the annotation:

weak var tappedAnnotationView: MKAnnotationView?

func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
    self.tappedAnnotationView = view // this property will only be non-nil for 0.1 seconds
    DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {
        self.tappedAnnotationView = nil
    }
}

func mapView(_ mapView: MKMapView, regionWillChangeAnimated animated: Bool) {
    if self.tappedAnnotationView != nil {
        print("map adjusted due to tap on an annotation view")
    }
}

It works for the basic tests I threw at it, but obviously there will be extreme cases when it break down since it's a hack.

Upvotes: 0

Related Questions