Использование настраиваемого изображения для AccessoryView UITableViewCell и его ответ на UITableViewDelegate

Я использую пользовательский нарисованный UITableViewCell, в том числе для ячейки accessoryView. Моя настройка для accessoryView происходит примерно так:

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

Также при инициализации ячейки с помощью initWithFrame:reuseIdentifier: я установил следующее свойство:

self.userInteractionEnabled = YES;

К сожалению, в моем UITableViewDelegate мой метод tableView:accessoryButtonTappedForRowWithIndexPath: (попробуйте повторить это 10 раз) не запускается. Делегат определенно подключен правильно.

Чего может не хватать?

Спасибо всем.


person Community    schedule 15.05.2009    source источник


Ответы (10)


К сожалению, этот метод не вызывается, пока не будет нажат внутренний тип кнопки, предоставленный при использовании одного из предопределенных типов. Чтобы использовать свой собственный, вам нужно будет создать свой аксессуар в виде кнопки или другого подкласса UIControl (я бы рекомендовал кнопку с использованием -buttonWithType:UIButtonTypeCustom и установкой изображения кнопки, а не с использованием UIImageView).

Вот некоторые вещи, которые я использую в Outpost, который настраивает достаточно стандартных виджетов (совсем немного, чтобы соответствовать нашей бирюзовой окраске), которые я закончил, создав свой собственный промежуточный подкласс UITableViewController для хранения служебного кода для всех других представлений таблиц (теперь они подклассы OPTableViewController).

Во-первых, эта функция возвращает новую кнопку раскрытия подробностей, используя нашу настраиваемую графику:

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

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

    return ( button );
}

Когда это будет сделано, кнопка вызовет эту процедуру, которая затем подает стандартную процедуру UITableViewDelegate для дополнительных кнопок:

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

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

person Jim Dovey    schedule 15.05.2009
comment
Спасибо, Джим. Жаль, что я потратил более 20 минут на размышления, почему я не могу сделать это с помощью настраиваемого imageView. Я только что видел, как это сделать, на примере приложения Apple Accessory. Ваш ответ хорошо объяснен и задокументирован, поэтому я отмечаю его и сохраняю. Еще раз спасибо. :-) - person ; 15.05.2009
comment
Джим, отличный ответ. Одна потенциальная проблема (по крайней мере, с моей стороны) - мне пришлось добавить следующую строку, чтобы прикоснуться к кнопке: button.userInteractionEnabled = YES; - person Mike Laurence; 27.08.2009
comment
Просто для тех, кто смотрит на этот ответ, вы также можете просто поместить тег на кнопку, которая соответствует строке (если у вас несколько разделов, вам нужно будет выполнить некоторую математику), а затем просто вытащите тег из кнопки в функция. Думаю, это может быть немного быстрее, чем вычисление касания. - person RyanJM; 10.11.2010
comment
для этого необходимо жестко запрограммировать self.tableView. что делать, если вы не знаете, какое представление таблицы содержит строку? - person user102008; 11.11.2011
comment
Поднимитесь по цепочке представлений, начиная с button.superview, пока не найдете экземпляр UITableView. - person Jim Dovey; 16.11.2011
comment
Странная ошибка получения [UIButton outpostDetailDisclosureButton] не может быть найдена. Использование iPhone SDK 5.1.1 Xcode 4.4 - person Matej; 11.08.2012
comment
Это не встроенный метод; вам необходимо реализовать собственный метод создания кнопки. - person Jim Dovey; 11.08.2012
comment
У меня тоже работает. Если вы используете раскадровки и последовательности для навигации, метод обработки касания должен выглядеть примерно так: {code} - (void) accessoryButtonTapped: (UIControl *) button withEvent: (UIEvent *) event {[self performSegueWithIdentifier: @segueName sender :себя]; } {code} - person stoffer; 21.03.2013
comment
@RyanJM Раньше я думал, что выполнение HitTest излишне, и тегов будет достаточно. Фактически, я использовал идею тегов в некоторых частях своего кода. Но сегодня я столкнулся с проблемой, когда пользователь может добавлять новые строки. Это убивает взлом с помощью тегов. Решение, предложенное Джимом Дови (как видно из примера кода Apple), является универсальным и работает во всех ситуациях. - person srik; 28.08.2013
comment
@srik - Отличный момент. Спасибо, что указали на это сегодня. Я как раз собираюсь реорганизовать код, в котором я это сделал. - person RyanJM; 28.08.2013

