How can I know when MKMapview setRegion:animated: has finished? - iphone

I want to set a region on my MKMapView and then find the coordinates corresponding to the NE and SW corner of the map.
This code works just fine to do that:
//Recenter and zoom map in on search location
MKCoordinateRegion region = {{0.0f, 0.0f}, {0.0f, 0.0f}};
region.center = mySearchLocation.searchLocation.coordinate;
region.span.longitudeDelta = 0.01f;
region.span.latitudeDelta = 0.01f;
[self.mapView setRegion:region animated:NO]; //When this is set to YES it seems to break the coordinate calculation because the map is in motion
//After the new search location has been added to the map, and the map zoomed, we need to update the search bounds
//First we need to calculate the corners of the map so we get the points
CGPoint nePoint = CGPointMake(self.mapView.bounds.origin.x + mapView.bounds.size.width, mapView.bounds.origin.y);
CGPoint swPoint = CGPointMake((self.mapView.bounds.origin.x), (mapView.bounds.origin.y + mapView.bounds.size.height));
//Then transform those point into lat,lng values
CLLocationCoordinate2D neCoord;
neCoord = [mapView convertPoint:nePoint toCoordinateFromView:mapView];
CLLocation *neLocation = [[CLLocation alloc] initWithLatitude:neCoord.latitude longitude:neCoord.longitude];
CLLocationCoordinate2D swCoord;
swCoord = [mapView convertPoint:swPoint toCoordinateFromView:mapView];
CLLocation *swLocation = [[CLLocation alloc] initWithLatitude:swCoord.latitude longitude:swCoord.longitude];
The problem is I would like the map zoom to be animated. However, when I set the setRegion:animated to YES, I end up getting the coordinates from the map when it's zoomed way out (i.e., before the animation is completed). Is there any way to get a signal that the animation is done?

Never used mapkit but the MKMapViewDelegate has a method mapView:regionDidChangeAnimated: that looks to be what you're looking for.
Be aware that mapView:regionDidChangeAnimated: will get called every time there's a change, such as when the user moves the map.

I know this is super old, but just in case anyone else comes by looking for an answer, here's an alternative.
The nice thing about this version is that you can run a completion animation at the exact moment the first one is complete instead of guessing/hardcoding it in the callback method since that one is called right away.
[MKMapView animateWithDuration:1.0 animations:^{
[mapView setRegion:mapRegion animated:YES];
} completion:^(BOOL finished) {
[UIView animateWithDuration:1.0 animations:^{
self.mapDotsImageView.alpha = 1.0;
}];
}];
or just
// zoom in...
let km3:CLLocationDistance = 3000
let crTight = MKCoordinateRegionMakeWithDistance(location.coordinate, km3, km3)
MKMapView.animate(withDuration: 1.0, animations: { self.theMap.region = crTight })

Related

Free hand Draw polyline overlay in ios

