4

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
    }
Community
  • 1
  • 1
Danny Wang
  • 429
  • 9
  • 24
  • 1
    I'm not sure of the intent of the `if (placenum==places.count)` pattern, but it seems unnecessary and redundant to me. How can you get to the end of that loop and for these two not be equal? And, if for some reason one of the annotations wasn't added, why wouldn't you still want to show the annotations that were? – Rob Apr 13 '15 at 16:56
  • @Rob I oversimplified the code. The relevant part is now added back. If "coordinate" falls in the current mapview region, then placenum++. – Danny Wang Apr 13 '15 at 17:15
  • So, I would have thought you'd just want `if (placenum > 0)`, no? But if you're only adding annotations that are within the visible rect, you don't need `showAnnotations` at all, do you? The purpose of `showAnnotations` is to change the map view to fit the annotations you added, but if they're all visible no change is needed, right? – Rob Apr 13 '15 at 17:50
  • @Rob `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, which with the current code doesn't work. I have to tap the map to trigger the adding of annotations. I'm planning to fix it by creating another annotation list with these in the visible rect and use `showAnnotations` with the list. On the other side, your answer observation1 seems pointing to an alternative way... – Danny Wang Apr 13 '15 at 18:14
  • First, definitely move all of this map stuff to the main thread and some of this curious behavior may go away. Second, I only raised the "when to zoom" logic issue as it seems backwards to me, but it sounds like this was a conscious decision, so do whatever you want on that score. Good luck! – Rob Apr 13 '15 at 18:41

2 Answers2

3

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.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • About 2, when the app is launched, there is no other annotations except the user's current location. So I'm using `didUpdateUserLocation` to set a default region. Once the user makes a request and get a list of locations from internet, the region will be updated accordingly with `showAnnotations` – Danny Wang Apr 13 '15 at 17:24
  • If use 'userTrackingMode', is it still possible to set the delta/size of the region in the mapview? – Danny Wang Apr 13 '15 at 17:28
  • Since you're stopping location services as soon as you re-center the map, that's not likely to be the source of the problem, so I've removed that portion of my answer. I was just worried about the risk that that you might have changed the location such that the annotations were no longer visible... – Rob Apr 13 '15 at 19:25
3

Also please make sure to set the delegate property of mapview to self.

mapview.delegate = self

You can also do this by connecting the delegate outlet of mapview to ViewController using Connection Inspector (Interface Builder)

Desert Rose
  • 3,376
  • 1
  • 30
  • 36