代码之家  ›  专栏  ›  技术社区  ›  Vladimir

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

  •  30
  • Vladimir  · 技术社区  · 15 年前

    问题是-有没有办法限制MKMapView的最大缩放级别?或者有没有办法跟踪用户何时缩放到没有可用地图图像的级别?

    10 回复  |  直到 15 年前
        1
  •  30
  •   Ted Avery    11 年前

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

    - (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;
        }
    }
    
        2
  •  29
  •   nevan king    15 年前

    你可以使用 mapView:regionWillChangeAnimated: 委托方法来侦听区域更改事件,如果区域比最大区域宽,请使用 setRegion:animated: 向您的用户指示他们不能缩小那么远。方法如下:

    - (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
    - (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
    
        3
  •  21
  •   Dan J Akhil    13 年前

    我只是花了一些时间为我正在构建的一个应用程序做这个。以下是我的想法:

    1. 我从特洛伊·布兰特的剧本开始 this page 我认为这是设置地图视图的更好方法。

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

      - (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来防止贴图“反弹”回来。

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

        4
  •  15
  •   dvs Matthew Gillingham    13 年前

    MKMapView+ZoomLevel

    - (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
  •  9
  •   Alina Egorova    8 年前

    下面是使用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)
            }
        }
    
        6
  •  2
  •   onmyway133    8 年前

    regionWillChangeAnimated . 使用 regionDidChangeAnimated

    • 我们也可以使用 setRegion(region, animated: true) MKMapView 如果我们使用 区域将发生变化 ,但是 区域ID更改动画 它工作得很好

      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()
          }
        }
      }
      
        7
  •  2
  •   Adam Smith    5 年前

    使用此示例可以锁定最大缩放范围,也可以限制最小缩放范围

        8
  •  2
  •   kbrady    4 年前

    如果您的目标是iOS 13+,请使用MKMapView setCameraZoomRange

    请参见此处的苹果文档: https://developer.apple.com/documentation/mapkit/mkmapview/3114302-setcamerazoomrange

        9
  •  1
  •   Raphael Petegrosso    13 年前

    这个 MKMapView MKScrollView UIScrollView . 该委员会的代表 这是它自己的 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 所有物当缩放大于一个数字时,设置最大缩放。

        10
  •  0
  •   Community CDub    8 年前

    拉斐尔·彼得格罗索(Raphael Petegroso)的文章带有扩展的MKMapView,经过一些小的修改后效果非常好。 下面的版本也更加“用户友好”,因为一旦用户放开屏幕,它就会优雅地“捕捉”到定义的缩放级别,在感觉上类似于苹果自己的弹跳滚动。

    编辑:此解决方案不是最佳解决方案,会破坏/损坏地图视图,我在此处找到了更好的解决方案: How to detect any tap inside an 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
    
        11
  •  0
  •   user3474985    6 年前

    下面的代码适用于我,概念上易于使用,因为它基于米的距离设置区域。

    将以下扩展添加到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)
        }
    }
    

    Reverse function of MKCoordinateRegionMakeWithDistance?

        12
  •  -1
  •   Vladimir    9 年前

    我利用的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];
        }
    }