I want to place a button in each cell of a table. I want the target to be a class that I'm using to handle all of the API calls. I want the button's method to be in the friendController class, not in the UITableView's View Controller.
[cell.deleteButton addTarget:friendController
action:#selector(deleteFriend: forID:cell.idNumber)
forControlEvents:UIControlEventTouchUpInside];
Where friendController is the class with the API calls, and cell.idNumber is an instance variable of each cell containing the ID number of the user associated with that cell.
I know (well, I presume) that I could set the cell itself as the target and then have a method which looks like:
-(IBAction)deleteFriend:(id)sender {
[friendController deleteFriend:self.idNumber];
}
Is that correct? It doesn't seem like an elegant way of doing this. Is there a way to do it the way I want to, or is there a better way to do it?
EDIT: I emboldened the last crucial part of my question. I want the button's action to be a method in another class (friendController). friendController is a class I created to house all of the API calls and business logic. If I set the target to friendController, must the action be a method in that class?
You can do this two ways.
Subclassing UIButton
The first would be to subclass UIButton and create a property for the idNumber. So you would first set the property like so:
cell.deleteButton.idNumber = /* set id here */
[cell.deleteButton addTarget:friendController
action:#selector(deleteFriend:)
forControlEvents:UIControlEventTouchUpInside];
and then access it inside your deleteFriend: method:
- (IBAction)deleteFriend:(id)sender {
YourButtonClass *button = (YourButtonClass *)sender;
[friendController deleteFriend:button.idNumber];
}
Retrieve the idNumber directly from the cell
Described in this post you can grab the index path of the button's cell and then access the idNumber from there.
Use the tag property of the button.
cell.deleteButton.tag = cell.idNumber;
[cell.deleteButton addTarget:friendController action:#selector(deleteFriend:) forControlEvents:UIControlEventTouchUpInside];
In the method, you can use the tag to retrieve the cell idNumber
-(IBAction)deleteFriend:(id)sender {
int idNumber = ((UIButton *) sender).tag;
[friendController deleteFriend: idNumber];
}
With 2 parameters indicating the control that sends the message and the event that triggered the message:
action:#selector(buttonAction:Second:)
- (void)viewDidLoad
{
[super viewDidLoad];
UIButton *btn = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[btn addTarget:self action:#selector(buttonAction:Second:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)buttonAction:(NSString *)firstCharacter
Second:(NSNumber *)second
{
}
Since the button is inside a UITableViewCell, you might consider subclass a UITableViewCell with your property idNumber(I don't like reuse Tag because it can only be an integer) and a delegate:
#protocol MyTableViewCellDelegate;
#interface MyTableViewCell : UITableViewCell
#property (nonatomic, assign) NSInteger idNumber;
#property (nonatomic, weak) id<MyTableViewCellDelegate> delegate;
#end
#protocol MyTableViewCellDelegate <NSObject>
-(void) shouldDeleteFriendById:(NSInteger)idNumber;
#end
And handle the button action inside the MyTableViewCell implementation:
- (void) myInit
{
UIButton* button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, self.frame.size.width, self.frame.size.height)];
[button addTarget:self action:#selector(onButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:button];
}
- (IBAction)onButtonClicked:(id)sender
{
[self.delegate shouldDeleteFriendById:self.idNumber];
}
Then implement cell's delegate in your tableViewController:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
MyTableViewCell *cell = (MyTableViewCell*)[tableView dequeueReusableCellWithIdentifier:#"MyCell" forIndexPath:indexPath];
// Configure the cell...
cell.idNumber = indexPath.row;
cell.delegate = self;
return cell;
}
- (void) shouldDeleteFriendById:(NSInteger)idNumber
{
NSLog(#"should delete by id:%d", idNumber);
}
Related
I'm creating a new UIViewController (call it MyViewController), and adding the view (MyView) as a subview of a TableViewCell. Within MyView , there's a button. That button is created programmatically during MyViewController's init, as such:
-(id)initWithFrame:(CGRect)frame {
self = [super init];
if(self) {
[self.view setFrame:frame];
_yesButton = [[UIButton alloc] initWithFrame:CGRectMake(frame.size.width-150, 40, 140, 30)];
[_yesButton setTitle:#"Yeah!" forState:UIControlStateNormal];
[_yesButton addTarget:self action:#selector(didClick) forControlEvents:UIControlEventTouchUpInside];
[self.view addSubview:_yesButton];
}
return self;
}
Now, seems straightforward. It displays properly, everything looks great in the simulator.
But when I click on the "_yesButton" within MyView, I get a crash with this error:
-[_UITableViewCellSeparatorView didClick]: unrecognized selector sent to instance 0x7b7f7ec0
What? When did "_UITableViewCellSeparatorView" come into the equation? I specifically told the _yesButton to set the Target to "self", so the selector should be sent to MyViewController, right? I could even imagine it getting tripped up and sending it to the UITableViewCell, since MyView is embedded within a TableViewCell, but why a SeperatorView?
Can anyone tell me how to get my _yesButton to send the call back to the MyViewController that it's being created within? And for bonus points, can you explain how "_UITableViewCellSeparatorView" became a thing in this conversation at all?
Edit: Here's how I'm building the cell in the TableView, and adding MyView to it. Note that I'm deliberately not using dequeuing for this row, although that might change if it's the source of the problem.
UITableViewCell *cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:#"MyViewCell"];
MyViewController *myViewController = [[MyViewController alloc] initWithFrame:cell.contentView.frame];
[cell.contentView addSubview:myViewController.view];
return cell;
And the didClick method is currently empty, (it never even gets there, so I haven't gotten that far in writing it), but it's currently defined within MyViewController simply as:
-(void)didClick {
}
Solution #1
Actually this is because you MyViewController is not retain by ARC. The dealloc method id being called. Add ans instance of your controller in your UITableViewController will fix the issue and make ARC retains your controller.
Solution #2
Try something like this.
Create a custom UITableViewCell :
MyCustomCell.h :
#import <UIKit/UIKit.h>
#interface MyCustomCell : UITableViewCell
#property (strong, nonatomic) UIButton *yesButton;
-(id)initWithFrame:(CGRect)frame;
#end
MyCustomCell.m :
#import "MyCustomCell.h"
#implementation MyCustomCell
- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
-(id)initWithFrame:(CGRect)frame {
self = [super init];
if(self) {
[self.view setFrame:frame];
_yesButton = [[UIButton alloc] initWithFrame:CGRectMake(frame)];
[_yesButton setTitle:#"Yeah!" forState:UIControlStateNormal];
[self.contentView addSubview:_yesButton];
}
return self;
}
#end
In your UITableViewControllerin the viewDidLoad function :
[self.tableView registerClass:[MyCustomCell class] forCellReuseIdentifier:CellIdentifier];
Then in your tableView:cellForRowAtIndexPath:
MyCustomCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifierr forIndexPath:indexPath];
if (cell == nil) {
cell = [[MyCustomCell alloc] initWithFrame:frame];
[cell.yesButton addTarget:self action:#selector(didClick) forControlEvents:UIControlEventTouchUpInside];
}
And now implement the following in your UITableViewController:
-(void)didClick {
// The following identify the in which cell the action has been triggered
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
if (indexPath != nil)
{
// Do whatever you want for the given cell
}
}
I think that you're adopting a wrong approach.
The view cells are not living in memory like views. We may say that once the drawing done, it does not exists anymore.
For all cells operations, you should set you TableViewController as target, and handle the click method in this controller.
Only this controller has a global knowledge of the data in the cells and the cells formats.
You can set the tag of the button to the row index to know what row has been clicked.
your didClick selector is also not correct. It should have this form
-(void)didClick:(id)sender
{
NSInteger clickedRow = ((UIButton*)sender).tag
}
And so be connected like this:
[_yesButton addTarget:<YourTableController> action:#selector(didClick:) forControlEvents:UIControlEventTouchUpInside];
The problem is that the instance you thing to be kind of MyViewController, it is no. It is instead UITableViewCellSeparatorView.
The biggest error, is that you are trying to init a view controller by a method which is not supported from the class UIViewController, but is instead part of UIView:
- (id)initWithFrame:(CGRect)frame
I suggest you to review it before going ahead.
I'm learning UITableView in Objective-C. Could you hint me how to access UIButton inside UITableviewHeaderFooterView subclass from my UIViewController class? Programatically, as I don't use IB.
Full code: https://gist.github.com/tomnaz/3d790b308d305af8b98c
[[??? btnEdit] addTarget:self
action:#selector(addNewItem:)
forControlEvents:UIControlEventTouchUpInside];
Don't do this in viewDidLoad, do it in viewForHeaderInSection: where you have a pointer to your header view.
- (UIView *)tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
static NSString *headerReuseIdentifier = #"TableViewSectionHeaderViewIdentifier";
ItemsHeaderView *sectionHeaderView = [tableView dequeueReusableHeaderFooterViewWithIdentifier:headerReuseIdentifier];
[sectionHeaderView.btnEdit addTarget:self action:#selector(addNewItem:) forControlEvents:UIControlEventTouchUpInside];
return sectionHeaderView;
}
When you initially create the buttons, you can store them in a property or array so you can easily find them later.
Alternatively, you could set a tag on the button, and then call viewWithTag: on your UITableviewHeaderFooterView subclass to find the button.
I added a button on the custom cell. It pushes a view. I created property for button on CustomTableViewCell.h
#property (weak, nonatomic) IBOutlet UIButton *selectedMapButton;
I want to use selectedMapButton for push a view on TableViewController.m
I tried this code, but something is not right.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cord = cellSight[#"S_Coodinates"];
[cell.selectedMapButton addTarget:self action:#selector(sightMapButton:cord) forControlEvents:UIControlEventTouchUpInside];
//When I call sightMapButton method. It gives an error
return cell;
}
- (void)sightMapButton: (NSString *)withCoordinates
{
TOCGSightseeingMapKitViewController *mapKitController = [[TOCGSightseeingMapKitViewController alloc] init];
mapKitController.sightCoordinates = withCoordinates;
[self.navigationController pushViewController:mapKitController animated:YES];
}
How can I solve it? Thanks.
Alternate way with delegate/protocol (my best way);
- Create a "CustomCellDelegate" class. (base class is NSObject)
- Open CustomCellDelegate.h and add this code;
#protocol CustomCellDelegate <NSObject>
- (void) uiTapButtonWithParameter:(NSString *)parameter;
#end
- Close CustomCellDelegate.h
- Open CustomCell.h and add this code;
#import "CustomCellDelegate"
#interface CustomCell : UITableViewCell
#property (nonatomic) id <CustomCellDelegate> delegate;
- Close CustomCell.h
- Open CustomCell.m and add code inside to UIButton action method;
[_delegate uiTapButtonWithParameter:#"hello world!"];
- Close CustomCell.m
- Open CustomViewController.h and add this code;
#interface CustomViewController : UIViewController <UITableViewDelegate, UITableViewDataSource, CategoryItemsCellDelegate>
- Close CustomViewController.h and open CustomViewController.m
- Add this code;
- ( NSInteger )tableView:( UITableView * )tableView numberOfRowsInSection:( NSInteger )section; {
return 20;
}
- ( UITableViewCell * )tableView:( UITableView * )tableView cellForRowAtIndexPath:( NSIndexPath * )indexPath; {
CategoryItemsCell *cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
cell.delegate = self;
return cell;
}
- (void) uiTapButtonWithParameter:(NSString *)parameter{
NSLog(#"%#",parameter)
}
Mert kardeşim delegate ve protocol en doğru çözüm olacaktır ;)
You cannot specify your own parameters to button action handler. Its variant with 1 parameter will accept "sender", that is button that triggered the action. Your workflow should be following:
Find indexPath of cell where button was clicked
Get cellSight object corresponding to that indexPath
Fill your controller with info from cellSite object.
(somewhat pseudo-)Code may look like, assuming you have array of SiteObjects:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell = ... // create/init cell somehow
[cell.selectedMapButton addTarget:self action:#selector(sightMapButton:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
- (void)sightMapButton: (UIButton *)sender
{
NSIndexPath *indexPath = ... // Find indexPath of button
SiteObject cellSight = sitesArray[indexPath.row];
TOCGSightseeingMapKitViewController *mapKitController = [[TOCGSightseeingMapKitViewController alloc] init];
mapKitController.sightCoordinates = cellSight[#"S_Coodinates"];
[self.navigationController pushViewController:mapKitController animated:YES];
}
How to find indexPath of cell containing button you can find for example in my other answer
in your custom cell class add action for button and define a protocol(Delegate) invoke the delegate method from button action. in cellForRowAtIndexPath set the delegate of your cell to self and implement the delegate method.
let me know if you've any query.
I suppose the problem is in #selector(sightMapButton:cord) which is used to retrieve method's address. Most likely you are not allowed to pass arguments here.
As an alternative to finding the indexPath of cell where button was clicked:
You can subclass UIButton and add required information to it as a property. Then you can go the way Vladimir suggested, and get your cord from sender after casting it to your Button class.
Vladimir's answer works correctly and I tried this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
cell.selectedMapButton.tag = indexPath.row;
[cell.selectedMapButton addTarget:self action:#selector(sightMapButton:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)sightMapButton:(UIButton *)sender
{
TOCGSightseeingMapKitViewController *mapKitController = [[TOCGSightseeingMapKitViewController alloc] init];
mapKitController.sightCoordinate = self.sights[sender.tag][#"S_Coordinates"];
[self.navigationController pushViewController:mapKitController animated:YES];
}
It is also works.
We can use blocks to get cellIndexPath .
Instead of using CellDelegate to call button actions Blocks will be
the better option .
#interface WACustomCell : UITableViewCell
#property(readwrite,copy)void (^cellCallBackMethod)(NSInteger rowIndexPath);
-(IBAction)buttonTappedFromCell:(id)sender;
#end
#implementation WACustomCell
#synthesize cellCallBackMethod;
-(IBAction)buttonTappedFromCell:(id)sender{
self.cellCallBackMethod(0);
//any value can be given as we will retrive this value from CellForRowAtIndex method
}
In View Controller
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
cel = ....
cell.cellCallBackMethod = ^(NSInteger cellIndex){
[self cellButtonTappedFromCellWithIndex:cellIndex];
};
return cell;
}
-(void)cellButtonTappedFromCellWithIndex:(NSInteger )cellIndex{
SiteObject cellSight = sitesArray[cellIndex];
TOCGSightseeingMapKitViewController *mapKitController = [[TOCGSightseeingMapKitViewController alloc] init];
mapKitController.sightCoordinates = cellSight[#"S_Coodinates"];
[self.navigationController pushViewController:mapKitController animated:YES];
}
You should add the method in Custom table view cell.
after a lot of search not able to figure out my problem. i am new to iOS development,, so please don't mind if i am wrong…. :P :)
i have a button in my collection view cell , when button is pressed it should change its background image and get the contents of the cell on which cell's button is pressed.
i am doing it like this
in my .h file
#interface MyCell : UICollectionViewCell
#property (nonatomic, strong) UIButton *button;
#end
#interface DetailInvoicing : UIViewController
in .m file
#implementation MyCell
- (id)initWithFrame:(CGRect)frame
{
if (self = [super initWithFrame:frame])
{
self.button = [UIButton buttonWithType:UIButtonTypeCustom];
self.button.frame = CGRectMake(175, 1, 50, 30);
self.button.backgroundColor = [UIColor clearColor];
[self.button addTarget:self action:#selector(buttonClicked:) forControlEvents:UIControlEventTouchUpInside];
[self.button setBackgroundImage:[UIImage imageNamed:#"buy.png"]forState:UIControlStateNormal];
[self.contentView addSubview:self.button];
}
return self;
}
- (void)buttonClicked:(UIButton *)sender
{
NSLog(#"button clicked!");
self.button.backgroundColor = [UIColor lightGrayColor];
[self.button setTitle:#"Sold" forState:UIControlStateNormal];
[self.button setBackgroundImage:[UIImage imageNamed:nil]forState:UIControlStateNormal];
}
#end
in viewdidload method
[self CollectionLoad];
and the CollectionLoad method is
// Use sqlite query to fetch data and save it in array, then
myCollection.delegate = self;
myCollection.dataSource = self;
[myCollection reloadData];
myCollection.backgroundColor=[UIColor clearColor];
[myCollection registerClass:[MyCell class] forCellWithReuseIdentifier:#"CellID"];
then
use datasource and delegate methods
on taping the button of desired cell button BG image is changed. and there is also some other random cell's button Background image is changed…
what is the problem here and second thing is
how to get the contents of the cell on this button tap....
Your problems are reuse and data management.
First, you are reusing cells by dequeuing them from a pool. You then add a new button with default configuration. So, each time the cell is reused you will add a new button (even if one is already there) and you won't give it any special configuration (part of your data management issue).
For data management, just changing the status of a button isn't enough - you also need to update your data model with the new status represented by the button selection. Then, next time you display that status you can set it to the correct value.
The general approach you should take is to subclass UICollectionViewCell so that you can add your button (only once). The cell subclass should be the target of the button (not the controller). The cell subclass could also have an #property which is the part of the data model associated with it so that it can update the contents when the button is tapped. And, when the property is set the cell can update itself based on the contents (set the appropriate button configuration). Alternatively, the cell could callback to the controller with the appropriate information (and the controller could configure the button in the data source methods).
In my tableview, I have several different custom cells. In one of them, it has a button. This button brings up another view controller. However, It is not needed until the tableview has fully loaded. In cellForRowAtIndexPath I set up all my different custom cells. I can uncomment [buttonCell.myButton setHidden:YES]; and it will hide my button. See below.
else if (indexPath.section == 3)
{
ButtonCell *buttonCell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
//[buttonCell.myButton setHidden:YES];
cell = buttonCell;
}
return cell;
However, I want to then unhide the button after the tableview loads. I finish loading all my arrays in another method where I call reloadData. In that method, I tried to unhide the button by doing this..
[ButtonCell.myButton setHidden:NO];
But the compiler gives me a warning that property myButton is not found in ButtonCell. Does anyone have any ideas how to go about unhiding my button. What am I doing wrong, and what do I not get! Thanks for all your help.
EDIT 1
My button cell class is...
.h
#import
#interface ButtonCell : UITableViewCell
#property (strong, nonatomic) IBOutlet UIButton *myButton;
- (IBAction)YDI:(id)sender;
#end
.m
#import "ButtonCell.h"
#import "AnotherWebViewController.h"
#implementation ButtonCell
- (id)initWithStyle:(UITableViewCellStyle)style reuseIdentifier:(NSString *)reuseIdentifier
{
self = [super initWithStyle:style reuseIdentifier:reuseIdentifier];
if (self) {
// Initialization code
}
return self;
}
- (void)setSelected:(BOOL)selected animated:(BOOL)animated
{
[super setSelected:selected animated:animated];
// Configure the view for the selected state
}
- (IBAction)YDI:(id)sender
{
}
#end
EDIT 2
With everyone's help that answered (thank you all) I have gotten a bit further, but the button is not showing itself. So I still hide the button in cellForRowAtIndexPath, that works as should. Then in my method that I reload the data in I put the following code.
NSIndexPath *index = [NSIndexPath indexPathForRow:0 inSection:3];
ButtonCell *buttonCell = (ButtonCell *) [self.tableView cellForRowAtIndexPath:index];
[buttonCell.myButton setHidden:NO];
The ButtonCell with the button is always the fourth section (counting the first as 0) and it only has one row. Any other help would be appreciated. Almost there!
EDIT 3
Got it! However, it was due to a comment that I was able to figure it out. Thanks to #A-Live. Although I do know how to get the cell in a method outside of cellForRowAtIndexPath thanks to ElJay. So I am giving him the check since I learned something new which is why we post questions anyway. So inside my method cellForRowAtIndexPath is where I hide/show the button. I have a BOOL in my App called finished, it is originally set to true. When the table view ends loading it is set to false. So I just used this bool to show/hide the button.
else if (indexPath.section == 3)
{
ButtonCell *buttonCell = [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
if (!_finished)
{
[buttonCell.myButton setHidden:YES];
}else{
[buttonCell.myButton setHidden:NO];
}
cell = buttonCell;
}
return cell;
Once again this is only part of my cellForRowAtIndexPath method. Thanks once again for all the help. I was surprised to see so many answers! Thanks.
Make the property publicaly accessible.
#property (nonatomic, retain) UIButton *myButton;
Then in cellForRowAtIndexpath
ButtonCell *buttonCell =(ButtonCell *) [tableView dequeueReusableCellWithIdentifier:#"ButtonCell"];
myButton belongs to a cell. You will need to get an instance of that UITableViewCell and then you can unhide it, this assumes you want to modify the cell's objects outside of cellForRowAtIndexPsth or willDisplayCell.
In your code
[ButtonCell.myButton setHidden:NO];
You are trying to use the object class name instead of the object name. You need to get the cell that contains your button
buttonCell = [tableView cellForRowAtIndexPath:indexPath];
buttonCell.myButton.hidden = NO;
Mistake in uppercase maybe ?
[buttonCell.myButton setHidden:NO]; // Trying to access instance variable
Instead of :
[ButtonCell.myButton setHidden:NO]; // Trying to access class variable
Do you have a public accessor for that property in the header file of ButtonCell? Something like #property (nonatomic, retain) UIButton *myButton;
This is how I usually see such a compiler warning.