超簡単!テーブルのセルの長押しでアクションシートを出す

テーブルに長押しジェスチャーを追加する場合、編集モードでは並べ替えのドラッグと干渉してしまうので工夫が必要です。

もし、長押しで指の追跡やリリースを検出しなくて良い(何らかのアクションを起こすだけ)なら前の記事で書いたMenuControllerのメソッドを拝借すれば簡単です。

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    // ここではジェスチャーが効いているのでハイライトが消せない
    // reloadして強制リセット
    // Cancel gestures during tracking
    [tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationNone];
    
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"title" message:@"message" preferredStyle:UIAlertControllerStyleActionSheet];
    if (alertController.popoverPresentationController != nil) {
        alertController.title = nil;
        alertController.message = nil;
        alertController.popoverPresentationController.sourceView = self.view;
        alertController.popoverPresentationController.sourceRect = [tableView rectForRowAtIndexPath:indexPath];
    }
    [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:nil]];
    [self presentViewController:alertController animated:YES completion:nil];
    return YES;
}

- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return NO;
}

- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
}

そもそもメニューは表示させてないので、UIMenuControllerWillShowMenuなどの通知は一切飛んできません。

ちなみに、この長押しジェスチャーはセル内のcontentViewにセットされています。iOS 10にて確認。

テーブルのセルに削除やカスタムのメニューを表示させる

テーブルにはUIMenuControllerのメニューを表示させるデリゲートが用意されていますが、項目をカスタマイズしたり、内蔵のDeleteを出したい時にはCellのサブクラスに一手間加える必要があります。しかも押した時のアクションはそのCell内のメソッドで受けるようになっているので、VC側で記述できるように工夫しておきます。

@implementation MyCell

- (BOOL)canBecomeFirstResponder {
    return YES;
}

- (BOOL)canPerformAction:(SEL)action withSender:(id)sender {
    if (action == @selector(delete:)) {
        return YES;
    }
    return NO;
}

- (void)performSelector:(SEL)aSelector sender:(id)sender {
    UIView *superview = self;
    while ((superview = superview.superview) != nil) {
        if ([superview isKindOfClass:[UITableView class]]) {
            UITableView *tableView = (id)superview;
            NSIndexPath *indexPath = [tableView indexPathForCell:self];
            if (indexPath != nil) {
                [tableView.delegate tableView:tableView performAction:aSelector forRowAtIndexPath:indexPath withSender:sender];
            }
            break;
        }
    }
}

- (void)delete:(id)sender {
    [self performSelector:_cmd sender:sender];
}

@end

セルからsuperviewを遡ってテーブルを探します。セル側でこうしておくと、Deleteが押された時の処理はテーブルのdelegateに渡されるので、実際の処理はVC側で実装できます。

- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath {
    return YES;
}

- (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    return YES;
}

- (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender {
    if (action == sel_registerName("delete:")) {
        // 削除処理
    }
}