In my app, I use an MKPolyline to track the user's path. Sometimes (and not all the time, which I don't understand), when a new line segment gets added to the map, the entire line flashes. Sometimes it doesn't. This is the code being used to add the lines:
CLLocationCoordinate2D coords[2];
coords[0] = CLLocationCoordinate2DMake(newLocation.coordinate.latitude, newLocation.coordinate.longitude);
coords[1] = CLLocationCoordinate2DMake(oldLocation.coordinate.latitude, oldLocation.coordinate.longitude);
MKPolyline* line = [MKPolyline polylineWithCoordinates:coords count:2];
[mapView addOverlay:line];
Am I missing something?
Edit: This usually happens upon the app's return from being sent to the background. I'm not exactly sure why, though, because I am only adding an overlay, not modifying the entire mapView.overlays array. ...right?
This may not be related, but Apple does state in the Managing the Map's Overlay Objects section of the Location Awareness Programming Guide...
Because the map view is an interface item, any modifications to the
overlays array should be synchronized and performed on the
application’s main thread.
I think your best bet is to try to get the flash over with before you show the map to the user.
Try one of the following:
[mapView setNeedsDisplay];
or
if ([[mapView overlays] count] > 0){
[[[mapView overlays] lastObject] setNeedsDisplay];
}
Put these in either your "viewWillAppear" method, or the "applicationWillEnterForeground" method in AppDelegate.m.
Related
I have a mapview in xcode, which is all working well.
What my page does just now is like this:
downloads a bunch of data and locations from a backend database
populates a mapview with locations and drops pins
populates a table underneath the mapview
That all works great, and I end up with a mapview with a load of pins, and a tableview that has the details of those pins.
What I want to do now, is allow the user to tap on a row from the tableview, and have the map zoom and centre to the corresponding map pin, and then automatically activate the annotation pin callout.
In my 'didselectrow' method, I have the following:
MKCoordinateSpan span = MKCoordinateSpanMake(0.1f, 0.1f);
CLLocationCoordinate2D coordinate = { [item.latitude floatValue], [item.longitude floatValue] };
MKCoordinateRegion region = { coordinate, span };
[self.mapView setRegion:region animated:YES];
This works great too. Tapping on the table row will zoom to and centre the map pin at this location.
I just can't get the last step of firing the annotation pin callout to work.
I have tried:
[mapview annotationsInMapRect:mapview.visibleMapRect];
But this isn't working, and it is possible that there still might be 2 or 3 map pins in the visible area.
What I need to do is to get the pin nearest to the centred location (see above - item.latitude / item.longitude) to automatically open it's callout.
Everything in the code is set up and working, and the map pins have callouts that fire when tapped on, I just need this last stage of having the pin nearest the centre location to open automatically.
Can anyone help with this?
I have tried various other suggestions on SO, but none seem to fit this requirement.
I think I have got solution for your problem you need to use this [_mapView setSelectedAnnotations:#[[[self.mapView annotations] lastObject]]];
For testing I have created an small project that have these 2 methods.
- (IBAction)buttonTouched:(id)sender {
[_mapView showAnnotations:[self.mapView annotations] animated:YES];
[self performSelector:#selector(showAnnotationCallOut) withObject:nil afterDelay:1.0f];
}
- (void) showAnnotationCallOut {
[_mapView setSelectedAnnotations:#[[[self.mapView annotations] lastObject]]];
}
Note: I have called just one annotation for test that why I am calling last object. You'll need to call it for specific annotation of your annotation array.
Edit: According to Richerd's comment here is solution for problem of finding the annotion and showing the callout fro that.
for (MapViewAnnotation *annotion in [self.mapView annotion]) {
if ([annotion.identifire isEqualToString:annotationToCallCallOutIdentifier]) {
//[_mapView setSelectedAnnotations:#[annotation]];
[_mapView selectAnnotation:annotation animated:YES];
break;//don't break if there are can be more than one callouts
}
}
if the user zooms out on a MKMapView, i want MKAnnotations which are near to each other automatically grouped into one "group" annotation.
if the user zooms back in, the "group" annotation should be split again to the unique/original annotations.
apple does this already in the iOS 4 Photos.app
is there a common, "predefined" way to do this?
Its normal working with more than 1500 annotations on the map:
-(void)mapView:(MKMapView *)mapView_ regionDidChangeAnimated:(BOOL)animated
{
NSMutableSet * coordSet = [[NSMutableSet alloc] init];
for(id<MKAnnotation> an in mapView_.annotations)
{
if([an isKindOfClass:[MKUserLocation class]])
continue;
CGPoint point = [mapView_ convertCoordinate:an.coordinate toPointToView:nil];
CGPoint roundedPoint;
roundedPoint.x = roundf(point.x/10)*10;
roundedPoint.y = roundf(point.y/10)*10;
NSValue * value = [NSValue valueWithCGPoint:roundedPoint];
MKAnnotationView * av = [mapView_ viewForAnnotation:an];
if([coordSet containsObject:value])
{
av.hidden = YES;
}
else
{
[coordSet addObject:value];
av.hidden = NO;
}
}
[coordSet release];
}
That's a brilliant idea. I'm working on a similar app, I hope you don't mind if I als implement the concept :).
To answer your question to the best of my own ability, no, I don't think there is a predefined way to do this.
The best way I can think of to do it (after looking at the iOS4 photos app), is to make use of the mapView:regionDidChangeAnimated: delegate method. Any time the user scrolls/zooms, this method will be called.
Inside that method, you could have some quick geometry math to determine whether your points are "close enough" to consider merging. Once they're "merged", you'd remove one or both annotations, and put another annotation back in the same place that is a reference to both (you could make an AnnotationCluster class very easily that could conform to MKAnnotation but also hold an NSArray of annotations, and also contain methods for "breaking out" or "absorbing" other annotations and/or AnnotationCluster instances, etc).
When I say "quick geometry math", I mean the distance of the two points relative to the span of the map, and taking their relative distance as a percentage of the span of the whole map.
Where that would get tricky is if you had hundreds of annotations, as I can't off-hand think of a good way to implement that w/o a double loop.
What do you reckon?
This project does something interesting. Though, have a look at reported issues before changing too many things in your code. Because it could be not good enough for your needs yet.
I personnaly ended up implementing this
How can I achieve a smooth resizing of a MKCircleView on a UIMapView when adjusting an NSSlider? Apple has managed to do it in the Find my friends app when creating geofences (http://reviewznow.com/wp-content/uploads/2013/03/find-my-friends-location-alerts-01.jpg), so I guess it's possible in some way. So far I've tried the following solutions, but with a very "flickery" result:
First attempt
I added a new MKCircleView with an updated radius and immediately after removing the one that was (as suggested here MKOverlay not resizing smoothly) when the slider changed value. I also tried the other way around: first removing the overlay then adding a new one, but with the same "flickery" result.
- (void)sliderChanged:(UISlider*)sender
{
double radius = (sender.value * 100);
[self addCircleWithRadius:radius];
[mapView removeOverlays:[self.mapView.overlays firstObject]];
}
Second attempt
In the linked SO answer, he suggests that NSOperation could be used to "help you create the MKCircle objects faster" and thus making the resizing smoother using the above method of adding/removing overlays when the slides changes value. I did a implementation where I start a new thread whenever the slider changes. In each thread I remove all old overlays and add a new one with the updated scale. Perhaps he has some other kind of implementation in mind, because the way I did it I still get the same flicker when changing the slider.
- (void)sliderChanged:(UISlider*)sender
{
NSInvocationOperation *operation = [[NSInvocationOperation alloc] initWithTarget:self
selector:#selector(updateOverlayWithScale:)
object:[NSNumber numberWithFloat:sender.scale]];
[self.queue addOperation:operation];
}
The method that runs i each thread:
- (void)updateOverlayWithScale:(NSNumber *)scale
{
MKCircle *circle = [MKCircle circleWithCenterCoordinate:self.currentMapPin.coordinate
radius:100*[scale floatValue]];
[self.mapView performSelectorOnMainThread:#selector(removeOverlays:) withObject:self.mapView.overlays waitUntilDone:NO];
[self.mapView performSelectorOnMainThread:#selector(addOverlay:) withObject:circle waitUntilDone:NO];
}
Third attempt
I also tried implementing my own subclass of MKOverlayView that draws itself based on its scale property. Whenever the slider changes I call setNeedsDisplay and let it redraw itself, but I get the same flicker.
- (void)sliderChanged:(UISlider*)sender
{
self.currentOverlayView.scale = sender.scale
[self.currentOverlayView setNeedsDisplay];
}
And in my custom overlay view I implement drawMapRect:zoomScale:inContext:(CGContextRef)context like this
- (void)drawMapRect:(MKMapRect)mapRect
zoomScale:(MKZoomScale)zoomScale
inContext:(CGContextRef)context
{
double radius = [(MKCircle *)[self overlay] radius];
radius *= self.scale;
// ... Create a rect using the updated radius and draw a circle inside it using CGContextAddEllipseInRect ...
}
So, do you have any ideas? Thanks in advance!
A few months ago I stumbled upon this animated MKCircleView. It also has a demo on git. So I guess you should give it a go!
Check it out as you might be able to tweak it to your needs with a slider etc.
Credits go to yickhong for providing this YHAnimatedCircleView
I have been working on the same app for a number of months, and this is a new problem. I am wondering if there has been a change in the server side of the Apple Map data. Here's the issue:
My app (at times) wants to set an MKMapView region to the most fully zoomed in value possible around a particular location. To do this, I do something like:
self.map.mapType = MKMapTypeHybrid;
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(CLLocationCoordinate2DMake(item.lat, item.lng), 1.0, 1.0);
[self.map setRegion:region animated:NO];
Regardless of where item's coordinate is, I get the gridded "no satellite images" background. This does not seem to be related to available satellite imagery, as it behaves consistently across many areas of the US.
I am aware that setRegion:animated: may adjust the region after the fact. And I am aware the a 1m square is an unreasonably small area to attempt to show on a fairly large map. So, I've tried
[self.map setRegion:[self.map regionsThatFits:region] animated:NO];
Setting animated:YES does seem to prevent this from occurring, but I do not want to animate these changes.
A few more observations:
If I zoom out just 1 or 2 pixels, the map imagery appears.
Attempting to implement the map delegate method: – mapViewDidFailLoadingMap:withError: does not help. It never is called.
This seems to be new. The working version I have of the app in the app store, now exhibits similar issues.
I have seen this happen in other popular apps recently.
Any thoughts on a solution to this, or confirmation that it is a systemic problem?
//fix for ios6
if (region.span.latitudeDelta < .0005f)
region.span.latitudeDelta = .0005f;
if (!region.span.longitudeDelta < .0005f)
region.span.longitudeDelta = .0005f;
Make sure your region span for lat/lon isn't set too low and it will clear up.
I ended up subclassing MKMapView and overriding setRegion:. I've created a sample app in Github if anyone is interested in seeing the issue in action, or my solution:
https://github.com/DeepFriedTwinkie/iOS6MapZoomIssue
My setRegion: method looks like this:
- (void) setRegion:(MKCoordinateRegion)region animated:(BOOL)animated {
#try {
// Get the zoom level for the proposed region
double zoomLevel = [self getFineZoomLevelForRegion:region];
// Check to see if any corrections are needed:
// - Zoom level is too big (a very small region)
// - We are looking at satellite imagery (Where the issue occurs)
// - We have turned on the zoom level protection
if (zoomLevel >= (MAX_GOOGLE_LEVELS-1) && self.mapType != MKMapTypeStandard && self.protectZoomLevel) {
NSLog(#"setRegion: Entered Protected Zoom Level");
// Force the zoom level to be 19 (20 causes the issue)
MKCoordinateRegion protectedRegion = [self coordinateRegionForZoomLevel:MAX_GOOGLE_LEVELS-1.0 atCoordinate:region.center];
[super setRegion:protectedRegion animated:animated];
} else {
[super setRegion:region animated:animated];
}
}
#catch (NSException *exception) {
[self setCenterCoordinate:region.center];
}
}
I put a map into my iOs app and I setted it in this way:
- (void)viewDidLoad {
[super viewDidLoad];
CLLocationCoordinate2D cord = {.latitude = 44.508473, .longitude = 11.375828};
[self.myMap setRegion:MKCoordinateRegionMake(cord, MKCoordinateSpanMake(.005, .005)) animated:YES];
AddressAnnotation * annotazione = [[AddressAnnotation alloc] init];
[annotazione setCoordinate:cord];
[self.myMap addAnnotation:annotazione];
}
and AddressAnnotation.m:
- (void)setCoordinate:(CLLocationCoordinate2D)coord {
coordinate = coord;
}
It works but first time I open map's view it shows a zone near the Antartica sea (pin head to coordinates but map shows Antartic sea zones) and closing and reopening map's view it shows exactly my pin.
How can I show map zoomed on Pin at first opening?
Thank you so much!
There's nothing wrong with this code. I would probably just define a coordinate property for your AddressAnnotation and let the compiler synthesize the appropriate setter and get rid of the custom setter, though that's probably unrelated to the problem you describe ... It's just easier to do it that way, it ensures that the annotation's coordinate will conform to KVO, etc.
I might suggest that you define your view controller to be the delegate of the map view and then implement regionDidChangeAnimated:
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
NSLog(#"%s %f, %f %d", __FUNCTION__, mapView.region.center.latitude, mapView.region.center.longitude, animated);
}
It might be useful to know what the region is getting set to and maybe you can reverse engineer what's going on.
But, bottom line, the problem is unrelated to the code you included in your question and the issue undoubtedly rests elsewhere.
By doing this in your project, I saw the following log:
2013-03-30 23:11:12.326 iBo[25408:c07] -[POIMapController mapView:regionDidChangeAnimated:] -47.422141, 0.000000 0
2013-03-30 23:11:12.328 iBo[25408:c07] -[POIMapController mapView:regionDidChangeAnimated:] 44.508473, 11.375828 0
2013-03-30 23:11:12.328 iBo[25408:c07] -[POIMapController mapView:regionDidChangeAnimated:] -47.422141, 0.000000 0
2013-03-30 23:11:12.329 iBo[25408:c07] -[POIMapController mapView:regionDidChangeAnimated:] -47.422141, 0.000000 0
So, clearly your setting the region is taking place, but there's something else which is resetting the region after that happens. Doing a little playing around, I discovered that this behavior only manifests itself when using auto layout. If you turn off auto layout, this behavior goes away and it works like you'd expect.
Alternatively, I noticed that if you moved your code to set the region out of viewDidLoad (which is quite early in the view creation process (after the view are created, but before they appear), and put it in viewDidAppear, it works.