iphone - kit - maps swift 4
Posicionando MKMapView para mostrar mĂșltiples anotaciones a la vez (22)
¿Por qué tan complicado?
MKCoordinateRegion coordinateRegionForCoordinates(CLLocationCoordinate2D *coords, NSUInteger coordCount) {
MKMapRect r = MKMapRectNull;
for (NSUInteger i=0; i < coordCount; ++i) {
MKMapPoint p = MKMapPointForCoordinate(coords[i]);
r = MKMapRectUnion(r, MKMapRectMake(p.x, p.y, 0, 0));
}
return MKCoordinateRegionForMapRect(r);
}
Tengo varias anotaciones que quiero agregar a mi MKMapView (podría 0-n elementos, donde n es generalmente alrededor de 5). Puedo agregar las anotaciones correctamente, pero quiero cambiar el tamaño del mapa para que se ajuste a todas las anotaciones en pantalla a la vez, y no estoy seguro de cómo hacerlo.
He estado mirando -regionThatFits:
pero no estoy muy seguro de qué hacer con eso. Voy a publicar un código para mostrar lo que tengo hasta ahora. Creo que esta debería ser una tarea generalmente sencilla, pero me siento un poco abrumado con MapKit hasta el momento.
- (void)locationManager:(CLLocationManager *)manager didUpdateToLocation:(CLLocation *)newLocation fromLocation:(CLLocation *)oldLocation{
location = newLocation.coordinate;
//One location is obtained.. just zoom to that location
MKCoordinateRegion region;
region.center = location;
//Set Zoom level using Span
MKCoordinateSpan span;
span.latitudeDelta = 0.015;
span.longitudeDelta = 0.015;
region.span = span;
// Set the region here... but I want this to be a dynamic size
// Obviously this should be set after I''ve added my annotations
[mapView setRegion:region animated:YES];
// Test data, using these as annotations for now
NSArray *arr = [NSArray arrayWithObjects:@"one", @"two", @"three", @"four", nil];
float ex = 0.01;
for (NSString *s in arr) {
JBAnnotation *placemark = [[JBAnnotation alloc] initWithLat:(location.latitude + ex) lon:location.longitude];
[mapView addAnnotation:placemark];
ex = ex + 0.005;
}
// What do I do here?
[mapView setRegion:[mapView regionThatFits:region] animated:YES];
}
Aviso, todo esto sucede cuando recibo una actualización de ubicación ... No sé si ese es un lugar apropiado para hacer esto. Si no, ¿dónde estaría un lugar mejor? -viewDidLoad
?
Gracias por adelantado.
A partir de iOS7 puedes usar showAnnotations:animated:
[mapView showAnnotations:annotations animated:YES];
Aquí está el equivalente de SWIFT (trabajo confirmado en: Xcode6.1, SDK 8.2) para las respuestas de Mustafa:
func zoomToFitMapAnnotations() {
if self.annotations.count == 0 {return}
var topLeftCoordinate = CLLocationCoordinate2D(latitude: -90, longitude: 180)
var bottomRightCoordinate = CLLocationCoordinate2D(latitude: 90, longitude: -180)
var i = 1
for object in self.annotations {
if let annotation = object as? MKAnnotation {
topLeftCoordinate.longitude = fmin(topLeftCoordinate.longitude, annotation.coordinate.longitude)
topLeftCoordinate.latitude = fmin(topLeftCoordinate.latitude, annotation.coordinate.latitude)
bottomRightCoordinate.longitude = fmin(bottomRightCoordinate.longitude, annotation.coordinate.longitude)
bottomRightCoordinate.latitude = fmin(bottomRightCoordinate.latitude, annotation.coordinate.latitude)
}
}
var center = CLLocationCoordinate2D(latitude: topLeftCoordinate.latitude - (topLeftCoordinate.latitude - bottomRightCoordinate.latitude) * 0.5, longitude: topLeftCoordinate.longitude - (topLeftCoordinate.longitude - bottomRightCoordinate.longitude) * 0.5)
print("/ncenter:/(center.latitude) /(center.longitude)")
// Add a little extra space on the sides
var span = MKCoordinateSpanMake(fabs(topLeftCoordinate.latitude - bottomRightCoordinate.latitude) * 1.01, fabs(bottomRightCoordinate.longitude - topLeftCoordinate.longitude) * 1.01)
print("/nspan:/(span.latitudeDelta) /(span.longitudeDelta)")
var region = MKCoordinateRegion(center: center, span: span)
region = self.regionThatFits(region)
self.setRegion(region, animated: true)
}
Basado en la excelente respuesta de me2
(ahora en Swift)
func coordinateRegionForCoordinates(coords: [CLLocationCoordinate2D]) -> MKCoordinateRegion {
var rect: MKMapRect = MKMapRectNull
for coord in coords {
let point: MKMapPoint = MKMapPointForCoordinate(coord)
rect = MKMapRectUnion(rect, MKMapRectMake(point.x, point.y, 0, 0))
}
return MKCoordinateRegionForMapRect(rect)
}
Basado en la respuesta me2, escribí una categoría para MKMapView para agregar algunos márgenes y omitir la anotación de ubicación del usuario:
@interface MKMapView (ZoomToFitAnnotations)
- (void)zoomToFitAnnotations:(BOOL)animated;
@end
@implementation MKMapView (ZoomToFitAnnotations)
- (void)zoomToFitAnnotations:(BOOL)animated {
if (self.annotations.count == 0)
return;
MKMapRect rect = MKMapRectNull;
for (id<MKAnnotation> annotation in self.annotations) {
if ([annotation isKindOfClass:[MKUserLocation class]] == false) {
MKMapPoint point = MKMapPointForCoordinate(annotation.coordinate);
rect = MKMapRectUnion(rect, MKMapRectMake(point.x, point.y, 0, 0));
}
}
MKCoordinateRegion region = MKCoordinateRegionForMapRect(rect);
region.span.longitudeDelta *= 2; // Margin
region.span.latitudeDelta *= 2; // Margin
[self setRegion:region animated:animated];
}
@end
Como no puedo comentar una respuesta, me gustaría agregar mi parte de la conveniencia en la respuesta de @me2 (ya que pensé que era el enfoque más elegante que se encuentra aquí).
Para mi proyecto personal, simplemente agregué una categoría en la clase MKMapView para encapsular la funcionalidad de "área visible" para una operación común: configuración para poder ver todas las anotaciones actualmente cargadas en la instancia de MKMapView. el resultado fue esto:
archivo .h
#import <MapKit/MapKit.h>
@interface MKMapView (Extensions)
-(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated;
-(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated;
@end
archivo .m
#import "MKMapView+Extensions.h"
@implementation MKMapView (Extensions)
/**
* Changes the currently visible portion of the map to a region that best fits all the currently loadded annotations on the map, and it optionally animates the change.
*
* @param animated is the change should be perfomed with an animation.
*/
-(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated
{
MKMapView * mapView = self;
NSArray * annotations = mapView.annotations;
[self ij_setVisibleRectToFitAnnotations:annotations animated:animated];
}
/**
* Changes the currently visible portion of the map to a region that best fits the provided annotations array, and it optionally animates the change.
All elements from the array must conform to the <MKAnnotation> protocol in order to fetch the coordinates to compute the visible region of the map.
*
* @param annotations an array of elements conforming to the <MKAnnotation> protocol, holding the locations for which the visible portion of the map will be set.
* @param animated wether or not the change should be perfomed with an animation.
*/
-(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated
{
MKMapView * mapView = self;
MKMapRect r = MKMapRectNull;
for (id<MKAnnotation> a in annotations) {
ZAssert([a conformsToProtocol:@protocol(MKAnnotation)], @"ERROR: All elements of the array MUST conform to the MKAnnotation protocol. Element (%@) did not fulfill this requirement", a);
MKMapPoint p = MKMapPointForCoordinate(a.coordinate);
//MKMapRectUnion performs the union between 2 rects, returning a bigger rect containing both (or just one if the other is null). here we do it for rects without a size (points)
r = MKMapRectUnion(r, MKMapRectMake(p.x, p.y, 0, 0));
}
[mapView setVisibleMapRect:r animated:animated];
}
@end
Como puede ver, agregué 2 métodos hasta ahora: uno para establecer la región visible del mapa que concuerde con todas las anotaciones actualmente cargadas en la instancia MKMapView y otro método para establecerlo en cualquier matriz de objetos. Entonces, para establecer la región visible de mapView, el código sería tan simple como:
//the mapView instance
[self.mapView ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:animated];
Espero que ayude =)
El link publicado por Jim ahora está muerto, pero pude encontrar el código (que había marcado en alguna parte). Espero que esto ayude.
- (void)zoomToFitMapAnnotations:(MKMapView *)mapView {
if ([mapView.annotations count] == 0) return;
CLLocationCoordinate2D topLeftCoord;
topLeftCoord.latitude = -90;
topLeftCoord.longitude = 180;
CLLocationCoordinate2D bottomRightCoord;
bottomRightCoord.latitude = 90;
bottomRightCoord.longitude = -180;
for(id<MKAnnotation> annotation in mapView.annotations) {
topLeftCoord.longitude = fmin(topLeftCoord.longitude, annotation.coordinate.longitude);
topLeftCoord.latitude = fmax(topLeftCoord.latitude, annotation.coordinate.latitude);
bottomRightCoord.longitude = fmax(bottomRightCoord.longitude, annotation.coordinate.longitude);
bottomRightCoord.latitude = fmin(bottomRightCoord.latitude, annotation.coordinate.latitude);
}
MKCoordinateRegion region;
region.center.latitude = topLeftCoord.latitude - (topLeftCoord.latitude - bottomRightCoord.latitude) * 0.5;
region.center.longitude = topLeftCoord.longitude + (bottomRightCoord.longitude - topLeftCoord.longitude) * 0.5;
// Add a little extra space on the sides
region.span.latitudeDelta = fabs(topLeftCoord.latitude - bottomRightCoord.latitude) * 1.1;
region.span.longitudeDelta = fabs(bottomRightCoord.longitude - topLeftCoord.longitude) * 1.1;
region = [mapView regionThatFits:region];
[mapView setRegion:region animated:YES];
}
En base a la información y las sugerencias de todos, surgió lo siguiente. Gracias a todos en esta discusión por contribuir :) Esto iría en la vista Controlador que contiene el mapView.
- (void)zoomToFitMapAnnotations {
if ([self.mapView.annotations count] == 0) return;
int i = 0;
MKMapPoint points[[self.mapView.annotations count]];
//build array of annotation points
for (id<MKAnnotation> annotation in [self.mapView annotations])
points[i++] = MKMapPointForCoordinate(annotation.coordinate);
MKPolygon *poly = [MKPolygon polygonWithPoints:points count:i];
[self.mapView setRegion:MKCoordinateRegionForMapRect([poly boundingMapRect]) animated:YES];
}
En mi caso, estoy comenzando con objetos CLLocation y creando anotaciones para cada uno de ellos.
Solo necesito colocar dos anotaciones, así que tengo un enfoque simple para construir la matriz de puntos, pero podría expandirse fácilmente para construir una matriz con una longitud arbitraria dado un conjunto de CLLocations.
Aquí está mi implementación (no requiere la creación de MKMapPoints):
//start with a couple of locations
CLLocation *storeLocation = store.address.location.clLocation;
CLLocation *userLocation = [LBLocationController sharedController].currentLocation;
//build an array of points however you want
CLLocationCoordinate2D points[2] = {storeLocation.coordinate, userLocation.coordinate};
//the magic part
MKPolygon *poly = [MKPolygon polygonWithCoordinates:points count:2];
[self.mapView setRegion:MKCoordinateRegionForMapRect([poly boundingMapRect])];
Espero que esto sea al menos relevante, esto es lo que armé para Mono (basado en la respuesta de pkclSoft):
void ZoomMap (MKMapView map)
{
var annotations = map.Annotations;
if (annotations == null || annotations.Length == 0)
return;
var points = annotations.OfType<MapAnnotation> ()
.Select (s => MKMapPoint.FromCoordinate (s.Coordinate))
.ToArray ();
map.SetVisibleMapRect(MKPolygon.FromPoints (points).BoundingMapRect, true);
}
Hay un nuevo método en ''MKMapView'' a partir de iOS 7 que puedes usar
Declaración
RÁPIDO
func showAnnotations(_ annotations: [AnyObject]!, animated animated: Bool)
C OBJETIVO
- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated
Parámetros
anotaciones Las anotaciones que desea que sean visibles en el mapa. si desea que el cambio de la región del mapa sea animado, o NO si desea que el mapa muestre la nueva región inmediatamente sin animaciones.
Discusión
Llamar a este método actualiza el valor en la propiedad de la región y potencialmente otras propiedades para reflejar la nueva región del mapa.
He hecho algo similar a esto para alejar (o acercarme) a un área que incluye una anotación de punto y la ubicación actual. Puede expandir esto al recorrer sus anotaciones.
Los pasos básicos son:
- Calcule el min lat / long
- Calcule el lat / lat máximo
- Crear objetos CLLocation para estos dos puntos
- Calcule la distancia entre los puntos
- Crear región usando punto central entre puntos y distancia convertida a grados
- Pase la región a MapView para ajustar
- Use la región ajustada para establecer la región MapView
-(IBAction)zoomOut:(id)sender {
CLLocationCoordinate2D southWest = _newLocation.coordinate;
CLLocationCoordinate2D northEast = southWest;
southWest.latitude = MIN(southWest.latitude, _annotation.coordinate.latitude);
southWest.longitude = MIN(southWest.longitude, _annotation.coordinate.longitude);
northEast.latitude = MAX(northEast.latitude, _annotation.coordinate.latitude);
northEast.longitude = MAX(northEast.longitude, _annotation.coordinate.longitude);
CLLocation *locSouthWest = [[CLLocation alloc] initWithLatitude:southWest.latitude longitude:southWest.longitude];
CLLocation *locNorthEast = [[CLLocation alloc] initWithLatitude:northEast.latitude longitude:northEast.longitude];
// This is a diag distance (if you wanted tighter you could do NE-NW or NE-SE)
CLLocationDistance meters = [locSouthWest getDistanceFrom:locNorthEast];
MKCoordinateRegion region;
region.center.latitude = (southWest.latitude + northEast.latitude) / 2.0;
region.center.longitude = (southWest.longitude + northEast.longitude) / 2.0;
region.span.latitudeDelta = meters / 111319.5;
region.span.longitudeDelta = 0.0;
_savedRegion = [_mapView regionThatFits:region];
[_mapView setRegion:_savedRegion animated:YES];
[locSouthWest release];
[locNorthEast release];
}
Sé que esta es una pregunta antigua pero, si quieres mostrar todas las anotaciones YA EN EL mapa, usa esto:
mapView.showAnnotations(mapView.annotations, animated: true)
Se agregó una pequeña cláusula if para manejar 1 ubicación, para agregar al fragmento de código cound de mustufa. Usé la función zoomToAnnotation de pkclSoft para eso:
if ([mapView.annotations count] == 1){
MKCoordinateSpan span = {0.027, 0.027};
region.span = span;
CLLocationCoordinate2D singleCoordinate = [[mapView.annotations objectAtIndex:0] coordinate];
region.center.latitude = singleCoordinate.latitude;
region.center.longitude = singleCoordinate.longitude;
}
else
{
// mustufa''s code
}
Tengo una respuesta diferente. Iba a implementar el algoritmo de zoom para ajustar yo mismo, pero pensé que Apple debía tener una manera de hacer lo que queríamos sin tanto trabajo. El uso de API doco mostró rápidamente que podía usar MKPolygon para hacer lo que se necesitaba:
/* this simply adds a single pin and zooms in on it nicely */
- (void) zoomToAnnotation:(MapAnnotation*)annotation {
MKCoordinateSpan span = {0.027, 0.027};
MKCoordinateRegion region = {[annotation coordinate], span};
[mapView setRegion:region animated:YES];
}
/* This returns a rectangle bounding all of the pins within the supplied
array */
- (MKMapRect) getMapRectUsingAnnotations:(NSArray*)theAnnotations {
MKMapPoint points[[theAnnotations count]];
for (int i = 0; i < [theAnnotations count]; i++) {
MapAnnotation *annotation = [theAnnotations objectAtIndex:i];
points[i] = MKMapPointForCoordinate(annotation.coordinate);
}
MKPolygon *poly = [MKPolygon polygonWithPoints:points count:[theAnnotations count]];
return [poly boundingMapRect];
}
/* this adds the provided annotation to the mapview object, zooming
as appropriate */
- (void) addMapAnnotationToMapView:(MapAnnotation*)annotation {
if ([annotations count] == 1) {
// If there is only one annotation then zoom into it.
[self zoomToAnnotation:annotation];
} else {
// If there are several, then the default behaviour is to show all of them
//
MKCoordinateRegion region = MKCoordinateRegionForMapRect([self getMapRectUsingAnnotations:annotations]);
if (region.span.latitudeDelta < 0.027) {
region.span.latitudeDelta = 0.027;
}
if (region.span.longitudeDelta < 0.027) {
region.span.longitudeDelta = 0.027;
}
[mapView setRegion:region];
}
[mapView addAnnotation:annotation];
[mapView selectAnnotation:annotation animated:YES];
}
Espero que esto ayude.
Una posible solución podría ser medir la distancia entre la ubicación actual y todas las anotaciones y usar el método MKCoordinateRegionMakeWithDistance para crear una región que tenga una distancia ligeramente mayor que la anotación más lejana.
Por supuesto, esto sería más lento cuanto más anotaciones hayas agregado.
Utilizando Swift, un polígono y algo de relleno adicional utilicé lo siguiente:
func zoomToFit() {
var allLocations:[CLLocationCoordinate2D] = [
CLLocationCoordinate2D(latitude: 32.768805, longitude: -117.167119),
CLLocationCoordinate2D(latitude: 32.770480, longitude: -117.148385),
CLLocationCoordinate2D(latitude: 32.869675, longitude: -117.212929)
]
var poly:MKPolygon = MKPolygon(coordinates: &allLocations, count: allLocations.count)
self.mapView.setVisibleMapRect(poly.boundingMapRect, edgePadding: UIEdgeInsetsMake(40.0, 40.0, 40.0, 40.0), animated: false)
}
este código funciona para mí, muestra todos los pines con la ubicación actual, espero que esto te ayude,
func setCenterForMap() {
var mapRect: MKMapRect = MKMapRectNull
for loc in mapView.annotations {
let point: MKMapPoint = MKMapPointForCoordinate(loc.coordinate)
print( "location is : /(loc.coordinate)");
mapRect = MKMapRectUnion(mapRect, MKMapRectMake(point.x,point.y,0,0))
}
if (locationManager.location != nil) {
let point: MKMapPoint = MKMapPointForCoordinate(locationManager.location!.coordinate)
print( "Cur location is : /(locationManager.location!.coordinate)");
mapRect = MKMapRectUnion(mapRect, MKMapRectMake(point.x,point.y,0,0))
}
mapView.setVisibleMapRect(mapRect, edgePadding: UIEdgeInsetsMake(40.0, 40.0, 40.0, 40.0), animated: true)
}
también puedes hacerlo de esta manera ...
// Position the map so that all overlays and annotations are visible on screen.
MKMapRect regionToDisplay = [self mapRectForAnnotations:annotationsToDisplay];
if (!MKMapRectIsNull(regionToDisplay)) myMapView.visibleMapRect = regionToDisplay;
- (MKMapRect) mapRectForAnnotations:(NSArray*)annotationsArray
{
MKMapRect mapRect = MKMapRectNull;
//annotations is an array with all the annotations I want to display on the map
for (id<MKAnnotation> annotation in annotations) {
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate);
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0);
if (MKMapRectIsNull(mapRect))
{
mapRect = pointRect;
} else
{
mapRect = MKMapRectUnion(mapRect, pointRect);
}
}
return mapRect;
}
Considera esta extensión:
extension MKCoordinateRegion {
init(locations: [CLLocationCoordinate2D], marginMultiplier: Double = 1.1) {
let mapRect = locations.reduce(MKMapRect(), {
let point = MKMapPointForCoordinate($1)
let rect = MKMapRect(origin: point, size: MKMapSize(width: 0.0, height: 0.0))
return MKMapRectUnion($0, rect)
})
var coordinateRegion = MKCoordinateRegionForMapRect(mapRect)
coordinateRegion.span.latitudeDelta *= marginMultiplier
coordinateRegion.span.longitudeDelta *= marginMultiplier
self = coordinateRegion
}
}
- (void)zoomToFitMapAnnotations {
if ([self.mapview.annotations count] == 0) return;
int i = 0;
MKMapPoint points[[self.mapview.annotations count]];
//build array of annotation points
for (id<MKAnnotation> annotation in [self.mapview annotations])
points[i++] = MKMapPointForCoordinate(annotation.coordinate);
MKPolygon *poly = [MKPolygon polygonWithPoints:points count:i];
[self.mapview setRegion:MKCoordinateRegionForMapRect([poly boundingMapRect]) animated:YES];
}
CLLocationCoordinate2D min = CLLocationCoordinate2DMake(99999.0, 99999.0);
CLLocationCoordinate2D max = CLLocationCoordinate2DMake(-99999.0, -99999.0);
// find max/min....
// zoom to cover area
// TODO: Maybe better using a MKPolygon which can calculate its own fitting region.
CLLocationCoordinate2D center = CLLocationCoordinate2DMake((max.latitude + min.latitude) / 2.0, (max.longitude + min.longitude) / 2.0);
MKCoordinateSpan span = MKCoordinateSpanMake(max.latitude - min.latitude, max.longitude - min.longitude);
MKCoordinateRegion region = MKCoordinateRegionMake(center, span);
[_mapView setRegion:[_mapView regionThatFits:region] animated:YES];