35

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

4

12 に答える 12

30

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;
    }
}
于 2014-01-31T01:11:47.807 に答える
29

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

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
于 2009-10-28T13:59:14.303 に答える
21

私が構築しているアプリのために、これに取り組むのに少し時間を費やしました。これが私が思いついたものです:

  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 回だけ繰り返されるようです。

于 2011-06-19T00:06:09.987 に答える
15

はい、これは可能です。まず、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];
    }
}
于 2011-10-06T19:35:08.587 に答える
1

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

于 2011-08-12T01:19:47.327 に答える
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 によってこの質問に投稿されました。

MKCoordinateRegionMakeWithDistanceの逆関数?

于 2018-12-05T15:21:31.133 に答える
-1

私は職場でまさにこの問題に遭遇し、グローバル制限を設定せずにかなりうまく機能するものを作成しました.

私が利用する 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];
    }
}
于 2016-06-05T15:38:56.540 に答える