长按UITableView

197

我想处理在UITableViewCell上的长按操作,以显示“快速访问菜单”。 已经有人做过这个了吗?

特别是在UITableView上的手势识别?

11个回答

435

首先将长按手势识别器添加到表视图:

UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc] 
  initWithTarget:self action:@selector(handleLongPress:)];
lpgr.minimumPressDuration = 2.0; //seconds
lpgr.delegate = self;
[self.myTableView addGestureRecognizer:lpgr];
[lpgr release];

然后在手势处理程序中:

-(void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    CGPoint p = [gestureRecognizer locationInView:self.myTableView];

    NSIndexPath *indexPath = [self.myTableView indexPathForRowAtPoint:p];
    if (indexPath == nil) {
        NSLog(@"long press on table view but not on a row");
    } else if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
        NSLog(@"long press on table view at row %ld", indexPath.row);
    } else {
        NSLog(@"gestureRecognizer.state = %ld", gestureRecognizer.state);
    }
}

在处理这个问题时,你需要小心,以免干扰用户对单元格的正常点击操作。同时,还要注意到handleLongPress可能会触发多次(这是由于手势识别器状态的变化所导致的)。


2
太棒了!非常感谢!但是最后一个小问题:为什么当触摸结束时会调用handleLongPress方法? - foOg
113
更正:它会多次触发来表示手势的不同状态(开始、变化、结束等)。因此,在处理方法中,检查手势识别器的状态属性以避免在每个手势状态下执行动作。例如:if (gestureRecognizer.state == UIGestureRecognizerStateBegan) ... - user467105
4
不要忘记,手势识别器现在可以直接在Interface Builder中添加到UI元素中,并通过一个IBAction方法连接,因此这个答案甚至更容易一些;-)(只需记住将识别器连接到UITableView,而不是UITableViewCell……) - user577537
11
请在class.h文件中确认实现UIGestureRecognizerDelegate协议。 - jay
2
如果您已经实现了'didSelectRowAtIndexPath',如何防止表视图进入单元格? - Marchy
显示剩余9条评论

47

我使用了Anna-Karenina的答案,它几乎完美地解决了一个非常严重的错误。

如果你正在使用章节,长按章节标题会导致你点击该章节上的第一行时出现错误结果,下面是我添加的修复版本(包括基于手势状态过滤虚拟调用的建议,来自Anna-Karenina)。

- (IBAction)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {

        CGPoint p = [gestureRecognizer locationInView:self.tableView];

        NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:p];
        if (indexPath == nil) {
            NSLog(@"long press on table view but not on a row");
        } else {
            UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
            if (cell.isHighlighted) {
                NSLog(@"long press on table view at section %d row %d", indexPath.section, indexPath.row);
            }
        }
    }
}

嗨@marmor:我想问一下,是否有可能仅识别用户触摸的视图部分? - Manthan
嘿,你可以使用hitTest实现这个功能(https://developer.apple.com/library/ios/documentation/uikit/reference/uiview_class/uiview/uiview.html#//apple_ref/occ/instm/UIView/hitTest:withEvent:)。参考这个答案中的示例来学习如何使用:https://dev59.com/s3E85IYBdhLWcg3wZyqt#2793253。 - marmor
虽然被接受的答案是有效的,但它会记录多次触发事件,并在屏幕拖动时进行记录。而这个答案不会。这是一个合法的复制粘贴答案。谢谢。 - ChrisOSX

41

在 Swift 5 中回答(Ricky 在 Swift 中的答案的延续)

UIGestureRecognizerDelegate 添加到您的 ViewController 中

 override func viewDidLoad() {
    super.viewDidLoad()

    //Long Press
    let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(handleLongPress))
    longPressGesture.minimumPressDuration = 0.5
    self.tableView.addGestureRecognizer(longPressGesture)
 }

而函数本身:

@objc func handleLongPress(longPressGesture: UILongPressGestureRecognizer) {
    let p = longPressGesture.location(in: self.tableView)
    let indexPath = self.tableView.indexPathForRow(at: p)
    if indexPath == nil {
        print("Long press on table view, not row.")
    } else if longPressGesture.state == UIGestureRecognizer.State.began {
        print("Long press on row, at \(indexPath!.row)")
    }
}

22
这里是将Dawn Song和Marmor的答案结合起来的明确指示。
拖动长按手势识别器并将其放入表格单元格中。它会跳到左侧列表的底部。

enter image description here

然后,您可以像连接按钮一样连接手势识别器。 输入图片描述

将Marmor的代码添加到操作处理程序中。

- (IBAction)handleLongPress:(UILongPressGestureRecognizer *)sender {
if (sender.state == UIGestureRecognizerStateBegan) {

    CGPoint p = [sender locationInView:self.tableView];

    NSIndexPath *indexPath = [self.tableView indexPathForRowAtPoint:p];
    if (indexPath == nil) {
        NSLog(@"long press on table view but not on a row");
    } else {
        UITableViewCell *cell = [self.tableView cellForRowAtIndexPath:indexPath];
        if (cell.isHighlighted) {
            NSLog(@"long press on table view at section %d row %d", indexPath.section, indexPath.row);
        }
    }
}

}


