Використання користувальницького зображення для аксесуараView UITableViewCell та відповіді на UITableViewDelegate


139

Я використовую власноруч намальований 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:I, я забезпечив встановлення наступного властивості:

self.userInteractionEnabled = YES;

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

Що може бути відсутнім?

Дякую усім.

Відповіді:


228

На жаль, цей метод не викликається, якщо не натиснути внутрішній тип кнопки, який використовується при використанні одного з попередньо визначених типів. Щоб використовувати власний, вам доведеться створити ваш аксесуар у вигляді кнопки або іншого підкласу 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];
}

Ця функція визначає рядок, отримуючи розташування у поданні таблиці дотику від події, передбаченої кнопкою, і запитує подання таблиці на шлях індексу рядка в цій точці.


Спасибі Джим. Прикро, що я витратив більше 20 хвилин на роздуми, чому я не можу це зробити зі спеціальним imageView. Я щойно бачив, як це зробити в зразковому додатку Apple. Ваша відповідь добре пояснена і задокументована, хоча я так її помічаю і зберігаю. Знову дякую. :-)

Джим, чудова відповідь. Одне потенційне питання (принаймні, з мого кінця) - мені довелося додати наступний рядок, щоб доторкнутися до реєстрації кнопки: button.userInteractionEnabled = ТАК;
Майк Лоренс,

11
Тільки для інших, хто дивиться на цю відповідь, ви також можете просто поставити тег на кнопку, яка відповідає рядку (якщо у вас є кілька розділів, вам потрібно буде зробити математику), а потім просто витягнути тег із кнопки в функція. Я думаю, це може бути трохи швидше, ніж обчислення дотику.
RyanJM

3
це вимагає жорсткого кодування self.tableView. що робити, якщо ви не знаєте, який перегляд таблиці містить рядок?
користувач102008

4
@RyanJM Раніше я вважав, що робити хіт-тест є надмірним, і тегів вистачить. Я фактично використовував ідею тегів у своєму коді. Але сьогодні я зіткнувся з проблемою, коли користувач може додавати нові рядки. Це вбиває хак за допомогою тегів. Рішення, запропоноване Джимом Дові (і як видно з зразка коду Apple), є загальним рішенням і працює у всіх ситуаціях
srik

77

Мені здається, що цей веб-сайт дуже корисний: перегляд спеціальних аксесуарів для вашого 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];
    }
}

4
Я б сказав +1 для цього, оскільки Apple рекомендує робити їх у зразковому коді у своїх документах: developer.apple.com/library/ios/#samplecode/Accessory/Listings/…
cleverbit

Налаштування кадру було для мене браком. Ви також можете просто встановити зображення (замість фону), поки ви також не хочете жодного тексту.
Джеремі Хікс

1
Посилання порвалася у відповіді @ richarddas. Нове посилання: developer.apple.com/library/prerelease/ios/samplecode/Accessory/…
delavega66

7

Мій підхід полягає у створенні 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

Це найкраща відповідь. Але button.superview, cell.superviewі [tableView.delegate tableView:...]недостатньо безпечні.
Містер Мінг

3

Розширення до відповіді Джима Дові вище:

Будьте обережні, коли ви використовуєте UISearchBarController зі своїм UITableView. У такому випадку ви хочете перевірити self.searchDisplayController.activeта використовувати self.searchDisplayController.searchResultsTableViewзамість цього self.tableView. В іншому випадку ви отримаєте несподівані результати під час активного пошукуDisplayController, особливо коли результати пошуку прокручуються.

Наприклад:

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

2
  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.
    }

1
Ніхто не повинен використовувати, tagякщо, коли це абсолютно необхідно, шукайте інше рішення.
Життєздатний

2

Після натискання кнопки ви можете викликати наступний метод всередині підкласу 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]];

 }

1

З підходом Янченка мені довелося додати: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Якщо ви використовуєте файл xib для настройки свого tableCell, тоді initWithStyle: reuseIdentifier: не буде викликано.

Замість перевизначення:

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

[super awakeFromNib];

}

1

Ви повинні використовувати a, UIControlщоб правильно отримати розсилку подій (наприклад, a UIButton), а не просту UIView/UIImageView.


1

Швидкий 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
}

0

Станом на iOS 3.2 ви можете уникнути кнопок, які рекомендують інші тут, і замість цього використовувати ваш UIImageView з розпізнавачем жестів. Обов’язково ввімкніть взаємодію користувачів, яка за умовчанням вимкнена в UIImageViews.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.