I'm trying to trace the route by free hand on a MKMapView using overlays (MKOverlay).
Each time when we move the finger i extend the polyline with last coordinate with new coordinate,all are working fine except when extending polyline overlay the whole overlay is blinking in device(only sometimes),so i can,t trace the problem.
The code i have tried is given below.
- (void)viewDidLoad
{
j=0;
coords1 = malloc(2* sizeof(CLLocationCoordinate2D));
coordinatearray=[[NSMutableArray alloc]init];
UIPanGestureRecognizer *GestureRecogonized = [[UIPanGestureRecognizer alloc] initWithTarget:self action:#selector(gestureDetacted:)];
[self.myMapView addGestureRecognizer:GestureRecogonized];
}
- (void)gestureDetacted:(UIPanGestureRecognizer *)recognizer
{
if(UIGestureRecognizerStateBegan==recognizer.state)
{
CGPoint point = [recognizer locationInView:self.myMapView];
CLLocationCoordinate2D tapPoint = [self.myMapView convertPoint:point toCoordinateFromView:self.view];
CLLocation *curLocation = [[CLLocation alloc] initWithLatitude:tapPoint.latitude longitude:tapPoint.longitude];
[coordinatearray addObject:curLocation];
}
coords1[0]=[[coordinatearray objectAtIndex:j] coordinate];
if(UIGestureRecognizerStateChanged==recognizer.state)
{
j++;
CGPoint point = [recognizer locationInView:self.myMapView];
CLLocationCoordinate2D tapPoint = [self.myMapView convertPoint:point toCoordinateFromView:self.view];
CLLocation *curLocation = [[CLLocation alloc] initWithLatitude:tapPoint.latitude longitude:tapPoint.longitude];
[coordinatearray addObject:curLocation];
coords1[1]=CLLocationCoordinate2DMake(tapPoint.latitude,tapPoint.longitude);
polyLine = [MKPolyline polylineWithCoordinates:coords1 count:2];
[self.myMapView addOverlay:polyLine];
}
}
in overlay delegate
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
if([overlay isKindOfClass:[MKPolyline class]]){
MKPolylineView *polylineView = [[MKPolylineView alloc] initWithPolyline:overlay];
polylineView.strokeColor = [UIColor orangeColor];
polylineView.lineWidth = 20;
polylineView.fillColor=[[UIColor orangeColor] colorWithAlphaComponent:.1];
return polylineView;
}
}
can anybody know why that flickering or blinking effect is coming and how to remove it.
Thanks in advance.
Rather than adding hundreds of really small views (which is really computationally intensive), i would rather remove the polyline overlay and add a new one with all the points on the map in it at each change in the pan recognizer (for a smoother effect, you can first add the new one and then remove the old one). Use your coordinateArray to create the MKPolyline overlay that contains all of the points, rather than the last 2 points.
You could do something like:
[coordinatearray addObject:curLocation];;
CLLocationCoordinate2D* coordArray = malloc(sizeof(CLLocationCoordinate2D)*[coordinatearray count]);
memcpy(coordArray, self.coordinates, sizeof(CLLocationCoordinate2D)*([coordinatearray count]-1));
coordArray[[coordinatearray count]-1] = curLocation;
free(self.coordinates);
self.coordinates = coordArray;
MKPolyline *polyline = [MKPolyline polylineWithCoordinates:coordArray count:[coordinatearray count]];
MKPolyline *old = [[self.mapView overlays] lastObject];
[self.mapView addOverlay:polyline];
[self.mapView removeOverlay:old];
Where self.coordinate is a property of type CLLocationCoordinate2D*, this way you can memcpy the existing array into a new one (memcpy is really efficent) and only need to add the last point to the array, rather than having to loop through each point of the NSArray *coordinatearray.
You also have to change your if(UIGestureRecognizerStateBegan==recognizer.state) part, to add in self.coordinates the first tapped point.
Just something like:
self.coordinates = malloc(sizeof(CLLocationCoordinate2D));
self.coordinates[0] = curLocation;
EDIT: I think the problem is that map overlays draw themselves for different discrete values of zoom levels. At a certain zoom level it would appear that the overlay first draws itself at the bigger zoom level, and then at the smaller zoom level (in fact drawing over the previously drawn overlay). When you try to show an animation such as that from drawing the user panning gesture at this zoom level, that is why the overlay keeps flickering. A possible solution would be to use a transparent view that you put on top of the map view, where the drawing is performed while the user keeps moving the finger. Only once the panning gesture ends you then create a map overlay that you "pin" to the map and you clean the drawing view. You also need to be careful when redrawing the view, as you should each time specify only the rect to be redrawn and then redraw only in that rect, as redrawing the whole thing each time would cause a flicker in this view as well. This is definitely much more coding involved, but it should work. Check this question for how to incrementally draw on a view.

MKMapViewprevent user from zooming out past a certain range

I have a lot of pin annotations on the MKMapView in my app, the iPhone gets very slow and unresponsive when a lot of them are in view on the map. I would like the user to be able to zoom, but not out past a certain level, such as 2km squared or something.
Here's what I've got:
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:YES];
[[BicycleLDNService sharedService] requestLocationForClient:self];
CLLocationCoordinate2D zoomLocation;
CLLocation *deviceLocation = [[BicycleLDNService sharedService] deviceLocation];
zoomLocation.latitude = deviceLocation.coordinate.latitude;
zoomLocation.longitude = deviceLocation.coordinate.longitude;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation, 0.5*kMetresPerKilometre, 0.5*kMetresPerKilometre);
MKCoordinateRegion adjustedRegion = [self.mapView regionThatFits:viewRegion];
[self.mapView setRegion:adjustedRegion animated:YES];
self.mapView.zoomEnabled = YES;
}
Is there some sort of property or delegate method I can employ? Couldn't find anything useful on google or here!
Thanks in advance!
The span defines how much of the map at the given point should be visible and is also how you set the zoom level.
You can access this by using
region.span.latitute=0.5;
region.span.longitude=0.6;
Check the zoom level of the map and then set the zoomEnabled Property NO.
mapView.zoomEnabled=NO;

Showing Specific Region Using MapKit