3
在我看来,最好的答案。 - Ahmed Ahmedov
9
长按手势识别器应该应用于表格视图而不是表格视图单元格。将其放置到表格视图单元格中只会使第0行监听长按操作。 - Alex

15

用 Swift 编写答案:

在你的 UITableViewController 中添加委托 UIGestureRecognizerDelegate

在 UITableViewController 中:

override func viewDidLoad() {
    super.viewDidLoad()

    let longPressGesture:UILongPressGestureRecognizer = UILongPressGestureRecognizer(target: self, action: "handleLongPress:")
    longPressGesture.minimumPressDuration = 1.0 // 1 second press
    longPressGesture.delegate = self
    self.tableView.addGestureRecognizer(longPressGesture)

}

而这个函数:

func handleLongPress(longPressGesture:UILongPressGestureRecognizer) {

    let p = longPressGesture.locationInView(self.tableView)
    let indexPath = self.tableView.indexPathForRowAtPoint(p)

    if indexPath == nil {
        print("Long press on table view, not row.")
    }
    else if (longPressGesture.state == UIGestureRecognizerState.Began) {
        print("Long press on row, at \(indexPath!.row)")
    }

}

15

7
为每一行分配一个新的手势识别对象如何比为整个表格分配单个识别器更高效? - user2393462435
8
请记住,如果出队列(dequeue)正常工作,只会创建几个细胞。 - Ants

7

我根据Anna Karenina的优秀答案,为UITableView准备了一个小类别。

这样一来,您就会像处理普通表视图时那样方便地拥有一个委托方法。请查看以下内容:

//  UITableView+LongPress.h

#import <UIKit/UIKit.h>

@protocol UITableViewDelegateLongPress;

@interface UITableView (LongPress) <UIGestureRecognizerDelegate>
@property(nonatomic,assign)   id <UITableViewDelegateLongPress>   delegate;
- (void)addLongPressRecognizer;
@end


@protocol UITableViewDelegateLongPress <UITableViewDelegate>
- (void)tableView:(UITableView *)tableView didRecognizeLongPressOnRowAtIndexPath:(NSIndexPath *)indexPath;
@end



//  UITableView+LongPress.m

#import "UITableView+LongPress.h"

@implementation UITableView (LongPress)
@dynamic delegate;

- (void)addLongPressRecognizer {
    UILongPressGestureRecognizer *lpgr = [[UILongPressGestureRecognizer alloc]
                                          initWithTarget:self action:@selector(handleLongPress:)];
    lpgr.minimumPressDuration = 1.2; //seconds
    lpgr.delegate = self;
    [self addGestureRecognizer:lpgr];
}


- (void)handleLongPress:(UILongPressGestureRecognizer *)gestureRecognizer
{
    CGPoint p = [gestureRecognizer locationInView:self];

    NSIndexPath *indexPath = [self indexPathForRowAtPoint:p];
    if (indexPath == nil) {
        NSLog(@"long press on table view but not on a row");
    }
    else {
        if (gestureRecognizer.state == UIGestureRecognizerStateBegan) {
            // I am not sure why I need to cast here. But it seems to be alright.
            [(id<UITableViewDelegateLongPress>)self.delegate tableView:self didRecognizeLongPressOnRowAtIndexPath:indexPath];
        }
    }
}

如果您想在UITableViewController中使用此功能,可能需要创建子类并遵循新协议。 对我来说效果很好,希望能帮助其他人!

委托和分类模式的惊人用法。 - valeCocoa

6

以下是Swift 3的答案,采用现代语法,融合其他答案,并消除不必要的代码。

override func viewDidLoad() {
    super.viewDidLoad()
    let recognizer = UILongPressGestureRecognizer(target: self, action: #selector(tablePressed))
    tableView.addGestureRecognizer(recognizer)
 }

@IBAction func tablePressed(_ recognizer: UILongPressGestureRecognizer) {
    let point = recognizer.location(in: tableView)

    guard recognizer.state == .began,
          let indexPath = tableView.indexPathForRow(at: point),
          let cell = tableView.cellForRow(at: indexPath),
          cell.isHighlighted
    else {
        return
    }

    // TODO
}

3

只需在Storyboard中的给定原型单元格中添加UILongPressGestureRecognizer,然后将手势拉到viewController的.m文件中创建一个操作方法。 我按照我说的做了。


你能再解释一下吗?你在VC中将原型单元格设置为属性了吗? - Ethan Parker

0
如果你想要一个纯Swift的答案而不使用@objc,你可以使用UITableViewDelegate的高亮函数来替代使用UILongPressGestureRecognizer
func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath)
func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath)

通过保留对高亮开始时间的引用,您可以轻松在您的UITableView中实现长按检测。
var highlightStartTime: Date?

func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
    highlightStartTime = Date()
}

func tableView(_ tableView: UITableView, didUnhighlightRowAt indexPath: IndexPath) {

    // Check if the highlight duration exceeds desired long-press duration
    guard let highlightStartTime,
          Date().timeIntervalSince(highlightStartTime) >= 2.0 
    else { 
        return 
    }

    // You can implement your long press here using the indexPath parameter
    self.highlightStartTime = nil
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接