multiple regionDidChangeAnimated calls - what gives? - iphone

I have a MKMapView inside a UITableView as a custom cell (don't ask ;) - don't know if it matters really), for which I register a regionDidChangeAnimated delegate method. This method gets called three times when the UITableView is loaded - once with the actual region and then two more times with a region that is way off. In the simulator, I consistently get a region with center (+37.43997405, -97.03125000). On the device, it seems to depend on the location reported by the location manager, which initializes the map view.
Why am I getting three regionDidChangeAnimated calls? And why are the center coordinates for the last two of them off?
This is the code I use to get the center coordinates:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated {
CLLocation *l = [[CLLocation alloc] initWithLatitude:self.mapView.centerCoordinate.latitude longitude:self.mapView.centerCoordinate.longitude];
(....)

I have set up a map view inside a custom table view cell and added that cell to a table view (although it definitely should not matter where/how the map view is displayed).
I do not see any unexpected calls to the regionDidChangeAnimated: delegate method.
I see calls to this method only when:
The user changes the position/zoom of the map, OR
Some code changes the center/span of the map
Are you sure that you are seeing unexpected calls? You are not using code to setup the region (center/span) of the map?

Related

Why is the MKMapView's userLocation property rubbish ... for a while?

I have a Map View defined in IB and it is set to show the user location.
In my app, in -viewDidAppear, I query self.mapView.userLocation.location.coordinate and it comes back with insane values such as:
latitude: 4.8194501961644877e-49
longitude: 2.2993313035571993e-59
However, the next time -viewDidAppear is called (after I've simply moved to another tabbed view and then back to this one) the userLocation property holds exactly the correct values for my current location.
It seems that at the time of my initial call, the userLocation property has not been initialised but despite having read Apple's documentation I can't see any caveats where it says that this property is only valid after doing xxx.
Is there something that has to happen before userLocation is valid to use or should I just use CLLocationManager and ask it instead?
Thanks in advance for any help.
Sadly, Thomas' suggestion didn't help. What I have since discovered is:
If showsUserLocation is NO, then userLocation is never set correctly and -MapView:didUpdateUserLocation: is never called, consequently I never ever get a sensible location value.
So, to get the user's location I have to set showsUserLocation to YES, however that then means that after all my annotations have been added to the view (without including the user's location) I then calculate the required span to encompass them all and display them all at the right zoom level. After I do that though, the view jumps sideways as the Map View then automatically displays the user's location as the blue blob! As it was never included in the annotations to work out the zoom level I can't incorporate it into my calculations. Aaargh!
Note that when showsUserLocation is YES, then -MapView:didUpdateUserLocation: is called, but only after I've calculated all the coordinates of my annotations, not before!
I'm assuming it hasn't finished finding the user location - it has to work this out and it may take a while.
Instead of using it in viewDidLoad use THIS delegate method:
- (void)mapView:(MKMapView *)myMapView didUpdateUserLocation:(MKUserLocation *)userLocation;
You will need to set your mapview delegate to self. :)
Same is often true of Core Location. You'll get the last location lingering it its buffer, sometimes, or a super-broad throw-the-dart-at-the-map kind of location...
Best bet is to check the .horizontalAccuracy property of the location object and toss any that are too vague. It's good practice to just chuck the first one too.
for didUpdateUserLocation to be called you have to have...
mapView.showsUserLocation = TRUE;

How to tell when MKMapView and visible MKAnnotationView are finished drawing?

I'm displaying a MKMapView with MKAnnotations some of which are selected and showing their Annotation.
I am trying to grab an image of the displayed map and annotations using the -renderInContext.
90% of the time the Map, MKPinAnnotationView's and selected annotations are correctly captured.
The other 10% of the time the image is missing something, usually the MKPinAnnotationViews or their annotations if selected.
I've added code to deal with the Map itself loading it's map data.
But I haven't been able to track down something that would indicate to me that all of the visible MKPinAnnotationView's have been drawn
and if selected their annotations displayed??
the closest hint I've come across is the addObserver, although I haven't figured out what could be observed that would tell me when all of the drawing is done.
Thoughts?
ok I'm an idiot...
I finally tracked down the problem. In my viewForAnnotation routine in the MKMapView Delegate protocol I wasn't correctly setting values for reused MKPinAnnotationView's.
So some of the time I would reuse a view that had .canShowCallout set to YES and other times I'd reuse a view that had .canShowCallout set to NO.
.<
Try using the MKMapViewDelegate didAddAnnotationViews method.
If in that method, the drawing is still not ready for your requirements, you could then in there call your capturing method with performSelector:withObject:afterDelay:.

moving/updating MKOverlay on MKMapView

