Нажмите кнопку внутри UITableViewCell


140

У меня есть контроллер представления с табличным представлением и отдельным наконечником для шаблона ячейки таблицы. В шаблоне ячейки есть несколько кнопок. Я хочу получить доступ к нажатию кнопки вместе с индексом ячейки, нажатой внутри контроллера представления, в котором я определил представление таблицы.

Итак, у меня есть ViewController.hand, ViewController.mгде у меня есть UITableViewи TableTemplate.h, TableTemplate.mи TableTemplate.xibгде у меня определено перо. Я хочу, чтобы событие нажатия кнопки с индексом ячейки в ViewController.m.

Любая помощь о том, как я могу это сделать?

Ответы:


258

1) В вашем cellForRowAtIndexPath:методе назначьте тег кнопки как индекс:

cell.yourbutton.tag = indexPath.row;

2) Добавьте цель и действие для кнопки, как показано ниже:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Кодируйте действия на основе индекса, как показано ниже в ViewControler:

-(void)yourButtonClicked:(UIButton*)sender
{
     if (sender.tag == 0) 
     {
         // Your code here
     }
}

Обновления для нескольких разделов:

Вы можете проверить эту ссылку, чтобы обнаружить нажатие кнопки в виде таблицы для нескольких строк и разделов.


1
Это также можно сделать с помощью Interface Builder (IB) на втором шаге. Просто убедитесь, что у вас установлен тег кнопок. Вы действительно не хотите путать свое призвание к действию. Либо сделайте это через IB, либо сделайте это явно в своем коде.
— Sententia

@Mani Это не нарушает MVC - действие находится в TableView, а не в ячейке.
— davecom

@davecom Если вы установите цель кнопки как ячейку (через IB), как она будет запускаться из tableView? Или есть ли у них способ подключить целевую кнопку к tableview, который помещается в xib ячейки?
— Мани

24
Это решение сталкивается с проблемами, когда вы начинаете вставлять и удалять строки. Тег не обновляется при смещении строк. Вместо того, чтобы хранить ссылку на строку. Может быть лучше сохранить ссылку на уникальный идентификатор объекта.
— Винсент Чеонг,

1
Каждый раз, когда вы обнаруживаете, что присваиваете значения атрибутам тегов представлений, у вас возникает очень неприятный запах кода, который может укусить вас позже. Ищите лучшие способы достижения своей цели, а не первое сообщение SO, которое вы найдете.
— TigerCoding

148

Делегаты - это то, что нужно.

Как видно из других ответов, использование представлений может устареть. Кто знает, завтра может появиться еще одна обертка и, возможно, потребуется ее использовать cell superview]superview]superview]superview]. И если вы используете теги, вы получите n условий if else для идентификации ячейки. Чтобы избежать всего этого, настройте делегатов. (Таким образом вы создадите повторно используемый класс ячейки. Вы можете использовать тот же класс ячейки, что и базовый класс, и все, что вам нужно сделать, это реализовать методы делегата.)

Сначала нам нужен интерфейс (протокол), который будет использоваться ячейкой для передачи (делегирования) нажатий кнопок. ( Вы можете создать отдельный файл .h для протокола и включить его как в контроллер табличного представления, так и в пользовательские классы ячеек ИЛИ просто добавить его в пользовательский класс ячеек, который в любом случае будет включен в контроллер табличного представления )

@protocol CellDelegate <NSObject>
- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data;
@end

Включите этот протокол в настраиваемый контроллер ячеек и таблиц. И убедитесь, что контроллер табличного представления подтверждает этот протокол.

В настраиваемой ячейке создайте два свойства:

@property (weak, nonatomic) id<CellDelegate>delegate;
@property (assign, nonatomic) NSInteger cellIndex;

В UIButtonделегате IBAction щелкните: ( То же самое можно сделать для любого действия в пользовательском классе ячейки, которое необходимо делегировать обратно для просмотра контроллера )

- (IBAction)buttonClicked:(UIButton *)sender {
    if (self.delegate && [self.delegate respondsToSelector:@selector(didClickOnCellAtIndex:withData:)]) {
        [self.delegate didClickOnCellAtIndex:_cellIndex withData:@"any other cell data/property"];
    }
}

В контроллере табличного представления cellForRowAtIndexPathпосле удаления ячейки установите указанные выше свойства.

