I'm trying to fade my annotations in through animation, so I create them with an alpha of 0, and then animate their alpha to 1 in didAddAnnotation. Sometimes I'm only adding a few annotations, subtracting others, but right now EVERY annotation on screen is being faded out/in when ANY are added rather than the expected behavior, which would be that only the recently added pins would fade to 1.
- (void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views{
for (MKAnnotationView *view in views){
if (view.alpha ==0){
[UIView animateWithDuration:.5 animations:^{
view.alpha = 1;
}];
}
}
For animating the annotations as its added, you can use this subclass of MKAnnotationView
class AnnotationView: MKAnnotationView {
override var annotation: MKAnnotation? {
didSet {
UIView.animate(withDuration: 0.5) { self.alpha = 1.0 }
}
}
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
self.alpha = 0
}
override func prepareForReuse() {
super.prepareForReuse()
self.alpha = 0.0
}
}
Usage:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if let view = mapView.dequeueReusableAnnotationView(withIdentifier: "annot") as? AnnotationView {
return view
}
return AnnotationView(annotation: annotation, reuseIdentifier: "annot")
}
Related
I looked at the posts for this and I still do not receive a custom pin....
Custom Annotation --> this includes setting my image
import UIKit
import MapKit
class CustomPointAnnotation: MKPointAnnotation {
var pinCustomImageName: UIImage!
}
View Controller:
I want to return current location until a button is selected to drop pin
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
//current Location
if !(annotation is CustomPointAnnotation) {
return nil
}
let reuseIdentifier = "pin"
var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseIdentifier)
if annotationView == nil {
annotationView = MKAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
annotationView!.canShowCallout = true
} else {
annotationView!.annotation = annotation
}
if let annotationView = annotationView {
annotationView.image = UIImage(named: "Skyscraper")
annotationView.canShowCallout = true
}
return annotationView
}
func addPin() {
pointAnnotation = CustomPointAnnotation()
pointAnnotation.pinCustomImageName = UIImage(named: "Skyscraper")
pointAnnotation.coordinate = currentLocation.coordinate
pointAnnotation.title = "First Building"
pointAnnotation.subtitle = "Latitude: \(currentLocation.coordinate.latitude), \
(currentLocation.coordinate.longitude)"
mapView.addAnnotation(pointAnnotation)
}
There's nothing seriously wrong with the code. But there can be a couple of things that would cause problems, including:
Have you set the delegate (either in IB or programmatically) for the map view? If not, your mapView(_:viewFor:) will never be called. Add breakpoint or debugging print statement to confirm.
Have you confirmed that UIImage(named: "Skyscraper") is successfully retrieving an image? Make sure this is not returning nil.
Note, if only iOS 11 and later, you can simplify this code a bit. Since iOS 11, we no longer need for mapView(_:viewFor:) in simple scenarios like this. I would suggest putting the annotation view configuration code within the annotation view subclass, where it belongs, and avoid cluttering our view controller with a viewFor implementation.
So when you do get the current issue behind you, the recommended process is:
Define classes for your annotation and annotation view:
class CustomAnnotation: MKPointAnnotation {
var pinCustomImage: UIImage!
}
And
class CustomAnnotationView: MKAnnotationView {
override init(annotation: MKAnnotation?, reuseIdentifier: String?) {
super.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
canShowCallout = true
update(for: annotation)
}
override var annotation: MKAnnotation? { didSet { update(for: annotation) } }
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func update(for annotation: MKAnnotation?) {
image = (annotation as? CustomAnnotation)?.pinCustomImage
}
}
In viewDidLoad register this annotation view class:
mapView.register(CustomAnnotationView.self, forAnnotationViewWithReuseIdentifier: MKMapViewDefaultAnnotationViewReuseIdentifier)
Remove mapView(_:viewFor:) implementation.
Now when you add a CustomAnnotation to your map’s list of annotations, it will be rendered correctly.
But I would suggest resolving your current problem first. There’s no point in refining you implementation until these more basic issues are resolved.
I would like to capture taps on an MKMarkerAnnotationView, and not animate the view when this happens.
Through a delegate for my MKMapView, I can capture selection and deselection that are roughly equivalent to taps on the MKMarkerAnnotationView (the selection also happens when tapping the label that is not part of the MKMarkerAnnotationView)
I am trying to remove the default animation. I didn't find a straightforward solution.
I tried:
1/ setting the view to not selected during the selection. This does cancel the animation but further taps are not captured.
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
view.setSelected(false, animated: false)
// or
view.isSelected = false
handleTap(view)
}
2/ adding another tap gesture recognizer on the view and preventing other gesture recognizers from receiving the touch. This works well unless I tap the label instead of the annotation view.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
let view = dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? MKMarkerAnnotationView ??
MKMarkerAnnotationView(annotation: annotation, reuseIdentifier: reuseIdentifier)
view.annotation = annotation
view.gestureRecognizers?.forEach { view.removeGestureRecognizer($0) }
let gestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(didTap(gesture:)))
gestureRecognizer.delegate = self
view.addGestureRecognizer(gestureRecognizer)
return view
}
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return false
}
You can try override class:
final class CustomMarkerAnnotationView: MKMarkerAnnotationView {
var onSelect: (() -> Void)?
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(false, animated: false)
if selected {
onSelect?() // or catch this in delegate
}
}
}
Overriding did not work for me. Here's what ultimately worked for me (Objc):
- (void)mapView:(MKMapView *)mapView
didSelectAnnotationView:(MKAnnotationView *)view
{
//stops animation
for( CALayer *sublayer in view.layer.sublayers )
{
[sublayer removeAllAnimations];
}
//stops selection
[view setSelected:NO animated:NO];
}
For my own purposes I used the below instead, as it allows for properly deselecting the pin when tapping the pin again immediately after selection.
- (void)mapView:(MKMapView *)mapView
didSelectAnnotationView:(MKAnnotationView *)view
{
//stops animation
for( CALayer *sublayer in view.layer.sublayers )
{
[sublayer removeAllAnimations];
}
//stops selection
[mapView deselectAnnotation:view.annotation animated:NO];
}
I am currently working on creating callouts for annotations I have added to my mapview via MapKit. The annotations work out well but currently callouts aren't being displayed even though I am using the right code to enable them (I believe).
HERE is my viewFor annotation code block.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
if let annotation = annotation as? ClusterAnnotation {
let identifier = "cluster"
return mapView.annotationView(annotation: annotation, reuseIdentifier: identifier)
} else {
let identifier = "pin"
let annotationView = mapView.annotationView(of: MKMarkerAnnotationView.self, annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.canShowCallout = true
annotationView.accessibilityLabel = "hi"
annotationView.isHidden = false
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.markerTintColor = UIColor.customBlue()
annotationView.glyphImage = UIImage(named: "person")
return annotationView
}
}
Extension code block for annotationView function.
extension MKMapView {
func annotationView<T: MKAnnotationView>(of type: T.Type, annotation: MKAnnotation?, reuseIdentifier: String) -> T {
guard let annotationView = dequeueReusableAnnotationView(withIdentifier: reuseIdentifier) as? T else {
return type.init(annotation: annotation, reuseIdentifier: reuseIdentifier)
}
annotationView.annotation = annotation
return annotationView
}
}
The annotation enlarges as I select it, and in the didSelect code block it runs the print statement I run through it. Not exactly sure what is going on that's not allowing the callout to show even though I've literally enabled just about everything.
Please Used This Code.
This code working fine for me.
This Code support Swift4 and Swift5.
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
// Don't want to show a custom image if the annotation is the user's location.
if (annotation is MKUserLocation) {
return nil
} else {
let annotationIdentifier = "AnnotationIdentifier"
let nibName = "MyAnnotationView" //My XIB Name
let viewFromNib = Bundle.main.loadNibNamed(nibName, owner: self, options: nil)?.first as! MyAnnotationView // get My XIB
var annotationView: MyAnnotationView?
// if there is a view to be dequeued, use it for the annotation
if let dequeuedAnnotationView = mapView.dequeueReusableAnnotationView(withIdentifier: annotationIdentifier) as? MyAnnotationView {
if dequeuedAnnotationView.subviews.isEmpty {
dequeuedAnnotationView.addSubview(viewFromNib)
}
annotationView = dequeuedAnnotationView
annotationView?.annotation = annotation
} else {
// if no views to dequeue, create an Annotation View
let av = MyAnnotationView(annotation: annotation, reuseIdentifier: annotationIdentifier)
av.addSubview(viewFromNib)
annotationView = av // extend scope to be able to return at the end of the func
}
// after we manage to create or dequeue the av, configure it
if let annotationView = annotationView {
annotationView.canShowCallout = true // callout bubble
annotationView.rightCalloutAccessoryView = UIButton(type: .detailDisclosure)
annotationView.frame = CGRect(x: 0, y: 0, width: 75, height: 80)
let customView = annotationView.subviews.first as! MyAnnotationView
customView.frame = annotationView.frame
}
return annotationView
}
}
This Code OutPut :
Happy To Help You.
From the source code documentation:
// If YES, a standard callout bubble will be shown when the annotation is selected.
// The annotation must have a title for the callout to be shown.
#property (nonatomic) BOOL canShowCallout;
Without the Title value being set the other items will not show up.
I currently have a custom MKAnnotationView set in a map callout, and it's working well. I however want to add a button to the callout view, but when i tap the button it closes the annotation view before it gets called. How can i get around this?
Here are pertinent bits of my code:
In my view for annotations:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "mapReuseId"
var mapView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if mapView == nil {
mapView = MKAnnotationView(annotation: annotation as! Annotation, reuseIdentifier: reuseId)
mapView!.canShowCallout = false
} else {
mapView!.annotation = annotation as! Annotation
}
mapDottView!.image = customImage
return mapDottView
}
In my didSelect delegate:
func mapView(_ mapView: MKMapView, didSelect view: MKAnnotationView) {
let callOut = customCallOutView(with: data)
view.addSubview(callOut)
// some layout here
}
The customCallOutView is longish, but the important part is that it has a UIButton which never gets called on tap. Any ideas?
You can try to put a delay when your button is tapped.
You can use Grand Central Dispatch or Perform Selector After Delay.
// Do what you need to do when your button was tapped.
DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
// Dismiss your annotation.
}
Try the following code:
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
if annotation is MKUserLocation {
return nil
}
let reuseId = "mapReuseId"
var mapView = mapView.dequeueReusableAnnotationView(withIdentifier: reuseId)
if mapView == nil {
mapView = MKAnnotationView(annotation: annotation as! Annotation, reuseIdentifier: reuseId)
mapView!.canShowCallout = true
let rightButton: AnyObject! = UIButton(type: UIButtonType.detailDisclosure)
pinView?.rightCalloutAccessoryView = rightButton as? UIView
} else {
mapView!.annotation = annotation as! Annotation
}
mapDottView!.image = customImage
return mapDottView
}
func mapView(_ mapView: MKMapView, annotationView view: MKAnnotationView, calloutAccessoryControlTapped control: UIControl) {
print(#function)
// this method will be called when the button is tapped.
// annotation view doesn't disappear
}
I want to create a custom CalloutView from xib. I created a xib and custom AnnotationView class. I want to use my MapViewController segue which name is showDetail segue because this segue is a Push segue. When the user taps the button in my calloutView it should perform my showDetail segue.
I searched all documents, tutorials, guides and questions but I could
not find a any solution with Swift. There are no custom libraries also. I couldn't find any solution it as been 3 days.
My CalloutView.xib file View is my CustomAnnotationView which name is CalloutAnnotationView.swift and File's Owner is MapViewController it allows me to use MapViewController segues.
There is my CalloutAnnotationView.swift
override func hitTest(point: CGPoint, withEvent event: UIEvent?) -> UIView? {
let hitView = super.hitTest(point, withEvent: event)
if (hitView != nil) {
self.superview?.bringSubviewToFront(self)
}
return hitView
}
override func pointInside(point: CGPoint, withEvent event: UIEvent?) -> Bool {
let rect = self.bounds
var isInside = CGRectContainsPoint(rect, point)
if (!isInside) {
for view in self.subviews {
isInside = CGRectContainsPoint(view.frame, point)
if (isInside) {
}
}
}
return isInside
}
override func setSelected(selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
let calloutViewController = MapViewController(nibName: "CalloutView", bundle: nil)
if selected {
calloutViewController.view.clipsToBounds = true
calloutViewController.view.layer.cornerRadius = 10
calloutViewController.view.center = CGPointMake(self.bounds.size.width * 0.5, -calloutViewController.view.bounds.size.height * 0.5)
self.addSubview(calloutViewController.view)
} else {
// Dismiss View
calloutViewController.view.removeFromSuperview()
}
}
My MapViewController.swift codes;
// MARK: - MapKit Delegate Methods
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
if let annotation = annotation as? Veterinary { // Checking annotations is not User Location.
var pinView = mapView.dequeueReusableAnnotationViewWithIdentifier(Constants.MapViewAnnotationViewReuseIdentifier) as? CalloutAnnotationView
if pinView == nil { // If it is nil it created new Pin View
pinView = CalloutAnnotationView(annotation: annotation, reuseIdentifier: Constants.MapViewAnnotationViewReuseIdentifier)
pinView?.canShowCallout = false
} else { pinView?.annotation = annotation } // If it is NOT nil it uses old annotations.
// Checking pin colors from Veterinary Class PinColor Method
pinView?.image = annotation.pinColors()
//pinView?.rightCalloutAccessoryView = UIButton(type: .DetailDisclosure)
return pinView
}
return nil
}
These codes are working fine to load my xib file. I created IBAction from a button and It performs segues from MapViewControllerto my DetailViewController with showDetail segue. But problem is It doesn't remove my customCalloutView from superview so I can't dismiss callout views.
How can I solve this problem or What is the best way to create custom calloutView for use my MapViewController push segues ?
Thank you very much.
In your MKMapView delegate see if this gets called when segueing:
func mapView(mapView: MKMapView, didDeselectAnnotationView view: MKAnnotationView) {
if let annotation = view.annotation as? Veterinary {
mapView.removeAnnotation(annotation)
}
}
I hope this helps you with the problem
func mapView(mapView: MKMapView, didDeselectAnnotationView view: MKAnnotationView) {
if view.isKindOfClass(AnnotationView)
{
for subview in view.subviews
{
subview.removeFromSuperview()
}
}
}