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

时间:2009-10-28 12:12:04

标签: iphone mkmapview zoom

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

12 个答案:

答案 0 :(得分:29)

如果您仅使用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;
    }
}

答案 1 :(得分:28)

您可以使用mapView:regionWillChangeAnimated:委托方法收听区域更改事件,如果区域比最大区域宽,请使用setRegion:animated:将其重新设置为最大区域,以便向您的用户表明他们无法缩小那么远。这是方法:

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

答案 2 :(得分:21)

我只是花了一些时间来处理我正在构建的应用程序。这就是我想出的:

  1. 我从this page开始使用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来阻止地图“反弹”。

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

答案 3 :(得分:13)

是的,这是可行的。首先,使用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];
    }
}

答案 4 :(得分:5)

以下是使用MKMapView+ZoomLevel和@ T.Markle答案在Swift 3中重写的代码:

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)
        }
    }

答案 5 :(得分:2)

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

答案 6 :(得分:1)

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

答案 7 :(得分:1)

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

map.cameraZoomRange = MKMapView.CameraZoomRange(maxCenterCoordinateDistance:1200000)

答案 8 :(得分:1)

如果您的目标是 iOS 13+,请使用 MKMapView setCameraZoomRange 方法。只需提供最小和最大中心坐标距离(以米为单位)。

在此处查看 Apple 的文档:https://developer.apple.com/documentation/mapkit/mkmapview/3114302-setcamerazoomrange

答案 9 :(得分:0)

拉斐尔·彼得格罗索(Raphael Petegrosso)在扩展的MKMapView上的帖子非常适用于一些小修改。 下面的版本也更加“用户友好”,因为只要用户放开屏幕,它就会优雅地“捕捉”回定义的缩放级别,与Apple自己的弹性滚动相似。

编辑:这个解决方案不是最优的,会破坏/破坏地图视图,我在这里找到了一个更好的解决方案: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

答案 10 :(得分:0)

以下代码对我有用,并且在概念上易于使用,因为它基于以米为单位的距离来设置区域。 该代码来自@ 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针对此问题发布的:

Reverse function of MKCoordinateRegionMakeWithDistance?

答案 11 :(得分:-1)

我在工作中遇到了这个问题,并创造了一些在不设置全局限制的情况下运作良好的东西。

我利用的MapView代理是: - mapViewDidFinishRendering - mapViewRegionDidChange

我的解决方案背后的前提是,由于卫星视图呈现的区域没有数据,因此它始终是相同的。这个可怕的图像(http://imgur.com/cm4ou5g)如果我们可以轻松地依赖那个失败的情况,我们可以将它用作确定用户看到的关键。在地图渲染之后,我拍摄渲染的地图边界的屏幕截图并确定平均RGB值。基于该RGB值,我假设所讨论的区域没有数据。如果是这种情况,我会将地图弹回到最后一个正确渲染的范围。

我唯一的全局检查是当它开始检查地图时,您可以根据需要增加或减少该设置。下面是将完成此任务的原始代码,并将整合一个示例项目进行贡献。您可以提供任何优化,并希望它有所帮助。

$(function() {
    $('.textarea-group > textarea[limit]').keyup(function() {
        var max = parseInt($(this).attr('limit'));
        var length = $(this).val().length;
        length = max - length;
        $('.chars span', $(this).parent()).html(length);
    });
});