限制MKMapView滚动

时间:2010-11-07 18:45:08

标签: iphone map scroll mkmapview overlay

我正在尝试将自定义图片作为MKMapView添加到MKOverlayView - 我需要限制用户无法滚动到叠加层的边界之外。有没有现成的功能呢?还是其他任何建议?

谢谢,马特

6 个答案:

答案 0 :(得分:19)

如果您只想冻结叠加层上的地图视图,可以将地图视图的区域设置为叠加层的边界,并将scrollEnabledzoomEnabled设置为NO

但这不会让用户在叠加层的边界内滚动或缩放。

没有内置的方法将地图视图限制为叠加层的边界,因此您必须手动执行此操作。首先,确保您的MKOverlay对象实现boundingMapRect属性。然后可以在regionDidChangeAnimated委托方法中使用它来根据需要手动调整视图。

这是一个如何做到这一点的例子 下面的代码应该在具有MKMapView的类中 确保地图视图最初设置为可以显示叠加层的区域。

//add two ivars to the .h...
MKMapRect lastGoodMapRect;
BOOL manuallyChangingMapRect;

//in the .m...
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
{
    if (manuallyChangingMapRect)
        return;     
    lastGoodMapRect = mapView.visibleMapRect;
}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
    if (manuallyChangingMapRect) //prevents possible infinite recursion when we call setVisibleMapRect below
        return;     

    // "theOverlay" below is a reference to your MKOverlay object.
    // It could be an ivar or obtained from mapView.overlays array.

    BOOL mapContainsOverlay = MKMapRectContainsRect(mapView.visibleMapRect, theOverlay.boundingMapRect);

    if (mapContainsOverlay)
    {
        // The overlay is entirely inside the map view but adjust if user is zoomed out too much...
        double widthRatio = theOverlay.boundingMapRect.size.width / mapView.visibleMapRect.size.width;
        double heightRatio = theOverlay.boundingMapRect.size.height / mapView.visibleMapRect.size.height;
        if ((widthRatio < 0.6) || (heightRatio < 0.6)) //adjust ratios as needed
        {
            manuallyChangingMapRect = YES;
            [mapView setVisibleMapRect:theOverlay.boundingMapRect animated:YES];
            manuallyChangingMapRect = NO;
        }
    }
    else
        if (![theOverlay intersectsMapRect:mapView.visibleMapRect])
        {
            // Overlay is no longer visible in the map view.
            // Reset to last "good" map rect...
            [mapView setVisibleMapRect:lastGoodMapRect animated:YES];
        }   
}

我使用内置的MKCircle叠加层进行了尝试,但似乎运行良好。


修改

它在95%的情况下运行良好,但是,我通过一些测试确认它可能在两个位置之间振荡,然后进入无限循环。所以,我编辑了一下,我认为这应该可以解决问题:

// You can safely delete this method:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {

}

- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
     // prevents possible infinite recursion when we call setVisibleMapRect below
    if (manuallyChangingMapRect) {
        return;
    }

    // "theOverlay" below is a reference to your MKOverlay object.
    // It could be an ivar or obtained from mapView.overlays array.

    BOOL mapContainsOverlay = MKMapRectContainsRect(mapView.visibleMapRect, theOverlay.boundingMapRect);

    if (mapContainsOverlay) {
        // The overlay is entirely inside the map view but adjust if user is zoomed out too much...
        double widthRatio = theOverlay.boundingMapRect.size.width / mapView.visibleMapRect.size.width;
        double heightRatio = theOverlay.boundingMapRect.size.height / mapView.visibleMapRect.size.height;
        // adjust ratios as needed
        if ((widthRatio < 0.6) || (heightRatio < 0.6)) {
            manuallyChangingMapRect = YES;
            [mapView setVisibleMapRect:theOverlay.boundingMapRect animated:YES];
            manuallyChangingMapRect = NO;
        }
    } else if (![theOverlay intersectsMapRect:mapView.visibleMapRect]) {
        // Overlay is no longer visible in the map view.
        // Reset to last "good" map rect...
        manuallyChangingMapRect = YES;
        [mapView setVisibleMapRect:lastGoodMapRect animated:YES];
        manuallyChangingMapRect = NO;
    } else {
        lastGoodMapRect = mapView.visibleMapRect;
    }
}

如果有人正在寻找快速MKOverlay解决方案,请参阅以下内容:

- (void)viewDidLoad {
    [super viewDidLoad];

    MKCircle* circleOverlay = [MKCircle circleWithMapRect:istanbulRect];
    [_mapView addOverlay:circleOverlay];

    theOverlay = circleOverlay;
}

- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
    MKCircleView* circleOverlay = [[MKCircleView alloc] initWithCircle:overlay];
    [circleOverlay setStrokeColor:[UIColor mainColor]];
    [circleOverlay setLineWidth:4.f];

    return circleOverlay;
}

答案 1 :(得分:4)

在我的情况下,我需要将边界限制为具有upperleft / lowerRight坐标的平铺覆盖。上面的代码仍然可以很好地工作,但是替换了OverMap.boundingMapRect for MKMapRect paddedBoundingMapRect

- (void)mapView:(MKMapView *)_mapView regionDidChangeAnimated:(BOOL)animated
{
if (manuallyChangingMapRect) //prevents possible infinite recursion when we call setVisibleMapRect below
    return;     

[self updateDynamicPaddedBounds];

MKMapPoint pt =  MKMapPointForCoordinate( mapView.centerCoordinate);

BOOL mapInsidePaddedBoundingRect = MKMapRectContainsPoint(paddedBoundingMapRect,pt );

if (!mapInsidePaddedBoundingRect)
{
    // Overlay is no longer visible in the map view.
    // Reset to last "good" map rect...

    manuallyChangingMapRect = YES;
    [mapView setVisibleMapRect:lastGoodMapRect animated:YES];
    manuallyChangingMapRect = NO;


}


-(void)updateDynamicPaddedBounds{

ENTER_METHOD;

CLLocationCoordinate2D  northWestPoint= CLLocationCoordinate2DMake(-33.841171,151.237318 );
CLLocationCoordinate2D  southEastPoint= CLLocationCoordinate2DMake(-33.846127, 151.245058);



MKMapPoint upperLeft = MKMapPointForCoordinate(northWestPoint);
MKMapPoint lowerRight = MKMapPointForCoordinate(southEastPoint);
double width = lowerRight.x - upperLeft.x;
double height = lowerRight.y - upperLeft.y;


MKMapRect mRect = mapView.visibleMapRect;
MKMapPoint eastMapPoint = MKMapPointMake(MKMapRectGetMinX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint westMapPoint = MKMapPointMake(MKMapRectGetMaxX(mRect), MKMapRectGetMidY(mRect));
MKMapPoint northMapPoint = MKMapPointMake(MKMapRectGetMidX(mRect), MKMapRectGetMaxY(mRect));
MKMapPoint southMapPoint = MKMapPointMake(MKMapRectGetMidX(mRect), MKMapRectGetMinY(mRect));

double xMidDist = abs(eastMapPoint.x -  westMapPoint.x)/2;
double yMidDist = abs(northMapPoint.y -  southMapPoint.y)/2;


upperLeft.x = upperLeft.x + xMidDist;
upperLeft.y = upperLeft.y + yMidDist;


double paddedWidth =  width - (xMidDist*2); 
double paddedHeight = height - (yMidDist*2);

paddedBoundingMapRect= MKMapRectMake(upperLeft.x, upperLeft.y, paddedWidth, paddedHeight);

}

答案 2 :(得分:0)

Anna在Swift 3.0中的解决方案(https://stackoverflow.com/a/4126011/3191130),我添加了扩展程序:

extension HomeViewController: MKMapViewDelegate {
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
    if manuallyChangingMapRect {
        return
    }
    guard let overlay = self.mapOverlay else {
        print("Overlay is nil")
        return
    }
    guard let lastMapRect = self.lastGoodMapRect else {
        print("LastGoodMapRect is nil")
        return
    }

    let mapContainsOverlay = MKMapRectContainsRect(mapView.visibleMapRect, overlay.boundingMapRect)
    if mapContainsOverlay {
        let widthRatio: Double = overlay.boundingMapRect.size.width / mapView.visibleMapRect.size.width
        let heightRatio: Double = overlay.boundingMapRect.size.height / mapView.visibleMapRect.size.height
        // adjust ratios as needed
        if (widthRatio < 0.9) || (heightRatio < 0.9) {
            manuallyChangingMapRect = true
            mapView.setVisibleMapRect(overlay.boundingMapRect, animated: true)
            manuallyChangingMapRect = false
        }
    } else if !overlay.intersects(mapView.visibleMapRect) {
            // Overlay is no longer visible in the map view.
            // Reset to last "good" map rect...
            manuallyChangingMapRect = true
            mapView.setVisibleMapRect(lastMapRect, animated: true)
            manuallyChangingMapRect = false
        }
        else {
            lastGoodMapRect = mapView.visibleMapRect
    }
}
}

要设置地图,请使用:

override func viewDidLoad() {
    super.viewDidLoad()
    setupMap()
}

func setupMap() {
    mapView.delegate = self
    let radius:CLLocationDistance = 1000000
    mapOverlay = MKCircle(center: getCenterCoord(), radius: radius)
    if let mapOverlay = mapOverlay  {
        mapView.add(mapOverlay)
    }
    mapView.setRegion(MKCoordinateRegionMake(getCenterCoord(), getSpan()), animated: true)
    lastGoodMapRect = mapView.visibleMapRect
}

func getCenterCoord() -> CLLocationCoordinate2D {
    return CLLocationCoordinate2DMake(LAT, LON)
}
func getSpan() -> MKCoordinateSpan {
    return MKCoordinateSpanMake(10, 10)
}

答案 3 :(得分:0)

  

Swift 4

的好答案

使用以下代码可以检测滚动的界限

注意:在下面的代码5000中,数字是以米为单位的限制区域的数量。所以你可以像这样使用&gt; Dim desk = Environment.GetFolderPath(Environment.SpecialFolder.Desktop) Dim args As String 'declare args 'args = " -i C:\mediapubisert\out%03d.jpg -r 10 C:\mediapubisert\out.mp4 " args = " -i C:\mediapubisert\out%02d.jpg -c:v libx264 -r 30 -pix_fmt yuv420p C:\mediapubisert\out.mp4 " Dim proc As New Process Dim proci As New ProcessStartInfo proci.FileName = My.Application.Info.DirectoryPath & "\ffmpeg.exe" proci.Arguments = args proci.WindowStyle = ProcessWindowStyle.Hidden proci.CreateNoWindow = True proci.UseShellExecute = False proc.StartInfo = proci proc.Start() Do Until proc.HasExited = True Me.Text = "Saving" Loop Me.Text = "your video done" 'My.Computer.FileSystem.CopyFile(TextBox1.Text, DestPath + TextBox1.Text) MsgBox("Done") Dim directoryName As String = "C:\mediapubisert\"

let restricedAreaMeters = 5000

最后如果用户从有界区域离开,则在下方使用覆盖方法将返回到最新允许的坐标。

func detectBoundingBox(location: CLLocation) {
        let latRadian = degreesToRadians(degrees: CGFloat(location.coordinate.latitude))
        let degLatKm = 110.574235
        let degLongKm = 110.572833 * cos(latRadian)
        let deltaLat = 5000 / 1000.0 / degLatKm 
        let deltaLong = 5000 / 1000.0 / degLongKm

        southLimitation = location.coordinate.latitude - deltaLat
        westLimitation = Double(CGFloat(location.coordinate.longitude) - deltaLong)
        northLimitation =  location.coordinate.latitude + deltaLat
        eastLimitation = Double(CGFloat(location.coordinate.longitude) + deltaLong)
    }

    func degreesToRadians(degrees: CGFloat) -> CGFloat {
        return degrees * CGFloat(M_PI) / 180
    }

答案 4 :(得分:0)

MapKit现在在iOS 13中本机执行

您可以显式设置边界以限制平移。

let boundaryRegion = MKCoordinateRegion(...) // the region you want to restrict
let cameraBoundary = CameraBoundary(region: boundaryRegion)
mapView.setCameraBoundary(cameraBoundary: cameraBoundary, animated: true)

请参见WWDC 2019 video at 2378 seconds进行演示。

您还可以限制缩放级别

let zoomRange = CameraZoomRange(minCenterCoordinateDistance: 100,
    maxCenterCoordinateDistance: 500)
mapView.setCameraZoomRange(zoomRange, animated: true)

参考文献

答案 5 :(得分:0)

SWIFT 5

mapViewDidFinishLoadingMap 中使用的简单解决方案:

    func mapViewDidFinishLoadingMap(_ mapView: MKMapView) {

        //center of USA, roughly. for example
        let center = CLLocationCoordinate2D(latitude: 38.573936, longitude: -92.603760) 
        let latMeters = CLLocationDistance(10_000_000.00) //left and right pan
        let longMeters = CLLocationDistance(5_000_000.00) //up and down pan
        
        let coordinateRegion = MKCoordinateRegion(
            center: center,
            latitudinalMeters: latMeters,
            longitudinalMeters: longMeters)
        
        let cameraBoundary = MKMapView.CameraBoundary(coordinateRegion: coordinateRegion)
        mapView.setCameraBoundary(cameraBoundary, animated: true)
    }