Позиционирование MKMapView для одновременного отображения нескольких аннотаций
У меня есть несколько аннотаций, которые я хочу добавить в свой MKMapView (он может содержать 0-n элементов, где n обычно равно 5). Я могу добавить annotations в порядке, но я хочу resize карты, чтобы сразу подогнать все annotations на экране, и я не знаю, как это сделать.
Я смотрел на -regionThatFits:
но я не совсем уверен, что с этим делать. Я отправлю код, чтобы показать, что у меня есть. Я думаю, что это должна быть простая задача, но до сих пор я немного перегружен MapKit.
- (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]; }
Обратите внимание: все это происходит, когда я получаю обновление местоположения … Я не знаю, подходит ли это для этого. Если нет, то где лучше? -viewDidLoad
?
- Как распечатать имя метода и номер строки и условно отключить NSLog?
- Убедитесь, что вход в UITextField только числовой
- Могу ли я делать запросы POST или GET из приложения iphone?
- Как получить счетчик ссылок NSObject?
- UIDatePicker, устанавливающий максимальные и минимальные даты на основе сегодняшней даты
Заранее спасибо.
- Использование Swift CFunctionPointer для передачи обратного вызова API CoreMIDI
- Нажмите ViewController справа налево с помощью UINavigationController
- Ошибка UICollectionView Assertion
- Можно ли изменить цвет фона UIButtons?
- Воспроизведение звука на iPhone даже в бесшумном режиме
- Что означает подчеркивание в имени переменной-члена в Objective-C?
- Прокрутка двумя пальцами с помощью UIScrollView
- Как я смогу удалить объекты из NSMutableArray?
Начиная с iOS7 вы можете использовать showAnnotations: анимированные:
[mapView showAnnotations:annotations animated:YES];
Ссылка, размещенная Джим, теперь мертва, но я смог найти код (который я где-то помещал в закладки). Надеюсь это поможет.
- (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 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]; }
Почему так сложно?
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(px, py, 0, 0)); } return MKCoordinateRegionForMapRect(r); }
Я сделал что-то похожее на это, чтобы уменьшить масштаб (или в) до области, которая включала аннотацию точки и текущее местоположение. Вы можете расширить это, заполнив annotations.
Основные этапы:
- Вычислить min lat / long
- Вычислить максимальный лат / длинный
- Создайте объекты CLLocation для этих двух точек
- Рассчитать расстояние между точками
- Создайте область, используя центральную точку между точками и расстоянием, преобразованными в gradleусы
- Перейдите в MapView, чтобы настроить
- Используйте настроенную область для установки области 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]; }
У меня другой ответ. Я собирался реализовать алгоритм масштабирования для соответствия, но я решил, что у Apple должен быть способ сделать то, что мы хотели, без большой работы. Использование API doco быстро показало, что я могу использовать MKPolygon для выполнения необходимых действий:
/* 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]; }
Надеюсь это поможет.
вы также можете сделать это таким образом ..
// 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 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; }
Основываясь на информации и предложениях от всех, я придумал следующее. Спасибо всем за участие в обсуждении за вклад 🙂 Это будет в представлении Controller, содержащем 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 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]; }
В моем случае я начинаю с объектов CLLocation и создаю annotations для каждого из них.
Мне нужно разместить только две annotations, поэтому у меня есть простой подход к построению массива точек, но его можно легко расширить, чтобы построить массив с произвольной длиной, заданный набором CLLocations.
Вот моя реализация (не требуется создание 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])];
Используя Swift, многоугольник и дополнительное дополнение, я использовал следующее:
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) }
Вот эквивалент SWIFT (Подтвержденная работа в: Xcode6.1, SDK 8.2) для ответов 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) }
В «MKMapView» есть новый метод с iOS 7, который вы можете использовать
декларация
СВИФТ
func showAnnotations(_ annotations: [AnyObject]!, animated animated: Bool)
Objective-C
- (void)showAnnotations:(NSArray *)annotations animated:(BOOL)animated
параметры
annotations Аннотации, которые вы хотите видеть на карте. анимированный YES, если вы хотите, чтобы изменение области карты изменилось, или НЕТ, если вы хотите, чтобы карта отображала новый регион сразу без анимации.
обсуждение
Вызов этого метода обновляет значение в свойстве region и потенциально других свойствах, чтобы отразить новую область карты.
Одним из возможных решений может быть измерение расстояния между текущим местоположением и всеми аннотациями и использование метода MKCoordinateRegionMakeWithDistance для создания области с немного большим расстоянием, чем самая дальняя аннотация.
Это, конечно, будет медленнее, чем больше добавленных вами аннотаций.
- (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 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]; }
Основываясь на отличном ответе me2
(теперь в 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) }
Добавлено небольшое предложение if для обработки 1 location-, чтобы добавить fragment кода суффинга. Используемая функция zoomToAnnotation pkclSoft для этого:
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 }
Я знаю, что это старый вопрос, но если вы хотите отобразить все annotations УЖЕ НА карте, используйте это:
mapView.showAnnotations(mapView.annotations, animated: true)
Надеюсь, это, по крайней мере, актуально, вот что я собрал для Mono (основанный на ответе pkclSoft):
void ZoomMap (MKMapView map) { var annotations = map.Annotations; if (annotations == null || annotations.Length == 0) return; var points = annotations.OfType () .Select (s => MKMapPoint.FromCoordinate (s.Coordinate)) .ToArray (); map.SetVisibleMapRect(MKPolygon.FromPoints (points).BoundingMapRect, true); }
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];
На основе ответа me2 я написал категорию для MKMapView, чтобы добавить некоторые поля и пропустить аннотацию местоположения пользователя:
@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 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
Поскольку я не могу прокомментировать ответ, я хотел бы добавить немного удобства в ответ @ me2 (так как я думал, что это самый элегантный подход, найденный здесь).
Для моего личного проекта я просто добавил категорию в class MKMapView, чтобы инкапсулировать функциональность «видимой области» для обычной операции: настройка для возможности видеть все загруженные в данный момент annotations в экземпляре MKMapView. результатом было следующее:
файл .h
#import @interface MKMapView (Extensions) -(void)ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:(BOOL)animated; -(void)ij_setVisibleRectToFitAnnotations:(NSArray *)annotations animated:(BOOL)animated; @end
.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 protocol in order to fetch the coordinates to compute the visible region of the map. * * @param annotations an array of elements conforming to the 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 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(px, py, 0, 0)); } [mapView setVisibleMapRect:r animated:animated]; } @end
Как вы можете видеть, я добавил до сих пор 2 метода: один для настройки видимой области карты на ту, которая соответствует всем загруженным в настоящее время аннотациям в экземпляре MKMapView, и другой метод, чтобы установить его в любой массив объектов. Поэтому, чтобы установить видимую область mapView, код будет таким же простым, как:
//the mapView instance [self.mapView ij_setVisibleRectToFitAllLoadedAnnotationsAnimated:animated];
Я надеюсь, что это поможет =)
этот код работает для меня, он показывает все контакты с текущим местоположением, надеюсь, что это поможет вам,
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) }
Рассмотрим это расширение:
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 } }