Używanie niestandardowego obrazu dla accessoryView UITableViewCell i odpowiadanie na UITableViewDelegate

139

Używam niestandardowego rysunku UITableViewCell, w tym tego samego dla komórki accessoryView. Moja konfiguracja dla accessoryView odbywa się w następujący sposób:

UIImage *accessoryImage = [UIImage imageNamed:@"accessoryDisclosure.png"];
UIImageView *accImageView = [[UIImageView alloc] initWithImage:accessoryImage];
accImageView.userInteractionEnabled = YES;
[accImageView setFrame:CGRectMake(0, 0, 28.0, 28.0)];
self.accessoryView = accImageView;
[accImageView release];

Również po zainicjowaniu komórki za pomocą initWithFrame:reuseIdentifier:I zapewniłem ustawienie następującej właściwości:

self.userInteractionEnabled = YES;

Niestety w moim UITableViewDelegate moja tableView:accessoryButtonTappedForRowWithIndexPath:metoda (spróbuj powtórzyć to 10 razy) nie jest wyzwalana. Delegat jest zdecydowanie prawidłowo podłączony.

Czego może brakować?

Dziękuje wszystkim.


źródło

Odpowiedzi:

228

Niestety ta metoda nie jest wywoływana, chyba że zostanie naciśnięty typ przycisku wewnętrznego dostarczony podczas korzystania z jednego z predefiniowanych typów. Aby użyć własnego, musisz utworzyć swoje akcesorium jako przycisk lub inną podklasę UIControl (polecam przycisk używający -buttonWithType:UIButtonTypeCustomi ustawiający obraz przycisku, zamiast używania UIImageView).

Oto kilka rzeczy, których używam w Outpost, który dostosowuje wystarczającą liczbę standardowych widżetów (tylko nieznacznie, aby pasowały do ​​naszego zielonkawego zabarwienia), które ostatecznie wykonałem własną podklasę pośredniczącą UITableViewController, aby przechowywać kod narzędziowy dla wszystkich innych widoków tabel do użycia (teraz podklasy OPTableViewController).

Po pierwsze, ta funkcja zwraca nowy przycisk ujawniania szczegółów za pomocą naszej niestandardowej grafiki:

- (UIButton *) makeDetailDisclosureButton
{
    UIButton * button = [UIButton outpostDetailDisclosureButton];

[button addTarget: self
               action: @selector(accessoryButtonTapped:withEvent:)
     forControlEvents: UIControlEventTouchUpInside];

    return ( button );
}

Po zakończeniu przycisk wywoła tę procedurę, która następnie przekazuje standardową procedurę UITableViewDelegate dla przycisków akcesoriów:

