Last active
May 17, 2024 02:07
-
-
Save andrewgleave/915374 to your computer and use it in GitHub Desktop.
Zooms out a MKMapView to enclose all its annotations (inc. current location)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
MKMapRect zoomRect = MKMapRectNull; | |
for (id <MKAnnotation> annotation in mapView.annotations) { | |
MKMapPoint annotationPoint = MKMapPointForCoordinate(annotation.coordinate); | |
MKMapRect pointRect = MKMapRectMake(annotationPoint.x, annotationPoint.y, 0, 0); | |
if (MKMapRectIsNull(zoomRect)) { | |
zoomRect = pointRect; | |
} else { | |
zoomRect = MKMapRectUnion(zoomRect, pointRect); | |
} | |
} | |
[mapView setVisibleMapRect:zoomRect animated:YES]; |
Even easier:
static func fittingMapRect(forCoordinates coordinates: [CLLocationCoordinate2D]) -> MKMapRect {
guard !coordinates.isEmpty else {
return MKMapRectNull
}
let mapPoints = coordinates.map { MKMapPointForCoordinate($0) }
let mapRects = mapPoints.map { MKMapRect(origin: $0, size: MKMapSize(width: 1, height: 1)) }
let fittingRect = mapRects.reduce(MKMapRectNull, MKMapRectUnion)
return fittingRect
}
Unfortunately, showAnnotations
is too zoomed out and doesn't look good. Thanks for this code everyone, here's my condensed version:
mapView.setVisibleMapRect(
mapView.annotations.reduce(MKMapRectNull) { result, next in
let point = MKMapPointForCoordinate(next.coordinate)
let rect = MKMapRectMake(point.x, point.y, 0, 0)
guard !MKMapRectIsNull(result) else { return rect }
return MKMapRectUnion(result, rect)
},
edgePadding: UIEdgeInsetsMake(20, 20, 40, 20),
animated: true
)
extension MKMapView {
var MERCATOR_OFFSET : Double {
return 268435456.0
}
var MERCATOR_RADIUS : Double {
return 85445659.44705395
}
private func longitudeToPixelSpaceX(longitude: Double) -> Double {
return round(MERCATOR_OFFSET + MERCATOR_RADIUS * longitude * Double.pi / 180.0)
}
private func latitudeToPixelSpaceY(latitude: Double) -> Double {
return round(MERCATOR_OFFSET - MERCATOR_RADIUS * log((1 + sin(latitude * Double.pi / 180.0)) / (1 - sin(latitude * Double.pi / 180.0))) / 2.0)
}
private func pixelSpaceXToLongitude(pixelX: Double) -> Double {
return ((round(pixelX) - MERCATOR_OFFSET) / MERCATOR_RADIUS) * 180.0 / Double.pi;
}
private func pixelSpaceYToLatitude(pixelY: Double) -> Double {
return (Double.pi / 2.0 - 2.0 * atan(exp((round(pixelY) - MERCATOR_OFFSET) / MERCATOR_RADIUS))) * 180.0 / Double.pi;
}
private func coordinateSpan(withMapView mapView: MKMapView, centerCoordinate: CLLocationCoordinate2D, zoomLevel: UInt) ->MKCoordinateSpan {
let centerPixelX = longitudeToPixelSpaceX(longitude: centerCoordinate.longitude)
let centerPixelY = latitudeToPixelSpaceY(latitude: centerCoordinate.latitude)
let zoomExponent = Double(20 - zoomLevel)
let zoomScale = pow(2.0, zoomExponent)
let mapSizeInPixels = mapView.bounds.size
let scaledMapWidth = Double(mapSizeInPixels.width) * zoomScale
let scaledMapHeight = Double(mapSizeInPixels.height) * zoomScale
let topLeftPixelX = centerPixelX - (scaledMapWidth / 2);
let topLeftPixelY = centerPixelY - (scaledMapHeight / 2);
// find delta between left and right longitudes
let minLng = pixelSpaceXToLongitude(pixelX: topLeftPixelX)
let maxLng = pixelSpaceXToLongitude(pixelX: topLeftPixelX + scaledMapWidth)
let longitudeDelta = maxLng - minLng;
let minLat = pixelSpaceYToLatitude(pixelY: topLeftPixelY)
let maxLat = pixelSpaceYToLatitude(pixelY: topLeftPixelY + scaledMapHeight)
let latitudeDelta = -1 * (maxLat - minLat);
let span = MKCoordinateSpanMake(latitudeDelta, longitudeDelta)
return span
}
func zoom(toCenterCoordinate centerCoordinate:CLLocationCoordinate2D ,zoomLevel: UInt) {
let zoomLevel = min(zoomLevel, 20)
let span = self.coordinateSpan(withMapView: self, centerCoordinate: centerCoordinate, zoomLevel: zoomLevel)
let region = MKCoordinateRegionMake(centerCoordinate, span)
self.setRegion(region, animated: true)
}
}
Original Code Credit: http://troybrant.net/blog/2010/01/set-the-zoom-level-of-an-mkmapview/
Swift 2.3 Translation Credit: https://stackoverflow.com/users/2729171/apinho
Swift 4.x Swift Translation: By me. 👍
This is the easiest way:
map.layoutMargins = UIEdgeInsets(top: 10, right: 10, bottom: 10, left: 10)
map.showAnnotations(map.annotations, animated: true)
It works! I promise.
Thanks for this!
In my particular case, I had annotations and overlays for them, like the user's location blue circle overlay (userLocation.horizontalAccuracy
). To fit all the annotations and their overlays, I modified this a bit and got these two extensions.
extension MKMapView {
func setVisibleMapRectToFitAllAnnotations(animated: Bool = true,
shouldIncludeUserAccuracyRange: Bool = true,
edgePadding: UIEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)) {
var mapOverlays = overlays
if shouldIncludeUserAccuracyRange, let userLocation = userLocation.location {
let userAccuracyRangeCircle = MKCircle(center: userLocation.coordinate, radius: userLocation.horizontalAccuracy)
mapOverlays.append(MKOverlayRenderer(overlay: userAccuracyRangeCircle).overlay)
}
let zoomRect = MKMapRect(bounding: mapOverlays)
setVisibleMapRect(zoomRect, edgePadding: edgePadding, animated: animated)
}
}
extension MKMapRect {
init(bounding overlays: [MKOverlay]) {
self = .null
overlays.forEach { overlay in
let rect: MKMapRect = overlay.boundingMapRect
self = self.union(rect)
}
}
}
I just modified SimonJanevski's answer to show all annotations and overlays present on MapView.
extension MKMapView {
func setVisibleMapRectToFitAllAnnotations(animated: Bool = true,
shouldIncludeUserAccuracyRange: Bool = true,
shouldIncludeOverlays: Bool = true,
edgePadding: UIEdgeInsets = UIEdgeInsets(top: 35, left: 35, bottom: 35, right: 35)) {
var mapOverlays = overlays
if shouldIncludeUserAccuracyRange, let userLocation = userLocation.location {
let userAccuracyRangeCircle = MKCircle(center: userLocation.coordinate, radius: userLocation.horizontalAccuracy)
mapOverlays.append(MKOverlayRenderer(overlay: userAccuracyRangeCircle).overlay)
}
if shouldIncludeOverlays {
let annotations = self.annotations.filter { !($0 is MKUserLocation) }
annotations.forEach { annotation in
let cirlce = MKCircle(center: annotation.coordinate, radius: 1)
mapOverlays.append(cirlce)
}
}
let zoomRect = MKMapRect(bounding: mapOverlays)
setVisibleMapRect(zoomRect, edgePadding: edgePadding, animated: animated)
}
}
extension MKMapRect {
init(bounding overlays: [MKOverlay]) {
self = .null
overlays.forEach { overlay in
let rect: MKMapRect = overlay.boundingMapRect
self = self.union(rect)
}
}
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks for the Solution, works perfekt.
I needed to run
mkMapView.layoutIfNeeded()
before
mkMapView.setVisibleMapRect(zoomRect, edgePadding: UIEdgeInsetsMake(40, 40, 40, 40), animated: true)
if i did not run layoutIfNeeded() the EdgeInset is not set in ViewDidLoad or ViewDidApear
or the zoom is a bit to large.