What I want to do is basically send some form of id with my button so I can calculate which one has been clicked. My buttons get dynamically created and all of them execute the same function that loads a new view, I want to know which button was clicked so I can display the correct data on the new view.
I have an NSMutableArray of annotations that I add a button to the details of the pin. The button works and it loads the next view, but I want to figure out which button was pressed.
I use a singleton with an NSMutableArray called array. The singleton name is Helper
What I did was:
Helper* array = [Helper sharedManager];
int clickedNum = [array.array indexOfObject:annotation];
[myDetailButton setTag:clickedNum];
[myDetailButton addTarget:self action:#selector(moreInfo:event:) forControlEvents:UIControlEventTouchUpInside];
That is where i create the annotations that go unto the map. The next line is my function ID
- (IBAction)moreInfo:(UIButton*)sender
And this is where I want to retrieve the tag
Helper *array = [Helper sharedManager];
array.clicked = sender.tag;
When I run this and click on the button in one of my annotations I get an exception saying NSInvalidArgumentException, reason: -(MapViewController moreInfo:event:]
Any help would be appriciated
Edit:
As requested the interface for helper:
#interface Helper : NSObject {
NSMutableArray *array;
int clicked;
}
#property (nonatomic, retain) NSMutableArray *array;
#property (nonatomic) int clicked;
+(id)sharedManager;
#end
Also I thought it wise to add:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
static NSString *identifier = #"Events";
MKPinAnnotationView *retval = nil;
if ([annotation isKindOfClass:[Events class]])
{
(MKPinAnnotationView *)[mapView dequeueReusableAnnotationViewWithIdentifier:identifier];
if (retval == nil) {
retval = [[[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:identifier] autorelease];
Helper *array = [Helper sharedManager];
int clickedNum = [array.array indexOfObject:annotation];
// Set up the Left callout
UIButton *myDetailButton = [UIButton buttonWithType:UIButtonTypeCustom];
myDetailButton.frame = CGRectMake(0, 0, 23, 23);
myDetailButton.contentVerticalAlignment = UIControlContentVerticalAlignmentCenter;
myDetailButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentCenter;
[myDetailButton setTag:clickedNum];
NSLog([NSString stringWithFormat:#"Testing tag: %#", clickedNum]);
[myDetailButton addTarget:self action:#selector(moreInfo:) forControlEvents:UIControlEventTouchUpInside];
// Set the image for the button
[myDetailButton setImage:[UIImage imageNamed:#"icon72.png"] forState:UIControlStateNormal];
// Set the button as the callout view
retval.leftCalloutAccessoryView = myDetailButton;
}
if (retval) {
[retval setPinColor:MKPinAnnotationColorGreen];
retval.animatesDrop = YES;
retval.canShowCallout = YES;
}
}
return retval;
}
Your method moreInfo: doesn't match what you have set as the selector in
[myDetailButton addTarget:self action:#selector(moreInfo:event:) forControlEvents:UIControlEventTouchUpInside];
So change that to
[myDetailButton addTarget:self action:#selector(moreInfo:) forControlEvents:UIControlEventTouchUpInside];
Or change the method definition.
Although Deepak's answer is correct and will work, since your buttons are annotation callout accessory views, there is an easier way to tell which annotation's button was selected.
Instead of adding your own target+action, use the map view's mapView:annotationView:calloutAccessoryControlTapped: delegate method which will get called when a callout view is tapped. There, the annotation is available as view.annotation. You don't need to set the button tag or use the annotation's index.
For an example, see this other answer.
A separate issue is that in your viewForAnnotation method, you are calling dequeueReusableAnnotationViewWithIdentifier but not doing anything with the result (so not getting any re-use). Instead, it should be assigned to retval and inside the last if, set retval.annotation.
Related
How do I keep data associated with an MKAnnotation object after the user taps the pin, sees a callout, and taps the disclosure button which opens a detailed view controller? I want to display all data associated with the pin in the detail view controller.
I have a simple MKAnnotation class that looks like:
#import <Foundation/Foundation.h>
#import <MapKit/MapKit.h>
#interface VoiceMemoryAnnotation : NSObject <MKAnnotation> {
NSString * blobkey;
}
#property (nonatomic, retain) NSString * blobkey;
-(id)initWithBlobkey:(NSString *) key andCoordinate:(CLLocationCoordinate2D) c;
#end
I implemented the call back "viewForAnnotation"
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation
{
MKPinAnnotationView*singleAnnotationView = [[MKPinAnnotationView alloc]initWithAnnotation:annotation reuseIdentifier:nil];
// PM: this pin will have a callout (i.e. dont' forget to override title function! Else exception thrown)
singleAnnotationView.canShowCallout = YES;
// PM: add disclosure button
UIButton* rightButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// PM: when user taps disclosure button, bring them to another page with details about the selected voice memory
[rightButton addTarget:self action:#selector(showPinDetails:) forControlEvents:UIControlEventTouchUpInside];
singleAnnotationView.rightCalloutAccessoryView = rightButton;
return singleAnnotationView;
}
If I understand correctly, the above method is called when you add a VoiceMemoryAnnotation to a map object. When this viewForAnnotation is called, I simply allocate a MKPinAnnotationView object and return it. When the user taps this retuned pin, they see the callout. As soon as they click the disclosure button it calls "showPinDetails":
- (void)showPinDetails:(id)sender
{
detailViewController = [[MemoryDetailViewController alloc]initWithNibName:#"MemoryDetailViewController" bundle:nil];
[self presentModalViewController:detailViewController animated:YES];
}
The problem is the "sender" object does not contain any information about which pin was selected. Is there some way I can pass in the selected annotation to the showPinDetails method?
In the showPinDetails: method, you can get the currently selected annotation from the map view's selectedAnnotations property.
That property is an NSArray but since the map view only allows one annotation to be selected at a time, you would just use the object at index 0. For example:
- (void)showPinDetails:(id)sender
{
if (mapView.selectedAnnotations.count == 0)
{
//no annotation is currently selected
return;
}
id<MKAnnotation> selectedAnn = [mapView.selectedAnnotations objectAtIndex:0];
if ([selectedAnn isKindOfClass[VoiceMemoryAnnotation class]])
{
VoiceMemoryAnnotation *vma = (VoiceMemoryAnnotation *)selectedAnn;
NSLog(#"selected VMA = %#, blobkey=%#", vma, vma.blobkey);
}
else
{
NSLog(#"selected annotation (not a VMA) = %#", selectedAnn);
}
detailViewController = [[MemoryDetailViewController alloc]initWithNibName:#"MemoryDetailViewController" bundle:nil];
[self presentModalViewController:detailViewController animated:YES];
}
Instead of using a custom button action method, it can be easier to use the map view's calloutAccessoryControlTapped delegate method which lets you get access to the selected annotation more directly. In viewForAnnotation, remove the addTarget and just implement the delegate method:
- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view
calloutAccessoryControlTapped:(UIControl *)control
{
id<MKAnnotation> selectedAnn = view.annotation;
if ([selectedAnn isKindOfClass[VoiceMemoryAnnotation class]])
{
VoiceMemoryAnnotation *vma = (VoiceMemoryAnnotation *)selectedAnn;
NSLog(#"selected VMA = %#, blobkey=%#", vma, vma.blobkey);
}
else
{
NSLog(#"selected annotation (not a VMA) = %#", selectedAnn);
}
//do something with the selected annotation...
}
I am using XCode's Navigation-based Application template to create an app that centers around an UITableView. I am having some problems deleting some rows in the tableView.
In all of the tableViews cells I have added a button by subclassing UITableViewCell like this:
TableViewCell.h:
#implementation TableViewCell
#synthesize button;
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString*)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
RoutinesAppDelegate *appDelegate = (RoutinesAppDelegate *) [[UIApplication sharedApplication]delegate];
// Initialization code
button = [[UIButton buttonWithType:UIButtonTypeRoundedRect] retain];
[button setFrame:CGRectMake(320.0 - 90.0, 6.0, 80.0, 30.0)];
[button setTitle:#"Done" forState:UIControlStateNormal];
[button addTarget:appDelegate action:#selector(routineDone) forControlEvents:UIControlEventTouchUpInside];
button.hidden = YES;
[self.contentView addSubview:button];
}
return self;
}
when the button is pressed the method "routineDone" in RoutinesAppDelegate.m is called.
The method looks like this:
RoutinesAppDelegate.m
-(void) routineDone
{
if ([[NSFileManager defaultManager] fileExistsAtPath:[self todayListPath]])
{
int indexToRemove = buttonIndex + 1;
todayListArray = [NSMutableArray arrayWithContentsOfFile:[self todayListPath]];
[todayListArray removeObjectAtIndex:indexToRemove];
[todayListArray writeToFile:[self todayListPath] atomically:YES];
}
}
This method removes an item from the array that the tableView loads. This works fine.
MY PROBLEM:
The cell is not removed from the screen, when the button is clicked.
In RoutinesAppDelegate.m I tried to use
[tableView reloadData]
but it does not seem to work from outside the RootViewController class.
It would be nice if I somehow could use the deleteRowsAtIndexPaths method, but I dont know how to call that method from outside commitEditingStyle in the RootViewController class.
So my question is really: How can make the cell go away? :)
Without seeing all the code it's hard to tell, but you can try adding after
[todayListArray writeToFile:[self todayListPath] atomically:YES];
[self.tableView reloadData];
Also, you don't have to retain that button, and probably don't have to have it as a property
I'm trying to get the name of the background image from a custom button.
I call this method when I click over the UIButton:
-(void)getButtonImageName:(id)sender{
UIButton *resultButton = (UIButton *)sender;
NSLog(#" The button's image is %#.", resultButton.currentImage);
}
I can get the title and other properties from the button but not the background image name.
Anyone know how to do that? Thanks!
This is the simplest approach to get the current image of a custom button
-(void)yesButtonButtonCliked:(id)sender
{
UIButton *tmpYes = (UIButton *)sender;
[self ClearAllButtons:tmpYes.tag:#"YES"];
UIImage* selectedImg=[UIImage imageNamed:#"checkboxtick.png"];
if (tmpYes.currentImage == selectedImg )
{
}
else
{
[tmpYes setImage:[UIImage imageNamed:#"checkboxtick.png"] forState:UIControlStateNormal];
}
NSLog(#"Button tag is %d",tmpYes.tag);
}
There is no method to turn an UIImage into the name used with [UIImage imageNamed:].
do you abuse the image to store data?
What do you try to accomplish with the name of the image?
Edit:
I have several buttons, I want to get the image name when I press a button, switch to another view controller and load that image with the name.
No need to do this, just pass the UIImage instance.
If you want to show a different image based on the name, like you have a thumbnail and want to show the big version in another view controller you have to get the image name from your data model and pass it to the view controller.
How do you decide which button gets which image?
Following on Matthias's answer "No need to do this, just pass the UIImage instance," which was the solution for me, with the specific code for fellow newbs:
Calling .m:
-(void) prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
//Create an instance of the destination controller
dmImageZoom *dmz = (dmImageZoom *)[segue destinationViewController];
//Get the tapped button (the image was in a button) into a button object
UIButton *btnImg= sender;
//Get the button's image object
UIImage *img=btnImg.currentImage;
//Pass the image object to the destination controller's instance variable
dmz.imgPassed=img;
Called .h:
#interface dmImageZoom : UIViewController {
//Instance variable
UIImage *imgPassed;
}
//Property for the object that presents the image
#property (strong, nonatomic) IBOutlet UIImageView *imgView;
//Property that enables it to be assigned a value
#property (strong, nonatomic) IBOutlet UIImage *imgPassed;
Called .m:
- (void)viewDidLoad
{
...
imgView.contentMode = UIViewContentModeScaleAspectFit;
//Assign the image to view object
imgView.image=imgPassed;
#fluchtpunkt As you say, I have some thumbnails that are UIbuttons with a background image. What happens is that I'm creating as follows for the category selected in the previous view controller. Something like this...
if( [strSection isEqualToString:#"animals"] ){
[button_thumbnail_1 setBackgroundImage:[UIImage imageNamed:#"image1.png"] forState:(UIControlState)normal];
[button_thumbnail_2 setBackgroundImage:[UIImage imageNamed:#"image2.png"] forState:(UIControlState)normal];
}
if( [strSection isEqualToString:#"cars"] ){
[button_thumbnail_1 setBackgroundImage:[UIImage imageNamed:#"image1.png"] forState:(UIControlState)normal];
[button_thumbnail_2 setBackgroundImage:[UIImage imageNamed:#"image2.png"] forState:(UIControlState)normal];
}
}
Thanks for response
You could inherit UIButton:
//.h
#interface MySpecialButton : UIButton {
int mySpecialAttribute;
}
#property (nonatomic) int mySpecialAttribute;
- (void) setBackgroundImage;
- (UIImage*) getImage;
#end
And use your special attribute to both set and return the image name:
//.m
#implementation LetterButton
#synthesize mySpecialAttribute;
- (void) setBackgroundImage {
[self setBackgroundImage:[self getImage] forState:UIControlStateNormal];
[self setBackgroundImage:[self getImage] forState:UIControlStateDisabled];
}
- (UIImage*) getImage {
NSString *fileName = [NSString stringWithFormat:
#"My_special_image_%i.png", mySpecialAttribute];
return [UIImage imageNamed:fileName];
}
#end
Set the class of the button in the xib:
UIButton *senderButton = (UIButton*)sender;
if ([senderButton.currentTitle isEqualToString:#"Hello"]) {
[senderButton setTitle:#"H" forState:UIControlStateNormal];
}
else {
[senderButton setTitle:#"Hello" forState:UIControlStateNormal];
forState:UIControlStateNormal];
}
iOS 11 Swift 4.2
#objc func btnWishListAddRemove(_ sender:UIButton){
let index = sender.tag
let product_id = array_packageList[index].product_id!
if sender.currentImage == UIImage(named: "wish list"){
sender.setImage(UIImage(imageLiteralResourceName: "wishlistfill"), for: .normal)
}
else if sender.currentImage == UIImage(named: "wish list fill"){
sender.setImage(UIImage(imageLiteralResourceName: "wishlist"), for: .normal)
}
}
Wanted some help with a problem with mapkit I am facing. Should be a silly problem or I have missed out something while going through the mapkit framework.
Here is the senario.
I am placing multiple annotation on the map when the user performs some search like pizza.
Added button for the right annotation view, on click which opens a next detail view. The problem is how to send some information to the next view, for example I add index to annotations while creating them, now I want to access this information from annotation, and pass it to the next view via the selector set on the button.
I have checked all the mapkit delicate, but don't find a one where I can map this information with the next view and annotation.
Hope I have not confused you guys in my question. Please let me know I will reframe it.
Thaking in advance.
When you create the UIButton for the annotation, set the tag property (tag is an NSInteger property of UIView) to an id or array index that identifies the relevant object. You can then retrieve that tag value from the sender parameter to your selector.
Edit: here's some sample code.
You create your annotation view and associate the button in your delegate's -mapView:viewForAnnotation: method:
- (MKAnnotationView *)mapView:(MKMapView *)mapView viewForAnnotation:(id <MKAnnotation>)annotation {
// Boilerplate pin annotation code
MKPinAnnotationView *pin = (MKPinAnnotationView *) [self.map dequeueReusableAnnotationViewWithIdentifier: #"restMap"];
if (pin == nil) {
pin = [[[MKPinAnnotationView alloc] initWithAnnotation: annotation reuseIdentifier: #"restMap"] autorelease];
} else {
pin.annotation = annotation;
}
pin.pinColor = MKPinAnnotationColorRed
pin.canShowCallout = YES;
pin.animatesDrop = NO;
// now we'll add the right callout button
UIButton *detailButton = [UIButton buttonWithType:UIButtonTypeDetailDisclosure];
// customize this line to fit the structure of your code. basically
// you just need to find an integer value that matches your object in some way:
// its index in your array of MKAnnotation items, or an id of some sort, etc
//
// here I'll assume you have an annotation array that is a property of the current
// class and we just want to store the index of this annotation.
NSInteger annotationValue = [self.annotations indexOfObject:annotation];
// set the tag property of the button to the index
detailButton.tag = annotationValue;
// tell the button what to do when it gets touched
[detailButton addTarget:self action:#selector(showDetailView:) forControlEvents:UIControlEventTouchUpInside];
pin.rightCalloutAccessoryView = detailButton;
return pin;
}
Then in your action method, you'll unpack the value from tag and use it to display the right detail:
-(IBAction)showDetailView:(UIView*)sender {
// get the tag value from the sender
NSInteger selectedIndex = sender.tag;
MyAnnotationObject *selectedObject = [self.annotations objectAtIndex:selectedIndex];
// now you know which detail view you want to show; the code that follows
// depends on the structure of your app, but probably looks like:
MyDetailViewController *detailView = [[MyDetailViewController alloc] initWithNibName...];
detailView.detailObject = selectedObject;
[[self navigationController] pushViewController:detailView animated:YES];
[detailView release];
}
In the Annotation view, is it possible to grab,say, the Title or Subtitle or any other information you used while creating pins? What i am looking to do is have a certain image popup in the annotation based on one of those variables.
#import "MapPin.h"
#implementation MapPin
#synthesize coordinate;
#synthesize title;
#synthesize subtitle;
#synthesize indexnumber;
#synthesize imageFile;
-(id)initWithCoordinates:(CLLocationCoordinate2D)location
placeName: placeName
description:description
indexnum:indexnum
imageFileLoc:imageFileLoc{
self = [super init];
if (self != nil) {
imageFile=imageFileLoc;
[imageFile retain];
indexnumber=indexnum;
[indexnumber retain];
coordinate = location;
title = placeName;
[title retain];
subtitle = description;
[subtitle retain];
}
return self;
}
-(void)addAnnotations {
// Normally read the data for these from the file system or a Web service
CLLocationCoordinate2D coordinate = {35.9077803, -79.0454936};
MapPin *pin = [[MapPin alloc]initWithCoordinates:coordinate
placeName:#"Keenan Stadium"
description:#"Tar Heel Football"
indexnum:#"1"
imageFileLoc:#"owl.jpg"];
[self.map addAnnotation:pin];
Another option:
You can implement these methods:
- (void)mapView:(MKMapView *)mapView didSelectAnnotation:(MKAnnotationView *)view;
- (void)mapView:(MKMapView *)mapView didDeselectAnnotation:(MKAnnotationView *)view;
- (IBAction)onClick1:(id)sender {
// Make sure it's a UIButton
if (![sender isKindOfClass:[UIButton class]])
return;
NSString *title = [(UIButton *)sender currentTitle];
}
I understand how to get the title and other current values but I don't see how I can get the value of the tag property.
I've got a test project here where I just used:
NSInteger i = [sender tag];
You can simply call:
NSInteger the_tag = ((UIView*)sender).tag;
Each UIButton is a subclass of UIView which contains the tag property.