Мне очень помог этот веб-сайт: настраиваемый вид аксессуаров для uitableview на iphone

Короче говоря, используйте это в 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;

затем реализуйте этот метод:

- (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];
    }
}
person Jon    schedule 06.10.2010
comment
Я бы сказал +1 для этого, поскольку это то, что Apple рекомендует делать в своем образце кода в своих документах: developer.apple.com/library/ios/#samplecode/Accessory/Listings/ - person cleverbit; 08.10.2012
comment
Установка рамы была для меня недостающей частью. Вы также можете просто установить изображение (вместо фона), если вам также не нужен текст. - person Jeremy Hicks; 30.05.2014
comment
Ссылка не работает в ответе @ richarddas. Новая ссылка: developer.apple.com/library/prerelease/ios/samplecode/Accessory/ - person delavega66; 12.01.2016

Мой подход заключается в создании подкласса UITableViewCell и инкапсуляции логики, которая будет вызывать в нем обычный метод UITableViewDelegate.

// 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
person yanchenko    schedule 04.05.2012
comment
Это ЛУЧШИЙ ответ. Но button.superview, cell.superview и [tableView.delegate tableView:...] недостаточно безопасны. - person Mr. Ming; 08.09.2015

Расширение ответа Джима Дови выше:

Будьте осторожны при использовании UISearchBarController с UITableView. В этом случае вы хотите проверить self.searchDisplayController.active и использовать self.searchDisplayController.searchResultsTableView вместо self.tableView. В противном случае вы получите неожиданные результаты, когда searchDisplayController активен, особенно когда результаты поиска прокручиваются.

Например:

- (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];
}
person Zaggo    schedule 02.05.2013

  1. Определите макрос для тегов кнопок:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
    
  2. Создать кнопку и установить cell.accessoryView при создании ячейки

    UIButton *accessoryButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
    accessoryButton.frame = CGRectMake(0, 0, 30, 30);
    [accessoryButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = accessoryButton;
    
  3. Установите cell.accessoryView.tag с помощью indexPath в методе UITableViewDataSource -tableView: cellForRowAtIndexPath:

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
    
  4. Обработчик событий для кнопок

    - (void) accessoryButtonTapped:(UIButton *)button {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag % AccessoryViewTagSinceValue
                                                    inSection:button.tag / AccessoryViewTagSinceValue];
    
        [self.tableView.delegate tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
    }
    
  5. Реализуйте метод UITableViewDelegate

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        // do sth.
    }
    
person Mr. Ming    schedule 11.07.2012
comment
Никто не должен использовать tag, кроме случаев крайней необходимости, ищите другое решение. - person Lifely; 10.07.2015

Когда кнопка нажата, вы можете вызвать следующий метод внутри подкласса 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]];

 }
person Eric Welander    schedule 22.02.2013

При подходе Янченко пришлось добавить: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Если вы используете файл xib для настройки tableCell, то initWithStyle: reuseIdentifier: не будет вызван.

Вместо этого переопределите:

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

[super awakeFromNib];

}
person Toydor    schedule 02.07.2012

Вы должны использовать UIControl для правильного получения диспетчеризации событий (например, UIButton) вместо простого UIView/UIImageView.

person ikarius    schedule 05.10.2010

Swift 5

В этом подходе UIButton.tag используется для хранения indexPath с использованием базового битового сдвига. Этот подход будет работать в 32- и 64-битных системах, если у вас не более 65535 разделов или строк.

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
}
person Brody Robertson    schedule 05.06.2019

Начиная с iOS 3.2, вы можете избегать кнопок, которые рекомендуют другие, и вместо этого использовать свой UIImageView с распознавателем жестов касания. Обязательно включите взаимодействие с пользователем, которое по умолчанию отключено в UIImageViews.

person aeu    schedule 03.11.2014