cell.delegate = self;
cell.cellIndex = indexPath.row; // Set indexpath if its a grouped table.

И реализуем делегат в контроллере табличного представления:

- (void)didClickOnCellAtIndex:(NSInteger)cellIndex withData:(id)data
{
    // Do additional actions as required.
    NSLog(@"Cell at Index: %d clicked.\n Data received : %@", cellIndex, data);
}

Это был бы идеальный подход для получения настраиваемых действий кнопок ячейки в контроллере табличного представления.


2
Почему вы сделали делегата сильным свойством ячейки? Это даст вам цикл сохранения, если вы не знаете, что контроллер только слабо удерживает ячейку.
— JulianSymes

как насчет обновления _cellIndex после удаления ячейки?
— Скорнос 07

2
Я слышал от одного из моих друзей, что использование делегата для каждой ячейки вызывает потребление памяти, поэтому используйте теги. Это правда?
— Bista

2
проверьте этот вопрос, чувак stackoverflow.com/questions/31649220/…
— Nischal Hada

@the_UB Между установкой тега и сохранением единственной ссылки мало что может быть. Возможно, тег займет больше памяти.
— Ian Warburton

66

Вместо того, чтобы играть с тегами, я применил другой подход. Сделал делегатом для моего подкласса UITableViewCell (OptionButtonsCell) и добавил переменную indexPath. С помощью моей кнопки в раскадровке я подключил @IBAction к OptionButtonsCell, и там я отправляю метод делегата с правильным indexPath всем, кто заинтересован. В ячейке для пути индекса я установил текущий indexPath, и он работает :)

Пусть код говорит сам за себя:

Swift 3 Xcode 8

OptionButtonsTableViewCell.swift

import UIKit
protocol OptionButtonsDelegate{
    func closeFriendsTapped(at index:IndexPath)
}
class OptionButtonsTableViewCell: UITableViewCell {
    var delegate:OptionButtonsDelegate!
    @IBOutlet weak var closeFriendsBtn: UIButton!
    var indexPath:IndexPath!
    @IBAction func closeFriendsAction(_ sender: UIButton) {
        self.delegate?.closeFriendsTapped(at: indexPath)
    }
}

MyTableViewController.swift

class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate {...

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "optionCell") as! OptionButtonsTableViewCell
    cell.delegate = self
    cell.indexPath = indexPath
    return cell   
}

func closeFriendsTapped(at index: IndexPath) {
     print("button tapped at index:\(index)")
}

class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate Можете ли вы мне помочь, я получаю сообщение об ошибке в этой строке: // error: Избыточное соответствие MyTableViewController протоколу UITableViewDataSource
— Улуг'бек Розимбоев

похоже, вы несколько раз пытаетесь соответствовать UITableViewDataSource. Возможно, у вас есть расширение, в котором вы уже соответствуете источнику данных? Больше ничего не можете сделать без кода
— Maciej Chrzastek

1
и как передать данные для выполнения перехода и перехода к другому контроллеру представления?
— Милад Фаридния

2
Лучшее и чистое решение!
— appsunny

31

Это должно помочь: -

UITableViewCell* cell = (UITableViewCell*)[sender superview];
NSIndexPath* indexPath = [myTableView indexPathForCell:cell];

Здесь отправитель - это экземпляр UIButton, который отправляет событие. myTableView - это экземпляр UITableView, с которым вы имеете дело.

Просто установите правильную ссылку на ячейку, и вся работа сделана.

Возможно, вам придется удалить кнопки из contentView ячейки и добавить их непосредственно в экземпляр UITableViewCell в качестве подпредставления.

Или

Вы можете сформулировать схему именования тегов для различных UIButton в cell.contentView. Используя этот тег, позже вы можете узнать информацию о строке и разделе, если это необходимо.


4
это должно быть [[superview отправителя] superview];
— Pierre23

2
Это хорошо для очень простых ячеек. Однако, если ваша ячейка имеет глубокое дерево взглядов, лучше всего будет ответить Мани.
— Sententia

3
Теперь в iOS 7 это должно быть UITableViewCell * cell = (UITableViewCell *) [[[sender superview] superview] superview]; Спасибо.
— Раджан Махарджан

проверьте этот вопрос, чувак stackoverflow.com/questions/31649220/…
— Nischal Hada