I want to know is it possible to show only specific region on map not the full world map using Map Kit.
Like if i want to show Asia map in my application then map kit hides remaining part of the map.
To handle the "map kit hides remaining part of the map" requirement, one thing you can do is create a black polygon overlay that covers the whole world with a cutout over Asia (or wherever you like).
For example, where you initialize the map (eg. in viewDidLoad):
CLLocationCoordinate2D asiaCoords[4]
= { {55,60}, {55,150}, {0,150}, {0,60} };
//change or add coordinates (and update count below) as needed
self.asiaOverlay = [MKPolygon polygonWithCoordinates:asiaCoords count:4];
CLLocationCoordinate2D worldCoords[4]
= { {90,-180}, {90,180}, {-90,180}, {-90,-180} };
MKPolygon *worldOverlay
= [MKPolygon polygonWithCoordinates:worldCoords
count:4
interiorPolygons:[NSArray arrayWithObject:asiaOverlay]];
//the array can have more than one "cutout" if needed
[myMapView addOverlay:worldOverlay];
and implement the viewForOverlay delegate method:
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if ([overlay isKindOfClass:[MKPolygon class]])
{
MKPolygonView *pv = [[[MKPolygonView alloc] initWithPolygon:overlay] autorelease];
pv.fillColor = [UIColor blackColor];
pv.alpha = 1.0;
return pv;
}
return nil;
}
This looks like this:
If you also want to restrict the user from scrolling beyond Asia or zooming too far out, then you'll need to do that manually as well. One possible way is described in Restrict MKMapView scrolling. Replace theOverlay in that answer with asiaOverlay.
You can specify the region as an MKCoordinateRegion and then tell an MKMapView instance to only show that region using the setRegion and regionThatFits message.
Alternatively you could use the visibleMapRect property instead of the region. This might better fit your needs.
In short read the MKMapView Class Reference document from Apple.
Lifting from some code I've done in the past that assumes a mapView and a given location called locationToShow I used an MKCoordinateRegion.
- (void) goToLocation {
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta=0.01;
span.longitudeDelta=0.01;
region.span=span;
region.center=locationToShow;
[mapView setRegion:region animated:TRUE];
[mapView regionThatFits:region];
}

GPS location on map in application

I have one application that shows maps. I need to show current position of user and manage to do that with
[mapView setShowsLocation:YES];
But when I zoom In or zoom Out map it needs a lot of time to show me that blue pin again.
Is it normal or I need to put something else to keep that blue pin on screen all time???
Thanks.
[self setCurrentLocation:self._mapView.userLocation.location.coordinate withZoom:1.0];
self._mapView.showsUserLocation = YES;
- (void)setCurrentLocation:(CLLocationCoordinate2D)coord withZoom:(float)zoomLevel {
MKCoordinateRegion region = self._mapView.region;
region.span.latitudeDelta = self.defaultSpanLevel.latitudeDelta*zoomLevel;
region.span.longitudeDelta = self.defaultSpanLevel.longitudeDelta*zoomLevel;
region.center = coord;
[self._mapView setRegion:region animated:YES];
}
You can use this in view will appear Method
Then You can use mapview delegate methods....
region did change animated:YES { and set map's region here.... take
current user location as region centre.. }
This will solve your problem
}

'Current location' BUTTON replica of the maps in iPhone

- (void)mapView:(MKMapView *)mv didAddAnnotationViews:(NSArray *)views {
for(MKAnnotationView *annotationView in views) {
if(annotationView.annotation == mv.userLocation) {
MKCoordinateRegion region;
MKCoordinateSpan span;
span.latitudeDelta=0.002;
span.longitudeDelta=0.002;
CLLocationCoordinate2D location=mv.userLocation.coordinate;
region.span=span;
region.center=location;
[mv setRegion:region animated:TRUE];
[mv regionThatFits:region];
}
}
Hello all. I have tried searching other posts and websites. In essence I want to duplicate the 'Current location' BUTTON.
Using the above code, I was able to zoom into the users current location. This however is not flawless, sometimes the GPS updates a little late or thinks I am somewhere else, zooms to that location but then the blue dot will shift somewhere off screen to where I really am.
I was wondering is there a way I can add a 'Button', exactly like maps on the iphone. It doesn't need to gain a new zoom etc, simply move to the new updated locaiton. My main source of code would be almost replica to here.
I appreciate any help.
Create a custom annotation with the same graphic. This object must implement MKAnnotationView, and you have to return the custom view for it in the method mapView:viewForAnnotation: of the MKMapViewDelegate. If you want the exact graphic, use the project UIKit-Artwork-Extractor to get it.
Save a reference to the annotation view, subscribe to core location updates, and update the position calling something like:
- (void) animateView:(UIView *)view toPosition:(NSValue*)value
{
CGPoint newCenter = [value CGPointValue];
[UIView animateWithDuration:0.5
animations:^{
view.center = newCenter;
}
completion:^(BOOL finished){ }];
}
Then set the center of the map on the annotation:
- (void)centerMap:(CLLocation *)location {
MKCoordinateRegion region = { { 0.0f, 0.0f }, { 0.0f, 0.0f } };
region.center = location.coordinate;
region.span.longitudeDelta = 0.15f;
region.span.latitudeDelta = 0.15f;
[self.mapView setRegion:region animated:YES];
}