In my app I've written the following code in a UICollectionView cellForRowAtIndexpath like this [CollectionView Cell is a custom cell]
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath{
CollectionViewCell *cell = (CollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"localMusicCell" forIndexPath:indexPath];
[[cell.downImageButton viewWithTag:indexPath.item] addTarget:self action:#selector(downImgClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
and the target method like this:
-(void)downImgClicked:(UIButton*)button{
}
And there are four items in UICollectionView, but for first item only this target method is called and for rest of them its not even triggering why?
try alternate way
cell.downImageButton.tag = indexPath.item;
[cell.downImageButton addTarget:self action:#selector(downImgClicked:) forControlEvents:UIControlEventTouchUpInside];
call method like
-(void)downImgClicked:(UIButton*)button{
NSLog (#"selected index ==%#",button.tag);
}
try this way,
override func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
let cell = collectionView.dequeueReusableCellWithReuseIdentifier(reuseIdentifier, forIndexPath: indexPath) as! AllCollectionViewCell
cell.playBtn.tag = indexPath.row
cell.playBtn.addTarget(self, action: Selector("buttonAction:"), forControlEvents: .TouchUpInside)
return cell
}
target method:
func buttonAction(sender:UIButton!) {
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier("SampleViewController") as! PlayViewController
self.navigationController?.pushViewController(controller, animated: true)
}
its working for me, hope its helpful
- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath
{
CollectionViewCell *cell = (CollectionViewCell*)[collectionView dequeueReusableCellWithReuseIdentifier:#"localMusicCell" forIndexPath:indexPath];
cell.downImageButton.tag =indexPath.row;
[cell.downImageButton addTarget:self action:#selector(downImgClicked:) forControlEvents:UIControlEventTouchUpInside];
return cell;
}
and the target method like this:
-(void)downImgClicked:(UIButton*)button
{
long selectedBtnRow;
selectedBtnRow = button.tag;
NSLog (#"selected index ==>%ld",selectedBtnRow);
}
I have button on uitableviewcell which have target on function like this:
likeButton?.addTarget(self, action: "likeButtonTapped:", forControlEvents: UIControlEvents.TouchUpInside)
inside the function I set my button title like this:
sender.setTitle("\(addedLikeCount) Likes", forState: UIControlState.Normal)
But whenever I scroll the view up or down, my button title change to it's default. Why is that happen? Is there any way I can fix this without reloading table?
Feel free to give me any advice, doesn't matter in swift or objective c.
UPDATE
So I did code below on my function:
self.likeArray.replaceObjectAtIndex(index!, withObject: addedLikeCount)
sender.setTitle("\(self.likeArray[index!]) Likes", forState: UIControlState.Normal)
And this on my uitableviewcell:
var totalLike = likeArray[indexPath.row] as? String
currentLikeCount = totalLike
likeButton?.setTitle("\(totalLike)", forState: UIControlState.Normal)
It worked, but when I scroll the tittle become default again
Tableview cell reloads everytime it's will be shown on the screen after scroll.
You must provide reusable identifier for your cell on you storyboard or XIB
Objective-C variant:
- (UITableViewCell*) tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *CellIdentifier = #"your_cell_identifier_on_storyboard_or_cell_xib";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (!cell)
{
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
}
/// datasource code here
return cell;
}
If you want to store all your context for specific indexpath, as on your question - to hold all buttons caption, use
dequeueReusableCellWithIdentifier: forIndexPath:
instead of
dequeueReusableCellWithIdentifier:
Warning: dequeuing cells for specific indexpath may slow performance
Do you use this in your code ?
tableView.dequeueReusableCellWithIdentifier("reuseIdentifer")
If yes, you have to save the state of each cell.
Because every time you scroll up and down, TableView will bring back the previous cell that outside of the screen.
What you need is setting the new state of the cell correspond to indexPath in cellForRowAtIndexPath method something like this
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if let cell = tableView.dequeueReusableCellWithIdentifier("reuseIdentifer") as? UITableViewCell{
cell.title = titles[indexPath.row]
return cell
}
...
...
}
Create a function which check for which row the title is changed and vice versa.For eg
-(BOOL)checkForSelectedRow:(NSIndexPath *)path
Now Create array which store for which button the title is changed,So whenever the button is tapped u add it to the array in your likeButtonTapped
Now in
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
if([self checkForSelectedRow:indexPath]){
//change the title of button
}
else{
//default of button
}
As Oyeoj commented
Cell is changing because "the cell is being reuse and therefore refreshed to its original setup under cellForRowAtIndexPath, solution is to have a global variable then setup on reload under cellForRowAtIndexPath."
For your desired task you have to store selected cell index some where i.e. your button on someIndex cell has Likes title.
for Objective-C
in .h
NSMutableArray likeArray;
in .m
in viewDidLoad
likeArray=[[NSMutableArray alloc]init];
in cellForRowAtIndexPath check
if ([likeArray containObject:[NSNumber numberWithInteger:indexPath.row]])
{
[button setTitle:#"Likes", forState: UIControlStateNormal];
}
else
{
[button setTitle:#"Some Other Title", forState: UIControlStateNormal];
}
in didSelectRowAtIndexPath -
NSNumber *cellIndex=[NSNumber numberWithInteger:indexPath.row];
if(![likeArray containObject:cellIndex])
{
[likeArray addObject:cellIndex];
}
[table reloadData];
Goal: when a user selects a cell, a button is added to that cell. Within my didSelectRowAtIndexPath function I have the following:
UIButton *downloadButton = [[UIButton alloc] init];
downloadButton.titleLabel.text = #"Download";
[downloadButton setFrame:CGRectMake(40, 0, 100, 20)];
[[self.tableView cellForRowAtIndexPath:indexPath].accessoryView addSubview:downloadButton];
[[self.tableView cellForRowAtIndexPath:indexPath].accessoryView setNeedsLayout];
[downloadButton release];
Unfortunately that doesn't seem to do anything. Am I redrawing the cell correction? Do I need to add it another way?
Try this block of code instead of the block you provided above:
UIButton *downloadButton = [UIButton buttonWithType:UIButtonTypeRoundedRect];
[downloadButton setTitle:#"Download" forState:UIControlStateNormal];
[downloadButton setFrame:CGRectMake(0, 0, 100, 35)];
[tableView cellForRowAtIndexPath:indexPath].accessoryView = downloadButton;
This should display the button, but you will still need to hook up some kind of selector to it using addTarget. (I am not sure if listening in for the accessoryButtonTappedForRowWithIndexPath delegate will work in this case, try that first and see if it fires on your button press.)
I had the same problem. Attempting to set the accessoryView to a UIButton which had an image caused it to not appear.
The trick was to call [UIButton sizeToFit], to ensure its frame is set properly.
Assign the button as the accessory view rather than a subview of the accessory view.
UITableViewCell* cell = [tableView cellForRowAtIndexPath:indexPath];
cell.accessoryView = downloadButton;
Swift 4 and above: Add button to UITableViewCell's Accessory View
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
{
let cell = Table.dequeueReusableCell(withIdentifier: "identifier", for: indexPath)
let accessoryButton = UIButton(type: .custom)
accessoryButton.addTarget(self, action: #selector(deleteCell(sender:)), for: .touchUpInside)
accessoryButton.setImage("Add_image", for: .normal)
accessoryButton.frame = CGRect(x: 0, y: 0, width: 40, height: 40)
accessoryButton.contentMode = .scaleAspectFit
cell.accessoryView = accessoryButton as UIView
return cell
}
Add Selector Method
func deleteCell(sender: AnyObject)
{
let pointInTable: CGPoint = sender.convert(sender.bounds.origin, to: self.Table)
let cellIndexPath = self.Table.indexPathForRow(at: pointInTable)
let point = cellIndexPath!.row
}
let button = UIButton(type:.roundedRect)
button.setTitle("A", for: .normal)
button.sizeToFit()
cell.accessoryView = button
Try This:
[[self.tableView cellForRowAtIndexPath:indexPath].contentView addSubview:downloadButton];
And remember to delete that button when the cell is being reused.
Here is my example for a the full solution to your request:
In my UITableViewCell subclass (I call it RNWNewItemCell):
-(void)configureCell...
{
// create the button
self.btnSeekDuplicate = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 22, 22)];
// just make it yellow until we get our real image..
self.btnSeekDuplicate.backgroundColor = [UIColor yellowColor];
// add the handler for the button press
[self.btnSeekDuplicate addTarget:self
action:#selector(seekDuplicateButtonWasPressed)
forControlEvents:UIControlEventTouchUpInside];
// make it visible in the table cell...
[self setAccessoryView:self.btnSeekDuplicate];
}
- (void)seekDuplicateButtonWasPressed
{
do something...
}
In my Table Code that uses the cell...
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
RNWNewItemCell *aNewItemCell = [tableView dequeueReusableCellWithIdentifier:cellIdentifierForNewItemCell forIndexPath:indexPath];
[aNewItemCell configureCell...]
...
}
Note that accessoryButtonTappedForRowWithIndexPath is NOT called when you set the table cell's accessoryView. Probably because they assume you are using a view that responds to events.
Its always best to add any views that you are adding to a cell to cell.contentView.
Also try to check if the accessoryView is nil.
Swift
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("Cell", forIndexPath: indexPath) as! UITableViewCell
let accessoryButton = UIButton.buttonWithType(UIButtonType.ContactAdd) as! UIButton
cell.accessoryView = accessoryButton
return cell
}
use this :
cell.accessoryType=UITableViewCellAccessoryDetailDisclosureButton;
I have a UITableView with 5 UITableViewCells. Each cell contains a UIButton which is set up as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
[cell autorelelase];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
[button addTarget:self action:#selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
[button setTag:1];
[cell.contentView addSubview:button];
[button release];
}
UIButton *button = (UIButton *)[cell viewWithTag:1];
[button setTitle:#"Edit" forState:UIControlStateNormal];
return cell;
}
My question is this: in the buttonPressedAction: method, how do I know which button has been pressed. I've considered using tags but I'm not sure this is the best route. I'd like to be able to somehow tag the indexPath onto the control.
- (void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
// how do I know which button sent this message?
// processing button press for this row requires an indexPath.
}
What's the standard way of doing this?
Edit:
I've kinda solved it by doing the following. I would still like to have an opinion whether this is the standard way of doing it or is there a better way?
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
[cell autorelelase];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
[button addTarget:self action:#selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:button];
[button release];
}
UIButton *button = (UIButton *)[cell.contentView.subviews objectAtIndex:0];
[button setTag:indexPath.row];
[button setTitle:#"Edit" forState:UIControlStateNormal];
return cell;
}
- (void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
int row = button.tag;
}
What's important to note is that I can't set the tag in the creation of the cell since the cell might be dequeued instead. It feels very dirty. There must be a better way.
In Apple's Accessory sample the following method is used:
[button addTarget:self action:#selector(checkButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
Then in touch handler touch coordinate retrieved and index path is calculated from that coordinate:
- (void)checkButtonTapped:(id)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
if (indexPath != nil)
{
...
}
}
I found the method of using the superview's superview to obtain a reference to the cell's indexPath worked perfectly. Thanks to iphonedevbook.com (macnsmith) for the tip link text
-(void)buttonPressed:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...
}
Here's how I do it. Simple and concise:
- (IBAction)buttonTappedAction:(id)sender
{
CGPoint buttonPosition = [sender convertPoint:CGPointZero
toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
...
}
With Swift 4.2 and iOS 12, you can choose one the 5 following complete examples in order to solve your problem.
#1. Using UIView's convert(_:to:) and UITableView's indexPathForRow(at:)
import UIKit
private class CustomCell: UITableViewCell {
let button = UIButton(type: .system)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button.setTitle("Tap", for: .normal)
contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.button.addTarget(self, action: #selector(customCellButtonTapped), for: .touchUpInside)
return cell
}
#objc func customCellButtonTapped(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
guard let indexPath = tableView.indexPathForRow(at: point) else { return }
print(indexPath)
}
}
#2. Using UIView's convert(_:to:) and UITableView's indexPathForRow(at:) (alternative)
This is an alternative to the previous example where we pass nil to the target parameter in addTarget(_:action:for:). This way, if the first responder does not implement the action, it will be send to the next responder in the responder chain until until a proper implementation is found.
import UIKit
private class CustomCell: UITableViewCell {
let button = UIButton(type: .system)
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button.setTitle("Tap", for: .normal)
button.addTarget(nil, action: #selector(TableViewController.customCellButtonTapped), for: .touchUpInside)
contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
return cell
}
#objc func customCellButtonTapped(_ sender: UIButton) {
let point = sender.convert(CGPoint.zero, to: tableView)
guard let indexPath = tableView.indexPathForRow(at: point) else { return }
print(indexPath)
}
}
#3. Using UITableView's indexPath(for:) and delegate pattern
In this example, we set the view controller as the delegate of the cell. When the cell's button is tapped, it triggers a call to the appropriate method of the delegate.
import UIKit
protocol CustomCellDelegate: AnyObject {
func customCellButtonTapped(_ customCell: CustomCell)
}
class CustomCell: UITableViewCell {
let button = UIButton(type: .system)
weak var delegate: CustomCellDelegate?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button.setTitle("Tap", for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func buttonTapped(sender: UIButton) {
delegate?.customCellButtonTapped(self)
}
}
import UIKit
class TableViewController: UITableViewController, CustomCellDelegate {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.delegate = self
return cell
}
// MARK: - CustomCellDelegate
func customCellButtonTapped(_ customCell: CustomCell) {
guard let indexPath = tableView.indexPath(for: customCell) else { return }
print(indexPath)
}
}
#4. Using UITableView's indexPath(for:) and a closure for delegation
This is an alternative to the previous example where we use a closure instead of a protocol-delegate declaration to handle the button tap.
import UIKit
class CustomCell: UITableViewCell {
let button = UIButton(type: .system)
var buttontappedClosure: ((CustomCell) -> Void)?
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
button.setTitle("Tap", for: .normal)
button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)
contentView.addSubview(button)
button.translatesAutoresizingMaskIntoConstraints = false
button.centerXAnchor.constraint(equalTo: contentView.centerXAnchor).isActive = true
button.centerYAnchor.constraint(equalTo: contentView.centerYAnchor).isActive = true
button.topAnchor.constraint(equalToSystemSpacingBelow: contentView.topAnchor, multiplier: 1).isActive = true
button.leadingAnchor.constraint(greaterThanOrEqualToSystemSpacingAfter: contentView.leadingAnchor, multiplier: 1).isActive = true
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
#objc func buttonTapped(sender: UIButton) {
buttontappedClosure?(self)
}
}
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
cell.buttontappedClosure = { [weak tableView] cell in
guard let indexPath = tableView?.indexPath(for: cell) else { return }
print(indexPath)
}
return cell
}
}
#5. Using UITableViewCell's accessoryType and UITableViewDelegate's tableView(_:accessoryButtonTappedForRowWith:)
If your button is a UITableViewCell's standard accessory control, any tap on it will trigger a call to UITableViewDelegate's tableView(_:accessoryButtonTappedForRowWith:), allowing you to get the related index path.
import UIKit
private class CustomCell: UITableViewCell {
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
accessoryType = .detailButton
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
}
import UIKit
class TableViewController: UITableViewController {
override func viewDidLoad() {
super.viewDidLoad()
tableView.register(CustomCell.self, forCellReuseIdentifier: "CustomCell")
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 3
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! CustomCell
return cell
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
print(indexPath)
}
}
Found a nice solution to this problem elsewhere, no messing around with tags on the button:
- (void)buttonPressedAction:(id)sender {
NSSet *touches = [event allTouches];
UITouch *touch = [touches anyObject];
CGPoint currentTouchPosition = [touch locationInView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];
// do stuff with the indexPath...
}
How about sending the information like NSIndexPath in the UIButton using runtime injection.
1) You need runtime on the import
2) add static constant
3) add NSIndexPath to your button on runtime using:
(void)setMetaData:(id)target withObject:(id)newObj
4) on button press get metadata using:
(id)metaData:(id)target
Enjoy
#import <objc/runtime.h>
static char const * const kMetaDic = "kMetaDic";
#pragma mark - Getters / Setters
- (id)metaData:(id)target {
return objc_getAssociatedObject(target, kMetaDic);
}
- (void)setMetaData:(id)target withObject:(id)newObj {
objc_setAssociatedObject(target, kMetaDic, newObj, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
#On the cell constructor
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
....
cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
....
[btnSocial addTarget:self
action:#selector(openComments:)
forControlEvents:UIControlEventTouchUpInside];
#add the indexpath here or another object
[self setMetaData:btnSocial withObject:indexPath];
....
}
#The action after button been press:
- (IBAction)openComments:(UIButton*)sender{
NSIndexPath *indexPath = [self metaData:sender];
NSLog(#"indexPath: %d", indexPath.row);
//Reuse your indexpath Now
}
To do (#Vladimir)'s answer is Swift:
var buttonPosition = sender.convertPoint(CGPointZero, toView: self.tableView)
var indexPath = self.tableView.indexPathForRowAtPoint(buttonPosition)!
Although checking for indexPath != nil gives me the finger..."NSIndexPath is not a subtype of NSString"
func buttonAction(sender:UIButton!)
{
var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tablevw)
let indexPath = self.tablevw.indexPathForRowAtPoint(position)
let cell: TableViewCell = tablevw.cellForRowAtIndexPath(indexPath!) as TableViewCell
println(indexPath?.row)
println("Button tapped")
}
I would use the tag property like you said, setting the tag like so:
[button setTag:indexPath.row];
then getting the tag inside of the buttonPressedAction like so:
((UIButton *)sender).tag
Or
UIButton *button = (UIButton *)sender;
button.tag;
Though I like the tag way... if you don't want to use tags for whatever reason,
you could create a member NSArray of premade buttons:
NSArray* buttons ;
then create those buttons before rendering the tableView and push them into the array.
Then inside of the tableView:cellForRowAtIndexPath: function you can do:
UIButton* button = [buttons objectAtIndex:[indexPath row] ] ;
[cell.contentView addSubview:button];
Then in the buttonPressedAction: function, you can do
- (void)buttonPressedAction:(id)sender {
UIButton* button = (UIButton*)sender ;
int row = [buttons indexOfObject:button] ;
// Do magic
}
TO HANDLE SECTIONS - I stored the NSIndexPath in a custom UITableViewCell
IN CLKIndexPricesHEADERTableViewCell.xib
IN IB Add UIButton to XIB - DONT add action!
Add outlet #property (retain, nonatomic) IBOutlet UIButton *buttonIndexSectionClose;
DO NOT CTRL+DRAG an action in IB(done in code below)
#interface CLKIndexPricesHEADERTableViewCell : UITableViewCell
...
#property (retain, nonatomic) IBOutlet UIButton *buttonIndexSectionClose;
#property (nonatomic, retain) NSIndexPath * indexPathForCell;
#end
In viewForHeaderInSection (should also work for cellForRow.... etc if you table has only 1 section)
- viewForHeaderInSection is called for each section 1...2...3
- get the cell CLKIndexPricesHEADERTableViewCell
- getTableRowHEADER just does the normal dequeueReusableCellWithIdentifier
- STORE the indexPath IN the UITableView cell
- indexPath.section = (NSInteger)section
- indexPath.row = 0 always (we are only interested in sections)
- (UIView *) tableView:(UITableView *)tableView1 viewForHeaderInSection:(NSInteger)section {
//Standard method for getting a UITableViewCell
CLKIndexPricesHEADERTableViewCell * cellHEADER = [self getTableRowHEADER];
...use the section to get data for your cell
...fill it in
indexName = ffaIndex.routeCode;
indexPrice = ffaIndex.indexValue;
//
[cellHEADER.buttonIndexSectionClose addTarget:self
action:#selector(buttonDELETEINDEXPressedAction:forEvent:)
forControlEvents:UIControlEventTouchUpInside];
cellHEADER.indexPathForCell = [NSIndexPath indexPathForRow:0 inSection:section];
return cellHEADER;
}
USER presses DELETE Button on a Section header and this calls
- (void)buttonDELETEINDEXPressedAction:(id)sender forEvent:(UIEvent *)event
{
NSLog(#"%s", __PRETTY_FUNCTION__);
UIView * parent1 = [sender superview]; // UiTableViewCellContentView
//UIView *myContentView = (UIView *)parent1;
UIView * parent2 = [parent1 superview]; // custom cell containing the content view
//UIView * parent3 = [parent2 superview]; // UITableView containing the cell
//UIView * parent4 = [parent3 superview]; // UIView containing the table
if([parent2 isMemberOfClass:[CLKIndexPricesHEADERTableViewCell class]]){
CLKIndexPricesHEADERTableViewCell *myTableCell = (CLKIndexPricesHEADERTableViewCell *)parent2;
//UITableView *myTable = (UITableView *)parent3;
//UIView *mainView = (UIView *)parent4;
NSLog(#"%s indexPath.section,row[%d,%d]", __PRETTY_FUNCTION__, myTableCell.indexPathForCell.section,myTableCell.indexPathForCell.row);
NSString *key = [self.sortedKeysArray objectAtIndex:myTableCell.indexPathForCell.section];
if(key){
NSLog(#"%s DELETE object at key:%#", __PRETTY_FUNCTION__,key);
self.keyForSectionIndexToDelete = key;
self.sectionIndexToDelete = myTableCell.indexPathForCell.section;
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:#"Remove Index"
message:#"Are you sure"
delegate:self
cancelButtonTitle:#"No"
otherButtonTitles:#"Yes", nil];
alertView.tag = kALERTVIEW_REMOVE_ONE_INDEX;
[alertView show];
[alertView release];
//------
}else{
NSLog(#"ERROR: [%s] key is nil for section:%d", __PRETTY_FUNCTION__,myTableCell.indexPathForCell.section);
}
}else{
NSLog(#"ERROR: [%s] CLKIndexPricesHEADERTableViewCell not found", __PRETTY_FUNCTION__);
}
}
In this example I added a Delete button so should show UIAlertView to confirm it
I store the section and key into the dictionary storing info about the section in a ivar in the VC
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if(alertView.tag == kALERTVIEW_REMOVE_ONE_INDEX){
if(buttonIndex==0){
//NO
NSLog(#"[%s] BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
//do nothing
}
else if(buttonIndex==1){
//YES
NSLog(#"[%s] BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
if(self.keyForSectionIndexToDelete != nil){
//Remove the section by key
[self.indexPricesDictionary removeObjectForKey:self.keyForSectionIndexToDelete];
//sort the keys so sections appear alphabetically/numbericsearch (minus the one we just removed)
[self updateTheSortedKeysArray];
//Delete the section from the table using animation
[self.tableView beginUpdates];
[self.tableView deleteSections:[NSIndexSet indexSetWithIndex:self.sectionIndexToDelete]
withRowAnimation:UITableViewRowAnimationAutomatic];
[self.tableView endUpdates];
//required to trigger refresh of myTableCell.indexPathForCell else old values in UITableViewCells
[self.tableView reloadData];
}else{
NSLog(#"ERROR: [%s] OBJECT is nil", __PRETTY_FUNCTION__);
}
}
else {
NSLog(#"ERROR: [%s] UNHANDLED BUTTON:%d", __PRETTY_FUNCTION__,buttonIndex);
}
}else {
NSLog(#"ERROR: [%s] unhandled ALERTVIEW TAG:%d", __PRETTY_FUNCTION__,alertView.tag);
}
}
A better way would be to subclass your button and add a indexPath property to it.
//Implement a subclass for UIButton.
#interface NewButton:UIButton
#property(nonatomic, strong) NSIndexPath *indexPath;
Make your button of type NewButton in the XIB or in the code whereever you are initializing them.
Then in the cellForRowAtIndexPath put the following line of code.
button.indexPath = indexPath;
return cell; //As usual
Now in your IBAction
-(IBAction)buttonClicked:(id)sender{
NewButton *button = (NewButton *)sender;
//Now access the indexPath by buttons property..
NSIndexPath *indexPath = button.indexPath; //:)
}
It works for me aswell, Thanks #Cocoanut
I found the method of using the superview's superview to obtain a reference to the cell's indexPath worked perfectly. Thanks to iphonedevbook.com (macnsmith) for the tip link text
-(void)buttonPressed:(id)sender {
UITableViewCell *clickedCell = (UITableViewCell *)[[sender superview] superview];
NSIndexPath *clickedButtonPath = [self.tableView indexPathForCell:clickedCell];
...
}
you can use the tag pattern:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = #"identifier";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
if (cell == nil) {
cell = [[UITableView alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:identifier];
[cell autorelelase];
UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(10, 5, 40, 20)];
[button addTarget:self action:#selector(buttonPressedAction:) forControlEvents:UIControlEventTouchUpInside];
[button setTag:[indexPath row]]; //use the row as the current tag
[cell.contentView addSubview:button];
[button release];
}
UIButton *button = (UIButton *)[cell viewWithTag:[indexPath row]]; //use [indexPath row]
[button setTitle:#"Edit" forState:UIControlStateNormal];
return cell;
}
- (void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
//button.tag has the row number (you can convert it to indexPath)
}
Am I missing something? Can't you just use sender to identify the button. Sender will give you info like this:
<UIButton: 0x4b95c10; frame = (246 26; 30 30); opaque = NO; tag = 104; layer = <CALayer: 0x4b95be0>>
Then if you want to change the properties of the button, say the background image you just tell sender:
[sender setBackgroundImage:[UIImage imageNamed:#"new-image.png"] forState:UIControlStateNormal];
If you need the tag then ACBurk's method is fine.
// how do I know which button sent this message?
// processing button press for this row requires an indexPath.
Pretty straightforward actually:
- (void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
CGPoint rowButtonCenterInTableView = [[rowButton superview] convertPoint:rowButton.center toView:self.tableView];
NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:rowButtonCenterInTableView];
MyTableViewItem *rowItem = [self.itemsArray objectAtIndex:indexPath.row];
// Now you're good to go.. do what the intention of the button is, but with
// the context of the "row item" that the button belongs to
[self performFooWithItem:rowItem];
}
Working well for me :P
if you want to adjust your target-action setup, you can include the event parameter in the method, and then use the touches of that event to resolve the coordinates of the touch. The coordinates still need to be resolved in the touch view bounds, but that may seem easier for some people.
create an nsmutable array and put all button in that array usint[array addObject:yourButton];
in the button press method
-
(void)buttonPressedAction:(id)sender
{
UIButton *button = (UIButton *)sender;
for(int i=0;i<[yourArray count];i++){
if([buton isEqual:[yourArray objectAtIndex:i]]){
//here write wat u need to do
}
}
A slight variation on Cocoanuts answer (that helped me solve this) when the button was in the footer of a table (which prevents you from finding the 'clicked cell':
-(IBAction) buttonAction:(id)sender;
{
id parent1 = [sender superview]; // UiTableViewCellContentView
id parent2 = [parent1 superview]; // custom cell containing the content view
id parent3 = [parent2 superview]; // UITableView containing the cell
id parent4 = [parent3 superview]; // UIView containing the table
UIView *myContentView = (UIView *)parent1;
UITableViewCell *myTableCell = (UITableViewCell *)parent2;
UITableView *myTable = (UITableView *)parent3;
UIView *mainView = (UIView *)parent4;
CGRect footerViewRect = myTableCell.frame;
CGRect rect3 = [myTable convertRect:footerViewRect toView:mainView];
[cc doSomethingOnScreenAtY:rect3.origin.y];
}
I always use tags.
You need to subclass the UITableviewCell and handle the button press from there.
It's simple; make a custom cell and take a outlet of button
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *identifier = #"identifier";
customCell *cell = [tableView dequeueReusableCellWithIdentifier:identifier];
cell.yourButton.tag = indexPath.Row;
- (void)buttonPressedAction:(id)sender
change id in above method to (UIButton *)
You can get the value that which button is being tapped by doing sender.tag.
Subclass the button to store the required value, maybe create a protocol (ControlWithData or something). Set the value when you add the button to the table view cell. In your touch up event, see if the sender obeys the protocol and extract the data. I normally store a reference to the actual object that is rendered on the table view cell.
SWIFT 2 UPDATE
Here's how to find out which button was tapped + send data to another ViewController from that button's indexPath.row as I'm assuming that's the point for most!
#IBAction func yourButton(sender: AnyObject) {
var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(position)
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
UITableViewCell
print(indexPath?.row)
print("Tap tap tap tap")
}
For those who are using a ViewController class and added a tableView, I'm using a ViewController instead of a TableViewController so I manually added the tableView in order to access it.
Here is the code for passing data to another VC when tapping that button and passing the cell's indexPath.row
#IBAction func moreInfo(sender: AnyObject) {
let yourOtherVC = self.storyboard!.instantiateViewControllerWithIdentifier("yourOtherVC") as! YourOtherVCVIewController
var position: CGPoint = sender.convertPoint(CGPointZero, toView: self.tableView)
let indexPath = self.tableView.indexPathForRowAtPoint(position)
let cell: UITableViewCell = tableView.cellForRowAtIndexPath(indexPath!)! as
UITableViewCell
print(indexPath?.row)
print("Button tapped")
yourOtherVC.yourVarName = [self.otherVCVariable[indexPath!.row]]
self.presentViewController(yourNewVC, animated: true, completion: nil)
}
Note here i am using custom cell this code is perfectly working for me
#IBAction func call(sender: UIButton)
{
var contentView = sender.superview;
var cell = contentView?.superview as EmployeeListCustomCell
if (!(cell.isKindOfClass(EmployeeListCustomCell)))
{
cell = (contentView?.superview)?.superview as EmployeeListCustomCell
}
let phone = cell.lblDescriptionText.text!
//let phone = detailObject!.mobile!
let url:NSURL = NSURL(string:"tel://"+phone)!;
UIApplication.sharedApplication().openURL(url);
}
Chris Schwerdt's solution but then in Swift worked for me:
#IBAction func rateButtonTapped(sender: UIButton) {
let buttonPosition : CGPoint = sender.convertPoint(CGPointZero, toView: self.ratingTableView)
let indexPath : NSIndexPath = self.ratingTableView.indexPathForRowAtPoint(buttonPosition)!
print(sender.tag)
print(indexPath.row)
}
This problem has two parts:
1) Getting the index path of UITableViewCell which contains pressed UIButton
There are some suggestions like:
Updating UIButton's tag in cellForRowAtIndexPath: method using index path's row value. This is not an good solution as it requires updating tag continuously and it does not work with table views with more than one section.
Adding an NSIndexPath property to custom cell and updating it instead of UIButton's tag in cellForRowAtIndexPath: method. This solves multiple section problem but still not good as it requires updating always.
Keeping a weak refence to parent UITableView in the custom cell while creating it and using indexPathForCell: method to get the index path. Seems a little bit better, no need to update anything in cellForRowAtIndexPath: method, but still requires setting a weak reference when the custom cell is created.
Using cell's superView property to get a reference to parent UITableView. No need to add any properties to the custom cell, and no need to set/update anything on creation/later. But cell's superView depends on iOS implementation details. So it can not be used directly.
But this can be achieved using a simple loop, as we are sure the cell in question has to be in a UITableView:
UIView* view = self;
while (view && ![view isKindOfClass:UITableView.class])
view = view.superview;
UITableView* parentTableView = (UITableView*)view;
So, these suggestions can be combined into a simple and safe custom cell method for getting the index path:
- (NSIndexPath *)indexPath
{
UIView* view = self;
while (view && ![view isKindOfClass:UITableView.class])
view = view.superview;
return [(UITableView*)view indexPathForCell:self];
}
From now on, this method can be used to detect which UIButton is pressed.
2) Informing other parties about button press event
After internally knowing which UIButton is pressed in which custom cell with exact index path, this information needs to be sent to other parties (most probably the view controller handling the UITableView). So, this button click event can be handled in a similar abstraction and logic level to didSelectRowAtIndexPath: method of UITableView delegate.
Two approaches can be used for this:
a) Delegation: custom cell can have a delegate property and can define a protocol. When button is pressed it just performs it's delegate methods on it's delegate property. But this delegate property needs to be set for each custom cell when they are created. As an alternative, custom cell can choose to perform its delegate methods on it's parent table view's delegate too.
b) Notification Center: custom cells can define a custom notification name and post this notification with the index path and parent table view information provided in userInfo object. No need to set anything for each cell, just adding an observer for the custom cell's notification is enough.
I use a solution that subclass UIButton and I thought I should just share it here, codes in Swift:
class ButtonWithIndexPath : UIButton {
var indexPath:IndexPath?
}
Then remember to update it's indexPath in cellForRow(at:)
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let returnCell = tableView.dequeueReusableCell(withIdentifier: "cellWithButton", for: indexPath) as! cellWithButton
...
returnCell.button.indexPath = IndexPath
returnCell.button.addTarget(self, action:#selector(cellButtonPressed(_:)), for: .touchUpInside)
return returnCell
}
So when responding to the button's event you can use it like
func cellButtonPressed(_ sender:UIButton) {
if sender is ButtonWithIndexPath {
let button = sender as! ButtonWithIndexPath
print(button.indexPath)
}
}