Last active
February 24, 2021 23:25
-
-
Save BrentMifsud/2b8ced4789e2c50e5269b1af60aa1717 to your computer and use it in GitHub Desktop.
Helper Extensions for zooming Annotations and Overlays with an offset from the center of the map.
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
extension MKCoordinateRegion { | |
/// Initialize a `MKCoordinateRegion` from a set of `MKAnnotations` | |
/// - Parameter annotations: Annotations | |
init(for annotations: [MKAnnotation]) { | |
var minLat: CLLocationDegrees = 90.0 | |
var maxLat: CLLocationDegrees = -90.0 | |
var minLon: CLLocationDegrees = 180.0 | |
var maxLon: CLLocationDegrees = -180.0 | |
for annotation in annotations { | |
let lat = Double(annotation.coordinate.latitude) | |
let long = Double(annotation.coordinate.longitude) | |
if lat < minLat { | |
minLat = lat | |
} | |
if long < minLon { | |
minLon = long | |
} | |
if lat > maxLat { | |
maxLat = lat | |
} | |
if long > maxLon { | |
maxLon = long | |
} | |
} | |
var span = MKCoordinateSpan( | |
latitudeDelta: (maxLat - minLat), | |
longitudeDelta: (maxLon - minLon) | |
) | |
let center = CLLocationCoordinate2D( | |
latitude: maxLat - span.latitudeDelta / 2, | |
longitude: maxLon - span.longitudeDelta / 2 | |
) | |
// Apply padding | |
span.latitudeDelta = span.latitudeDelta * 1.2 | |
span.longitudeDelta = span.longitudeDelta * 1.2 | |
self.init(center: center, span: span) | |
} | |
/// Initialize a `MKCoordinateRegion` from an `MKOverlay` | |
/// - Parameter annotations: Annotations | |
init(for overlay: MKOverlay) { | |
let mapRect = overlay.boundingMapRect | |
let paddedMapRect = mapRect.insetBy(dx: mapRect.width * -0.16, dy: mapRect.height * -0.16) | |
self.init(paddedMapRect) | |
} | |
} |
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
import Foundation | |
import MapKit | |
extension MKMapView { | |
/// Zooms to fit all the specified annotations on screen. | |
/// Offsets the center point based on landscape or portrait mode. | |
/// - Parameters: | |
/// - annotations: annotations to zoom. | |
/// - zoomLevel: how many meters around the point to show (this does nothing if there is more than one annotation). | |
/// - offsetFromCenter: number between -1 and 1. | |
/// - animated: animate the zoom. | |
/// | |
/// - Note: | |
/// If you use an offset of 0.25, the center will be in the top 25% of the screen in portrait mode. | |
/// In landscape mode it will be in the right 25% of the screen. | |
func offsetZoomTo( | |
annotations: [MKAnnotation], | |
zoomLevel: CLLocationDistance, | |
offsetFromCenter: Double, | |
animated: Bool | |
) { | |
guard !annotations.isEmpty else { return } | |
let isLandscape: Bool | |
switch UIDevice.current.orientation { | |
case .landscapeLeft, .landscapeRight: | |
isLandscape = true | |
default: | |
isLandscape = false | |
} | |
var annotationRegion: MKCoordinateRegion | |
if annotations.count == 1, | |
let first = annotations.first { | |
annotationRegion = MKCoordinateRegion( | |
center: first.coordinate, | |
latitudinalMeters: zoomLevel, | |
longitudinalMeters: zoomLevel | |
) | |
} else { | |
annotationRegion = MKCoordinateRegion(for: annotations) | |
} | |
// We need to get the map view's region that will enclose the annotation region. | |
let fullMapRegion = regionThatFits(annotationRegion) | |
var newCenter = fullMapRegion.center | |
// Offset the map center point. | |
if isLandscape { | |
newCenter.longitude = newCenter.longitude - fullMapRegion.span.longitudeDelta * offsetFromCenter | |
} else { | |
newCenter.latitude = newCenter.latitude - fullMapRegion.span.latitudeDelta * offsetFromCenter | |
} | |
setRegion( | |
MKCoordinateRegion( | |
center: newCenter, | |
span: fullMapRegion.span | |
), | |
animated: true | |
) | |
} | |
/// Zooms to fit all the specified annotations on screen. | |
/// Offsets the center point based on landscape or portrait mode. | |
/// - Parameters: | |
/// - annotations: annotations to zoom. | |
/// - offsetFromCenter: number between -1 and 1. | |
/// - animated: animate the zoom. | |
/// | |
/// - Note: | |
/// If you use an offset of 0.25, the center will be in the top 25% of the screen in portrait mode. | |
/// In landscape mode it will be in the right 25% of the screen. | |
func offsetZoomTo( | |
overlay: MKOverlay, | |
offsetFromCenter: Double, | |
animated: Bool | |
) { | |
let isLandscape: Bool | |
switch UIDevice.current.orientation { | |
case .landscapeLeft, .landscapeRight: | |
isLandscape = true | |
default: | |
isLandscape = false | |
} | |
// We need to get the map view's region that will enclose the annotation region. | |
let fullMapRegion = regionThatFits(MKCoordinateRegion(for: overlay)) | |
var newSpan: MKCoordinateSpan = fullMapRegion.span | |
// I've found that landscape needs more padding to prevent things from being cutoff... | |
if isLandscape { | |
newSpan.longitudeDelta = newSpan.longitudeDelta * 1.75 | |
newSpan.latitudeDelta = newSpan.latitudeDelta * 1.75 | |
} | |
var newCenter = fullMapRegion.center | |
// Offset the map center point. | |
if isLandscape { | |
newCenter.longitude = newCenter.longitude - newSpan.longitudeDelta * offsetFromCenter | |
} else { | |
newCenter.latitude = newCenter.latitude - newSpan.latitudeDelta * offsetFromCenter | |
} | |
setRegion( | |
MKCoordinateRegion( | |
center: newCenter, | |
span: newSpan | |
), | |
animated: true | |
) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment