Uzyskaj kliknięcie przycisku wewnątrz UITableViewCell

140

Mam kontroler widoku z widokiem tabeli i oddzielną końcówką dla szablonu komórki tabeli. Szablon komórki zawiera kilka przycisków. Chcę uzyskać dostęp do przycisku kliknięcia wraz z indeksem komórki klikniętej wewnątrz kontrolera widoku, w którym zdefiniowałem widok tabeli.

Więc mam ViewController.hi ViewController.mgdzie mam UITableViewi TableTemplate.h, TableTemplate.mi TableTemplate.xibgdzie mam stalówka zdefiniowane. Chcę, aby zdarzenie kliknięcia przycisku z indeksem komórki w ViewController.m.

Jakaś pomoc, jak mogę to zrobić?

ankit_rck
źródło

Odpowiedzi:

258

1) W swojej cellForRowAtIndexPath:metodzie przypisz tag przycisku jako indeks:

cell.yourbutton.tag = indexPath.row;

2) Dodaj cel i akcję dla swojego przycisku, jak poniżej:

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

3) Działania kodu oparte na indeksie, jak poniżej w ViewControler:

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

Aktualizacje dla wielu sekcji:

Możesz sprawdzić ten link, aby wykryć kliknięcie przycisku w widoku tabeli dla wielu wierszy i sekcji.

Mani
źródło
1
Można to również zrobić za pomocą Interface Builder (IB) w kroku drugim. Tylko upewnij się, że tag przycisków jest ustawiony. Naprawdę nie chcesz mieszać swoich akcji. Albo zrób to przez IB, albo zrób to bezpośrednio w swoim kodzie.
Sententia
@Mani Nie przerywa MVC - akcja znajduje się w TableView, a nie w komórce.
davecom
@davecom Jeśli ustawisz cel przycisku jako komórkę (przez IB), w jaki sposób będzie on wyzwalany z tableView? Czy jest jakiś sposób na połączenie celu przycisku z widokiem tabeli, który jest umieszczony w xib komórki?
Mani
24
To rozwiązanie napotyka problemy podczas rozpoczynania wstawiania i usuwania wierszy. Znacznik nie jest aktualizowany po przesunięciu wierszy. Zamiast zachowywać odniesienie do wiersza. Lepiej jest zachować odniesienie do unikalnego identyfikatora obiektu.
Vincent Cheong
1
Za każdym razem, gdy przypisujesz wartości do atrybutów tagów widoków, masz bardzo nieprzyjemny zapach kodu, który może Cię później ugryźć. Poszukaj lepszych sposobów osiągnięcia celu, a nie pierwszego znalezionego posta SO.
TigerCoding
148

Delegaci są do zrobienia.

Jak widać w przypadku innych odpowiedzi, korzystanie z widoków może być nieaktualne. Kto wie, że jutro może być inne opakowanie i być może trzeba będzie go użyć cell superview]superview]superview]superview]. A jeśli użyjesz tagów, otrzymasz n liczbę warunków if else do identyfikacji komórki. Aby uniknąć tego wszystkiego, ustaw delegatów. (W ten sposób utworzysz klasę komórek wielokrotnego użytku. Możesz użyć tej samej klasy komórki jako klasy bazowej, a wszystko, co musisz zrobić, to zaimplementować metody delegatów.)

Najpierw potrzebujemy interfejsu (protokołu), który będzie używany przez komórkę do komunikowania (delegata) kliknięć przycisków. ( Możesz utworzyć oddzielny plik .h dla protokołu i dołączyć go zarówno do kontrolera widoku tabeli, jak i niestandardowych klas komórek LUB po prostu dodaj go do niestandardowej klasy komórki, która i tak zostanie uwzględniona w kontrolerze widoku tabeli )

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

Dołącz ten protokół do niestandardowego kontrolera widoku komórki i tabeli. I upewnij się, że kontroler widoku tabeli potwierdza ten protokół.

W komórce niestandardowej utwórz dwie właściwości:

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

W funkcji UIButtondelegata IBAction kliknij: (To samo można zrobić dla dowolnej akcji w niestandardowej klasie komórki, która musi zostać oddelegowana z powrotem do kontrolera widoku )

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

W kontrolerze widoku tabeli cellForRowAtIndexPathpo usunięciu z kolejki komórki ustaw powyższe właściwości.

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