- (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event
{
    NSIndexPath * indexPath = [self.tableView indexPathForRowAtPoint: [[[event touchesForView: button] anyObject] locationInView: self.tableView]];
    if ( indexPath == nil )
        return;

    [self.tableView.delegate tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
}

Ta funkcja lokalizuje wiersz, pobierając lokalizację dotyku w widoku tabeli ze zdarzenia dostarczonego przez przycisk i pytając widok tabeli o ścieżkę indeksu wiersza w tym punkcie.

Jim Dovey
źródło
Dzięki Jim. Szkoda, że ​​spędziłem ponad 20 minut zastanawiając się, dlaczego nie mogę tego zrobić z niestandardowym imageView. Właśnie zobaczyłem, jak to zrobić w przykładowej aplikacji Apple Accessory. Twoja odpowiedź jest jednak dobrze wyjaśniona i udokumentowana, więc zaznaczam ją i trzymam. Dzięki jeszcze raz. :-)
Jim, świetna odpowiedź. Jeden potencjalny problem (przynajmniej z mojej strony) - musiałem dodać następujący wiersz, aby uzyskać dotknięcia, aby zarejestrować przycisk: button.userInteractionEnabled = YES;
Mike Laurence
11
Tylko dla innych, którzy patrzą na tę odpowiedź, możesz również po prostu umieścić tag na przycisku, który odpowiada wierszowi (jeśli masz wiele sekcji, musisz zrobić trochę matematyki), a następnie po prostu wyciągnąć tag z przycisku w funkcja. Myślę, że to może być trochę szybsze niż obliczanie dotyku.
RyanJM
3
wymaga to sztywnego zakodowania self.tableView. co jeśli nie wiesz, który widok tabeli zawiera wiersz?
user102008
4
@RyanJM Kiedyś myślałem, że wykonanie hitTestu to przesada i tagi wystarczą. W rzeczywistości użyłem pomysłu tagów w części mojego kodu. Ale dzisiaj napotkałem problem, w którym użytkownik może dodawać nowe wiersze. To zabija włamania za pomocą tagów. Rozwiązanie zaproponowane przez Jima Doveya (i jak widać w przykładowym kodzie Apple) jest rozwiązaniem ogólnym i działa we wszystkich sytuacjach
srik
77

Uważam, że ta strona internetowa jest bardzo pomocna: niestandardowy widok akcesoriów dla twojego uitableview na iPhonie

Krótko mówiąc, użyj tego w cellForRowAtIndexPath::

UIImage *image = (checked) ? [UIImage imageNamed:@"checked.png"] : [UIImage imageNamed:@"unchecked.png"];

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
CGRect frame = CGRectMake(0.0, 0.0, image.size.width, image.size.height);
button.frame = frame;
[button setBackgroundImage:image forState:UIControlStateNormal];

[button addTarget:self action:@selector(checkButtonTapped:event:)  forControlEvents:UIControlEventTouchUpInside];
button.backgroundColor = [UIColor clearColor];
cell.accessoryView = button;

następnie zaimplementuj tę metodę:

- (void)checkButtonTapped:(id)sender event:(id)event
{
    NSSet *touches = [event allTouches];
    UITouch *touch = [touches anyObject];
    CGPoint currentTouchPosition = [touch locationInView:self.tableView];
    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint: currentTouchPosition];

    if (indexPath != nil)
    {
        [self tableView: self.tableView accessoryButtonTappedForRowWithIndexPath: indexPath];
    }
}
Jon
źródło
4
Powiedziałbym, że to +1, ponieważ Apple zaleca to w ich przykładowym kodzie w swoich dokumentach: developer.apple.com/library/ios/#samplecode/Accessory/Listings/ ...
sprytny bit
Brakowało mi ustawienia ramy. Możesz także ustawić obraz (zamiast tła), o ile nie chcesz również żadnego tekstu.
Jeremy Hicks
1
Link został uszkodzony w odpowiedzi @ richarddas. Nowy link: developer.apple.com/library/prerelease/ios/samplecode/Accessory/…
delavega66
7

Moje podejście polega na utworzeniu UITableViewCellpodklasy i hermetyzacji logiki, która wywoła w niej zwykłą UITableViewDelegatemetodę.

// CustomTableViewCell.h
@interface CustomTableViewCell : UITableViewCell

- (id)initForIdentifier:(NSString *)reuseIdentifier;

@end

// CustomTableViewCell.m
@implementation CustomTableViewCell

- (id)initForIdentifier:(NSString *)reuseIdentifier;
{
    // the subclass specifies style itself
    self = [super initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:reuseIdentifier];
    if (self) {
        // get the button elsewhere
        UIButton *accBtn = [ViewFactory createTableViewCellDisclosureButton];
        [accBtn addTarget: self
                   action: @selector(accessoryButtonTapped:withEvent:)
         forControlEvents: UIControlEventTouchUpInside];
        self.accessoryView = accBtn;
    }
    return self;
}

#pragma mark - private

