有没有办法限制MKMapView的最大缩放级别?

问题是 – 有没有办法限制MKMapView的最大缩放级别? 或者有什么方法可以跟踪用户放大到没有地图图像的级别?

您可以使用mapView:regionWillChangeAnimated: delegate方法侦听区域更改事件,如果区域比最大区域宽,请使用setRegion:animated:将其设置回最大区域setRegion:animated:向用户指示他们不能放大那么远。 以下是方法:

 - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated 

如果您仅使用iOS 7+,则可以使用新的camera.altitude属性来设置强制缩放级别。 它相当于azdev的解决scheme,但不需要外部代码。

在testing中,我还发现,如果您反复尝试放大细节,则可能会进入无限循环,所以我有一个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; } } 

我只是花了一些时间在这个为我正在build设的应用程序工作。 以下是我想到的:

  1. 我从这个页面上的Troy Brant的脚本开始,这是设置我认为的地图视图的一个更好的方法。

  2. 我添加了一个方法来返回当前缩放级别。

    在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; } 

    这种方法依赖于上面链接的代码中的一些私有方法。

  3. 我把它添加到我的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来防止地图“跳回”。

    关于上面的循环注释,它看起来像这个方法只迭代一次。

是的,这是可行的。 首先,使用MKMapView + ZoomLevel扩展MKMapView。

然后,在你的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]; } } 

MKMapView里面有一个MKScrollView (私有API),它是UIScrollView的子类。 这个MKScrollView的代表是它自己的mapView

所以,为了控制最大变焦,请执行以下操作:

创build一个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属性。 当变焦大于数字时,请设置最大变焦。

这里是代码重写在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 map's 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) } } 

由Raphael Petegrosso与扩展的MKMapView的职位工作很好,一些小的修改。 下面的版本也更加“用户友好”,因为只要用户放开屏幕,它就会优雅地“定格”回到定义的缩放级别,与苹果自己的有弹性滚动类似。

编辑:这个解决scheme是不是最佳的,将打破/损坏地图视图,我发现这里有一个更好的解决scheme: 如何检测任何内部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 

不要使用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() } } } 

我已经在工作中遇到了这个问题,并创造了一些相当不错的工具,但没有设置全局限制。

我使用的MapView委托是: – mapViewDidFinishRendering – mapViewRegionDidChange

我的解决scheme背后的前提是,由于卫星视图呈现一个没有数据的区域,它总是一样的东西。 这可怕的形象( http://imgur.com/cm4ou5g )如果我们可以舒适地依靠这种失败的情况下,我们可以用它作为决定用户看到的关键。 地图渲染后,我截取渲染的地图边界并确定平均RGB值。 基于这个RGB值,我假设这个区域没有数据。 如果是这种情况,我将地图popup回到正确显示的最后一个跨度。

我唯一的全球支票是当它开始检查地图时,可以根据您的需要增加或减less设置。 以下是将完成此项工作的原始代码,并将汇编一个示例项目以供参考。 任何优化,你可以提供将不胜感激,希望它有帮助。

 @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]; } }