Skip to content

Instantly share code, notes, and snippets.

@danielrotaermel
Last active February 2, 2024 04:34
Show Gist options
  • Save danielrotaermel/dbf64e25b24933eb321567522fbd3cc6 to your computer and use it in GitHub Desktop.
Save danielrotaermel/dbf64e25b24933eb321567522fbd3cc6 to your computer and use it in GitHub Desktop.
MKAnnotationView extensions to show/hide annotations completely
//
// 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)
}
}
@J0onYEong
Copy link

Thank you for yout code, I solve my problem.

but i have some questions about your code

  • why use both isHidden and alpha to hide and show a annotationView?
  • why execute self.alpha = 0, self.isHidden = true when hide annimation is finished?
    • what is the meaning of complete the animation?

would you mind to give me answers about my questions? thank you :D

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment