Есть ли способ ограничить максимальный уровень масштабирования MKMapView? - PullRequest
32 голосов
/ 28 октября 2009

вопрос - есть ли способ ограничить максимальный уровень масштабирования для MKMapView? Или есть способ отслеживать, когда пользователь приближается к уровню, на котором нет доступного изображения карты?

Ответы [ 10 ]

29 голосов
/ 31 января 2014

Если вы работаете только с 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;
    }
}
28 голосов
/ 28 октября 2009

Вы можете использовать метод делегата mapView:regionWillChangeAnimated: для прослушивания событий изменения региона, и если регион шире, чем ваш максимальный регион, установите для него максимальный регион с помощью setRegion:animated:, чтобы указать вашему пользователю, что он может ' так далеко. Вот методы:

- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
21 голосов
/ 19 июня 2011

Я просто потратил некоторое время, работая над этим для приложения, которое я создаю. Вот что я придумал:

  1. Я начал со сценария Троя Бранта на этой странице , который, как мне кажется, является более приятным способом задания вида карты.

  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, чтобы предотвратить «подпрыгивание» карты обратно.

    Что касается зацикленных комментариев выше, похоже, что этот метод повторяется только один раз.

13 голосов
/ 06 октября 2011

Да, это выполнимо. Во-первых, расширьте MKMapView, используя MKMapView + ZoomLevel .

Затем внедрите это в свой 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];
    }
}
5 голосов
/ 01 марта 2017

Вот код, переписанный в Swift 3 с использованием MKMapView + ZoomLevel и @ 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)
        }
    }
2 голосов
/ 12 августа 2011

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. Если увеличение превышает число, установите максимальное увеличение.

1 голос
/ 20 апреля 2017

Не используйте 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()
        }
      }
    }
    
0 голосов
/ 05 декабря 2018

Следующий код работал для меня и концептуально прост в использовании, поскольку он устанавливает регион на основе расстояния в метрах. Код получен из ответа, отправленного: @ 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?

0 голосов
/ 05 июня 2016

Я столкнулся с этой самой проблемой на работе и создал что-то, что работает довольно хорошо, не устанавливая глобальное ограничение.

Используемые мной делегаты 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];
    }
}
0 голосов
/ 22 февраля 2012

Пост Рафаэля Петегроссо с расширенным MKMapView прекрасно работает с некоторыми небольшими изменениями. Приведенная ниже версия также гораздо более «удобна для пользователя», поскольку она грациозно «привязывается» к заданному уровню масштабирования, как только пользователь отпускает экран, что похоже на собственную упругую прокрутку 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
...