Search code examples
swiftmapkitmapkitannotation

When is mapView:viewForAnnotation called in Swift?


According to this post whenever you call addAnnotation method, mapView:viewForAnnotation gets called

However the following code called mapView:viewForAnnotation only once. I have several annotations, but "view called" was only printed once. I guess this has something to do with the thread?

import UIKit
import MapKit
import CoreLocation

class ViewController: UIViewController, UITextFieldDelegate, MKMapViewDelegate, CLLocationManagerDelegate {

    @IBOutlet var searchtext: UITextField!
    @IBOutlet var map: MKMapView!

    var locationManager = CLLocationManager()

    override func viewDidLoad() {
        super.viewDidLoad()

        self.searchtext.delegate = self

        locationManager.delegate = self
        self.map.delegate = self

        locationManager.desiredAccuracy = kCLLocationAccuracyBest
        locationManager.requestWhenInUseAuthorization()
        locationManager.startUpdatingLocation()
    }

    func textFieldDidBeginEditing(textField: UITextField!) {    //delegate method
        var allAnnotations = self.map.annotations
        self.map.removeAnnotations(allAnnotations)
    }

    func textFieldShouldReturn(textField: UITextField!) ->Bool {
        textField.resignFirstResponder()

        //...

        let session = NSURLSession.sharedSession()

        var task = session.dataTaskWithURL(url!, completionHandler: { (date, response, error) -> Void in
            if (error != nil) {
                println(error)
            }else {
                var placenum = 0
                //placenum is to see if all the places are in the visible rect. If so I will use showAnnotations to zoom (in) to the best-fitted view show them. If not, I will only show these in the visible rect
                for place in places {
                    //...
                    if (regionContains(self.map.region, coordinate)) {
                        placenum = placenum+1
                    }
                    self.map.addAnnotation(annotation)
                }

                if (placenum==places.count) {
                    self.map.showAnnotations(self.map.annotations, animated: true)
                }
            }

        })

        task.resume()

        return true
    }

    func locationManager(manager: CLLocationManager!, didUpdateLocations locations: [AnyObject]!) {

        //...
        self.map.setRegion(region, animated: false)

        locationManager.stopUpdatingLocation()
    }

    func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! {
        println("view called")

        return nil
    }

Solution

  • In short, mapView(_:viewFor:) is called when an annotation falls within the visible portion of the map view. Namely, either (a) an annotation has been added to a map view and it falls with the region or (b) the region changes such that an annotation that was not previously visible now is. Needless to say, this method also will only be called if you set the delegate of the map view (either programmatically or in Interface Builder).

    By the way, the completion handler of dataTask(with:completionHandler:) will not be called on the main thread. Thus, any UI updates must be explicitly dispatched back to the main thread, e.g.

    DispatchQueue.main.async {
        for place in places {
            //...
            placenum = placenum + 1
            self.map.addAnnotation(annotation)
        }
    
        self.map.showAnnotations(self.map.annotations, animated: true)
    }
    

    I would recommend ensuring that interaction with the map view happens on the main thread, as shown above.

    By the way, remember that if you're showing the user location on the map, that, itself, results in mapView(_:viewFor:) being called. So if you're seeing it called only once, you might want to confirm whether the annotation is a MKUserLocation or one of the annotations you added.