- (void)accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
{
    UITableViewCell *cell = (UITableViewCell*)button.superview;
    UITableView *tableView = (UITableView*)cell.superview;
    NSIndexPath *indexPath = [tableView indexPathForCell:cell];
    [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}

@end
yanchenko
źródło
To jest NAJLEPSZA odpowiedź. Ale button.superview, cell.superviewi [tableView.delegate tableView:...]nie są wystarczająco bezpieczne.
Pan Ming
3

Rozszerzenie do powyższej odpowiedzi Jima Doveya:

Zachowaj ostrożność, używając UISearchBarController z UITableView. W takim przypadku chcesz sprawdzić self.searchDisplayController.activei użyć self.searchDisplayController.searchResultsTableViewzamiast self.tableView. W przeciwnym razie uzyskasz nieoczekiwane wyniki, gdy searchDisplayController jest aktywny, zwłaszcza gdy wyniki wyszukiwania są przewijane.

Na przykład:

- (void) accessoryButtonTapped:(UIControl *)button withEvent:(UIEvent *)event
{
    UITableView* tableView = self.tableView;
    if(self.searchDisplayController.active)
        tableView = self.searchDisplayController.searchResultsTableView;

    NSIndexPath * indexPath = [tableView indexPathForRowAtPoint:[[[event touchesForView:button] anyObject] locationInView:tableView]];
    if(indexPath)
       [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
}
Zaggo
źródło
2
  1. Zdefiniuj makro dla tagów przycisków:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
  2. Utwórz przycisk i ustaw cell.accessoryView podczas tworzenia komórki

    UIButton *accessoryButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
    accessoryButton.frame = CGRectMake(0, 0, 30, 30);
    [accessoryButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = accessoryButton;
  3. Ustaw cell.accessoryView.tag przez indexPath w metodzie UITableViewDataSource -tableView: cellForRowAtIndexPath:

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
  4. Program obsługi zdarzeń dla przycisków

    - (void) accessoryButtonTapped:(UIButton *)button {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag % AccessoryViewTagSinceValue
                                                    inSection:button.tag / AccessoryViewTagSinceValue];
    
        [self.tableView.delegate tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
    }
  5. Zaimplementuj metodę UITableViewDelegate

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        // do sth.
    }
Panie Ming
źródło
1
Nikt nie powinien używać, tagchyba że jest to absolutnie konieczne, poszukaj innego rozwiązania.
Lifely,
2

Po dotknięciu przycisku można wywołać następującą metodę w podklasie UITableViewCell

 -(void)buttonTapped{
     // perform an UI updates for cell

     // grab the table view and notify it using the delegate
     UITableView *tableView = (UITableView *)self.superview;
     [tableView.delegate tableView:tableView accessoryButtonTappedForRowWithIndexPath:[tableView indexPathForCell:self]];

 }
Eric Welander
źródło
1

Z podejściem yanchenko musiałem dodać: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Jeśli używasz pliku xib do dostosowywania tableCell, to initWithStyle: reuseIdentifier: nie zostanie wywołany.

Zamiast tego zastąp:

-(void)awakeFromNib
{
//Put your code here 

[super awakeFromNib];

}
Toydor
źródło
1

Musisz użyć a, UIControlaby poprawnie uzyskać wysyłkę zdarzenia (na przykład a UIButton) zamiast prostego UIView/UIImageView.

ikarius
źródło
1

Szybki 5

W tym podejściu UIButton.tagdo przechowywania indexPath przy użyciu podstawowego przesuwania bitów. To podejście będzie działać w systemach 32 i 64-bitowych, o ile nie masz więcej niż 65535 sekcji lub wierszy.

public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

    let cell = tableView.dequeueReusableCell(withIdentifier: "cellId")
    let accessoryButton = UIButton(type: .custom)
    accessoryButton.setImage(UIImage(named: "imageName"), for: .normal)
    accessoryButton.sizeToFit()
    accessoryButton.addTarget(self, action: #selector(handleAccessoryButton(sender:)), for: .touchUpInside)

    let tag = (indexPath.section << 16) | indexPath.row
    accessoryButton.tag = tag
    cell?.accessoryView = accessoryButton

}

@objc func handleAccessoryButton(sender: UIButton) {
    let section = sender.tag >> 16
    let row = sender.tag & 0xFFFF
    // Do Stuff
}
Brody Robertson
źródło
0

Począwszy od iOS 3.2, możesz uniknąć przycisków, które inni tutaj zalecają, i zamiast tego użyj UIImageView z rozpoznawaniem gestów dotknięcia. Pamiętaj, aby włączyć interakcję z użytkownikiem, która jest domyślnie wyłączona w UIImageViews.

aeu
źródło