22

Следующий код может вам помочь.

Я взял UITableViewсобственный прототип класса ячейки, названный UITableViewCellвнутри UIViewController.

Итак, у меня есть ViewController.h, ViewController.mи TableViewCell.h,TableViewCell.m

Вот код для этого:

ViewController.h

@interface ViewController : UIViewController<UITableViewDataSource,UITableViewDelegate>

@property (strong, nonatomic) IBOutlet UITableView *tblView;

@end

ViewController.m

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{
    return (YourNumberOfRows);
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{

    static NSString *cellIdentifier = @"cell";

    __weak TableViewCell *cell = (TableViewCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

    if (indexPath.row==0) {
        [cell setDidTapButtonBlock:^(id sender)
         {
             // Your code here

         }];
    }    
    return cell;
}

Пользовательский класс ячейки:

TableViewCell.h

@interface TableViewCell : UITableViewCell

@property (copy, nonatomic) void (^didTapButtonBlock)(id sender);

@property (strong, nonatomic) IBOutlet UILabel *lblTitle;
@property (strong, nonatomic) IBOutlet UIButton *btnAction;

- (void)setDidTapButtonBlock:(void (^)(id sender))didTapButtonBlock;

@end

а также

UITableViewCell.m

@implementation TableViewCell

- (void)awakeFromNib {
    // Initialization code
    [self.btnAction addTarget:self action:@selector(didTapButton:) forControlEvents:UIControlEventTouchUpInside];

}

- (void)setSelected:(BOOL)selected animated:(BOOL)animated {
    [super setSelected:selected animated:animated];

    // Configure the view for the selected state
}
- (void)didTapButton:(id)sender {
    if (self.didTapButtonBlock)
    {
        self.didTapButtonBlock(sender);
    }
}

Примечание : здесь я взял все, что UIControlsиспользовал Storyboard.

Надеюсь, что это поможет вам ... !!!


Лучший способ
— Даниэль Рауф

15

Причина, по которой мне нравится техника ниже, потому что она также помогает мне идентифицировать раздел таблицы.

Добавить кнопку в ячейке cellForRowAtIndexPath:

 UIButton *selectTaskBtn = [UIButton buttonWithType:UIButtonTypeCustom];
        [selectTaskBtn setFrame:CGRectMake(15, 5, 30, 30.0)];
        [selectTaskBtn setTag:indexPath.section]; //Not required but may find useful if you need only section or row (indexpath.row) as suggested by MR.Tarun 
    [selectTaskBtn addTarget:self action:@selector(addTask:)   forControlEvents:UIControlEventTouchDown];
[cell addsubview: selectTaskBtn];

Событие addTask:

-(void)addTask:(UIButton*)btn
{
    CGPoint buttonPosition = [btn convertPoint:CGPointZero toView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:buttonPosition];
    if (indexPath != nil)
    {
     int currentIndex = indexPath.row;
     int tableSection = indexPath.section;
    }
}

Надеюсь на эту помощь.


проверьте этот вопрос, чувак stackoverflow.com/questions/31649220/…
— Nischal Hada

12

Используйте закрытия Swift:

class TheCell: UITableViewCell {

    var tapCallback: (() -> Void)?

    @IBAction func didTap(_ sender: Any) {
        tapCallback?()
    }
}

extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.tapCallback = {
                //do stuff
            }
            return cell
    }
}

7

Код Таруна не работает на iOS7, поскольку структура UITableViewCell изменилась, и теперь вместо этого он получит «UITableViewCellScrollView».

В этом посте Получение UITableViewCell с супервизором в iOS 7 есть хорошее решение, создающее цикл для поиска правильного родительского представления, независимо от любых будущих изменений в структуре. Все сводится к созданию цикла:

    UIView *superView = [sender superview];
    UIView *foundSuperView = nil;

    while (nil != superView && nil == foundSuperView) {
        if ([superView isKindOfClass:[UITableViewCell class]]) {
            foundSuperView = superView;
        } else {
            superView = superView.superview;
        }
    }

По ссылке есть код для решения, которое можно использовать повторно, но это должно сработать.


6

Swift 2.2

Вам нужно добавить цель для этой кнопки.

