Sử dụng hình ảnh tùy chỉnh cho phụ kiện của UITableViewCell và để nó phản hồi với UITableViewDelegate


139

Tôi đang sử dụng một UITableViewCell được vẽ tùy chỉnh, bao gồm tương tự cho các ô accessoryView. Thiết lập của tôi cho phụ kiện Xem xảy ra bằng cách như thế này:

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

Ngoài ra khi ô được khởi tạo, sử dụng initWithFrame:reuseIdentifier:I đảm bảo để đặt thuộc tính sau:

self.userInteractionEnabled = YES;

Thật không may trong UITableViewDelegate của tôi, tableView:accessoryButtonTappedForRowWithIndexPath:phương thức của tôi (thử lặp lại 10 lần) không được kích hoạt. Các đại biểu chắc chắn được lên dây đúng cách.

Những gì có thể thiếu?

Cảm ơn tất cả.

Câu trả lời:


228

Đáng buồn là phương thức đó không được gọi trừ khi loại nút bên trong được cung cấp khi bạn sử dụng một trong các loại được xác định trước được gõ. Để sử dụng của riêng bạn, bạn sẽ phải tạo phụ kiện của mình dưới dạng nút hoặc lớp con UIControl khác (Tôi khuyên bạn nên sử dụng nút -buttonWithType:UIButtonTypeCustomvà đặt hình ảnh của nút, thay vì sử dụng UIImageView).

Đây là một số thứ tôi sử dụng trong Outpost, có thể tùy chỉnh đủ các tiện ích tiêu chuẩn (chỉ một chút, để phù hợp với màu teal của chúng tôi) mà tôi đã sử dụng lớp con trung gian UITableViewControll của riêng mình để giữ mã tiện ích cho tất cả các chế độ xem bảng khác để sử dụng (chúng hiện đang phân lớp OPTableViewControll).

Đầu tiên, chức năng này trả về nút tiết lộ chi tiết mới bằng đồ họa tùy chỉnh của chúng tôi:

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

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

    return ( button );
}

Nút sẽ gọi thủ tục này khi hoàn thành, sau đó cung cấp thói quen UITableViewDelegate tiêu chuẩn cho các nút phụ kiện:

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

Hàm này định vị hàng bằng cách lấy vị trí trong chế độ xem bảng của một lần chạm từ sự kiện được cung cấp bởi nút và hỏi chế độ xem bảng cho đường dẫn chỉ mục của hàng tại điểm đó.


Cảm ơn Jim. Đó là một sự xấu hổ tôi đã dành hơn 20 phút để tự hỏi tại sao tôi không thể làm điều đó với một imageView tùy chỉnh. Tôi chỉ thấy cách làm điều này trên ứng dụng Phụ kiện mẫu của Apple. Câu trả lời của bạn được giải thích rõ ràng và được ghi lại mặc dù vậy tôi đang đánh dấu nó và giữ nó xung quanh. Cảm ơn một lần nữa. :-)

Jim, câu trả lời tuyệt vời. Một vấn đề tiềm ẩn (ít nhất là ở phần cuối của tôi) - Tôi đã phải thêm dòng sau để có được các liên lạc để đăng ký trên nút: button.userInteractionEnables = YES;
Mike Laurence

11
Chỉ cần những người khác nhìn vào câu trả lời này, bạn cũng có thể đặt một thẻ vào nút tương ứng với hàng (nếu bạn có nhiều phần, bạn sẽ cần làm một số phép toán) và sau đó chỉ cần kéo thẻ ra khỏi nút trong chức năng. Tôi nghĩ rằng nó có thể nhanh hơn một chút so với tính toán cảm ứng.
RyanJM

3
điều này đòi hỏi bạn phải cứng mã self.tableView. Điều gì nếu bạn không biết xem bảng nào chứa hàng?
user102008

4
@RyanJM Tôi từng nghĩ rằng làm hitTest là quá mức cần thiết và các thẻ sẽ đủ. Trong thực tế tôi đã sử dụng ý tưởng thẻ trong một số mã của tôi. Nhưng hôm nay tôi gặp phải một vấn đề trong đó người dùng có thể thêm hàng mới. Điều này giết chết hack bằng cách sử dụng thẻ. Giải pháp được đề xuất bởi Jim Dovey (và như đã thấy trong mã mẫu của Apple) là một giải pháp chung và hoạt động trong mọi tình huống
srik

77

Tôi thấy trang web này rất hữu ích: chế độ xem phụ kiện tùy chỉnh cho uitableview của bạn trong iphone