I zaimplementuj delegata w kontrolerze widoku tabeli:

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

Byłoby to idealne podejście do uzyskania niestandardowych akcji przycisków komórki w kontrolerze widoku tabeli.

GoodSp33d
źródło
2
Dlaczego nadałeś delegatowi silną właściwość komórki? To da ci cykl utrzymania, chyba że wiesz, że kontroler tylko słabo trzyma komórkę.
JulianSymes
co z beign _cellIndex zaktualizowanym po usunięciu komórki?
skornos
2
Słyszałem od jednego z moich znajomych, że użycie delegata na każdej komórce powoduje zużycie pamięci, więc używaj tagów. Czy to prawda?
Bista,
2
sprawdź to pytanie stary stackoverflow.com/questions/31649220/…
Nischal Hada
@the_UB Nie może być wiele między ustawieniem tagu a przechowywaniem pojedynczego odniesienia. Być może tag zajmowałby więcej pamięci.
Ian Warburton
66

Zamiast bawić się tagami, przyjąłem inne podejście. Wykonano delegata dla mojej podklasy UITableViewCell (OptionButtonsCell) i dodano zmienną indexPath. Z mojego przycisku w storyboardzie połączyłem @IBAction z OptionButtonsCell i tam wysyłam metodę delegata z właściwą indexPath do wszystkich zainteresowanych. W komórce na ścieżkę indeksu ustawiam aktualną ścieżkę indexPath i działa :)

Niech kod mówi sam za siebie:

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)")
}
Maciej Chrzastek
źródło
czy możesz mi pomóc, class MyTableViewController: UIViewController, UITableViewDelegate, UITableViewDataSource, OptionButtonsDelegate pojawia się błąd w tej linii: // błąd: Nadmiarowa zgodność 'MyTableViewController' z protokołem 'UITableViewDataSource'
Ulug'bek Ro'zimboyev
wygląda na to, że próbujesz wielokrotnie dostosować się do UITableViewDataSource. Może masz rozszerzenie, w którym już dostosowujesz się do źródła danych ?, bez kodu nie da się więcej pomóc
Maciej Chrzastek
1
i jak przekazać dane, aby wykonać segue i przejść do innego kontrolera widoku?
Milad Faridnia
2
Najlepsze i najczystsze rozwiązanie!
appsunited
31

To powinno pomóc: -

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

W tym przypadku nadawca to instancja UIButton, która wysyła zdarzenie. myTableView to instancja UITableView, z którą masz do czynienia.

Po prostu uzyskaj prawidłowe odwołanie do komórki i cała praca zostanie wykonana.

Może być konieczne usunięcie przycisków z contentView komórki i dodanie ich bezpośrednio do instancji UITableViewCell jako podwidoku.

Lub

Możesz sformułować schemat nazewnictwa tagów dla różnych UIButtons w cell.contentView. Używając tego tagu, możesz później poznać potrzebne informacje o wierszu i sekcji.

Tarun
źródło
4
powinno to być [[sender superview] superview];
pierre23
2
Jest to dobre dla bardzo prostych komórek. Jeśli jednak twoja komórka ma głębokie drzewo widoków, odpowiedź Mani jest najlepsza.
Sententia
3
Teraz w iOS 7 powinno to być UITableViewCell * cell = (UITableViewCell *) [[[nadawca superview] superview] superview]; Dziękuję Ci.
Rajan Maharjan
sprawdź to pytanie stary stackoverflow.com/questions/31649220/…
Nischal Hada
22

Poniższy kod może ci pomóc.

Wziąłem UITableViewz niestandardową prototypową klasą komórek o nazwie UITableViewCellwewnątrz UIViewController.

Tak mam ViewController.h, ViewController.mi TableViewCell.h,TableViewCell.m

Oto kod:

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;
}

Niestandardowa klasa komórek:

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

i

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);
    }
}

Uwaga : tutaj zrobiłem wszystko UIControlsprzy użyciu Storyboard.

Mam nadzieję, że to ci pomoże ... !!!

Piyush
źródło
Najlepszy sposób w historii
Daniel Raouf
15

Powód, dla którego lubię poniższą technikę, ponieważ pomaga mi również zidentyfikować sekcję tabeli.