myButton.addTarget(self, action: #selector(ClassName.FunctionName(_:), forControlEvents: .TouchUpInside)

FunctionName: connected // например

И, конечно, вам нужно установить тег этой кнопки, поскольку вы ее используете.

myButton.tag = indexPath.row

Вы можете добиться этого, создав подкласс UITableViewCell. Используйте его в построителе интерфейса, поместите кнопку в эту ячейку, подключите ее к розетке и готово.

Чтобы получить тег в подключенной функции:

func connected(sender: UIButton) {
    let buttonTag = sender.tag
    // Do any additional setup
}

6

Swift 3 с закрытием

Хорошим решением является использование закрытия в пользовательском UITableViewCell для обратного вызова viewController для действия.

В ячейке:

final class YourCustomCell: UITableViewCell {

    var callbackClosure: (() -> Void)?

    // Configure the cell here
    func configure(object: Object, callbackClosure: (() -> Void)?) {
       self.callbackClosure = callbackClosure
    }


// MARK: - IBAction
extension YourCustomCell {
    @IBAction fileprivate func actionPressed(_ sender: Any) {
        guard let closure = callbackClosure else { return }
        closure()
    }
}

Контроллер представления: делегат представления таблицы

extension YourViewController: UITableViewDelegate {

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let cell: YourCustomCell = cell as? YourCustomCell else { return }
        cell.configure(object: object, callbackClosure: { [weak self] in
            self?.buttonAction()
        })
     }
 }

fileprivate extension YourViewController {

    func buttonAction() {
        // do your actions here 
    }
}

5

Я считаю, что проще всего создать подкласс кнопки внутри вашей ячейки (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

В вашем классе ячеек:

class MyCell: UICollectionViewCell {
    @IBOutlet weak var infoButton: MyCellInfoButton!
   ...
}

В источнике данных табличного представления или представления коллекции при исключении ячейки из очереди укажите для кнопки путь индекса:

cell.infoButton.indexPath = indexPath

Итак, вы можете просто поместить этот код в контроллер табличного представления:

@IBAction func handleTapOnCellInfoButton(_ sender: MyCellInfoButton) {
        print(sender.indexPath!) // Do whatever you want with the index path!
}

И не забудьте установить класс кнопки в Интерфейсном Разработчике и связать его с handleTapOnCellInfoButtonфункцией!


отредактировал:

Использование внедрения зависимостей. Чтобы настроить вызов закрытия:

class MyCell: UICollectionViewCell {
    var someFunction: (() -> Void)?
    ...
    @IBAction func didTapInfoButton() {
        someFunction?()
    }
}

и введите закрытие в метод willDisplay делегата представления коллекции:

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    (cell as? MyCell)?.someFunction = {
        print(indexPath) // Do something with the indexPath.
    }
}

Подход к закрытию - это самый быстрый способ, который я видел. Хорошая работа!
— Клифтон

5

Это работа для меня.

 - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
     UIButton *Btn_Play = (UIButton *)[cell viewWithTag:101];
     [Btn_Play addTarget:self action:@selector(ButtonClicked:) forControlEvents:UIControlEventTouchUpInside];
}
-(void)ButtonClicked:(UIButton*)sender {
     CGPoint buttonPosition = [sender convertPoint:CGPointZero toView:self.Tbl_Name];
     NSIndexPath *indexPath = [self.Tbl_Name indexPathForRowAtPoint:buttonPosition];
}

1
// Add action in cell for row at index path -tableView

cell.buttonName.addTarget(self, action: #selector(ViewController.btnAction(_:)), for: .touchUpInside)

// Button Action

  @objc func btnAction(_ sender: AnyObject) {



        var position: CGPoint = sender.convert(.zero, to: self.tableView)


        let indexPath = self.tableView.indexPathForRow(at: position)
        let cell: UITableViewCell = tableView.cellForRow(at: indexPath!)! as
        UITableViewCell




}

1

для быстрого 4:

inside the cellForItemAt ,
   
cell.chekbx.addTarget(self, action: #selector(methodname), for: .touchUpInside)

then outside of cellForItemAt
@objc func methodname()
{
//your function code
}


1

Если вы хотите передать значение параметра из ячейки в UIViewController с помощью закрытия, тогда

//Your Cell Class
class TheCell: UITableViewCell {

    var callBackBlockWithParam: ((String) -> ()) = {_ in }

//Your Action on button
    @IBAction func didTap(_ sender: Any) {
        callBackBlockWithParam("Your Required Parameter like you can send button as sender or anything just change parameter type. Here I am passing string")
    }
}

//Your Controller
extension TheController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell = tableView.dequeueReusableCell(withIdentifier: TheCell.identifier, for: indexPath) as! TheCell {
            cell.callBackBlockWithParam = { (passedParamter) in 

             //you will get string value from cell class
                print(passedParamter)     
      }
            return cell
    }
}

0

@Mani ответ хорош, однако теги представлений внутри contentView ячейки часто используются для других целей. Вместо этого вы можете использовать тег ячейки (или тег contentView ячейки):

1) В вашем cellForRowAtIndexPath:методе назначьте тег ячейки как индекс:

