adding Overlays to MKMapView to fast! Is that even possible? - iphone

Hi I have the following problem!
I do add a lot of Overlays to my MKMapView!
For example I add 150 Overlays, but for some reason not all of them show up!
I know that mapView:viewForOverlay: gets called 150 times.
I found out that if I add this: [NSThread sleepForTimeInterval:1] to the method where all my overlays are created all the overlays show up.
so can it be that the overlays being added to fast? or what could be the problem?
all the overlays are created in a background thread like this!
MKPolyline* routeLine;
....
....
dispatch_async(dispatch_get_main_queue(), ^{
[self.myMKMapView addOverlay:routeLine];
});
and this is the mapView:viewForOverlay:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
MKOverlayView* overlayView = nil;
MKPolylineView * rLV = [[[MKPolylineView alloc] initWithPolyline:overlay]autorelease];
rLV.fillColor = [UIColor blueColor];
rLV.strokeColor = [UIColor blueColor];
rLV.lineWidth = 1;
rLV.alpha = 0.5;
overlayView = rLV;
return overlayView;
}

If you're calling addOverlay: multiple times (and queueing up hundreds of blocks on the main thread), you may get better results calling the addOverlays: method with an array instead.

Related

ios6 MKMapView deselection bug

I observed a strange behavior with the map in iOS 6.
Here is a code which adds a single annotation, absolutely nothing else in the project:
- (void)viewDidLoad
{
[super viewDidLoad];
MKPointAnnotation * p = [[MKPointAnnotation alloc] init];
p.coordinate = CLLocationCoordinate2DMake(10, 10);
p.title = #" test test";
[self.mapView addAnnotation:p];
self.mapView.centerCoordinate = p.coordinate;
}
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id < MKAnnotation >)annotation
{
MKPinAnnotationView * p = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"wassup" ];
p.canShowCallout = YES;
p.pinColor = MKPinAnnotationColorGreen;
return p;
}
When it is selected and I move (pan) the map small distances like 0.5-1 cm, on iOS6 the annotation gets deselected. On earlier versions works as expected and remains selected.
Is it a bug ? Is there any workaround for this ?
Thanks !
Apple maps are full of bugs.. LOL you noticed that too..
Its a feature of iOS 6 sometime can irritate the the user.. :)
in annotaionView method use:
// iOS6 BUG WORKAROUND !!!!!!!
if (is6orMore) {
[annotationView setTransform:CGAffineTransformMakeRotation(.001)]; //any small positive rotation
}
Also see the link
If you want to not deselect an annotation view, you should subclass MKAnnotationView and override the setSelected:animated: method and stop the annotation view from being deselected.

iPhone - set MKPolylineView lineDashPattern to be animated

I have been trying to work on a application using the MapKit and MKPolyline.
I have gotten the annotations and paths drawn on the map, however I am trying to change the lineDashPattern to be a set of animated dashes in the direction of the course. I know that the lineDashPhase and lineDashPattern together give you a dashed line, and the more you increase lineDashPhase, the more the dashes move, however is there a way to increase the values in such a way that it appears that it is moving, sort of like ants along a line I guess is a good analogy.
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
{
MKOverlayView* overlayView = nil;
if(overlay == self.routeLine)
{
//if we have not yet created an overlay view for this overlay, create it now.
if(self.routeLineView == nil)
{
self.routeLineView = [[[MKPolylineView alloc] initWithPolyline:self.routeLine] autorelease];
self.routeLineView.lineWidth = 5;
self.routeLineView.lineDashPhase = 15;
NSArray* array = [NSArray arrayWithObjects:[NSNumber numberWithInt:20], [NSNumber numberWithInt:20], nil];
self.routeLineView.lineDashPattern = array;
}
overlayView = self.routeLineView;
}
return overlayView;
}
This right now give me dashed lines. I know there is a similar topic here, however I am not sure how to go about this. Any input would be greatly appreciated!
I know this is a pretty old question, but I have just implemented a similar solution and posted it here.

MapView - Have Annotations Appear One at a Time

