So I want to display where my app's user walked on a MKMapView, I collect datas with the following code :
#pragma mark - CLLocationManagerDelegate
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
// calc. distance walked
CLLocationDistance meters = [newLocation distanceFromLocation:oldLocation];
self.totalMetters += meters;
[[self labelDistance] setText:[self formatDistanceIntoString:self.totalMetters]];
// create another annotation
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = newLocation.coordinate;
// Also add to our map so we can remove old values later
[self.locations addObject:annotation];
// Remove values if the array is too big
while (self.locations.count > 100)
{
annotation = [self.locations objectAtIndex:0];
[self.locations removeObjectAtIndex:0];
// Also remove from the map
[self.map removeAnnotation:annotation];
}
Once it's finished, I call my draw method :
[self drawRoute];
Which contains the following :
- (void)drawRoute {
NSLog(#"drawRoute");
NSInteger pointsCount = self.locations.count;
CLLocationCoordinate2D pointsToUse[pointsCount];
for(int i = 0; i < pointsCount; i++) {
MKPointAnnotation *an = [self.locations objectAtIndex:i];
pointsToUse[i] = CLLocationCoordinate2DMake(an.coordinate.latitude,an.coordinate.latitude);
}
MKPolyline *myPolyline = [MKPolyline polylineWithCoordinates:pointsToUse count:pointsCount];
[self.map addOverlay:myPolyline];
}
Finally my mapView delegate :
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id <MKOverlay>)overlay {
NSLog(#"route");
if ([overlay isKindOfClass:MKPolyline.class]) {
MKPolylineView *lineView = [[MKPolylineView alloc] initWithOverlay:overlay];
lineView.strokeColor = [UIColor greenColor];
return lineView;
}
return nil;
}
Obviously my controller is MKMapView Delegate conform
#interface NewWalkViewController : UIViewController <CLLocationManagerDelegate, MKMapViewDelegate>
And the mapView in the Storyboard is linked to the controller (outlet and delegate)
I use the "bike road" debug tool and there is the output :
2014-01-25 20:27:30.132 The walking dog[2963:70b] new location : 37.330435
2014-01-25 20:27:30.133 The walking dog[2963:70b] drawRoute
As I can see the method for drawing the overlay is never called, and I don't have a single clue how to fix it.
The main problem is that in drawRoute, this line is passing latitude for both parameters to CLLocationCoordinate2DMake:
pointsToUse[i] = CLLocationCoordinate2DMake
(an.coordinate.latitude,an.coordinate.latitude);
This results in the line being drawn in a different part of the world than where the actual an.coordinate is. For example, if an.coordinate is 37,-122 (somewhere near San Francisco), the line is being drawn instead at 37,37 (somewhere in southern Turkey).
Since you are not actually positioning the map at the wrong location (you are looking for the line at the "right" location), viewForOverlay is never called because the map only calls it when it's possible that the overlay will be visible.
Change that line to:
pointsToUse[i] = CLLocationCoordinate2DMake
(an.coordinate.latitude,an.coordinate.longitude);
or simply:
pointsToUse[i] = an.coordinate;
As James Frost mentions in the comments, as of iOS 7, you should be using rendererForOverlay instead of viewForOverlay though the map view will still call viewForOverlay in iOS 7 if rendererForOverlay has not been implemented. Though this isn't preventing your overlay from displaying in the current case, you should implement the new delegate method as well as the old one (if the iOS 7 app will also be running on iOS 6 or earlier).
Another important but unrelated issue is that you are unnecessarily creating multiple, overlapping overlays. In drawRoute, since the overlay you are creating includes all the locations, you should remove any existing overlays before adding the new one. Otherwise, the map ends up with an overlay for location 0 to 1, an overlay for location 0 to location 2, an overlay for location 0 to location 3, etc. The previous overlays are not obviously visible since they have the same color and line width. Before the addOverlay line, put:
[self.map removeOverlays:self.map.overlays];
Related
I am trying to track user's route and drawing lines of the route, but the addOverlay only gives me correct points but no connection between each point.
-(void)viewWillAppear:(BOOL)animated{
self.trackPointArray = [[NSMutableArray alloc] init];
}
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(CLLocation *)userLocation
{
[self.trackPointArray addObject:userLocation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 1000, 1000);
[self.myMapView setRegion:[self.myMapView regionThatFits:region] animated:YES];
NSInteger stepsNumber = self.trackPointArray.count;
CLLocationCoordinate2D coordinates[stepsNumber];
for (NSInteger index = 0; index < stepsNumber; index++) {
CLLocation *location = [self.trackPointArray objectAtIndex:index];
coordinates[index] = [location coordinate];
}
MKPolyline *polyLine = [MKPolyline polylineWithCoordinates:coordinates count:stepsNumber];
[self.myMapView addOverlay:polyLine];
}
- (MKOverlayRenderer *)mapView:(MKMapView *)myMapView rendererForOverlay:(id<MKOverlay>)overlay
{
MKPolylineRenderer *polylineRenderer = [[MKPolylineRenderer alloc] initWithOverlay:overlay];
polylineRenderer.lineWidth = 4.0f;
polylineRenderer.strokeColor = [UIColor redColor];
return polylineRenderer;
}
The userLocation object the map view passes to the didUpdateUserLocation delegate method is the same object every time.
The coordinate inside the object may be different at each moment but each call to the delegate method always points to the same container object.
Specifically, it always points to the same object that the map view's userLocation property points to (mapView.userLocation). You can see this if you NSLog userLocation and mapView.userLocation and notice their memory addresses are the same each time.
For this reason, when the code does this:
[self.trackPointArray addObject:userLocation];
it just adds the same object reference to the array multiple times.
Later, when the code loops through the trackPointArray array, each call to [location coordinate] returns the same coordinate every time because location always points to the same object (mapView.userLocation) and the coordinate does not change during the loop.
So each time the delegate method is called, a polyline is created with N coordinates (all the same) which ends up drawing a "dot".
The reason you see multiple dots is because the code is not removing previous overlays.
To fix all this, one easy way is to create a new CLLocation instance each time you want to add the updated coordinates:
CLLocation *tpLocation = [[CLLocation alloc]
initWithLatitude:userLocation.coordinate.latitude
longitude:userLocation.coordinate.longitude];
[self.trackPointArray addObject:tpLocation];
Additionally, you should remove the previous overlay before adding the updated line. You won't notice the previous lines if you don't do this but they'll be there using up memory and performance:
[self.myMapView removeOverlays:self.myMapView.overlays];
[self.myMapView addOverlay:polyLine];
I'm implementing search in my MKMapView and I've faced two problems:
When I perform search, location appears in the result and map moves to the found location only after I start to move to the destination. This happens, when the search results are out of the view bounds. If they are inside of the map view bounds or near them it's fine.
It "hops" all the time from one search result to another or to the user's location. I don't expect such behaviour from it.
I've tried several things and I suppose, that the problem is in: didAddAnnotationViews:
[self.locationManager stopUpdatingLocation];
MKAnnotationView *annotationView = [views objectAtIndex:0];
NSLog(#"_Here_ %#", [views description]);
id<MKAnnotation> mp = [annotationView annotation];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance([mp coordinate], 250, 250);
[mv setRegion:region animated:YES];
[self.mapView selectAnnotation:mp animated:YES];
Though, I also thought that the problem is in didUpdateToLocation, so I disable updating after the first pin is drop (by search or by tap):
- (void)locationManager:(CLLocationManager *)manager
didUpdateToLocation:(CLLocation *)newLocation
fromLocation:(CLLocation *)oldLocation {
double miles = 0.3;
double scalingFactor =
ABS( cos(2 * M_PI * newLocation.coordinate.latitude /360.0) );
MKCoordinateSpan span;
span.latitudeDelta = miles/69.0;
span.longitudeDelta = miles/( scalingFactor*69.0 );
MKCoordinateRegion region;
region.span = span;
region.center = newLocation.coordinate;
[self.mapView setRegion:region animated:YES];
self.mapView.showsUserLocation = YES;
}
Finally, search method:
-(void)searchBarSearchButtonClicked:(UISearchBar *)theSearchBar
{
MKLocalSearchRequest *searchRequest = [[MKLocalSearchRequest alloc] init];
[searchRequest setNaturalLanguageQuery:theSearchBar.text];
searchRequest.region = MKCoordinateRegionMakeWithDistance(self.mapView.userLocation.coordinate, 1000, 1000);
MKLocalSearch *localSearch = [[MKLocalSearch alloc] initWithRequest:searchRequest];
[localSearch startWithCompletionHandler:^(MKLocalSearchResponse *response, NSError *error) {
if (!error) {
NSMutableArray *annotations = [NSMutableArray array];
[response.mapItems enumerateObjectsUsingBlock:^(MKMapItem *item, NSUInteger idx, BOOL *stop) {
for (id<MKAnnotation>annotation in self.mapView.annotations)
{
if (annotation.coordinate.latitude == item.placemark.coordinate.latitude &&
annotation.coordinate.longitude == item.placemark.coordinate.longitude)
{
return;
}
}
MKPointAnnotation *addAnnotation = [[MKPointAnnotation alloc] init];
addAnnotation.title = [item.placemark.addressDictionary objectForKey:#"Street"];
addAnnotation.coordinate = item.placemark.coordinate;
[annotations addObject:addAnnotation];
}];
for (id<MKAnnotation>annotation in self.mapView.annotations) {
[self.mapView removeAnnotation:annotation];
}
[self.mapView addAnnotations:annotations];
} else {
NSLog(#"Search Request Error: %#", [error localizedDescription]);
}
}];
//Hide the keyboard.
[self.searchBar resignFirstResponder];
}
My aim is to create a MapView, where user can pin the location by tap or via search and, obviously, see the search result.
For the first problem:
When I perform search, location appears in the result and map moves to
the found location only after I start to move to the destination. This
happens, when the search results are out of the view bounds. If they
are inside of the map view bounds or near them it's fine.
This happens because you are moving the map to the annotations found (at least the first one) in the didAddAnnotationViews delegate method.
But that delegate method is only called when an annotation is in the visible area. If an annotation is added to the map but it's not in the visible area (yet), viewForAnnotation won't get called and therefore didAddAnnotationViews won't get called.
Then, when you manually move the map, the annotations that were added start coming into the visible area and then the delegate method gets called and suddenly the map jumps to one of those annotations.
Don't call setRegion inside the didAddAnnotationViews delegate method.
Sometimes, doing so can also cause an endless cycle of viewForAnnotation and didAddAnnotationViews calls because when the region is changed, it causes other annotations to come into view that weren't previously, so viewForAnnotation gets called and then didAddAnnotationViews gets called, and so on.
Instead, set the region right after you call addAnnotations: (or better, just call showAnnotations:) in the searchBarSearchButtonClicked: method.
I would also remove the call to stopUpdatingLocation from didAddAnnotationViews. You probably don't even need the location manager at all if you set the map's showsUserLocation to YES.
For the second problem:
It "hops" all the time from one search result to another or to the
user's location. I don't expect such behaviour from it.
This is also partly due to calling setRegion in didAddAnnotationViews but also because setRegion is called in didUpdateToLocation.
So for the reason described for the first problem, the two delegate methods and the user's manual movements are fighting with each other and the map ends up hopping around.
Don't call setRegion in the didUpdateToLocation method (or, call it once by keeping track in a BOOL whether you've already zoomed to the user location or not).
Not affecting the behavior, but setting showsUserLocation to YES in the didUpdateToLocation doesn't make sense. Why not set this in viewDidLoad or turn it on in the storyboard/xib?
Also, there's no need to calculate the region span manually like that (it's better to let the MapKit do that work for you). Just convert the miles to meters and call MKCoordinateRegionMakeWithDistance.
Everytime I start the app, the first time seeing the map results in a default map that is always zoomed out with no annotations. When I go back on the navigation controller and go back into the map, it now shows the correct region with the appropriate pins. The code I use to add the
- (void) zoomIn {
mapView.showsUserLocation = YES;
CLLocationCoordinate2D annotation;
annotation.latitude = 47.640071;
annotation.longitude = -122.129598;
MKPointAnnotation *annoPoint = [[MKPointAnnotation alloc] init];
annoPoint.coordinate = annotation;
annoPoint.title = #"name";
[mapView addAnnotation:annoPoint];
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(annotation, 500, 500);
[mapView setRegion:region animated:YES];
}
I call this block of code from the viewDidLoad, but it only works after I go back to the main page from the navigation controller and enter this UIViewController again.
Does anyone know what the problem is or have seen it before?
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
// this delegate fonction is called when the userlocation is updated
// try to move your code here
}
you have also
- (void)mapView:(MKMapView *)mapView regionDidChangeAnimated:(BOOL)animated
{
}
hope this helps
Show us your viewDidLoad function, you're probably calling zoomIn too early, maybe before your MKMapView has been initialized.
I've read many posts about it and still i have a problem.
This is my code to draw a polyLine between two points:
-(void) drawAline:(CLLocation*)newLocation
{
//drawing a line
CLLocationCoordinate2D coordinateArray[2];
coordinateArray[0] = CLLocationCoordinate2DMake(newLocation.coordinate.latitude, newLocation.coordinate.longitude);
coordinateArray[1] = CLLocationCoordinate2DMake(self.jerusalem.coordinate.latitude, self.jerusalem.coordinate.longitude);
self.routeLine = [MKPolyline polylineWithCoordinates:coordinateArray count:2];
[self.mapView setVisibleMapRect:[self.routeLine boundingMapRect]];
[self.mapView addOverlay:self.routeLine];
}
-(MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay
{
if(overlay == self.routeLine)
{
if(nil == self.routeLineView)
{
self.routeLineView = [[MKPolylineView alloc] initWithPolyline:self.routeLine];
self.routeLineView.fillColor = [UIColor blueColor];
self.routeLineView.strokeColor = [UIColor blueColor];
self.routeLineView.lineWidth = 5;
}
return self.routeLineView;
}
return nil;
}
thats works fine.
The problem is to remove the line.
The next code doesn't work:
for (id<MKOverlay> overlayToRemove in self.mapView.overlays)
{
if ([overlayToRemove isKindOfClass:[MKPolylineView class]])
{
[mapView removeOverlay:overlayToRemove];
}
}
the next code doesn't work neither:
if (self.routeLine)
{
[self.mapView removeOverlay:self.routeLine];
self.routeLineView = nil;
self.routeLine = nil;
}
Thanks!
In the code that loops through the map view's overlays array, this line is the problem:
if ([overlayToRemove isKindOfClass:[MKPolylineView class]])
The map view's overlays array contains objects of type id<MKOverlay> (the for-loop correctly declares overlayToRemove as such).
So the overlays array contains the model objects for the overlays and not the views.
The MKPolylineView class is the view for an MKPolyline overlay model.
So the if condition should be:
if ([overlayToRemove isKindOfClass:[MKPolyline class]])
Note that such a loop will remove all polylines from the map. If you wanted to delete specific polylines, you could set the title on each one when adding it and then check it before removing.
The second piece of code that checks and deletes self.routeLine directly should work as long as self.routeLine is not nil and contains a valid reference to an overlay currently on the map.
If you have only a single overlay on the map (the one polyline), you could also just call removeOverlays to delete all overlays from the map (whatever they are):
[self.mapView removeOverlays:self.mapView.overlays];
Your overlay is a MKPolyline the MKPolylineView is just how the overlay is displayed when the map is zoomed or scrolled so that the overlay's data shows in the window. The map view's overlays array contains the data that will be used to generate the overlay views. There are no views in the overlays array. So, to make your code work, change this line
if ([overlayToRemove isKindOfClass:[MKPolylineView class]])
to
if ([overlayToRemove isKindOfClass:[MKPolyline class]])
in your third snippet and you will be fine
I've looked at several StackOverflow posts and Apple documentation on how to implement overlays in MKMapView. For me, I'm interested specifically in displaying MKPolygon objects on my map. I've found that fundamentally, the process boils down to the following:
Link to MapKit and CoreLocation frameworks
Make an outlet to an MKMapKit object and declare view controller as delegate
Declare a CLLocationCoordinate2D array containing the points of a polygon and create an MKPolygon object with the class method polygonWithCoordinates:count:
Call addOverlay: of map and pass the newly created MKPolygon object as the parameter
Implement (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id)overlay
Later on, I'll likely be having to display 20-30 polygons at a given time on the map. However, in my exploration of how to display overlays (hardcoding test examples right now, rather than reading in data from a file), I've found that I can get some overlays to appear, but not others. Reading the Location Awareness Programming Guide by Apple, I came across an example of a polygon overlaid above the state of Colorado. That worked. But when I tried to make a polygon that covered Kansas, I couldn't get it to work. It seems that any polygon that I tried to make on my own (Embry-Riddle Aeronautical University polygon and Kansas polygon) won't display, but those that I got online work perfectly. I used Google Earth to create the polygons and then exported them as KML files to get the coordinates.
Code for the implementation of my ViewController is below. Just trying to find out what I may be unintentionally doing wrong to create this problem. Thanks in advance for help.
#import "ViewController.h"
#import <CoreLocation/CoreLocation.h>
#interface ViewController ()
#end
#implementation ViewController
#synthesize mapView;
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
// Array of coordinates for polygon covering state of Colorado ... DISPLAYS PERFECTLY
CLLocationCoordinate2D points[4];
points[0] = CLLocationCoordinate2DMake(41.000512, -109.050116);
points[1] = CLLocationCoordinate2DMake(36.99892, -109.045267);
points[2] = CLLocationCoordinate2DMake(36.993076, -102.041981);
points[3] = CLLocationCoordinate2DMake(41.002371, -102.052066);
MKPolygon *polygon = [MKPolygon polygonWithCoordinates:points count:4];
[mapView addOverlay:polygon];
[polygon release];
// Array of coordinates for polygon covering state of Kansas ... DOESN'T DISPLAY
CLLocationCoordinate2D kansasPoints[9];
kansasPoints[0] = CLLocationCoordinate2DMake(-102.0595440241806, 39.99774930940907);
kansasPoints[1] = CLLocationCoordinate2DMake(-102.0424467175215, 36.99846609483674);
kansasPoints[2] = CLLocationCoordinate2DMake(-94.62550551403953, 36.98936020770036);
kansasPoints[3] = CLLocationCoordinate2DMake(-94.58798745384412, 39.11683771419185);
kansasPoints[4] = CLLocationCoordinate2DMake(-94.79955391183, 39.21290793052091);
kansasPoints[5] = CLLocationCoordinate2DMake(-95.13489191971419, 39.51613476830012);
kansasPoints[6] = CLLocationCoordinate2DMake(-94.86553124171813, 39.78380472206268);
kansasPoints[7] = CLLocationCoordinate2DMake(-95.02618283417986, 39.89072859904893);
kansasPoints[8] = CLLocationCoordinate2DMake(-95.31904155494097, 39.99390420513669);
MKPolygon *kansasPolygon = [MKPolygon polygonWithCoordinates:kansasPoints count:9];
[mapView addOverlay:kansasPolygon];
[kansasPolygon release];
// Array of coordinates for polygon covering part of Daytona Beach, FL campus
// of Embry-Riddle Aeronautical University... DOESN'T DISPLAY
CLLocationCoordinate2D erauPoints[7];
erauPoints[0] = CLLocationCoordinate2DMake(-81.05176, 29.18492);
erauPoints[1] = CLLocationCoordinate2DMake(-81.04409, 29.18801);
erauPoints[2] = CLLocationCoordinate2DMake(-81.05166, 29.19293);
erauPoints[3] = CLLocationCoordinate2DMake(-81.05365, 29.19536);
erauPoints[4] = CLLocationCoordinate2DMake(-81.05465, 29.19493);
erauPoints[5] = CLLocationCoordinate2DMake(-81.05376, 29.19323);
erauPoints[6] = CLLocationCoordinate2DMake(-81.05506, 29.19188);
MKPolygon *erauPolygon = [MKPolygon polygonWithCoordinates:erauPoints count:7];
[mapView addOverlay:erauPolygon];
[erauPolygon release];
// Array of coordinates taken from http://www.shawngrimes.me/2011/04/adding-polygon-map-overlays/
// for commuter parking lot at Capitol College in Maryland ... DISPLAYS PERFECTLY
CLLocationCoordinate2D commuterLotCoords[5]={
CLLocationCoordinate2DMake(39.048019,-76.850535),
CLLocationCoordinate2DMake(39.048027,-76.850234),
CLLocationCoordinate2DMake(39.047407,-76.850181),
CLLocationCoordinate2DMake(39.047407,-76.8505),
CLLocationCoordinate2DMake(39.048019,-76.850535)
};
MKPolygon *commuterPoly1 = [MKPolygon polygonWithCoordinates:commuterLotCoords count:5];
[mapView addOverlay:commuterPoly1];
[commuterPoly1 release];
}
- (void)viewDidUnload
{
[super viewDidUnload];
// Release any retained subviews of the main view.
}
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation
{
return (interfaceOrientation != UIInterfaceOrientationPortraitUpsideDown);
}
- (MKOverlayView *)mapView:(MKMapView *)mapView viewForOverlay:(id<MKOverlay>)overlay {
if ([overlay isKindOfClass:[MKPolygon class]]) {
MKPolygonView *polygonView = [[[MKPolygonView alloc] initWithOverlay:overlay] autorelease];
polygonView.fillColor = [[UIColor cyanColor] colorWithAlphaComponent:0.3f];
polygonView.strokeColor = [UIColor redColor];
polygonView.lineWidth = 1.0f;
return polygonView;
}
return nil;
}
#end
It looks like the latitude and longitude parameters of the coordinates for the polygons that don't display are backwards.
For example, this:
kansasPoints[0] = CLLocationCoordinate2DMake(-102.0595440241806, 39.99774930940907);
should be
kansasPoints[0] = CLLocationCoordinate2DMake(39.99774930940907, -102.0595440241806);
Also, you should not be calling release on the MKPolygon objects you are creating using polygonWithCoordinates since they will be autoreleased.