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];
Related
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.
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];
I am adding annotations as hardcoded values into the method
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
CLLocationCoordinate2D coords1;
coords1.latitude = 40.579754;
coords1.longitude = -120.1303229;
MKPointAnnotation *annotationPoint1 = [[MKPointAnnotation alloc] init];
annotationPoint1.coordinate = coords1;
annotationPoint1.title = #"TJ11234";
annotationPoint1.subtitle = #"Power Failure \n Start Time:12hrs 30min \n End Time:14hrs ";
[self.mapView addAnnotation:annotationPoint1];
with values of lats and longitude hardcode intially its loading fine, but when I go to another page and come back to the page the annotations are not loading. What should I do to correct it?
mapView:didUpdateUserLocation: is called whenever a new location update is received by the map view. Check the documentation.
If you want to set annotation when your map is shown, you can place your code in viewDidAppear or similar.
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated]
CLLocationCoordinate2D coords1;
coords1.latitude = 40.579754;
coords1.longitude = -120.1303229;
MKPointAnnotation *annotationPoint1 = [[MKPointAnnotation alloc] init];
annotationPoint1.coordinate = coords1;
annotationPoint1.title = #"TJ11234";
annotationPoint1.subtitle = #"Power Failure \n Start Time:12hrs 30min \n End Time:14hrs ";
[self.mapView addAnnotation:annotationPoint1];
}
Hope this helps.
Im trying to load annotation point into mapview in which i have the seperate array for latitude and a seperate array for longitude here is my code
- (void)viewDidLoad
{
[super viewDidLoad];
delegate=[[UIApplication sharedApplication]delegate];
delegate.arrForLat=[[NSMutablearray alloc]initwithobjects:#"33.930216",#"33.939788",#"33.9272",#"33.902237"];
delegate.arrForLon=[[NSMutablearray alloc]initwithobjects:#"-118.050392",#"-118.076549",#"-118.065817",#"-118.081733"];
for (int i=0 ; i< delegate.arrForLat.count;i++)
{
annotationCoord.latitude = [[delegate.arrForLat objectAtIndex:i] doubleValue];
annotationCoord.longitude = [[delegate.arrForLng objectAtIndex:i] doubleValue];
MKPointAnnotation *annotationPoint = [[MKPointAnnotation alloc] init];
annotationPoint.coordinate = annotationCoord;
[MapView addAnnotation:annotationPoint];
}
}
Now i get only 1 annotation point in mapview but i have 4 coordinates.I don't know what mistake i have done.
Ah, I think I see it. You only have one annotationCoord. On the first trip through the loop you set its lat and long to (33.930216,-118.050392) and create a new object called annotationPoint point its coordinate attribute to your annotationCoord. Then on the next time through the loop you edit annotationCoord by giving it new coordinates. But it's still the same annotationCoord and the annotation you added to the map is still using it, now with the new coordinates.
So the solution is to make a new CLLocationCoordinate2D each time through the loop.
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