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

发布于 2024-08-09 08:30:02 字数 67 浏览 3 评论 0 原文

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

the question is - is there a way to limit maximum zoom level for MKMapView? Or is there a way to track when user zooms to the level where there's no map image available?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(12

顾北清歌寒 2024-08-16 08:30:02

您可以使用mapView:regionWillChangeAnimated:委托方法来侦听区域更改事件,如果该区域比最大区域宽,请使用setRegion:animated将其设置回最大区域: 向您的用户表明他们无法缩小那么远。方法如下:

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

You could use the mapView:regionWillChangeAnimated: delegate method to listen for region change events, and if the region is wider than your maximum region, set it back to the max region with setRegion:animated: to indicate to your user that they can't zoom out that far. Here's the methods:

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
暗恋未遂 2024-08-16 08:30:02

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

If you're working with iOS 7+ only, there's a new camera.altitude property that you can get/set to enforce a zoom level. Its equivalent to azdev's solution, but no external code is required.

In testing, I also discovered that it was possible to enter an infinite loop if you repeatedly tried to zoom in at detail, so I have a var to prevent that in my code below.

- (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;
    }
}
樱桃奶球 2024-08-16 08:30:02

我刚刚花了一些时间为我正在构建的应用程序进行此工作。这是我的想法:

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

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

    在 MKMapView+ZoomLevel.h 中:

    - (double)getZoomLevel;
    

    在MKMapView+ZoomLevel.m中:

    // 返回当前地图的 ZoomLevel 等效值,与上面相同但相反
    - (双)getZoomLevel{
        MKCooperativeRegion reg=self.region; // 当前可见区域
        MKCoordinateSpan span=reg.span; // 增量
        CLLocationCooperative2D centerCoordinate=reg.center; // 中心的度数
        // 获取最左边和最右边的经度
        CLLocationDegrees leftLongitude=(centerCoordinate.longitude-(span.longitudeDelta/2));
        CLLocationDegrees rightLongitude=(centerCoordinate.longitude+(span.longitudeDelta/2));
        CGSize mapSizeInPixels = self.bounds.size; //显示窗口的大小
    
        // 获取屏幕左侧和右侧完全放大的像素
        double leftPixel=[self longitudeToPixelSpaceX:leftLongitude]; 
        double rightPixel=[self longitudeToPixelSpaceX:rightLongitude];
        // 屏幕宽度的跨度(以完全放大的像素为单位)
        双pixelDelta=abs(rightPixel-leftPixel);
    
        // 像素与实际显示内容的比率
        双zoomScale=mapSizeInPixels.width/pixelDelta;
        // 反指数
        双 ZoomExponent=log2(zoomScale);
        // 调整我们的比例
        双缩放级别=zoomExponent+20; 
        返回缩放级别;
    }
    

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

  3. 我将其添加到我的 MKMapView 委托中(如上面 @vladimir 推荐的那样)

    - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
        NSLog(@"%f",[mapView getZoomLevel]);
        if([mapView getZoomLevel]<10) {
            [mapView setCenterCo整理:[mapView centerCo整理] ZoomLevel:10 动画:TRUE];
        }
    }
    

    如果用户距离太远,这会产生重新缩放的效果。您可以使用regionWillChangeAnimated 来防止地图“弹回”回来。

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

I just spent some time working on this for an app i'm building. Here's what I came up with:

  1. I started with Troy Brant's script on this page which is a nicer way to set the map view I think.

  2. I added a method to return the current zoom level.

    In MKMapView+ZoomLevel.h:

    - (double)getZoomLevel;
    

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

    This method relies on a few private methods in the code linked above.

  3. I added this in to my MKMapView delegate (as @vladimir recommended above)

    - (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
        NSLog(@"%f",[mapView getZoomLevel]);
        if([mapView getZoomLevel]<10) {
            [mapView setCenterCoordinate:[mapView centerCoordinate] zoomLevel:10 animated:TRUE];
        }
    }
    

    This has the effect of re-zooming if the user gets too far out. You can use regionWillChangeAnimated to prevent the map from 'bouncing' back in.

    Regarding the looping comments above, it looks like this method only iterates once.

那请放手 2024-08-16 08:30:02

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

Yes, this is doable. First, extend MKMapView by using MKMapView+ZoomLevel.

Then, implement this in your 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];
    }
}
眼眸印温柔 2024-08-16 08:30:02

以下是使用 MKMapView+ZoomLevel 和 @ 在 Swift 3 中重写的代码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)
        }
    }

Here is code rewritten in Swift 3 using MKMapView+ZoomLevel and @T.Markle answer:

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

Example of use in your view controller:

func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
        print("Zoom: \(mapView.getZoomLevel())")
        if mapView.getZoomLevel() > 6 {
            mapView.setCenter(coordinate: mapView.centerCoordinate, zoomLevel: 6, animated: true)
        }
    }
剩余の解释 2024-08-16 08:30:02

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

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

Use this example to lock the maximum zoom range, also equally you can limit the minimum

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

转身泪倾城 2024-08-16 08:30:02

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

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

If you are targeting iOS 13+, use the MKMapView setCameraZoomRange method. Simply provide the min and max center coordinate distances (measured in meters).

See Apple's Documentation here: https://developer.apple.com/documentation/mapkit/mkmapview/3114302-setcamerazoomrange