I'm currently adding annotations to my map through a loop... but the annotations are only appearing on my map in groups. Also, on load, only about 4 annotations are actually displayed on the map... but as I move the map a little, all of the annotations that should be there, suddenly appear.
How can I get all of the annotations to load in the right place, one at a time?
Thanks in advance!
Here is the code I'm using to add annotations:
NSString *incident;
for (incident in weekFeed) {
NSString *finalCoordinates = [[NSString alloc] initWithFormat:#"%#", [incident valueForKey:#"coordinates"]];
NSArray *coordinatesArray = [finalCoordinates componentsSeparatedByString:#","];
latcoord = (#"%#", [coordinatesArray objectAtIndex:0]);
longcoord = (#"%#", [coordinatesArray objectAtIndex:1]);
// Final Logs
NSLog(#"Coordinates in NSString: [%#] - [%#]", latcoord, longcoord);
CLLocationCoordinate2D coord;
coord.latitude = [latcoord doubleValue];
coord.longitude = [longcoord doubleValue];
DisplayMap *ann = [[DisplayMap alloc] init];
ann.title = [NSString stringWithFormat: #"%#", [incident valueForKey:#"incident_type"]];
ann.subtitle = [NSString stringWithFormat: #"%#", [incident valueForKey:#"note"]];
ann.coordinate = coord;
[mapView addAnnotation:ann];
[ann release];
}
// Custom Map Markers
-(MKAnnotationView *)mapView:(MKMapView *)map viewForAnnotation:(id <MKAnnotation>)annotation {
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil; //return nil to use default blue dot view
static NSString *AnnotationViewID = #"annotationViewID";
MKAnnotationView *annotationView = (MKAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationViewID];
if (annotationView == nil) {
annotationView = [[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationViewID] autorelease];
}
annotationView.canShowCallout = YES;
if ([annotationView.annotation.title isEqualToString:#"one"]) {
UIImage *pinImage = [UIImage imageNamed:#"marker_1.png"];
[annotationView setImage:pinImage];
}
if ([annotationView.annotation.title isEqualToString:#"two"]) {
UIImage *pinImage = [UIImage imageNamed:#"marker_2.png"];
[annotationView setImage:pinImage];
}
annotationView.annotation = annotation;
return annotationView;
}
- (void) mapView:(MKMapView *)mapV didAddAnnotationViews:(NSArray *)views {
CGRect visibleRect = [mapV annotationVisibleRect];
for (MKAnnotationView *view in views) {
CGRect endFrame = view.frame;
CGRect startFrame = endFrame; startFrame.origin.y = visibleRect.origin.y - startFrame.size.height;
view.frame = startFrame;
[UIView beginAnimations:#"drop" context:NULL];
[UIView setAnimationDuration:0.4];
view.frame = endFrame;
[UIView commitAnimations];
}
}
Adam,
This solution is a bit messy as I had to munge up one of my current projects to test, but hopefully this will work for you.
First an explanation, it's critical to separate data from UI presentation. The [MKMapView addAnnotation(s)] are just a data update to MKMapView and have no direct impact on animation or timing.
The delegate method mapView:didAddAnnotationViews: is where all of the custom presentation behavior should be defined. In your description you didn't want these to appear all at once, so you need to sequence your animations instead of performing them simultaneously.
One method is to add all of the annotations at once and then just add them with increasing animation delays, however new annotations that get added for whatever reason will begin their animations at zero again.
The method below sets up an animation queue self.pendingViewsForAnimation (NSMutableArray) to hold annotation views as they are added and then chains the animation sequentially.
I've replaced the frame animation with alpha to focus on the animation problem to separate it from the issue of some items not appearing. More on this after the code...
// Interface
// ...
// Add property or iVar for pendingViewsForAnimation; you must init/dealloc the array
#property (retain) NSMutableArray* pendingViewsForAnimation;
// Implementation
// ...
- (void)processPendingViewsForAnimation
{
static BOOL runningAnimations = NO;
// Nothing to animate, exit
if ([self.pendingViewsForAnimation count]==0) return;
// Already animating, exit
if (runningAnimations)
return;
// We're animating
runningAnimations = YES;
MKAnnotationView* view = [self.pendingViewsForAnimation lastObject];
[UIView animateWithDuration:0.4 animations:^(void) {
view.alpha = 1;
NSLog(#"Show Annotation[%d] %#",[self.pendingViewsForAnimation count],view);
} completion:^(BOOL finished) {
[self.pendingViewsForAnimation removeObject:view];
runningAnimations = NO;
[self processPendingViewsForAnimation];
}];
}
// This just demonstrates the animation logic, I've removed the "frame" animation for now
// to focus our attention on just the animation.
- (void) mapView:(MKMapView *)mapV didAddAnnotationViews:(NSArray *)views {
for (MKAnnotationView *view in views) {
view.alpha = 0;
[self.pendingViewsForAnimation addObject:view];
}
[self processPendingViewsForAnimation];
}
Regarding your second issue, items are not always appearing until you move the map. I don't see any obvious errors in your code, but here are some things I would do to isolate the problem:
Temporarily remove your mapView:didAddAnnotationViews:, mapView:annotationForView: and any other custom behaviors to see if default behavior works.
Verify that you have a valid Annotation at the addAnnotation: call and that the coordinates are visible (use [mapView visibleMapRect], MKMapRectContainsPoint(), and MKMapPointForCoordinate().
If it is still not functioning, look at where you are calling the add annotations code from. I try to avoid making annotation calls during map movement by using performSelector:withObject:afterDelay. You can precede this with an [NSObject cancelPreviousPerformRequestsWithTarget:selector:object:] to create a slight delay prior to loading annotations in case the map is being moved a long distance with multiple swipes.
One last point, to achieve the pin-drop effect you're looking for, you probably want to offset by a fixed distance from the original object instead of depending on annotationVisibleRect. Your current implementation will result in pins moving at different speeds depending on their distance from the edge. Items at the top will slowly move into place while items at the bottom will fly rapidly into place. Apple's default animation always drops from the same height. An example is here: How can I create a custom "pin-drop" animation using MKAnnotationView?
Hope this helps.
Update:
To demonstrate this code in action I've attached a link to a modified version of Apple's Seismic demo with the following changes:
Changed Earthquake.h/m to be an MKAnnotation object
Added SeismicMapViewController.h/m with above code
Updated RootViewController.h/m to open the map view as a modal page
See: http://dl.dropbox.com/u/36171337/SeismicXMLWithMapDelay.zip
You will need to pause updating after you add each annotation and allow the map to have time to refresh.

iPhone - MapKit : How may I customize the color and alpha of an overlay on the fly?

I have a MapView onto which I put some overlays (MKPolygon).
I have to maintain groups of MKPolygon.
For this, I have a PolyGonGroupClass that keeps each poly and the color and alpha that should be used to display all these polygons.
So, each time I find a poly, I add it as a map overlay and memorize in into the correct group of polys.
All my groups of poly are kepts into an instance var.
[self.mapView addOverlay:poly];
[thecorrectpolygroup addObject:poly];
...
[self.mypolygroups addObject:thecorrectpolygroup];
#interface PolyGonGroupClass : NSObject {
UIColor* __color;
float __alpha;
NSMutableArray* __polygons;
}
Well...
At this point, how may I tell to the MapView the color and alpha of each poly and update them on the fly when one of these color change ?
I found a - (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay method, but I don't see how I may use it to retrieve the values I put into the memorised values.
I guess I should use the overlay parameter, but how may I retrieve the correct group of poly that correspond to that overlay ?
In viewForOverlay, you can loop through your mypolygroups array and check if the overlay is in that group's polygons array:
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView *pv = [[[MKPolygonView alloc] initWithPolygon:overlay] autorelease];
for (PolyGonGroupClass *pgc in mypolygroups)
{
if ([pgc.polygons containsObject:overlay])
{
pv.fillColor = pgc.color;
pv.alpha = pgc.alpha;
break;
}
}
return pv;
}
return nil;
}
However, for this to work right, you'll have to slightly change where you call addOverlay. In the code you've shown, you are calling addOverlay before adding it to the polygons array and before adding the group to the mypolygroups array. This can cause viewForOverlay to fire before the arrays contain data.
Instead, move just the addOverlay calls to after the mypolygroups array is fully populated. So after adding the last group to mypolygroups:
...
[self.mypolygroups addObject:theLastPolygroup];
for (PolyGonGroupClass *pgc in mypolygroups)
{
for (MKPolygon *p in pgc.polygons)
{
[mapView addOverlay:p];
}
}
Finally, to update a group's color and alpha later after it's already on the map, you can do something like this:
//pgc is some instance of PolyGonGroupClass
pgc.alpha = newAlpha;
pgc.color = newColor;
for (MKPolygon *p in pgc.polygons)
{
MKPolygonView *pv = (MKPolygonView *)[mapView viewForOverlay:p];
pv.alpha = pgc.alpha;
pv.fillColor = pgc.color;
}

Replacing a calloutaccessoryview in mkannotationview

In my viewForAnnotation delegate, I create a MKAnnotationView with leftCalloutaccessory as a info button. When the info button is tapped, I want to replace it with a UIActivityIndicator. So in the calloutAccessoryTapped delegate, I wrote
view.leftCalloutaccessoryView = [UIActivityIndicator indicatorWithStyle:UIActivityIndicatorWhite];
The callout accessory seems to change, but it seems like the view doesn't get updated immediately.
That's when the callout accessory gets hidden (by tapping another pin) and is re-opened, I see a spinner. But otherwise, I don't.
I tried calling [view setNeedsdisplay] and [view setNeedsLayout] and [view layoutsubviews] as well but in vain.
Any suggestions/pointers?
I would consider removing and re-adding your annotation. It seems a little heavy handed, but might work.
I was having a similar problem -- I needed to display an image for the leftCalloutAccessoryView if there was one associated with the annotation. Try setting it in mapView:didSelectAnnotationView instead of in mapView:viewForAnnotation
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)annotView
{
...
if(imgURL.length>1){
// set a thread that will load the image
// but don't set the leftCalloutAccessoryView until the image is loaded
dispatch_queue_t downloader = dispatch_queue_create("annotation image downloader",NULL);
dispatch_async(downloader, ^{
NSURL* photoURL = [NSURL URLWithString:imgURL];
NSData* photoData = [NSData dataWithContentsOfURL:photoURL];
UIImage* photoImage = [UIImage imageWithData:photoData];
dispatch_async(dispatch_get_main_queue(), ^{
annotView.leftCalloutAccessoryView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 32, 32)];
[(UIImageView *) annotView.leftCalloutAccessoryView setImage:photoImage];
});
});
}
}