Skip to content

Instantly share code, notes, and snippets.

@BrentMifsud
Last active February 24, 2021 23:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BrentMifsud/2b8ced4789e2c50e5269b1af60aa1717 to your computer and use it in GitHub Desktop.
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.
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)
}
}
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