Centrando MKMapView en puntos N-pixels debajo del pin

Desea centrar MKMapView en un punto N-pixels debajo de un pin dado ( que puede o no ser visible en el MapRect actual ).

He estado tratando de resolver esto usando varias jugadas con -(CLLocationCoordinate2D)convertPoint:(CGPoint)point toCoordinateFromView:(UIView *)view sin éxito.

Alguien ha pasado por este camino ( sin juego de palabras )?

enter image description here

La técnica más sencilla es simplemente desplazar el mapa hacia abajo, digamos un 40% desde donde estaría la coordinate , aprovechando el span de la region de MKMapView . Si no necesita píxeles reales, solo necesita que se mueva hacia abajo para que CLLocationCoordinate2D en cuestión esté cerca de la parte superior del mapa (digamos 10% desde la parte superior):

 CLLocationCoordinate2D center = coordinate; center.latitude -= self.mapView.region.span.latitudeDelta * 0.40; [self.mapView setCenterCoordinate:center animated:YES]; 

Si desea tener en cuenta la rotación y el paso de la cámara, la técnica anterior puede no ser adecuada. En ese caso, podrías:

  • Identifique la posición en la vista a la que desea cambiar la ubicación del usuario;

  • Convierta eso en una CLLocation ;

  • Calcule la distancia de la ubicación actual del usuario desde esa nueva ubicación deseada;

  • Mueva la cámara en esa distancia en la dirección de 180 ° desde el encabezado actual de la cámara del mapa.

Por ejemplo, en Swift 3, algo así como:

 var point = mapView.convert(mapView.centerCoordinate, toPointTo: view) point.y -= offset let coordinate = mapView.convert(point, toCoordinateFrom: view) let offsetLocation = coordinate.location let distance = mapView.centerCoordinate.location.distance(from: offsetLocation) / 1000.0 let camera = mapView.camera let adjustedCenter = mapView.centerCoordinate.adjust(by: distance, at: camera.heading - 180.0) camera.centerCoordinate = adjustedCenter 