Tóm lại, sử dụng điều này trong 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;

Sau đó, thực hiện phương pháp này:

- (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
Tôi muốn nói +1 cho điều này vì đó là những gì Apple khuyên bạn nên làm trong mã mẫu của họ trong tài liệu của họ: developer.apple.com/l
Library / ios / # samplecode /Acoryory / Listings / minh

Đặt khung là phần còn thiếu đối với tôi. Bạn cũng có thể chỉ setImage (thay vì nền) miễn là bạn không muốn bất kỳ văn bản nào.
Jeremy Hicks

1
Liên kết đã bị hỏng trong câu trả lời của @ richarddas. Liên kết mới: developer.apple.com/lvern/prerelease/ios/samplecode/Accessory/
mẹo

7

Cách tiếp cận của tôi là tạo một UITableViewCelllớp con và đóng gói logic sẽ gọi UITableViewDelegatephương thức thông thường trong nó.

// 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

Đây là câu trả lời TỐT NHẤT. Nhưng button.superview, cell.superview[tableView.delegate tableView:...]không đủ an toàn.
Ông Minh

3

Một phần mở rộng cho câu trả lời của Jim Dovey ở trên:

Hãy cẩn thận khi bạn sử dụng UISearchBarControll với UITableView của bạn. Trong trường hợp đó bạn muốn kiểm tra self.searchDisplayController.activevà sử dụng self.searchDisplayController.searchResultsTableViewthay vì self.tableView. Nếu không, bạn sẽ nhận được kết quả không mong muốn khi searchDisplayControll hoạt động, đặc biệt là khi kết quả tìm kiếm được cuộn.

Ví dụ:

- (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. Xác định một macro cho các thẻ của các nút:

    #define AccessoryViewTagSinceValue 100000 // (AccessoryViewTagSinceValue * sections + rows) must be LE NSIntegerMax
  2. Tạo nút và đặt cell.accessoryView khi tạo một ô

    UIButton *accessoryButton = [UIButton buttonWithType:UIButtonTypeContactAdd];
    accessoryButton.frame = CGRectMake(0, 0, 30, 30);
    [accessoryButton addTarget:self action:@selector(accessoryButtonTapped:) forControlEvents:UIControlEventTouchUpInside];
    cell.accessoryView = accessoryButton;
  3. Đặt cell.accessoryView.tag theo indexPath trong phương thức UITableViewDataSource -tableView: cellForRowAtIndexPath:

    cell.accessoryView.tag = indexPath.section * AccessoryViewTagSinceValue + indexPath.row;
  4. Trình xử lý sự kiện cho các nút

    - (void) accessoryButtonTapped:(UIButton *)button {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:button.tag % AccessoryViewTagSinceValue
                                                    inSection:button.tag / AccessoryViewTagSinceValue];
    
        [self.tableView.delegate tableView:self.tableView accessoryButtonTappedForRowWithIndexPath:indexPath];
    }
  5. Thực hiện phương thức UITableViewDelegate

    - (void)tableView:(UITableView *)tableView accessoryButtonTappedForRowWithIndexPath:(NSIndexPath *)indexPath {
        // do sth.
    }

1
Không ai nên sử dụng tagtrừ khi cần thiết tuyệt đối, tìm kiếm giải pháp khác.
Trọn đời

2

Khi nhấn nút, bạn có thể gọi nó theo phương thức sau trong lớp con 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

Với cách tiếp cận yanchenko tôi đã phải thêm: [accBtn setFrame:CGRectMake(0, 0, 20, 20)];

Nếu bạn đang sử dụng tệp xib để tùy chỉnh bảngCell của mình thì initWithStyle: reuseIdentifier: sẽ không được gọi.

Thay vì ghi đè:

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

[super awakeFromNib];

}

1

Bạn phải sử dụng a UIControlđể nhận công văn sự kiện (ví dụ a UIButton) thay vì đơn giản UIView/UIImageView.


1

Swift 5

Cách tiếp cận này sử dụng UIButton.tagđể lưu trữ indexPath bằng cách sử dụng dịch chuyển bit cơ bản. Cách tiếp cận sẽ hoạt động trên các hệ thống 32 & 64 bit miễn là bạn không có hơn 65535 phần hoặc hàng.

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

Kể từ iOS 3.2, bạn có thể tránh các nút mà những người khác ở đây đang khuyến nghị và thay vào đó hãy sử dụng UIImageView của bạn bằng một công cụ nhận dạng cử chỉ nhấn. Đảm bảo bật tương tác người dùng, tắt theo mặc định trong UIImageViews.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.