许你一世情深 2024-08-16 08:30:02

不要使用regionWillChangeAnimated。使用regionDidChangeAnimated

  • 我们还可以使用setRegion(region,animated: true)。通常,如果我们使用 regionWillChangeAnimated,它会冻结 MKMapView,但是使用 regionDidChangeAnimated 它可以完美工作

    func mapView(_ mapView: MKMapView, RegionDidChangeAnimated 动画: Bool) {
      mapView.checkSpan()
    }
    
    扩展 MKMapView {
      函数缩放(){
        让区域 = MKCooperativeRegionMakeWithDistance(userLocation.坐标, 2000, 2000)
        setRegion(区域,动画:true)
      }
    
      函数 checkSpan() {
        让矩形 = 可见地图矩形
        让 westMapPoint = MKMapPointMake(MKMapRectGetMinX(矩形), MKMapRectGetMidY(矩形))
        让 eastMapPoint = MKMapPointMake(MKMapRectGetMaxX(矩形), MKMapRectGetMidY(矩形))
    
        让 distanceInMeter = MKMetersBetweenMapPoints(westMapPoint, eastMapPoint)
    
        如果距离以米> 2100 {
          飞涨()
        }
      }
    }
    

Don't use regionWillChangeAnimated. Use regionDidChangeAnimated

  • we can also use setRegion(region, animated: true). Normally it will freeze MKMapView if we use regionWillChangeAnimated, but with regionDidChangeAnimated it works perfectly

    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()
        }
      }
    }
    
一桥轻雨一伞开 2024-08-16 08:30:02

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

The MKMapView has, inside of it, a MKScrollView (private API), that is a subclass of UIScrollView. The delegate of this MKScrollView is its own mapView.

So, in order to control the max zoom do the following:

Create a subclass of 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

Then, access the scroll subview and see the zoomScale property. When the zoom is greater than a number, set your max zoom.

风渺 2024-08-16 08:30:02

Raphael Petegrosso 发布的带有扩展 MKMapView 的文章经过一些小的修改后效果很好。
下面的版本也更加“用户友好”,因为一旦用户放开屏幕,它就会优雅地“快速”回到定义的缩放级别,与苹果自己的弹性滚动感觉类似。

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

The post by Raphael Petegrosso with the extended MKMapView works great with some small modifications.
The version below is also much more "user friendly", as it gracefully "snaps" back to the defined zoom level as soon as the user lets go of the screen, being similar in feel to Apple's own bouncy scrolling.

Edit: This solution is not optimal and will break/damage the map view, I found a much better solution here: How to detect any tap inside an MKMapView. This allows you to intercept pinching and other motions.


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

For a hard limit, use this:

#import "MyMapView.h"

@implementation MyMapView

-(void)scrollViewDidZoom:(UIScrollView *)scrollView
{
    if (scrollView.zoomScale > 0.001)
    {
        [scrollView setZoomScale:0.001 animated:NO];
    }

}

@end
梦旅人picnic 2024-08-16 08:30:02

以下代码对我有用,并且在概念上易于使用,因为它根据以米为单位的距离设置区域。
该代码源自@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)
        }
    }
}

然后为MKCooperativeRegion 定义一个扩展,如下所示。

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

上面的 MKCooperativeRegion 代码片段是由@Gerd-Castan 在这个问题上发布的:

MKCooperativeRegionMakeWithDistance 的反向函数?

The following code worked for me and is conceptually easy to use because it sets the region based on a distance in meters.
The code is derived from the answer posted by: @nevan-king and the comment posted by @Awais-Fayyaz to use regionDidChangeAnimated

Add the following extension to your 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)
        }
    }
}

Then define an extension for MKCoordinateRegion as follows.

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

The above snippet for MKCoordinateRegion was posted by @Gerd-Castan on this question:

Reverse function of MKCoordinateRegionMakeWithDistance?

悟红尘 2024-08-16 08:30:02

我在工作中遇到了这个问题,并且创建了一些效果相当好的东西,而无需设置全局限制。

我利用的 MapView 委托是:
-mapViewDidFinishRendering
- mapViewRegionDidChange

我的解决方案背后的前提是,由于卫星视图呈现没有数据的区域,因此它始终是同一件事。这个可怕的图像(https://i.sstatic.net/7Guev.jpg)如果我们可以轻松地依赖该失败案例,我们可以将其用作确定用户所看到内容的关键。地图渲染后,我截取渲染地图边界的屏幕截图并确定平均 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];
    }
}

I've run into this very issue at work and have created something that works fairly well without setting a global limit.

The MapView delegates that I leverage are:
- mapViewDidFinishRendering
- mapViewRegionDidChange

The premise behind my solution is that since a satellite view renders an area with no data it is always the same thing. This dreaded image (https://i.sstatic.net/7Guev.jpg) If we can comfortably rely on that fail case we can use it as a key for determining wha the user is seeing. After the map renders, I take a screenshot of the rendered map bounds and determing an average RGB value. Based off of that RGB value, I assume that the area in question has no data. If that's the case I pop the map back out to the last span that was rendered correctly.

The only global check I have is when it starts to check the map, you can increase or decrease that setting based on your needs. Below is the raw code that will accomplish this and will be putting together a sample project for contribution. Any optimizations you can offer would be appreciated and hope it helps.

@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];
    }
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文