Donde CLLocationCoordinate2D tiene la siguiente extension :

 extension CLLocationCoordinate2D { var location: CLLocation { return CLLocation(latitude: latitude, longitude: longitude) } private func radians(from degrees: CLLocationDegrees) -> Double { return degrees * .pi / 180.0 } private func degrees(from radians: Double) -> CLLocationDegrees { return radians * 180.0 / .pi } func adjust(by distance: CLLocationDistance, at bearing: CLLocationDegrees) -> CLLocationCoordinate2D { let distanceRadians = distance / 6_371.0 // 6,371 = Earth's radius in km let bearingRadians = radians(from: bearing) let fromLatRadians = radians(from: latitude) let fromLonRadians = radians(from: longitude) let toLatRadians = asin( sin(fromLatRadians) * cos(distanceRadians) + cos(fromLatRadians) * sin(distanceRadians) * cos(bearingRadians) ) var toLonRadians = fromLonRadians + atan2(sin(bearingRadians) * sin(distanceRadians) * cos(fromLatRadians), cos(distanceRadians) - sin(fromLatRadians) * sin(toLatRadians)) // adjust toLonRadians to be in the range -180 to +180... toLonRadians = fmod((toLonRadians + 3.0 * .pi), (2.0 * .pi)) - .pi let result = CLLocationCoordinate2D(latitude: degrees(from: toLatRadians), longitude: degrees(from: toLonRadians)) return result } } 

Entonces, incluso con la cámara inclinada y en un rumbo que no sea el norte, esto mueve la ubicación del usuario (que está centrada, donde está el punto de mira inferior) hasta 150 píxeles (donde está la cruz superior), produciendo algo como:

enter image description here

Obviamente, debes ser consciente de las situaciones degeneradas (por ejemplo, estás a 1 km del polo sur e intentas desplazar el mapa a 2 km), estás usando un ángulo de cámara tan alto que la ubicación de la pantalla deseada está más allá del horizonte, etc.), pero para escenarios prácticos del mundo real, algo como lo anterior podría ser suficiente. Obviamente, si no permite que el usuario cambie el tono de la cámara, la respuesta es aún más fácil.


Respuesta original: para mover la anotación n píxeles

Si tiene un CLLocationCoordinate2D , puede convertirlo en un CGPoint , moverlo x píxeles y luego convertirlo de nuevo a CLLocationCoordinate2D :

 - (void)moveCenterByOffset:(CGPoint)offset from:(CLLocationCoordinate2D)coordinate { CGPoint point = [self.mapView convertCoordinate:coordinate toPointToView:self.mapView]; point.x += offset.x; point.y += offset.y; CLLocationCoordinate2D center = [self.mapView convertPoint:point toCoordinateFromView:self.mapView]; [self.mapView setCenterCoordinate:center animated:YES]; } 

Puedes llamar esto por:

 [self moveCenterByOffset:CGPointMake(0, 100) from:coordinate]; 

Desafortunadamente, esto solo funciona si la coordinate es visible antes de comenzar, por lo que es posible que primero tenga que ir a la coordenada original y luego ajustar el centro.

La única forma de hacer esto de manera confiable es usar lo siguiente:

 - (void)setVisibleMapRect:(MKMapRect)mapRect edgePadding:(UIEdgeInsets)insets animated:(BOOL)animate 

Para hacerlo, dada la región del mapa sobre la que desea centrarse, debe convertir la región del mapa a MKMapRect. Utilice el margen de relleno para el desplazamiento de píxeles, obviamente.

Vea aquí para eso: Convertir MKCoordinateRegion a MKMapRect

Comentario: Me parece bastante extraño que esa sea la única forma de hacerlo, dado que MKMapRect no es algo que normalmente se usa con MKMapView: todos los métodos de conversión son para MKMapRegion. Pero, vale, al menos funciona. Probado en mi propio proyecto.

Para Swift:

 import MapKit extension MKMapView { func moveCenterByOffSet(offSet: CGPoint, coordinate: CLLocationCoordinate2D) { var point = self.convert(coordinate, toPointTo: self) point.x += offSet.x point.y += offSet.y let center = self.convert(point, toCoordinateFrom: self) self.setCenter(center, animated: true) } func centerCoordinateByOffSet(offSet: CGPoint) -> CLLocationCoordinate2D { var point = self.center point.x += offSet.x point.y += offSet.y return self.convert(point, toCoordinateFrom: self) } } 

Una solución fácil es hacer que el marco de la vista del mapa sea más grande que el área visible. Luego coloque su pin en el centro de la vista del mapa y oculte todas las áreas no deseadas detrás de otra vista o fuera de los límites de la pantalla.

Déjame elaborar. Si miro tu captura de pantalla, haz lo siguiente:

La distancia entre su pin y la parte inferior es de 353 píxeles. Así que haz que tus vistas de mapa tengan el doble de altura: 706 píxeles. Tu captura de pantalla tiene una altura de 411 píxeles. Coloque su marco en un origen de 706px – 411px = -293 pixel. Ahora centra tu vista de mapa en la coordenada del pin y listo.

Actualización 4 de marzo de 2014:

Creé una pequeña aplicación de muestra con Xcode 5.0.2 para probar esto: http://cl.ly/0e2v0u3G2q1d

SWIFT 3 ACTUALIZADO

Se actualizó la función con Zoom

 func zoomToPos() { let span = MKCoordinateSpan(latitudeDelta: 0.1, longitudeDelta: 0.1) // Create a new MKMapRegion with the new span, using the center we want. let coordinate = moveCenterByOffset(offset: CGPoint(x: 0, y: 100), coordinate: (officeDetail?.coordinate)!) let region = MKCoordinateRegion(center: coordinate, span: span) mapView.setRegion(region, animated: true) } func moveCenterByOffset (offset: CGPoint, coordinate: CLLocationCoordinate2D) -> CLLocationCoordinate2D { var point = self.mapView.convert(coordinate, toPointTo: self.mapView) point.x += offset.x point.y += offset.y return self.mapView.convert(point, toCoordinateFrom: self.mapView) } 

Después de leer este hilo y jugar, especialmente con la ampliación de la anotación, terminé con los siguientes procedimientos:

** Centrado en la anotación: **

 - (void) centerOnSelection:(id)annotation { MKCoordinateRegion region = self.mapView.region; region.center = annotation.coordinate; CGFloat per = ([self sizeOfBottom] - [self sizeOfTop]) / (2 * self.mapView.frame.size.height); region.center.latitude -= self.mapView.region.span.latitudeDelta * per; [self.mapView setRegion:region animated:YES]; } 

** Zoom en la anotación: **

 - (void) zoomAndCenterOnSelection:(id)annotation { DLog(@"zoomAndCenterOnSelection"); MKCoordinateRegion region = self.mapView.region; MKCoordinateSpan span = MKCoordinateSpanMake(0.005, 0.005); region.center = annotation.coordinate; CGFloat per = ([self sizeOfBottom] - [self sizeOfTop]) / (2 * self.mapView.frame.size.height); region.center.latitude -= self.mapView.region.span.latitudeDelta * span.latitudeDelta / region.span.latitudeDelta * per; region.span = span; [self.mapView setRegion:region animated:YES]; } 

-(CGFloat) sizeOfBottom y -(CGFloat) sizeOfTop devuelven la altura de los paneles que cubren la vista de mapa desde las guías de diseño

Mire este método en MKMapView :

 - (void)setCenterCoordinate:(CLLocationCoordinate2D)coordinate animated:(BOOL)animated