Ignore MKUserLocation selection to register UILongTapGestureRecognizer - ios

I'm looking to create a 'long hold' pin drop on an MKMapView.
Currently, everything works to the way I want it, when you tap and hold on the Map, it registers the gesture and the code drops a pin.
-(void)viewDidLoad
{
[self.mapView addGestureRecognizer:longPressGesture];
}
-(void)handleLongPressGesture:(UIGestureRecognizer*)sender
{
if(sender.state == UIGestureRecognizerStateBegan || sender == nil)
{
CGPoint point = [sender locationInView:self.mapView];
CLLocationCoordinate2D locCoord;
locCoord = [self.mapView convertPoint:point toCoordinateFromView:self.mapView];
//Drop Pin
}
}
That is, however, it only works if you are not too close to the MKUserLocation annotation (the Blue pulsing dot) otherwise the gesture does not get registered, and the didSelectAnnotationView: function gets called.
Is there a way to Ignore the user taping the MKUserLocation annotation? I was looking for something like setUserEnabled but it doesn't exist for MKAnnotations.

The MKAnnotationView class has an enabled property (not the id<MKAnnotation> objects).
To set enabled on the map view's user location annotation view, get a reference to it in the mapView:didAddAnnotationViews: delegate method:
-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
MKAnnotationView *av = [mapView viewForAnnotation:mapView.userLocation];
av.enabled = NO; //disable touch on user location
}
(In viewForAnnotation, you have to return nil to tell the map view to create the view itself so enabled can't be set there -- at least for the user location.)

Related

How to tell which button is being pressed in calloutAcessoryControlTapped?

I'm using MapKit and I have 2 callout accessories in my pins.
I'm trying to implement a button for updating the pin's title and one for deleting the pin.
Right now, anytime I press a button on the annotation, it only deletes the pin.
How do I get it to respond differently for the right button vs the left button?
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
id <MKAnnotation> annotation = [view annotation];
if ([annotation isKindOfClass:[MKPointAnnotation class]])
{
NSLog(#"Clicked");
if(view.rightCalloutAccessoryView){
[self.mapView removeAnnotation:annotation];
}
else{
float lat= annotation.coordinate.latitude;
float longitude = annotation.coordinate.longitude;
[self.mapView removeAnnotation:annotation];
MKPointAnnotation *pointAnnotation = [[MKPointAnnotation alloc] init];
pointAnnotation.title = _titleOut.text;
pointAnnotation.subtitle = _subtitle.text;
pointAnnotation.coordinate = CLLocationCoordinate2DMake(lat, longitude);
[self.mapView addAnnotation:pointAnnotation];
}
}
}
This line:
if(view.rightCalloutAccessoryView){
says essentially "if view.rightCalloutAccessoryView is not nil".
Since you are setting the right-side accessory on all annotation views, that if condition will always be true and so tapping either accessory will execute the code inside that if which is to remove the annotation.
Instead, you want to check what button or control was tapped in this specific case of the delegate method being called (not whether the view has a right-side accessory defined).
Fortunately, the delegate method passes exactly what control was tapped in the control parameter.
The control parameter can be directly compared against the view's right/left accessory view to tell which was tapped:
if (control == view.rightCalloutAccessoryView) {
Some unrelated points:
The latitude and longitude properties in annotations are of type CLLocationDegrees (aka double) which has higher precision than float so to avoid losing accuracy, use CLLocationDegrees or double:
CLLocationDegrees lat= annotation.coordinate.latitude;
The MKPointAnnotation allows you to change the title directly (it's not read-only like the default id<MKAnnotation>) so you don't need to remove and create a new annotation. It simplifies the code a bit:
-(void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
if ([view.annotation isKindOfClass:[MKPointAnnotation class]])
{
NSLog(#"Clicked");
if (control == view.rightCalloutAccessoryView) {
[self.mapView removeAnnotation:view.annotation];
}
else {
// Cast view.annotation as an MKPointAnnotation
// (which we know it is) so that compiler sees
// title is read-write instead of the
// default id<MKAnnotation> which is read-only.
MKPointAnnotation *pa = (MKPointAnnotation *)view.annotation;
pa.title = _titleOut.text;
pa.subtitle = _subtitle.text;
//If you want callout to be closed automatically after
//title is changed, uncomment the line below:
//[mapView deselectAnnotation:pa animated:YES];
}
}
}

Prevent touch events on MKMapView being detected when a MKAnnotation is tapped

I have a UITapGestureRecognizer that will hide and show a toolbar over my MKMap when the user taps the Map - simple.
However, when the user taps on an MKMapAnnotation, I do not want the map to respond to a tap in the normal way (above). Additionally, when the user taps elsewhere on the map to de-select an MKAnnotation callout, I also don't want the toolbar to respond. So, the toolbar should only respond when there are no MKAnnotations currently in selected state. Nor should it respond when the user clicks on an annotation directly.
So far, I have being trying the following action that reacts to the tap gesture on the map - however the Annotation View is never detected (the first if statement) and also, the annotation view is also launched regardless of this method.
-(void)mapViewTapped:(UITapGestureRecognizer *)tgr
{
CGPoint p = [tgr locationInView:self.mapView];
UIView *v = [self.mapView hitTest:p withEvent:nil];
id<MKAnnotation> ann = nil;
if ([v isKindOfClass:[MKAnnotationView class]])<---- THIS CONDITION IS NEVER MET BUT ANNOTATIONS ARE SELECTED ANYWAY
{
//annotation view was tapped, select it…
ann = ((AircraftAnnotationView *)v).annotation;
[self.mapView selectAnnotation:ann animated:YES];
}
else
{
//annotation view was not tapped, deselect if some ann is selected...
if (self.mapView.selectedAnnotations.count != 0)
{
ann = [self.mapView.selectedAnnotations objectAtIndex:0];
[self.mapView deselectAnnotation:ann animated:YES];
}
// If no annotation view is selected currently then assume control of
// the navigation bar.
else{
[self showToolBar:self.navigationController.toolbar.hidden];
}
}
}
I need to control the launch of the annotation call out programmatically and detect when the tap event has hit an annotation in order to achieve this.
Any help would be appreciated.
I think you will find the following links very useful:
http://blog.asynchrony.com/2010/09/building-custom-map-annotation-callouts-part-2/
How do I make a MKAnnotationView touch sensitive?
The first link discusses (among other things) how to prevent the propagation of touches to the annotations so that they selectively respond, and the second one how to detect the touches.
I think that because MKMapAnnotationView are on top of MKMapView, they will get the touch event and respond to it (be selected) so I don't think you need to select your annotation manually.
Then, if you have a look at Advanced Gesture Recognizer WWDC 2010 video, you will see that your MKMapView will receive tap event anyway, even if it's below the annotation view. That's probably why your -(void)mapViewTapped:(UITapGestureRecognizer *)tgr method get called.
Apart from that, I can't see why your if ([v isKindOfClass:[MKAnnotationView class]]) is never true. I do the exact same thing in my code and it works fine!
Finally, to answer your last question, if you don't want to do anything when the user is just trying to close the callout, you could keep track of a custom isCalloutOpen boolean value like this:
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
//some code
_isCalloutOpen = YES;
}
- (void)mapView:(MKMapView *)mapView didDeselectAnnotationView:(MKAnnotationView *)view {
// delay the reset because didDeselectAnnotationView could (and is often) called before your gesture recgnizer handler method get called.
[self performSelector:#selector(resetCalloutOpenState) withObject:Nil afterDelay:0.1];
}
- (void)resetCalloutOpenState {
_isCalloutOpen = NO;
}
- (void)mapViewTapped:(UITapGestureRecognizer *)tgr {
if (_isCalloutOpen) {
return;
}
}

Drop a pin on MKMapView

iPhone newbie is here coming from Java. So my objective at this stage is to allow the user to 'drop a pin' on the map. My initialization of the map looks like this:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(#"your view did load, I'm going to initizlie the map by your location");
CLLocationCoordinate2D location = theMap.userLocation.coordinate;
NSLog(#"Location found from Map: %f %f",location.latitude,location.longitude);
MKCoordinateRegion region;
MKCoordinateSpan span;
NSLog(#"coordinates: %f %f",location.latitude,location.longitude);
if (TARGET_IPHONE_SIMULATOR) {
NSLog(#"You're using the simulator:");
location.latitude = 40.8761620;
location.longitude = -73.782596;
} else {
location.latitude = theMap.userLocation.location.coordinate.latitude;
location.longitude = theMap.userLocation.location.coordinate.longitude;
}
span.latitudeDelta = 0.001;
span.longitudeDelta = 0.002;
region.span = span;
region.center = location;
[theMap setRegion:region animated:YES];
[theMap regionThatFits:region];
[theMap setMapType:MKMapTypeSatellite];
[theMap setZoomEnabled:YES];
[theMap setScrollEnabled:YES];
[theMap setShowsUserLocation:YES];
}
For the requested pin drop I have
- (MKAnnotationView *)mapView:(MKMapView *)mV viewForAnnotation:(id <MKAnnotation>)annotation {
MKPinAnnotationView *pinView = nil;
if (annotation != theMap.userLocation) {
static NSString *defaultPinID = #"aPin";
pinView = (MKPinAnnotationView *)[theMap dequeueReusableAnnotationViewWithIdentifier:defaultPinID];
if (pinView == nil)
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:defaultPinID] autorelease];
} else {
}
pinView.pinColor = MKPinAnnotationColorRed;
pinView.canShowCallout = YES;
pinView.animatesDrop = YES;
return pinView;
}
I'm not sure I fully understand how this map (theMap) works for pins in viewForAnnotation? I mean, what action the user does will activate the viewForAnnotation method? This code doesn't work and I'm not sure why.
I'm using the simulator so I'm not sure if there's a button I should press or Alt click it?
I'm not sure I fully understand how this map (theMap) works for pins in viewForAnnotation?
MKPinAnnotationView is just another kind of annotation view -- that is, you add an annotation (an object conforming to the MKAnnotation protocol) to the map. When the map wants to display the annotation (maybe because the user scrolled the map so that the annotation is in view), it asks you for a view to use to represent the annotation. At that point, your mapView:viewForAnnotation: method can fetch or create a pin annotation view and return that. The user doesn't do anything directly to trigger mapView:viewForAnnotation:, except for scrolling or zooming.
If you want to the user to be able to drop a pin, that's a different thing. You'll need to provide a view (possibly even a MKPinAnnotationView) that they can drag around. When they indicate that they want to drop the pin (perhaps by lifting their finger), you remove the view and add an appropriate annotation at that point. Then the map view will ask you for a view to represent the annotation by calling its delegate's mapView:viewForAnnotation: method.
This code doesn't work and I'm not sure why.
Have you added any annotations to the map? If so, are you looking at the part of the map where they should be displayed?
I'm guessing that you're looking at the animatesDrop property and expecting it to do the entire user pin-dropping interaction. It doesn't do that. Setting that property to YES merely animates the pin as it appears on the map.
ok, after a while, I understood what went wrong:
theMap.delegate = (id) self;
in the constructor was missing. Once I did that any action by end user will activate other methods (protocols) of the map.

How to set location in map in IOS

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.

How can the MKUserLocation be programmatically selected?

Titles and subtitles can be added to the user location that iOS shows using MKUserLocation. When the user taps on the location, these will show in a bubble above the location. The thought bubbles for other annotations can be shown by selecting the annotation with setSelected:animated: from MKAnnotationView. Unfortunately, MKUserLocation does not descend from MKAnnotationView.
How can I programmatically select the user location so the annotation appears over the user location without the user first tapping on it?
The documentation for MKAnnotationView says this about its setSelected:animated: method (and something similar for its selected property):
You should not call this method directly.
Instead, use the MKMapView method selectAnnotation:animated:. If you call it in the didAddAnnotationViews delegate method, you can be sure the annotation view is ready to show the callout otherwise calling selectAnnotation will do nothing.
For example:
-(void)mapView:(MKMapView *)mapView didAddAnnotationViews:(NSArray *)views
{
for (MKAnnotationView *av in views)
{
if ([av.annotation isKindOfClass:[MKUserLocation class]])
{
[mapView selectAnnotation:av.annotation animated:NO];
//Setting animated to YES for the user location
//gives strange results so setting it to NO.
return;
}
}
}

Resources