Last active
February 2, 2024 04:34
-
-
Save danielrotaermel/dbf64e25b24933eb321567522fbd3cc6 to your computer and use it in GitHub Desktop.
MKAnnotationView extensions to show/hide annotations completely
This file contains hidden or 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
// | |
// MKMapView+ShowHide.swift | |
// | |
// Created by Daniel Rotärmel on 27.10.20. | |
// | |
import MapKit | |
// MKAnnotationView extensions to show/hide annotations completely | |
extension MKMapView { | |
/** | |
zooms at given coordinate with a span in meters | |
*/ | |
func zoomAtLocation(location: CLLocationCoordinate2D, spanInMeters: Int) { | |
let region = MKCoordinateRegion(center: location, latitudinalMeters: CLLocationDistance(exactly: spanInMeters)!, | |
longitudinalMeters: CLLocationDistance(exactly: spanInMeters)!) | |
setRegion(regionThatFits(region), animated: true) | |
} | |
/** | |
get all currently visible annotations | |
*/ | |
func visibleAnnotations() -> [MKAnnotation] { | |
return annotations(in: visibleMapRect).map { obj -> MKAnnotation in obj as! MKAnnotation } | |
} | |
/** | |
hides given overlay | |
completion is run after | |
*/ | |
func hideOverlay(_ overlay: MKOverlay, animated: Bool = false, withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
if animated { | |
DispatchQueue.main.async { | |
UIView.animate(withDuration: withDuration, delay: delay, options: .curveEaseInOut, | |
animations: { self.renderer(for: overlay)?.alpha = 0 }, | |
completion: { _ in completion?() } | |
) | |
} | |
} else { | |
renderer(for: overlay)?.alpha = 0 | |
completion?() | |
} | |
} | |
/** | |
shows given overlay | |
completion is run after | |
*/ | |
func unhideOverlay(_ overlay: MKOverlay, animated: Bool = false, withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
if animated { | |
DispatchQueue.main.async { | |
UIView.animate(withDuration: withDuration, delay: delay, options: .curveEaseInOut, | |
animations: { self.renderer(for: overlay)?.alpha = 1 }, | |
completion: { _ in completion?() } | |
) | |
} | |
} else { | |
renderer(for: overlay)?.alpha = 1 | |
completion?() | |
} | |
} | |
/** | |
hides all given overlays | |
completion is run after all overlays are hidden | |
*/ | |
func hideOverlays(_ overlays: [MKOverlay], animated: Bool = false, withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
let dispGroup = DispatchGroup() | |
for overlay in overlays { | |
dispGroup.enter() | |
hideOverlay(overlay, animated: animated, withDuration: withDuration, delay: delay, completion: { | |
dispGroup.leave() | |
}) | |
} | |
dispGroup.notify(queue: .main) { | |
completion?() | |
} | |
} | |
/** | |
shows all given overlays | |
completion is run after all overlays are shown | |
*/ | |
func unhideOverlays(_ overlays: [MKOverlay], animated: Bool = false, withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
for overlay in overlays { | |
unhideOverlay(overlay, animated: animated, withDuration: withDuration , delay: delay, completion: completion) | |
} | |
} | |
/** | |
hides given annotation | |
completion is run after | |
*/ | |
func hideAnnotation(_ annotation: MKAnnotation, animated: Bool = false, withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
DispatchQueue.main.async { | |
if let annotationView = self.view(for: annotation) { | |
if animated { | |
annotationView.hideAnimated(withDuration: withDuration , delay: delay, completion: completion) | |
} else { | |
annotationView.hide() | |
completion?() | |
} | |
} else { | |
// when no annotationview is found run the completion | |
completion?() | |
} | |
} | |
} | |
/** | |
hides all given annotations | |
completion is run after all annotations are hidden | |
*/ | |
func hideAnnotations(_ annotations: [MKAnnotation], animated: Bool = false, withDuration: Double = 0.2, delay: Double = 0, | |
completion: (() -> Void)? = nil) { | |
let dispGroup = DispatchGroup() | |
for annotation in annotations { | |
dispGroup.enter() | |
hideAnnotation(annotation, animated: animated, withDuration: withDuration , delay: delay, completion: { | |
dispGroup.leave() | |
}) | |
} | |
dispGroup.notify(queue: .main) { | |
completion?() | |
} | |
} | |
/** | |
shows given annotations | |
completion is run after | |
*/ | |
func unhideAnnotation(_ annotation: MKAnnotation, animated: Bool = false, withDuration: Double = 0.2, delay: Double = 0, | |
completion: (() -> Void)? = nil) { | |
DispatchQueue.main.async { | |
if let annotationView = self.view(for: annotation) { | |
if animated { | |
annotationView.showAnimated(withDuration: withDuration , delay: delay, completion: completion) | |
} else { | |
annotationView.show() | |
completion?() | |
} | |
} | |
} | |
} | |
/** | |
shows all given annotations | |
completion is run after all annotations are shown | |
*/ | |
func unhideAnnotations(_ annotations: [MKAnnotation], animated: Bool = false, withDuration: Double = 0.2, delay: Double = 0, | |
completion: (() -> Void)? = nil) { | |
let dispGroup = DispatchGroup() | |
for annotation in annotations { | |
dispGroup.enter() | |
unhideAnnotation(annotation, animated: animated, withDuration: withDuration , delay: delay, completion: { | |
dispGroup.leave() | |
}) | |
} | |
dispGroup.notify(queue: .main) { | |
completion?() | |
} | |
} | |
} | |
// extensions to MKAnnotationView to enable complete hiding/showing | |
extension MKAnnotationView { | |
// saves the collision used by the annotationview | |
// holder stores properties in extensions -> https://medium.com/@valv0/computed-properties-and-extensions-a-pure-swift-approach-64733768112c | |
private struct Holder { | |
static var heldSavedCollision = [String: CollisionMode]() | |
static var heldSavedAnnotation = [String: MKAnnotation]() | |
} | |
// used for saving and restoring collisionmode | |
var savedCollision: CollisionMode? { | |
get { return Holder.heldSavedCollision[debugDescription] ?? nil } | |
set(newValue) { Holder.heldSavedCollision[debugDescription] = newValue } | |
} | |
// used to check weather the annotation has changed while running the animation | |
var savedAnnotation: MKAnnotation? { | |
get { return Holder.heldSavedAnnotation[debugDescription] ?? nil } | |
set(newValue) { Holder.heldSavedAnnotation[debugDescription] = newValue } | |
} | |
func hide() { | |
savedCollision = collisionMode | |
collisionMode = .none | |
alpha = 0 | |
isHidden = true | |
} | |
func show() { | |
collisionMode = savedCollision ?? .rectangle | |
alpha = 1 | |
isHidden = false | |
} | |
func hideAnimated(withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
savedAnnotation = self.annotation | |
self.savedCollision = self.collisionMode | |
let dispGroup = DispatchGroup() | |
dispGroup.enter() | |
DispatchQueue.main.async(group: dispGroup) { | |
// small delay is needed to make the collisons recalculate | |
UIView.animate(withDuration: withDuration, delay: delay, options: .curveEaseInOut, | |
animations: { self.alpha = 0 }, | |
completion: { _ in dispGroup.leave()}) | |
} | |
dispGroup.notify(queue: .main) { | |
self.collisionMode = .none | |
// don't complete the animation if the annotation has changed meanwhile | |
// when using reusable annotations the annotation could have changed | |
// while the animation was running | |
// therefore we dont want to complete the animation when the annotation has changed | |
if self.hasAnnotationChanged() { | |
completion?() | |
return | |
} else { | |
self.alpha = 0 | |
self.isHidden = true | |
completion?() | |
} | |
} | |
} | |
func showAnimated(withDuration: Double = 0.2 , delay: Double = 0, completion: (() -> Void)? = nil) { | |
savedAnnotation = self.annotation | |
collisionMode = savedCollision ?? .rectangle | |
isHidden = false | |
let dispGroup = DispatchGroup() | |
dispGroup.enter() | |
DispatchQueue.main.async(group: dispGroup) { | |
// small delay is needed to make the collisons recalculate | |
UIView.animate(withDuration: withDuration, delay: delay, options: .curveEaseInOut, | |
animations: { self.alpha = 1.0 }, | |
completion: { _ in dispGroup.leave() } | |
) | |
} | |
dispGroup.notify(queue: .main) { | |
// don't complete the animation if the annotation has changed meanwhile | |
if self.hasAnnotationChanged() { | |
completion?() | |
return | |
} else { | |
self.alpha = 1 | |
completion?() | |
} | |
} | |
} | |
private func hasAnnotationChanged() -> Bool { | |
return !(self.savedAnnotation?.title == self.annotation?.title && self.savedAnnotation?.subtitle == self.annotation?.subtitle) | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thank you for yout code, I solve my problem.
but i have some questions about your code
isHidden
andalpha
to hide and show a annotationView?self.alpha = 0, self.isHidden = true
when hide annimation is finished?would you mind to give me answers about my questions? thank you :D