UITableViewCellのアクセサリビューのカスタム画像を使用してUITableViewDelegateに応答させる


139

セルの同じものを含む、カスタム描画されたUITableViewCellを使用していますaccessoryView。accessoriesViewのセットアップは、次のような方法で行われます。

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回繰り返してみます)がトリガーされません。デリゲートは間違いなく正しく接続されています。

何が欠けている可能性がありますか?

皆さんありがとう。

回答:


228

悲しいことに、事前定義されたタイプの1つを使用するときに提供される内部ボタンタイプがタップされない限り、そのメソッドは呼び出されません。独自のアクセサリを使用するには、ボタンまたは他のUIControlサブクラスとしてアクセサリを作成する必要があります(UIImageView -buttonWithType:UIButtonTypeCustomではなく、ボタンの画像を使用して設定することをお勧めします)。

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

この関数は、ボタンによって提供されるイベントからタッチのテーブルビュー内の位置を取得し、テーブルビューにその時点での行のインデックスパスを要求することにより、行を見つけます。


ジムに感謝します。それが、なぜカスタムのimageViewでそれができないのかと思って20分以上費やしたのは残念です。Appleのサンプルアクセサリアプリでこれを行う方法を見たところです。あなたの答えはよく説明され文書化されているので、私はそれをマークアップし、それを維持します。再度、感謝します。:-)

ジム、すばらしい答え。1つの潜在的な問題(少なくとも私の側では)-ボタンに登録するタッチを取得するために、次の行を追加する必要がありました。button.userInteractionEnabled = YES;
マイクローレンス、

11
他の人がこの答えを見ている場合は、行に対応するボタンにタグを付けて(複数のセクションがある場合は、いくつかの計算を行う必要があります)、ボタンからタグを引き出すこともできます。関数。タッチを計算するよりも少し速いかもしれません。
RyanJM 2010年

3
これには、をハードコーディングする必要がありますself.tableView。行を含むテーブルビューがわからない場合はどうなりますか?
user102008

4
@RyanJM以前は、hitTestを実行するのはやりすぎで、タグで十分だと思っていました。実際、一部のコードでタグのアイデアを使用しています。しかし、今日、ユーザーが新しい行を追加できるという問題が発生しました。これはタグを使用してハックを殺します。Jim Doveyが提案したソリューション(およびAppleのサンプルコードに見られる)は一般的なソリューションであり、すべての状況で機能します
srik

77

私はこのウェブサイトがとても役に立ったと思いました: iphoneのuitableviewのカスタムアクセサリービュー

つまり、これを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
それはAppleがそのドキュメントでのサンプルコードでやってお勧めします何だと私は、このための1を言うと思います:developer.apple.com/library/ios/#samplecode/Accessory/Listings/...
cleverbit

フレームを設定することは私にとって欠けている部分でした。テキストも必要ない限り、(背景の代わりに)単にsetImageを使用することもできます。
Jeremy Hicks、2014年

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.superviewcell.superviewそして[tableView.delegate tableView:...]十分に安全ではありません。
Ming氏、

3

上記のジム・ドベイの答えの拡張:

UITableViewでUISearchBarControllerを使用するときは注意してください。その場合は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];
}

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. UITableViewDataSourceメソッド-tableView:cellForRowAtIndexPathのindexPathでcell.accessoryView.tagを設定します。

    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とき、絶対に必要な、他のソリューションを検索していない限り。
活気ある2015

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

simpleの代わりに、UIControlを使用してイベントディスパッチ(たとえばUIButton)を適切に取得する必要がありますUIView/UIImageView


1

スウィフト5

このアプローチでは、UIButton.tag基本的なビットシフトを使用してindexPathを格納します。このアプローチは、セクションまたは行が65535を超えない限り、32ビットシステムと64ビットシステムで機能します。

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ではデフォルトでオフになっているユーザー操作を必ず有効にしてください。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.