cell.tag = indexPath.row; // or cell.contentView.tag...

2) Добавьте цель и действие для кнопки, как показано ниже:

[cell.yourbutton addTarget:self action:@selector(yourButtonClicked:) forControlEvents:UIControlEventTouchUpInside];

3) Создайте метод, который возвращает строку отправителя (спасибо @Stenio Ferreira):

- (NSInteger)rowOfSender:(id)sender
{
    UIView *superView = sender.superview;
    while (superView) {
        if ([superView isKindOfClass:[UITableViewCell class]])
            break;
        else
            superView = superView.superview;
    }

    return superView.tag;
}

4) Кодовые действия на основе индекса:

-(void)yourButtonClicked:(UIButton*)sender
{
     NSInteger index = [self rowOfSender:sender];
     // Your code here
}

0

CustomTableCell.h - это UITableViewCell:

@property (weak, nonatomic) IBOutlet UIButton *action1Button;
@property (weak, nonatomic) IBOutlet UIButton *action2Button;

MyVC.m после импорта:

@interface MYTapGestureRecognizer : UITapGestureRecognizer
@property (nonatomic) NSInteger dataint;
@end

Внутри cellForRowAtIndexPath в MyVC.m:

//CustomTableCell 
CustomTableCell *cell = (CustomTableCell *)[tableView dequeueReusableCellWithIdentifier:cellIdentifier forIndexPath:indexPath];

//Set title buttons
[cell.action1Button setTitle:[NSString stringWithString:NSLocalizedString(@"action1", nil)] forState:UIControlStateNormal];
[cell.action2Button setTitle:[NSString stringWithString:NSLocalizedString(@"action2", nil)] forState:UIControlStateNormal];

//Set visibility buttons
[cell.action1Button setHidden:FALSE];
[cell.action2Button setHidden:FALSE];

//Do 1 action
[cell.action1Button addTarget:self action:@selector(do1Action :) forControlEvents:UIControlEventTouchUpInside];

//Do 2 action
MYTapGestureRecognizer *action2Tap = [[MYTapGestureRecognizer alloc] initWithTarget:self action:@selector(do2Action :)];
cancelTap.numberOfTapsRequired = 1;
cancelTap.dataint = indexPath.row;
[cell.action2Button setUserInteractionEnabled:YES];
[cell.action2Button addGestureRecognizer:action2Tap];

MyVC.m:

-(void)do1Action :(id)sender{
//do some action that is not necessary fr data
}

-(void)do2Action :(UITapGestureRecognizer *)tapRecognizer{
MYTapGestureRecognizer *tap = (MYTapGestureRecognizer *)tapRecognizer;
numberTag = tap.dataint;
FriendRequest *fr = [_list objectAtIndex:numberTag];

//connect with a WS o do some action with fr data

//actualize list in tableView
 [self.myTableView reloadData];
}

-1
cell.show.tag=indexPath.row;
     [cell.show addTarget:self action:@selector(showdata:) forControlEvents:UIControlEventTouchUpInside];

-(IBAction)showdata:(id)sender
{
    UIButton *button = (UIButton *)sender;

    UIStoryboard *storyBoard;
    storyBoard = [UIStoryboard storyboardWithName:@"Main" bundle:nil];
    SecondViewController *detailView = [storyBoard instantiateViewControllerWithIdentifier:@"SecondViewController"];

    detailView.string=[NSString stringWithFormat:@"%@",[_array objectAtIndex:button.tag]];

    [self presentViewController:detailView animated:YES completion:nil];

}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.