Dodaj przycisk w komórce 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];

Zadanie:

-(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;
    }
}

Mam nadzieję, że ta pomoc.

Yogesh Lolusare
źródło
sprawdź to pytanie stary stackoverflow.com/questions/31649220/…
Nischal Hada
12

Użyj szybkich zamknięć:

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
    }
}
valexa
źródło
7

Kod Taruna nie działa na iOS7, ponieważ zmieniła się struktura UITableViewCell i zamiast tego otrzymywałby „UITableViewCellScrollView”.

Ten post Pobieranie UITableViewCell z superviewem w iOS 7 ma dobre rozwiązanie, tworząc pętlę w celu znalezienia prawidłowego widoku nadrzędnego, niezależnie od wszelkich przyszłych zmian w strukturze. Sprowadza się do stworzenia pętli:

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

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

Odsyłacz zawiera kod rozwiązania wielokrotnego użytku, ale to powinno działać.

Stenio Ferreira
źródło
6

Swift 2.2

Musisz dodać cel dla tego przycisku.

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

FunctionName: połączono // na przykład

Oczywiście musisz ustawić tag tego przycisku, ponieważ go używasz.

myButton.tag = indexPath.row

Możesz to osiągnąć, tworząc podklasę UITableViewCell. Użyj go w kreatorze interfejsów, upuść przycisk na tej komórce, podłącz go przez gniazdko i gotowe.

Aby uzyskać tag w połączonej funkcji:

func connected(sender: UIButton) {
    let buttonTag = sender.tag
    // Do any additional setup
}
Himanshu padia
źródło
6

Swift 3 z zamknięciem

Fajnym rozwiązaniem jest użycie zamknięcia w niestandardowym UITableViewCell do wywołania zwrotnego do viewController dla akcji.

W komórce:

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()
    }
}

Kontroler w widoku: delegat widoku tabeli

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 
    }
}
Sevy11
źródło
5

Najłatwiej jest utworzyć podklasę przycisku wewnątrz komórki (Swift 3):

class MyCellInfoButton: UIButton {
    var indexPath: IndexPath?
}

W twojej klasie komórkowej:

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

W źródle danych widoku tabeli lub widoku kolekcji podczas usuwania z kolejki komórki podaj przyciskowi ścieżkę indeksu:

cell.infoButton.indexPath = indexPath

Możesz więc po prostu umieścić ten kod w kontrolerze widoku tabeli:

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

I nie zapomnij ustawić klasy przycisku w swoim Interface Builder i połączyć go z handleTapOnCellInfoButtonfunkcją!


edytowano:

Korzystanie z iniekcji zależności. Aby skonfigurować wywołanie zamknięcia:

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

i wstrzyknij zamknięcie w metodzie willDisplay delegata widoku kolekcji:

 func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    (cell as? MyCell)?.someFunction = {
        print(indexPath) // Do something with the indexPath.
    }
}
takleon
źródło
Podejście do zamykania jest najszybszym sposobem, jaki widziałem. Dobra robota!
Clifton Labrum
5

To dla mnie praca.

 - (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];
}
Yogesh Tarsariya
źródło
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




}
Hitesh Chauhan
źródło
1

dla swift 4:

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

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

Radhe Yadav
źródło
1

Jeśli chcesz przekazać wartość parametru z komórki do UIViewController przy użyciu zamknięcia, to

//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
    }
}
prachit
źródło
0

@Mani odpowiedź jest dobra, jednak tagi widoków wewnątrz contentView komórki często są używane do innych celów. Możesz zamiast tego użyć tagu cell (lub tagu contentView komórki):

1) W swojej cellForRowAtIndexPath:metodzie przypisz tag komórki jako indeks:

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

2) Dodaj cel i akcję dla swojego przycisku, jak poniżej:

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

3) Utwórz metodę, która zwraca wiersz nadawcy (dzięki @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) Działania kodu oparte na indeksie:

-(void)yourButtonClicked:(UIButton*)sender
{
     NSInteger index = [self rowOfSender:sender];
     // Your code here
}
Borzh
źródło
0

CustomTableCell.h jest UITableViewCell:

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

MyVC.m po imporcie:

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

Wewnątrz „cellForRowAtIndexPath” w 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];
}
Mer
źródło
-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];

}
user8132169
źródło