問題は-MKMapViewの最大ズームレベルを制限する方法はありますか?または、ユーザーが利用可能なマップイメージがないレベルにズームしたときに追跡する方法はありますか?
mapView:regionWillChangeAnimated:
デリゲートメソッドを使用して地域変更イベントをリッスンし、地域が最大地域より広い場合は、setRegion:animated:
を使用して最大地域に戻し、ユーザーに次のことができることを示すことができますそれほどズームアウトしないでください。メソッドは次のとおりです。
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated
- (void)setRegion:(MKCoordinateRegion)region animated:(BOOL)animated
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;
}
}
私は自分が構築しているアプリのためにこれに取り組んでいるだけの時間です。これが私が思いついたものです:
私は このページ のTroy Brantのスクリプトから始めました。これは、マップビューを設定するためのより良い方法です。
現在のズームレベルを返すメソッドを追加しました。
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;
}
このメソッドは、上記のリンクされたコードのいくつかのプライベートメソッドに依存しています。
これを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回だけ反復するようです。
はい、これは可能です。まず、 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];
}
}
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)
}
}
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()
}
}
}
この例を使用して、最大ズーム範囲をロックします。同様に、最小ズーム範囲を制限することもできます
map.cameraZoomRange = MKMapView.CameraZoomRange(maxCenterCoordinateDistance:1200000)
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
プロパティを確認します。ズームが数値より大きい場合は、最大ズームを設定します。
次のコードは私にとってはうまくいき、メートル単位の距離に基づいてリージョンを設定するため、概念的には簡単に使用できます。このコードは、@ 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がこの質問に投稿したものです。
拡張された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