is there a way to update (i.e. moving around) a MKOverlay that is already added to the MKMapView. Removing a old one and adding a new one is terrible (slow).
i.e i would like to trigger the background function that is calling this function when an overlay moves on the screen:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
(with MKAnnotions its a little better i think, but i cant use MKPolyline, MKPolygon, etc. and the whole information is reduced to a single point)
MKOverlayView has the following methods which force MapKit to re-render the given mapRect:
- (void)setNeedsDisplayInMapRect:(MKMapRect)mapRect
- (void)setNeedsDisplayInMapRect:(MKMapRect)mapRect zoomScale:(MKZoomScale)zoomScale
If you’re using a timer (or periodical HTTP request or some sort of other method for determining that your overlay should be updated), calling one of the above methods on the overlayView will cause it to re-render that spot on the map (i.e. -canDrawMapRect:zoomScale: will be called again, and then -drawMapRect:zoomScale:inContext: will be called if the former returns YES).
Update:
If you’re not sure what mapRect you need to re-render, you might be able to use the MKMapRectWorld constant as the mapRect — which I believe would cause the overlay across the entire map to reload (once visible).
Use MKAnnotations. You can change the coordinate of them. Just disable any touch-related stuff. You would need your own drawing code to draw the annotations, OpenGL would probably do the trick. Nobody would know the difference.
I actually had to force the overlay view to invalidate the path, to clear out the old path before setting up a new one. [polygonView invalidatePath]. Telling the map view that it needs a display refresh was insufficient for me.
This seems to force the map to refresh overlays. Probably annotations as well.
[self.mapView setCenterCoordinate:self.mapView.centerCoordinate];
Swift 3+ update, this worked for me, thanks #zenchemical for the inspiration.
DispatchQueue.main.async {
self.map.add(overlay, level: .aboveRoads)
self.map.setCenter(self.map.centerCoordinate, animated: false)
}
Side note, I can't help feeling that this shouldn't be necessary and there is a more appropriate solution, however, I haven't found it. Even though the add command is dispatched to the main queue, the overlay reproducibly does not render until I move the map.
there is a problem with raw setNeedsDisplay if the bounding rectangle of the polygon changes then MKMapView would not update whole rectangle correctly,and secondly,MKPolygon/MKPolygonRenderer's polygon is not updatable,and the problem with add/remove pipeline is if there is too much request to update the MapView it can get slower in the pipeline, I currently use a 30Hz thread loop if there is an update in polygon i do add/remove

MapKit custom annotations being added to map, but are not visible on the map

I have an interface with a mapView and UITableView. Data is loaded from a server, and the annotations are created and added to the map with
[mapView addAnnotation:truck]
the tableview is then populated using the array thats retured from
[mapView annotations]
once this process is completed, i check the number of annotations on the map with [[mapView annotations] count] called whenever i click on a cell in the table and its equal to the number it ought to be, so all the annotations are getting added onto the mapView, but for some reason I cant see any annotations in the simulator.
The images are named just as they are assigned in the custom AnnotationView, the loadAnnotation function is done properly, etc... i dont know what it could be but ive looked at the associate between the image file and wheres its loaded a hundred times to find a discrepancy, but it all looks fine.
One interesting point is that when i print and coordinate value after clicking on the cell (remember this data comes straight from [mapView annotations], it looks good... but for whatever reason the annotation view isnt being displayed.
so i suppose if i could have the answer to one question it would be, what are possible causes for a mapView to contain several annotations, but to not show any on the map?
Thanks
EDITED WITH IMPORTANT ADDITIONAL INFO
There appears to be a disconnect between what is being displayed on my map in the simulator and what im seeing in the mapView object. For example, when i select a row in the tableView, I am calling:
[mapView setRegion:MKCoordinateRegionMake([annotation coordinate], MKCoordinateSpanMake(.01, .01)) animated:YES];
and this has no effect on the map in the simulator. I have checked that the coordinate being passed is actually a valid coordinate, but it doesnt seem to matter because the map is being unresponsive. I thought it could be something wrong with my .xib, but its all connected properly. delegate is the outlet and mapView as a referencing outlet.
Does anyone now understand what may be happening?
This really looks like missing images.
Try replacing your custom annotations with MKPinAnnotations to test, and see if your annotations are visible then.
Have you implemented the following delegate function ?
(MKAnnotationView*) mapView:(MKMapView *)mapView viewForAnnotation:(id )annotation;
This line:
mapView = [[MKMapView alloc] initWithFrame:self.view.bounds];
was creating messing everything up. once i commented it out, the app began working fine.

Multiple annotation callouts displaying in MKMapView

Is it possible to open simultaneously more then one callout?
The code:
- (void)mapViewDidFinishLoadingMap:(MKMapView *)theMapView {
for (id<MKAnnotation> currentAnnotation in theMapView.annotations) {
[theMapView selectAnnotation:currentAnnotation animated:YES];
}
}
opens only one callout.
Note that there is a method on MKMapView (not MKAnnotationView) for selecting an annotation programmatically that works more or less as you would expect:
- (void)selectAnnotation:(id < MKAnnotation >)annotation animated:(BOOL)animated
However, it automatically deselects any currently annotation at the same time so this doesn't solve your problem.
Oddly, there is a property on MKMapView that appears to hold an array of currently selected annotations:
#property(nonatomic, copy) NSArray *selectedAnnotations
But the documentation on this method says:
"Assigning a new array to this property
selects the first annotation in the
array only."
Just thought this might be of interest.
From a strict API perspective, this does not seem possible.
The -(void)setSelected:(BOOL)selected animated:(BOOL)animated selector on MKAnnotationView states : "You should not call this method directly. An MKMapView object calls this method in response to user interactions with the annotation." so the underlying message is that the selection of annotationView instances in under the full responsability of user selection, and as the user can only select one of them at a time, you shouldn't be able to get several of them selected at the same time.
Even if the documentation says that should not call this method directly, did you try to invoke it anyway with setSelected:YES on several MKAnnotationView instances to see what it gives ?
THE CLEAN WAY I WOULD DO IT : (not tested myself however)
don't rely on the selection mechanism of the MKMapView
subclass the MKAnnotationView to implement a custom one
do the customization in such a way that the callout is part of the annotation view so that you can display several of them.
If you do it like this, you can make appear several callout bubble at the same time and get something that would look like :
alt text http://a1.phobos.apple.com/us/r1000/048/Purple/2b/b2/ec/mzl.ttcsrlee.480x480-75.jpg
Since iOS 11, Apple added new annotation view called MKMarkerAnnotationView.
In func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView?, when you dequeue the annotation view, make sure you cast it as MKMarkerAnnotationView (e.g. var annotationView = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKMarkerAnnotationView), and set view's attributes titleVisibility and subtitleVisibility to .visible.