I am trying to make an application where various pins are placed on the map.
When I touch a pin, the standard bubble pops up with a disclosure button. When I click my disclosure button, I would like it to open up a new ViewController so I can display more information.
The problem is that every time I run the application, it crashes and it gives me an error in the output.
How could I fix this?
The error in the output is:
2013-06-29 16:51:33.575 ...[2723:13d03] -[FirstViewController
...Clicked:]: unrecognized selector sent to instance 0x867d0d0
2013-06-29 16:51:33.576 lam[2723:13d03] * Terminating app due to
uncaught exception 'NSInvalidArgumentException', reason:
'-[FirstViewController ...Clicked:]: unrecognized selector sent
to instance 0x867d0d0'
* First throw call stack: (0x1d8d012 0x11cae7e 0x1e184bd 0x1d7cbbc 0x1d7c94e 0x11de705 0x182c0 0x18258 0xd9021 0xd957f 0xd86e8 0x47cef
0x47f02 0x25d4a 0x17698 0x1ce8df9 0x1ce8ad0 0x1d02bf5 0x1d02962
0x1d33bb6 0x1d32f44 0x1d32e1b 0x1ce77e3 0x1ce7668 0x14ffc 0x1e12
0x1d45) libc++abi.dylib: terminate called throwing an exception
The code in my FirstViewController.m is:
[...]
[...]
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
MyPin.pinColor = MKPinAnnotationColorPurple;
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[annotation title] isEqualToString:#"..."]) {
[advertButton addTarget:self action:#selector(...Clicked:) forControlEvents:UIControlEventTouchUpInside];
}
if ([[annotation title] isEqualToString:#"..."]) {
[advertButton addTarget:self action:#selector(...Clicked:) forControlEvents:UIControlEventTouchUpInside];
}
MyPin.rightCalloutAccessoryView = advertButton;
MyPin.draggable = NO;
MyPin.highlighted = YES;
MyPin.animatesDrop=TRUE;
MyPin.canShowCallout = YES;
return MyPin;
}
- (void)mapView:(MKMapView *)mapView MyPin:(MKPinAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
// Go to edit view
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"...Controller" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
}
//-(void) ...Clicked:(id)sender {
// NSLog(#"... Clicked");
//}
-(void) ...:(id)sender {
NSLog(#"...");
}
-(IBAction)findmylocation:(id)sender {
mapView.showsUserLocation = YES;
mapView.delegate = self;
[mapView setUserTrackingMode:MKUserTrackingModeFollow animated:YES];
}
What causes the error is:
- (void)mapView:(MKMapView *)mapView MyPin:(MKPinAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control {
// Go to edit view
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"..." bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
}
EDIT 2:
I have removed the advertButton addTarget lines, but now I don't have the disclosure button when I run the simulator... is what I removed correct?
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation {
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
MyPin.pinColor = MKPinAnnotationColorPurple;
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
if ([[annotation title] isEqualToString:#"..."])
if ([[annotation title] isEqualToString:#"..."])
The error:
[FirstViewController ...Clicked:]: unrecognized selector sent to instance
means that it's trying to call the ...Clicked: method on FirstViewController (because you set the advertButton's target to that method) but there is no such method (you've commented it out).
You could just un-comment it.
But since you are using the calloutAccessoryControlTapped delegate method anyway to detect accessory taps (a better choice than a custom method), you should instead do the following:
Remove the advertButton addTarget lines from viewForAnnotation and just handle the taps in calloutAccessoryControlTapped.
Also remove the ...Clicked: method.
Another issue is the method calloutAccessoryControlTapped is incorrectly named as mapView:MyPin:calloutAccessoryControlTapped:.
The method must be named mapView:annotationView:calloutAccessoryControlTapped: like this:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// Go to edit view
UIViewController *detailViewController = [[UIViewController alloc] initWithNibName:#"...Controller" bundle:nil];
[self.navigationController pushViewController:detailViewController animated:YES];
}
By the way, in the calloutAccessoryControlTapped, you'll be able to access the annotation that was tapped through view.annotation.
The updated viewForAnnotation should be something like this:
-(MKAnnotationView *) mapView:(MKMapView *)mapView viewForAnnotation:(id<MKAnnotation>)annotation
{
MKPinAnnotationView *MyPin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:#"current"];
MyPin.pinColor = MKPinAnnotationColorPurple;
UIButton *advertButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
MyPin.rightCalloutAccessoryView = advertButton;
MyPin.draggable = NO;
MyPin.highlighted = YES;
MyPin.animatesDrop=TRUE;
MyPin.canShowCallout = YES;
return MyPin;
}
Related
I found out how to push titles and subtitles to DVcontroller but not pictures. I would like to push annotations specific pictures to a new DV when detail disclosure button is tapped. (using Xcode5)
I have the storyboard setup with 2 VC and the first one is embeded in a Nav Controller. The pins show on the map (NSMutable array) and the callout comes up with detail disclosure button on the right (and one on the left as well for future use). The segue from the detail disclosure button works as I can see a blue background screen on the DVcontroller upon tapping it. I have imported pictures files and associated them to each annotation.
I believe the answer is in the "calloutAccessoryControlTapped" method. I tried several codes in that method but still cannot return a picture in the DVcontroller.
ViewController.m is as follows:
(each annotation is set up like this):
//Chateau l'Evesque annotation
myAnn = [[myAnnotation alloc] init];
location.latitude = CLEVESQUE_LATITUDE;
location.longitude = CLEVESQUE_LONGITUDE;
myAnn.coordinate = location;
myAnn.title = #"Château l'Evesque";
myAnn.imageView=[UIImage imageNamed:#"Château l'Evesque"];
[locations addObject:myAnn];
(then viewForAnnotation method):
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:
(id<MKAnnotation>)annotation
{
if([annotation isKindOfClass: [MKUserLocation class]]) return nil;
static NSString *reuseId = #"myAnn";
MKAnnotationView *customAnnotationView = [mapView
dequeueReusableAnnotationViewWithIdentifier:reuseId];
if (customAnnotationView == nil)
{
myAnnotation *myAnn = (myAnnotation *)annotation;
}
MKPinAnnotationView *Mypin=[[MKPinAnnotationView alloc] initWithAnnotation:annotation
reuseIdentifier:#"current"];
Mypin.pinColor = MKPinAnnotationColorPurple;
UIButton *directionButton = [UIButton buttonWithType:UIButtonTypeCustom];
directionButton.frame = CGRectMake(0, 0, 23, 23);
[directionButton setBackgroundImage:[UIImage imageNamed:#"PetitVolant.png"]
forState:UIControlStateNormal];
UIButton *infoButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
Mypin.leftCalloutAccessoryView = directionButton;
Mypin.rightCalloutAccessoryView = infoButton;
Mypin.draggable = NO;
Mypin.highlighted = YES;
Mypin.animatesDrop = TRUE;
Mypin.canShowCallout = YES;
return Mypin;
}
(then the culprit):
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
myAnnotation *myAnn = (myAnnotation *)view.annotation;
NSLog(#"myAnn.imageView = %#",myAnn.imageView);
[self performSegueWithIdentifier:#"ShowPhotoSegue" sender:self];
}
(I greened out the "prepareForSegue" method because it is not changing anything to the result so far.)
I rewrote the whole thing. Still the same problem. I am going to try to better explain the issue. I just want to push a picture (.png file) to the next view controller. I only get a blank (white) screen on the Detail VC after tapping the detail disclosure button.
If I include the following prepareForSegue method (as described in the suggested post):
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
{
if ([segue.identifier isEqualToString:#"ToDetail"])
{
MKAnnotationView *annotationView = sender;
[segue.destinationViewController setAnnotation:annotationView.annotation];
I get the following error:
GuideDesVinsCPA[2523:70b] myAnn.imageForMyAnnotation = <UIImage: 0x9a4f510>
GuideDesVinsCPA[2523:70b] -[MapViewController annotation]: unrecognized selector sent to instance 0x9f31970
GuideDesVinsCPA[2523:70b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MapViewController annotation]: unrecognized selector sent to instance 0x9f31970'
I want all my view is tappable and not just leftCalloutAccessoryView or rightCalloutAccessoryView, I also want the center to be tappable as well
You can use,
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInView:calloutView];
BOOL isPointInsideView = [calloutView pointInside:touchPoint withEvent:nil];
if (isPointInsideView)
{
// place your action code.
}
}
or you can use,
UITapGestureRecognizer *tapGestureRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:#selector(tapCalloutAction:)];
tapGestureRecognizer.delegate = self;
tapGestureRecognizer.numberOfTapsRequired = 1;
[calloutView addGestureRecognizer:tapGestureRecognizer];
-(void) tapCalloutAction:(id)sender
{
// do stuff
}
the idea is that you want to make all of the "accessory view" tappable without interfering with the original tap of the actual annotation.. this is how i do it:
first I create and assign a tap gesture to the annotation view after it has been selected like so (I use obj-c runtime object association here.. see this gist):
// set up vars
static NSString *const kExtraTapGestureRecognizer = #"extraGesture";
UIControl *currentAnnotationControl;
- (void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view {
UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc]
initWithTarget:self
action:#selector(handleAnnotationViewTap:)];
tap.numberOfTapsRequired = 1;
[tap setInfo:kExtraTapGestureRecognizer];
[view addGestureRecognizer:tap];
}
to handle the tap, I call MKMapViewDelegate Protocol's mapView:annotationView:calloutAccessoryControlTapped:, which basically means that if the view in between the accessory views gets tapped, it's as if one of the accessory views just got tapped:
- (void)handleAnnotationViewTap:(UITapGestureRecognizer *)gestureRecognizer {
MKAnnotationView *annotationView = (MKAnnotationView *)gestureRecognizer.view;
// currentAnnotationControl is just a reference to one of the
// accessory views of the annotation that has just been selected..
// see comment below
[self mapView:self.mapView
annotationView:annotationView
calloutAccessoryControlTapped:currentAnnotationControl];
}
when the annotationView is returned, I save a reference to one of its (left or right) accessoryViews like so:
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[Random class]])
{
static NSString *annotationIdentifier = #"annotation";
MKAnnotationView *annotationView =
(MKAnnotationView *) [self.mapView annotationIdentifier];
if (annotationView == nil)
{
annotationView = [[MKAnnotationView alloc]
initWithAnnotation:annotation reuseIdentifier:RiderAnnotationIdentifier];
annotationView.canShowCallout = YES;
UIButton *leftButton = [UIButton buttonWithType:UIButtonTypeCustom];
leftButton.frame = CGRectMake(0, 0,21, 21);
[leftButton setImage:[UIImage imageNamed:#"smallInfo_rider_left.png"] forState:UIControlStateNormal];
[leftButton addTarget:nil action:nil forControlEvents:UIControlEventTouchUpInside];
annotationView.leftCalloutAccessoryView = leftButton;
// this is where i store a reference to one of the accessory views
currentAnnotationControl = leftButton;
return annotationView;
}
else
{
annotationView.annotation = annotation;
}
return annotationView;
keep in mind that we created an extra tap gesture, we must get rid of it as soon as one of the accessory views has been tapped like so:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
// we remove the extra tap gesture so that it doesn't interfere
// with normal app flow in the future
for (UIGestureRecognizer *gesture in [view gestureRecognizers]) {
if ([[gesture info] isEqualToString:kExtraTapGestureRecognizer]) {
[gesture removeTarget:nil action:NULL];
}
}
// more stuff
I have a detailDisclousure button on the callout of a MKAnnotation. When this button is pressed I need to call a method passing a parameter that identifies the annotation that called it. How could it be done?
this is my viewForAnnotation:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[Annotation class]])
{
static NSString* identifier = #"identifier";
MKPinAnnotationView* pinView = (MKPinAnnotationView*)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (pinView == nil)
{
pinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier]autorelease];
}
Annotation *antt = annotation;
pinView.canShowCallout = YES;
pinView.draggable = YES;
UIButton* detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[detailButton performSelector:#selector(goToViewWithAnnotation:) withObject:antt];
pinView.rightCalloutAccessoryView = detailButton;
return pinView;
}
else
{
return nil;
}
}
And this is the method that should be called with the parameter:
-(void)goToViewWithAnnotation:(Annotation *)annotation
{
NextViewController *nextView = [[NextViewController alloc]initWithNibName:#"NextViewController" bundle:nil];
nextView.id = annotation.id;
[self.navigationController nextView animated:YES];
}
you can pass any NSInteger via the tag property of the UIButton only.
It's not possible to send data with the -addTarget:action:forControlEvent: for UIButton:
[detailButton addTarget:self action:#selector(goToViewWithAnnotation:) forControlEvents:UIControlEventTouchUpInside];
Try to handle the data with some other way, when you trigger the selector. Like saving a pointer or var as you like.
UIButton have these callers as you can see:
- (void)action
- (void)action:(id)sender
- (void)action:(id)sender forEvent:(UIEvent *)event
I think you can add a property(which is (Annotation *)annotation) in .h file. Then you can use annotation in the "-(void)goToViewWithAnnotation" method. Or you can customized a new button which inherits UIControl
Here's a sample custom callout that I'd want to get a similar style of. I was thinking of inheriting from MKAnnotation but I'm lost how to start it and I don't know if the callout's design could be overridden.
Any ideas how to implement this custom callout with ui controls inside?
EDIT: Here's my code from following the similar StackOverflow answer:
- (MKAnnotationView *)mapView:(MKMapView *)mapview viewForAnnotation:(id <MKAnnotation>)annotation
{
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
static NSString* AnnotationIdentifier = #"AnnotationIdentifier";
MKAnnotationView *annotationView = [mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if(annotationView)
return annotationView;
else
{
UIImage *img = [UIImage imageNamed:#"default_thumb.png"];
MKAnnotationView *annotationView =
[[MKAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier];
annotationView.canShowCallout = YES;
annotationView.image = img;
annotationView.draggable = YES;
/*
// change left accessory view button with image
UIImageView *leftAccView = [[UIImageView alloc] initWithImage:img];
annotationView.leftCalloutAccessoryView = leftAccView;
//include a right button to show more info
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(calloutSubMenu:) forControlEvents:UIControlEventTouchUpInside];
[rightButton setTitle:annotation.title forState:UIControlStateNormal];
annotationView.rightCalloutAccessoryView = rightButton;
*/
return annotationView;
}
return nil;
}
// Customize accessory view
- (void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
{
[mapview deselectAnnotation:view.annotation animated:YES];
PopOverCallout *popOverCallout = [[PopOverCallout alloc]init];
UIPopoverController *popOver = [[UIPopoverController alloc] initWithContentViewController:popOverCallout];
self.annotationPopoverController = popOver;
popOver.popoverContentSize = CGSizeMake(500, 500);
[popOver presentPopoverFromRect:view.bounds inView:view permittedArrowDirections:UIPopoverArrowDirectionAny animated:YES];
}
The
(void)mapView:(MKMapView *)mapview annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control
should be called whenever the pin is touched to show the callout right? but what happens is that a blank regular callout is shown. What might I be doing wrong?
BTW: the UIViewController subclass only has a label on it's XIB and is pretty much blank.
Found out the answer after a few mins of nap.
using this as the guide, I just needed to override
-(void)mapView:(MKMapView *)mapView didSelectAnnotationView:(MKAnnotationView *)view
which shows the annotation callout. Thanks again for the answer, Ms. Anna.
I have a map view with pins annotations into it. I need to pass to the detail view the coordinates of a pin when the user press the disclosure button of the pin information. How can I get the coordinates of the pin when I am into the showDetails method? I am using the next code.
- (void)showDetails:(id)sender {
DetailViewController *detailViewController = [[DetailViewController alloc] initWithNibName:#"DetailViewController" bundle:nil ];
// HERE I NEED TO PASS THE COORDINATES OF THE PIN TO THE DETAILVIEWCONTROLLER.
[self.navigationController pushViewController:detailViewController animated:YES];
[detailViewController release];
}
- (MKAnnotationView *)mapView:(MKMapView *)theMapView viewForAnnotation:(id <MKAnnotation>)annotation {
// If it's the user location, just return nil.
if ([annotation isKindOfClass:[MKUserLocation class]])
return nil;
else { // Handles the other annotations.
// Try to dequeue an existing pin view first.
static NSString *AnnotationIdentifier = #"AnnotationIdentifier";
MKPinAnnotationView *pinView = (MKPinAnnotationView *)[self.mapView dequeueReusableAnnotationViewWithIdentifier:AnnotationIdentifier];
if (!pinView) {
// If an existing pin view was not available, creates one.
MKPinAnnotationView *customPinView = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:AnnotationIdentifier] autorelease];
customPinView.animatesDrop = YES;
customPinView.canShowCallout = YES;
// Adds a detail disclosure button.
UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
return customPinView;
} else
pinView.annotation = annotation;
}
return nil;
}
Thanks for reading.
I cannot give you a working code solution right now since i don't have my mac here. But I can tell you about a possible approach.
Create a method in DetailViewController that populates its attributes, so you can call it from showDetails, before you push it. So the sequence of events would be like this.
Click on disclosure button -> showDetails called -> DetailViewController created and populated -> push DetailViewController
Hope this helped you
You can your UIButton button custom like this :
#interface CustomButton : UIButton {
CLLocationCoordinate2D pinCoordinate;
}
Now in your viewForAnnotation :
// Adds a detail disclosure button.
CustomButton *rightButton = [CustomButton buttonWithType:UIButtonTypeDetailDisclosure];
rightButton.pinCoordinate = annotation.coordinate
[rightButton addTarget:self action:#selector(showDetails:) forControlEvents:UIControlEventTouchUpInside];
customPinView.rightCalloutAccessoryView = rightButton;
And finally to get the coordinate in the showDetails method :
CLLocationCoordinate2D currentCoord = ((CustomButton*)sender).pinCoordinate;