la question est de savoir s'il existe un moyen de limiter le niveau de zoom maximal pour MKMapView. Ou existe-t-il un moyen de faire un suivi lorsque l'utilisateur effectue un zoom sur le niveau où aucune image de carte n'est disponible?
Vous pouvez utiliser la méthode déléguée mapView:regionWillChangeAnimated:
pour écouter les événements de changement de région. Si la région est plus large que votre région maximale, réglez-la sur la région maximale avec setRegion:animated:
pour indiquer à votre utilisateur qu'il ne peut pas effectuer un zoom arrière. Voici les méthodes:
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
Si vous travaillez uniquement avec iOS 7+, il existe une nouvelle propriété camera.altitude
que vous pouvez obtenir/définir pour appliquer un niveau de zoom. C'est l'équivalent de la solution azdev, mais aucun code externe n'est requis.
Lors des tests, j'ai également découvert qu'il était possible d'entrer dans une boucle infinie si vous essayiez à plusieurs reprises de zoomer dans les détails. J'ai donc une var pour empêcher cela dans mon code ci-dessous.
- (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;
}
}
Je viens de passer quelque temps à travailler dessus pour une application que je construis. Voici ce que je suis venu avec:
J'ai commencé avec le script de Troy Brant sur cette page qui est un moyen plus agréable de définir l'affichage de la carte, je pense.
J'ai ajouté une méthode pour renvoyer le niveau de zoom actuel.
Dans MKMapView + ZoomLevel.h:
- (double)getZoomLevel;
Dans 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;
}
Cette méthode repose sur quelques méthodes privées dans le code lié ci-dessus.
Je l'ai ajouté à mon délégué MKMapView (comme @vladimir l'a recommandé ci-dessus)
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
NSLog(@"%f",[mapView getZoomLevel]);
if([mapView getZoomLevel]<10) {
[mapView setCenterCoordinate:[mapView centerCoordinate] zoomLevel:10 animated:TRUE];
}
}
Cela a pour effet de re-zoomer si l'utilisateur s'éloigne trop. Vous pouvez utiliser regionWillChangeAnimated pour empêcher la carte de "rebondir" dans.
En ce qui concerne les commentaires en boucle ci-dessus, il semble que cette méthode n’itère qu’une fois.
Oui, c'est faisable. Tout d’abord, étendez MKMapView en utilisant MKMapView + ZoomLevel .
Ensuite, implémentez ceci dans votre 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];
}
}
Voici le code réécrit dans Swift 3 en utilisant MKMapView + ZoomLevel et @ 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
}
}
Exemple d'utilisation dans votre contrôleur de vue:
func mapView(_ mapView: MKMapView, regionDidChangeAnimated animated: Bool) {
print("Zoom: \(mapView.getZoomLevel())")
if mapView.getZoomLevel() > 6 {
mapView.setCenter(coordinate: mapView.centerCoordinate, zoomLevel: 6, animated: true)
}
}
La MKMapView
a, à l'intérieur de celle-ci, une MKScrollView
(API privée), c'est-à-dire une sous-classe de UIScrollView
. Le délégué de cette MKScrollView
est son propre mapView
.
Pour contrôler le zoom maximal, procédez comme suit:
Créez une sous-classe de 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
Accédez ensuite à la sous-vue Défilement et consultez la propriété zoomScale
. Lorsque le zoom est supérieur à un nombre, définissez votre zoom maximal.
N'utilisez pas regionWillChangeAnimated
. Utilisez regionDidChangeAnimated
nous pouvons aussi utiliser setRegion(region, animated: true)
. Normalement, il va geler MKMapView
si nous utilisons regionWillChangeAnimated
, mais avec regionDidChangeAnimated
cela fonctionne parfaitement
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()
}
}
}
Le code suivant a fonctionné pour moi et est conceptuellement facile à utiliser car il définit la région en fonction d'une distance en mètres. Le code est dérivé de la réponse publiée par: @ nevan-king et du commentaire posté par @Awais -Fayyaz utilisera regionDidChangeAnimated
Ajoutez l'extension suivante à votre 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)
}
}
}
Définissez ensuite une extension pour MKCoordinateRegion comme suit.
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)
}
}
L'extrait ci-dessus pour MKCoordinateRegion a été posté par @ Gerd-Castan sur cette question:
Le message de Raphael Petegrosso avec MKMapView étendu fonctionne très bien avec quelques petites modifications. La version ci-dessous est également beaucoup plus conviviale, car elle se "ramasse" gracieusement au niveau de zoom défini dès que l'utilisateur laisse aller de l'écran, étant semblable au défilement dynamique d'Apple.
Edit: Cette solution n’est pas optimale et va casser/endommager la vue cartographique, j’ai trouvé une bien meilleure solution ici: Comment détecter un tap à l’intérieur d’un MKMapView . Cela vous permet d'intercepter les pincements et autres mouvements.
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
Pour une limite stricte, utilisez ceci:
#import "MyMapView.h"
@implementation MyMapView
-(void)scrollViewDidZoom:(UIScrollView *)scrollView
{
if (scrollView.zoomScale > 0.001)
{
[scrollView setZoomScale:0.001 animated:NO];
}
}
@end