Нажмите кнопку внутри 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.