возникает вопрос: существует ли способ ограничения максимального уровня масштабирования для MKMapView? Или есть способ отслеживать, когда пользователь приближается к уровню, на котором нет изображения карты?
Есть ли способ ограничить максимальный уровень масштабирования MKMapView?
Ответ 1
Вы можете использовать метод делегата mapView:regionWillChangeAnimated:
для прослушивания событий изменения области, а если область более широкая, чем ваша максимальная область, верните ее в область максимума с помощью setRegion:animated:
, чтобы указать вашему пользователю, что они могут " t уменьшить это далеко. Здесь методы:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
Ответ 2
Если вы работаете только с iOS 7+, есть новое свойство camera.altitude
, которое вы можете получить/установить для обеспечения уровня масштабирования. Это эквивалентно решению azdev, но внешний код не требуется.
В ходе тестирования я также обнаружил, что можно было ввести бесконечный цикл, если вы неоднократно пытались увеличить масштаб, поэтому у меня есть var, чтобы предотвратить это в моем коде ниже.
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
// enforce maximum zoom level
if (_mapView.camera.altitude < 120.00 && !_modifyingMap) {
_modifyingMap = YES; // prevents strange infinite loop case
_mapView.camera.altitude = 120.00;
_modifyingMap = NO;
}
}
Ответ 3
Я просто потратил некоторое время на это для приложения, которое я создаю. Вот что я придумал:
-
Я начал с Troy Brant script на на этой странице, который лучше всего подходит для отображения карты.
-
Я добавил метод возврата текущего уровня масштабирования.
В MKMapView + ZoomLevel.h:
- (double)getZoomLevel;
В MKMapView + ZoomLevel.m:
// Return the current map zoomLevel equivalent, just like above but in reverse - (double)getZoomLevel{ MKCoordinateRegion reg=self.region; // the current visible region MKCoordinateSpan span=reg.span; // the deltas CLLocationCoordinate2D centerCoordinate=reg.center; // the center in degrees // Get the left and right most lonitudes CLLocationDegrees leftLongitude=(centerCoordinate.longitude-(span.longitudeDelta/2)); CLLocationDegrees rightLongitude=(centerCoordinate.longitude+(span.longitudeDelta/2)); CGSize mapSizeInPixels = self.bounds.size; // the size of the display window // Get the left and right side of the screen in fully zoomed-in pixels double leftPixel=[self longitudeToPixelSpaceX:leftLongitude]; double rightPixel=[self longitudeToPixelSpaceX:rightLongitude]; // The span of the screen width in fully zoomed-in pixels double pixelDelta=abs(rightPixel-leftPixel); // The ratio of the pixels to what we're actually showing double zoomScale= mapSizeInPixels.width /pixelDelta; // Inverse exponent double zoomExponent=log2(zoomScale); // Adjust our scale double zoomLevel=zoomExponent+20; return zoomLevel; }
Этот метод основан на нескольких частных методах в приведенном выше коде.
-
Я добавил это в свой делегат MKMapView (как рекомендовал выше @vladimir)
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated { NSLog(@"%f",[mapView getZoomLevel]); if([mapView getZoomLevel]<10) { [mapView setCenterCoordinate:[mapView centerCoordinate] zoomLevel:10 animated:TRUE]; } }
Это приводит к повторному масштабированию, если пользователь слишком далеко выходит. Вы можете использовать regionWillChangeAnimated, чтобы не допустить возврата карты от "bouncing".
Что касается комментариев цикла, описанных выше, похоже, что этот метод только итерации выполняется один раз.
Ответ 4
Да, это выполнимо. Во-первых, расширьте MKMapView, используя MKMapView + ZoomLevel.
Затем реализуйте это в своем MKMapViewDelegate:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
// Constrain zoom level to 8.
if( [mapView zoomLevel] < 8 )
{
[mapView setCenterCoordinate:mapView.centerCoordinate
zoomLevel:8
animated:NO];
}
}
Ответ 5
Вот код, переписанный в Swift 3 с использованием MKMapView + ZoomLevel и @T.Markle:
import Foundation
import MapKit
fileprivate let MERCATOR_OFFSET: Double = 268435456
fileprivate let MERCATOR_RADIUS: Double = 85445659.44705395
extension MKMapView {
func getZoomLevel() -> Double {
let reg = self.region
let span = reg.span
let centerCoordinate = reg.center
// Get the left and right most lonitudes
let leftLongitude = centerCoordinate.longitude - (span.longitudeDelta / 2)
let rightLongitude = centerCoordinate.longitude + (span.longitudeDelta / 2)
let mapSizeInPixels = self.bounds.size
// Get the left and right side of the screen in fully zoomed-in pixels
let leftPixel = self.longitudeToPixelSpaceX(longitude: leftLongitude)
let rightPixel = self.longitudeToPixelSpaceX(longitude: rightLongitude)
let pixelDelta = abs(rightPixel - leftPixel)
let zoomScale = Double(mapSizeInPixels.width) / pixelDelta
let zoomExponent = log2(zoomScale)
let zoomLevel = zoomExponent + 20
return zoomLevel
}
func setCenter(coordinate: CLLocationCoordinate2D, zoomLevel: Int, animated: Bool) {
let zoom = min(zoomLevel, 28)
let span = self.coordinateSpan(centerCoordinate: coordinate, zoomLevel: zoom)
let region = MKCoordinateRegion(center: coordinate, span: span)
self.setRegion(region, animated: true)
}
// MARK: - Private func
private func coordinateSpan(centerCoordinate: CLLocationCoordinate2D, zoomLevel: Int) -> MKCoordinateSpan {
// Convert center coordiate to pixel space
let centerPixelX = self.longitudeToPixelSpaceX(longitude: centerCoordinate.longitude)
let centerPixelY = self.latitudeToPixelSpaceY(latitude: centerCoordinate.latitude)
// Determine the scale value from the zoom level
let zoomExponent = 20 - zoomLevel
let zoomScale = NSDecimalNumber(decimal: pow(2, zoomExponent)).doubleValue
// Scale the maps size in pixel space
let mapSizeInPixels = self.bounds.size
let scaledMapWidth = Double(mapSizeInPixels.width) * zoomScale
let scaledMapHeight = Double(mapSizeInPixels.height) * zoomScale
// Figure out the position of the top-left pixel
let topLeftPixelX = centerPixelX - (scaledMapWidth / 2)
let topLeftPixelY = centerPixelY - (scaledMapHeight / 2)
// Find delta between left and right longitudes
let minLng: CLLocationDegrees = self.pixelSpaceXToLongitude(pixelX: topLeftPixelX)
let maxLng: CLLocationDegrees = self.pixelSpaceXToLongitude(pixelX: topLeftPixelX + scaledMapWidth)
let longitudeDelta: CLLocationDegrees = maxLng - minLng
// Find delta between top and bottom latitudes
let minLat: CLLocationDegrees = self.pixelSpaceYToLatitude(pixelY: topLeftPixelY)
let maxLat: CLLocationDegrees = self.pixelSpaceYToLatitude(pixelY: topLeftPixelY + scaledMapHeight)
let latitudeDelta: CLLocationDegrees = -1 * (maxLat - minLat)
return MKCoordinateSpan(latitudeDelta: latitudeDelta, longitudeDelta: longitudeDelta)
}
private func longitudeToPixelSpaceX(longitude: Double) -> Double {
return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * M_PI / 180.0)
}
private func latitudeToPixelSpaceY(latitude: Double) -> Double {
if latitude == 90.0 {
return 0
} else if latitude == -90.0 {
return MERCATOR_OFFSET * 2
} else {
return round(MERCATOR_OFFSET - MERCATOR_RADIUS * Double(logf((1 + sinf(Float(latitude * M_PI) / 180.0)) / (1 - sinf(Float(latitude * M_PI) / 180.0))) / 2.0))
}
}
private func pixelSpaceXToLongitude(pixelX: Double) -> Double {
return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / M_PI
}
private func pixelSpaceYToLatitude(pixelY: Double) -> Double {
return (M_PI / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / M_PI
}
}
Пример использования в вашем контроллере вида:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print("Zoom: \(mapView.getZoomLevel())")
if mapView.getZoomLevel() > 6 {
mapView.setCenter(coordinate: mapView.centerCoordinate, zoomLevel: 6, animated: true)
}
}
Ответ 6
MKMapView
имеет внутри него MKScrollView
(частный API), который является подклассом UIScrollView
. Делегатом этого MKScrollView
является его собственный mapView
.
Итак, чтобы контролировать максимальное масштабирование, сделайте следующее:
Создайте подкласс MKMapView
:
MapView.h
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
@interface MapView : MKMapView <UIScrollViewDelegate>
@end
MapView.m
#import "MapView.h"
@implementation MapView
-(void)scrollViewDidZoom:(UIScrollView *)scrollView {
UIScrollView * scroll = [[[[self subviews] objectAtIndex:0] subviews] objectAtIndex:0];
if (scroll.zoomScale > 0.09) {
[scroll setZoomScale:0.09 animated:NO];
}
}
@end
Затем перейдите в подпрограмму прокрутки и посмотрите свойство zoomScale
. Когда масштаб больше, чем число, установите максимальное масштабирование.
Ответ 7
Сообщение Raphael Petegrosso с расширенным MKMapView отлично работает с небольшими изменениями. Версия ниже также гораздо более удобна для пользователя, так как она изящно "привязывается" к определенному уровню масштабирования, как только пользователь позволяет перейти на экран, будучи похожим на себя, чтобы чувствовать себя надуманной прокруткой Apple.
Изменить: это решение не является оптимальным и разрушит или повредит вид карты, я нашел здесь гораздо лучшее решение: Как обнаружить любой щелчок внутри MKMapView. Это позволяет перехватывать защемление и другие движения.
MyMapView.h
#import <MapKit/MapKit.h>
@interface MyMapView : MKMapView <UIScrollViewDelegate>
@end
MyMapView.m
#import "MyMapView.h"
@implementation MyMapView
- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
if (scale > 0.001)
{
[scrollView setZoomScale:0.001 animated:YES];
}
}
@end
Для жесткого ограничения используйте это:
#import "MyMapView.h"
@implementation MyMapView
-(void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (scrollView.zoomScale > 0.001)
{
[scrollView setZoomScale:0.001 animated:NO];
}
}
@end
Ответ 8
Не используйте regionWillChangeAnimated
. Используйте regionDidChangeAnimated
-
мы также можем использовать
setRegion(region, animated: true)
. Обычно он замораживаетMKMapView
, если мы используемregionWillChangeAnimated
, но сregionDidChangeAnimated
он отлично работаетfunc mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) { mapView.checkSpan() } extension MKMapView { func zoom() { let region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 2000, 2000) setRegion(region, animated: true) } func checkSpan() { let rect = visibleMapRect let westMapPoint = MKMapPointMake(MKMapRectGetMinX(rect), MKMapRectGetMidY(rect)) let eastMapPoint = MKMapPointMake(MKMapRectGetMaxX(rect), MKMapRectGetMidY(rect)) let distanceInMeter = MKMetersBetweenMapPoints(westMapPoint, eastMapPoint) if distanceInMeter > 2100 { zoom() } } }
Ответ 9
Следующий код работал для меня и концептуально прост в использовании, поскольку он устанавливает область на основе расстояния в метрах. Код получен из ответа, отправленного: @nevan-king и комментария, опубликованного @Awais-Fayyaz, для использования regionDidChangeAnimated
Добавьте в свой MapViewDelegate следующее расширение
var currentLocation: CLLocationCoordinate2D?
extension MyMapViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
if self.currentLocation != nil, mapView.region.longitudinalMeters > 1000 {
let initialLocation = CLLocation(latitude: (self.currentLocation?.latitude)!,
longitude: (self.currentLocation?.longitude)!)
let coordinateRegion = MKCoordinateRegionMakeWithDistance(initialLocation.coordinate,
regionRadius, regionRadius)
mapView.setRegion(coordinateRegion, animated: true)
}
}
}
Затем определите расширение для MKCoordinateRegion следующим образом.
extension MKCoordinateRegion {
/// middle of the south edge
var south: CLLocation {
return CLLocation(latitude: center.latitude - span.latitudeDelta / 2, longitude: center.longitude)
}
/// middle of the north edge
var north: CLLocation {
return CLLocation(latitude: center.latitude + span.latitudeDelta / 2, longitude: center.longitude)
}
/// middle of the east edge
var east: CLLocation {
return CLLocation(latitude: center.latitude, longitude: center.longitude + span.longitudeDelta / 2)
}
/// middle of the west edge
var west: CLLocation {
return CLLocation(latitude: center.latitude, longitude: center.longitude - span.longitudeDelta / 2)
}
/// distance between south and north in meters. Reverse function for MKCoordinateRegionMakeWithDistance
var latitudinalMeters: CLLocationDistance {
return south.distance(from: north)
}
/// distance between east and west in meters. Reverse function for MKCoordinateRegionMakeWithDistance
var longitudinalMeters: CLLocationDistance {
return east.distance(from: west)
}
}
Вышеприведенный фрагмент для MKCoordinateRegion был отправлен @Gerd-Castan по этому вопросу:
Ответ 10
Я столкнулся с этой проблемой на работе и создал что-то, что работает достаточно хорошо, не устанавливая глобальный предел.
Представители MapView, которые я использую: - mapViewDidFinishRendering - mapViewRegionDidChange
Предпосылка моего решения заключается в том, что, поскольку спутниковый вид отображает область без данных, это всегда одно и то же. Это ужасное изображение (http://imgur.com/cm4ou5g). Если мы сможем комфортно полагаться на этот случай сбоя, мы можем использовать его в качестве ключа для определения того, что пользователь видит, После отображения карты я делаю снимок экрана с отображенными границами карты и определяя среднее значение RGB. Исходя из этого значения RGB, я предполагаю, что данная область не имеет данных. Если в этом случае я вытащил карту обратно в последний диапазон, который был отображен правильно.
Единственный глобальный чек, который у меня есть, - это когда он начинает проверять карту, вы можете увеличить или уменьшить этот параметр в зависимости от ваших потребностей. Ниже приведен исходный код, который выполнит это и будет собирать примерный проект для вклада. Любая оптимизация, которую вы можете предложить, будет оценена и надеюсь, что это поможет.
@property (assign, nonatomic) BOOL isMaxed;
@property (assign, nonatomic) MKCoordinateSpan lastDelta;
self.lastDelta = MKCoordinateSpanMake(0.006, 0.006);
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
if (mapView.mapType != MKMapTypeStandard && self.isMaxed) {
[self checkRegionWithDelta:self.lastDelta.longitudeDelta];
}
}
- (void)checkRegionWithDelta:(float)delta {
if (self.mapView.region.span.longitudeDelta < delta) {
MKCoordinateRegion region = self.mapView.region;
region.span = self.lastDelta;
[self.mapView setRegion:region animated:NO];
} else if (self.mapView.region.span.longitudeDelta > delta) {
self.isMaxed = NO;
}
}
- (void)mapViewDidFinishRenderingMap:(MKMapView *)mapView fullyRendered:(BOOL)fullyRendered {
if (mapView.mapType != MKMapTypeStandard && !self.isMaxed) {
[self checkToProcess:self.lastDelta.longitudeDelta];
}
}
- (void)checkToProcess:(float)delta {
if (self.mapView.region.span.longitudeDelta < delta) {
UIGraphicsBeginImageContext(self.mapView.bounds.size);
[self.mapView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage *mapImage = UIGraphicsGetImageFromCurrentImageContext();
[self processImage:mapImage];
}
}
- (void)processImage:(UIImage *)image {
self.mapColor = [self averageColor:image];
const CGFloat* colors = CGColorGetComponents( self.mapColor.CGColor );
[self handleColorCorrection:colors[0]];
}
- (void)handleColorCorrection:(float)redColor {
if (redColor < 0.29) {
self.isMaxed = YES;
[self.mapView setRegion:MKCoordinateRegionMake(self.mapView.centerCoordinate, self.lastDelta) animated:YES];
} else {
self.lastDelta = self.mapView.region.span;
}
}
- (UIColor *)averageColor:(UIImage *)image {
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
unsigned char rgba[4];
CGContextRef context = CGBitmapContextCreate(rgba, 1, 1, 8, 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);
CGContextDrawImage(context, CGRectMake(0, 0, 1, 1), image.CGImage);
CGColorSpaceRelease(colorSpace);
CGContextRelease(context);
if(rgba[3] > 0) {
CGFloat alpha = ((CGFloat)rgba[3])/255.0;
CGFloat multiplier = alpha/255.0;
return [UIColor colorWithRed:((CGFloat)rgba[0])*multiplier
green:((CGFloat)rgba[1])*multiplier
blue:((CGFloat)rgba[2])*multiplier
alpha:alpha];
}
else {
return [UIColor colorWithRed:((CGFloat)rgba[0])/255.0
green:((CGFloat)rgba[1])/255.0
blue:((CGFloat)rgba[2])/255.0
alpha:((CGFloat)rgba[3])/255.0];
}
}