web-dev-qa-db-ja.com

MKMapViewの最大ズームレベルを制限する方法はありますか?

問題は-MKMapViewの最大ズームレベルを制限する方法はありますか?または、ユーザーが利用可能なマップイメージがないレベルにズームしたときに追跡する方法はありますか?

34
Vladimir

mapView:regionWillChangeAnimated:デリゲートメソッドを使用して地域変更イベントをリッスンし、地域が最大地域より広い場合は、setRegion:animated:を使用して最大地域に戻し、ユーザーに次のことができることを示すことができますそれほどズームアウトしないでください。メソッドは次のとおりです。

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

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;
    }
}
30
Ted Avery

私は自分が構築しているアプリのためにこれに取り組んでいるだけの時間です。これが私が思いついたものです:

  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を使用して、マップが「跳ね返る」のを防ぐことができます。

    上記のループコメントに関して、このメソッドは1回だけ反復するようです。

21
T. Markle

はい、これは可能です。まず、 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];
    }
}
13
azdev

Swift 3 MKMapView + ZoomLevel と@ T.Markleの回答を使用して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)
        }
    }
7
Alina Egorova

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()
        }
      }
    }
    
2
onmyway133

この例を使用して、最大ズーム範囲をロックします。同様に、最小ズーム範囲を制限することもできます

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

1
Adam Smith

MKMapViewの内部には、MKScrollViewのサブクラスであるUIScrollView(プライベートAPI)があります。この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プロパティを確認します。ズームが数値より大きい場合は、最大ズームを設定します。

1

次のコードは私にとってはうまくいき、メートル単位の距離に基づいてリージョンを設定するため、概念的には簡単に使用できます。このコードは、@ nevan-kingによって投稿された回答と、regionAid-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がこの質問に投稿したものです。

MKCoordinateRegionMakeWithDistanceの逆関数?

0
user3474985

拡張されたMKMapViewを使用したRaphael Petegrossoによる投稿は、いくつかの小さな変更でうまく機能します。以下のバージョンは、ユーザーが画面から離れるとすぐに定義されたズームレベルに正常に「スナップ」して戻るため、はるかに「ユーザーフレンドリー」であり、Appleの弾力性のあるスクロールと同じように感じられます。

編集:この解決策は最適ではなく、マップビューを破損/損傷します。ここではるかに優れた解決策を見つけました: 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
0
zeraien