Trying to learn how to have a pin drop at a users location when they push a button on screen. Do I need to use Mkannotation, also I want this pin to disappear when they drop a new pin in the future. This is the code I have in controller.h. Also the longitude and latitude are just for example.
thanks
#implementation ViewController
- (void)viewWillAppear:(BOOL)animated {
CLLocationCoordinate2D zoomLocation;
zoomLocation.latitude = 39.281516;
zoomLocation.longitude = -76.580806;
MKCoordinateRegion viewRegion = MKCoordinateRegionMakeWithDistance(zoomLocation,
0.1*METERS_PER_MILE, 0.1*METERS_PER_MILE);
[_mapView setRegion:viewRegion animated:YES];
}
While I think this is discussed in some detail in the Adding Annotations to a Map section of the Location Awareness Programming Guide, I have a few observations:
You ask "do I need to use MKAnnotation?" Yes and no.
Yes, all annotations should conform to the MKAnnotation protocol. As that guide describes, if you can create your own annotation subclass, you'd want it explicitly declare it to conform to the MKAnnotation protocol.
But, no, you don't have to always create your own annotation class that conforms to the MKAnnotation protocol. You can also use a predefined annotation class, MKPointAnnotation (which, itself, already conforms to the MKAnnotation protocol), such as:
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = CLLocationCoordinate2DMake(39.281516, -76.580806);
annotation.title = #"Lens Crafters";
annotation.subtitle = #"2400 Boston St.";
[self.mapView addAnnotation:annotation];
If you want the old annotation to disappear when you drop a new one on your map, you just (a) keep a reference to the old annotation; (b) when adding a new annotation, remove the old one (if you have an old one); and then (c) add your new annotation.
Thus you might have defined some class property for your annotation:
#property (weak, nonatomic) id<MKAnnotation> annotation;
(Two side observations: First, whether you use weak or strong is up to you and your app design. By saying weak, I'm saying that when the annotation is removed from the map, I'm happy to have the annotation released. Maybe you want it retained until you explicitly nil this property, in which case you'd make this property strong. That's entirely up to you and the goals of your app. Second, I use the type id<MKAnnotation> (i.e. "an object that conforms to MKAnnotation") which makes this more flexible. If you later replace MKPointAnnotation with your own custom annotation class, this property will still work. But if you want to explicitly define this annotation property to be a MKPointAnnotation to match your annotation adding routine, that's fine, too.)
Anyway, now that you have this property, you can now write a method to add an annotation to your map (which removes the old one):
- (void)addAnnotationAtCoordinate:(CLLocationCoordinate2D)coordinate
title:(NSString *)title
subtitle:(NSString *)subtitle
{
if (self.annotation)
[self.mapView removeAnnotation:self.annotation];
MKPointAnnotation *annotation = [[MKPointAnnotation alloc] init];
annotation.coordinate = coordinate;
annotation.title = title;
annotation.subtitle = subtitle;
[self.mapView addAnnotation:annotation];
self.annotation = annotation;
}
Related
I'm adding two different MKGeodesicPolyline instances to an MKMapView like this
CLLocation *LAX = [[CLLocation alloc] ...];
CLLocation *JFK = [[CLLocation alloc] ...];
CLLocation *LHR = [[CLLocation alloc] ...];
CLLocationCoordinate2D laxToJfkCoords[2] = {LAX.coordinate, JFK.coordinate};
CLLocationCoordinate2D jfkToLhrCoords[2] = {JFK.coordinate, LHR.coordinate};
MKGeodesicPolyline *laxToJfk = [MKGeodesicPolyline polylineWithCoordinates:laxToJfkCoords count:2];
MKGeodesicPolyline *jfkToLhr = [MKGeodesicPolyline polylineWithCoordinates:jfkToLhrCoords count:2];
[mapView addOverlay:laxToJfk];
[mapView addOverlay:jfkToLhr];
I want to render both of these overlays with different styles which need to be configured in the rendererForOverlay delegate method.
- (MKOverlayRenderer *)mapView:(MKMapView *)mapView rendererForOverlay:(id <MKOverlay>)overlay {
if (![overlay isKindOfClass:[MKPolyline class]]) {
return nil;
}
MKPolylineRenderer *renderer = [[MKPolylineRenderer alloc] initWithPolyline:(MKPolyline *)overlay];
renderer.lineWidth = 3.0f;
// How to set different colors for LAX-JFK and JFK-LHR?
renderer.strokeColor = [UIColor blueColor];
return renderer;
}
My question is what options are there to identify the two different overlays in the above method?
Here's what I considered so far:
Subclassing: Not an option because MKGeodesicPolyline is initialized through a static factory method.
Keep references to the overlays in properties and then compare the delegate's overlay parameter against those. This does work but it feels a little clumsy. Also, for more than two overlays this approach would need to be extended by using an NSSet or an NSArray.
Is there anything else I could do to simplify this? It seems that MKGeodesicPolyline does not possess any properties that could be used for tagging.
One alternative to subclassing is to use associated objects. But its use is often discouraged.
A longer, but more stable solution, is to make a custom MKOverlay and a MKOverlayRenderer that forward most of their implementations to a private instance of MKGeodesicPolyline and MKPolylineRenderer respectively. Then you can add a custom property to set the color.
I have to remove all the annotations added to my MKMapView but when I execute :
NSMutableArray *annotationsToRemove = [[NSMutableArray alloc] initWithArray: mapView.annotations];
[mapView removeAnnotations: annotationsToRemove];
The array annotationsToRemove contains a MKUserLocation annotation and it doesn't delete it.
Is there a way to reset the map? I need to delete all the annotations from it!
You can just set the showsUserLocation property of your mapView to NO.
mapView.showsUserLocation = NO;
Actually you can not edit MKUserLocation annotation. I mean you can not remove it from map annotation's array as it is a read-only property of MKMapView.
If you visit MKMapView.h class. You will find below line
#property (nonatomic, readonly) MKUserLocation *userLocation;
Here we can see that this property is a read only. So we can not delete it from MKMapView annotations array. How ever you face difficulties in calculation with other annotations then you can runtime hide it.
What I am trying to explain is when user location is not required any more you can set NO for user location property.
For example with your code:
NSMutableArray *annotationsToRemove = [[NSMutableArray alloc] initWithArray: mapView.annotations];
[mapView removeAnnotations: annotationsToRemove];
[self.mapView setShowsUserLocation:NO];
NSLog(#"MapView annotations :%#", mapView.annotations);
Check NSLog output, you will see that MKUserLocation annotation is removed from mapView.annotations array.
It is the simple way I did follow. How ever I am not sure about there is other way to do this. Please leave a comment if you found any other solution.
In the h insert
#property (weak) MKAnnotationView *ulv;
In the m insert
- (void)mapView:(MKMapView *)mapView regionWillChangeAnimated:(BOOL)animated {
MKZoomScale currentZoomScale = mapView.bounds.size.width / mapView.visibleMapRect.size.width;
NSLog(#"current zoom scale is %f",currentZoomScale);
ulv = [mapView viewForAnnotation:mapView.userLocation];
if( currentZoomScale > 0.049 ){
ulv.hidden = YES;
}else{
ulv.hidden = NO;
}
}
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 trying to learn MAP for iPhone.
What I have right now is below.
Created new project
Added framework for MAP
Brought map object on storyboard (UIViewController)
Run the project.
What I see is, its not showing any location. When I change location in xcode, it shows me the dot at location.
What I wanted is, by default it should show me the PIN to the location that I will set by using latitude and longitude. Also the map should be zoomed. What I meant by zoom is, I should see the location with lets say 13 zoom effect. Right now, I see world map on screen.
Any idea how to get this done?
You can center your map around a location by doing something like this:
MKCoordinateRegion mapRegion;
mapRegion.center.latitude = aLatitude;
mapRegion.center.longitude = aLongitude;
mapRegion.span.latitudeDelta = 0.005;
mapRegion.span.longitudeDelta = 0.005;
self.mapView.region = mapRegion;
Use the span values to determine the zoom level you want.
In order to show a pin you need to create an annotation with the coordinates of your location and then add it to the map.
Also, check out this tutorial.. http://www.raywenderlich.com/2847/introduction-to-mapkit-on-ios-tutorial
Dot is showing your current location.
If you want to add a pin with coordinate you should call addAnnotation method with object which conforms to MKAnnotation protocol. Such object has a property coordinate (you should add it to your class):
#property (nonatomic, assign) CLLocationCoordinate2D coordinate;
Also you should add MKMapViewDelegate protocol to your controller and implement -mapView:viewForAnnotation: method. It works as -tableView:viewForRowAtIndexPath:.
- (MKAnnotationView *)mapView:(MKMapView *)_mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]]) {
return nil;
}
static NSString *annotationIdentifier = #"annotation";
MKPinAnnotationView *annotationView = (MKPinAnnotationView *)[_mapView dequeueReusableAnnotationViewWithIdentifier:annotationIdentifier]; // Reusing
if (!annotationView) {
MKPinAnnotationView *pinView = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:annotationIdentifier];
pinView.animatesDrop = YES;
annotationView = pinView;
}
else {
annotationView.annotation = annotation; // Reusing already created pin as UITableViewCell does
}
return annotationView;
}
Then when you call
MKMapView *mapView = ...;
id<MKAnnotation> obj = ...;
[mapView addAnnotation:obj];
The pin would be placed on map.
For zoom look there. There is a handy category for those purposes.
If you want to remove current location dot you should find an object with class MKUserLocation in mapView.annotations and then call [mapView removeAnnotation:userLocationDot].
For creating an application with Map you need to implement the MKAnnotation, MKMapViewDelegate delgates.
This is a good tutorial for you.
I would like to find out userlocation coordinate while my app is loading. I have implemented following code but it returns 0.000
mapView.showsUserLocation=YES;
CLLocationCoordinate2D annotationCoordinate;
annotationCoordinate.latitude=mapView.userLocation.location.coordinate.latitude;
NSLog(#"%f",annotationCoordinate.latitude);
I could not able to figure out. Any help?
First of all you should take into account that it takes time to retrieve the user location. Moreover the user can disable the location service for your application or even the location service can be unavailable during the connectivity conditions. So you'd better to rethink your application starting procedures.
When you make up you decision take a look at mapView:didUpdateUserLocation: method of MKMapViewDelegate protocol. After this method fires out the location can be available via the userLocation property of the MKMapView.
UPDATE
In case you want to open map view with the user location already checked, you may consider using CLLocationManager and CLLocationManagerDelegate. This way you can check if the location service is available and open map view after the method locationManager:didUpdateToLocation:fromLocation: fires up.
For the complete info take a look at Getting the User’s Location programing guide
You can not get users location coordinate in view did load what you need to do is using the delegate method below.
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation
{
NSLog(#"coordinates = %f,%f", mapView.userLocation.coordinate.latitude,
mapView.userLocation.coordinate.longitude);
}
Make sure your map object is connected with the delegate
Just tested it should work
1) Add the FrameWork CoreLocation and Mapkit
2) In ViewController.h file
#import <UIKit/UIKit.h>
#import <MapKit/MapKit.h>
#interface MapViewController : UIViewController<MKMapViewDelegate,CLLocationManagerDelegate>
{
CLLocationManager *locationManager;
}
#property (nonatomic, strong) IBOutlet MKMapView *mapView;
3) In viewController.m file
- (void)viewDidLoad
{
[super viewDidLoad];
self.mapView.delegate = self;
locationManager = [[CLLocationManager alloc] init];
locationManager.delegate=self;
locationManager.distanceFilter = kCLDistanceFilterNone; // whenever we move
locationManager.desiredAccuracy = kCLLocationAccuracyHundredMeters; // 100 m
[locationManager startUpdatingLocation];
}
now in didUpdateUserLocation
- (void)mapView:(MKMapView *)mapView didUpdateUserLocation:(MKUserLocation *)userLocation{
MKCoordinateRegion region = MKCoordinateRegionMakeWithDistance(userLocation.coordinate, 800, 800);
[self.mapView setRegion:[self.mapView regionThatFits:region] animated:YES];
// Add an annotation
MKPointAnnotation *point = [[MKPointAnnotation alloc] init];
point.coordinate = userLocation.coordinate;
point.title = #"Where am I?";
point.subtitle = #"I'm here!!!";
[self.mapView addAnnotation:point];}
4) Now add Mapview in your UI
NOTE: select MapView and goto Attribute Inspector and CKECK Mark the Shows user location Under BEHAVIOUR
I think you should use this:
#import <CoreLocation/CoreLocation.h>
Then in viewDidLoad method:
CLLocationManager *locationManager = [[CLLocationManager alloc] init];
[locationManager startUpdatingLocation];
[mapView setRegion:MKCoordinateRegionMake(locationManager.location.coordinate, MKCoordinateSpanMake(0.2, 0.2))];
mapView.showsUserLocation = YES;
NSLog(#"%f",mapView.region.center.latitude);
Okay, so I just managed to fix it myself too. What no one is telling you is that you need to set a key/value pair in the info.plist.
Depending on what method (requestAlwaysAuthorization or requestWhenInUseAuthorization) you need to add a key under the 'Information Property List' dictionary. For the first method use: NSLocationAlwaysUsageDescription and for the second method use: NSLocationWhenInUseUsageDescription .
The value fields of both keys can be set to whatever string you would like to display to the user.
Both keys at the top are what we are looking for.
Now you should see a dialogue that prompts you for confirmation.
PS: No tutorial on the internet listed this step of the process, but when you read the documentation for each of those methods (requestWhenInUse on CLLocationManager) it does mention that nothing gets displayed without those two keys.