Pressão longa no UITableView

191

Gostaria de pressionar uma vez a UITableViewCellpara imprimir um "menu de acesso rápido". Alguém já fez isso?

Particularmente o gesto reconhece UITableView?

foOg
fonte

Respostas:

427

Primeiro, adicione o reconhecedor de gestos de pressão longa à exibição da tabela:

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

Em seguida, no manipulador de gestos:

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

Você deve ter cuidado com isso para que não interfira com o toque normal da célula no usuário e também observe que handleLongPresspode disparar várias vezes (isso ocorrerá devido às alterações de estado do reconhecedor de gestos).

CD-espaço
fonte
1
Impressionante !!! Muito obrigado! Mas uma última pergunta: por que o método handleLongPress é chamado quando o toque terminou ???
foOg 13/10/10
111
Correção: dispara várias vezes para indicar os diferentes estados do gesto (iniciado, alterado, finalizado, etc.). Portanto, no método manipulador, verifique a propriedade state do reconhecedor de gestos para evitar executar a ação em cada estado do gesto. Por exemplo: if (gestureRecognizer.state == UIGestureRecognizerStateBegan) ....
3
Não se esqueça, agora os reconhecedores de gestos podem ser adicionados aos elementos da interface do usuário diretamente no Interface Builder e conectados através de um método IBAction, portanto, essa resposta é ainda mais fácil ;-) (lembre-se de anexar o reconhecedor ao UITableView, e não ao UITableViewCell...)
10
Confirme também o protocolo UIGestureRecognizerDelegate no arquivo class.h
Vaquita
1
Como você evita que a exibição da tabela navegue na célula (se você implementou 'didSelectRowAtIndexPath')?
março
46

Eu usei a resposta de Anna-Karenina e funciona quase perfeitamente com um bug muito sério.

Se você estiver usando seções, pressionar longamente o título da seção fornecerá um resultado errado ao pressionar a primeira linha dessa seção. Adicionei uma versão fixa abaixo (incluindo a filtragem de chamadas falsas com base no estado do gesto, por Sugestão de 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
fonte
Oi @ marmor: quero perguntar se é possível identificar apenas uma parte da visualização que o usuário tocou?
precisa saber é o seguinte
Ei, você pode usar o hitTest para isso ( developer.apple.com/library/ios/documentation/uikit/reference/… ). Verifique esta resposta, por exemplo, sobre como usar: stackoverflow.com/a/2793253/819355
marmor 10/14
Enquanto a resposta aceita funciona. Ele registra vários incêndios, além de registrar enquanto arrasta na tela. Esta resposta não. Uma resposta legível para copiar e colar. Obrigado.
precisa saber é o seguinte
29

Resposta em Swift 5 (continuação da resposta de Ricky em Swift)

Adicione UIGestureRecognizerDelegateao seu ViewController

 override func viewDidLoad() {
    super.viewDidLoad()

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

E a função:

@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)")
    }
}
Ben
fonte
20

Aqui estão as instruções esclarecidas combinando a resposta de Dawn Song e a de Marmor.

Arraste um longo Press Gesture Recognizer e solte-o na sua célula da tabela. Irá pular para o final da lista à esquerda.

insira a descrição da imagem aqui

Em seguida, conecte o reconhecedor de gestos da mesma maneira que conectaria um botão. insira a descrição da imagem aqui

Adicione o código do Marmor no manipulador de ações

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

}

Ryan Heitner
fonte
2
A melhor resposta na minha opinião
Asen Kasimov
8
O reconhecimento de gestos de pressão longa deve ser aplicado à célula de exibição de tabela e não à exibição de tabela. Soltá-lo na célula de exibição de tabela terá apenas a linha 0 ouvindo pressão longa.
Alex
14

Parece ser mais eficiente adicionar o reconhecedor diretamente à célula, como mostrado aqui:

Toque e segure para células TableView, então e agora

(role até o exemplo na parte inferior)

JR
fonte
7
Como a alocação de um novo objeto reconhecedor de gestos para cada linha pode ser mais eficiente do que um único reconhecedor para toda a tabela?
user2393462435
7
Lembre-se de que existem apenas algumas células criadas se a desenfileiramento estiver funcionando corretamente.
Formigas
11

Resposta em Swift:

Adicione delegado UIGestureRecognizerDelegateao seu UITableViewController.

No 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)

}

E a função:

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)")
    }

}
Ricky
fonte
6

Montei uma pequena categoria no UITableView com base na excelente resposta de Anna Karenina.

Assim, você terá um método de delegação conveniente, como está acostumado ao lidar com visualizações de tabela regulares. Confira:

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

Se você deseja usar isso em um UITableViewController, provavelmente precisará subclassificar e estar em conformidade com o novo protocolo.

Funciona muito bem para mim, espero que ajude os outros!

de.
fonte
Uso surpreendente de padrões delegação e categoria
valeCocoa
5

Resposta Swift 3, usando sintaxe moderna, incorporando outras respostas e eliminando códigos desnecessários.

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
}
phatmann
fonte
2

Basta adicionar UILongPressGestureRecognizer à célula de protótipo fornecida no storyboard e puxar o gesto para o arquivo .m do viewController para criar um método de ação. Eu fiz como eu disse.

DawnSong
fonte
Você pode explicar um pouco mais? Você transformou a célula protótipo em uma propriedade no seu VC?
Ethan Parker
-2

Use a propriedade de carimbo de data / hora do UITouch em touchesBegan para iniciar um timer ou pará-lo quando toquesEnded foi demitido

Thomas Joulin
fonte
Obrigado pela sua resposta, mas como posso detectar qual linha está preocupada com o toque?
foOg 13/10/10
Posso estar errado, mas nada é fornecido para ajudá-lo a fazer isso. Você precisará obter os índices das células visíveis atuais com [tableView indexPathsForVisibleRows] e, em seguida, usando alguns cálculos (seu tableView deslocado da parte superior + X vezes as linhas), você saberá que as coordenadas do seu dedo estão nas quais linha.
Thomas Joulin 13/10/10
Eu tenho certeza que existe uma maneira mais fácil de fazer isso, de qualquer maneira, se você tiver outra idéia, eu estarei aqui :) #
#
Ficaria feliz em saber também se algo mais fácil é possível. Mas acho que não, principalmente porque não é assim que a Apple quer que lidemos com interações ... Parece uma maneira Android de pensar neste "menu de acesso rápido". Se fosse meu aplicativo, lidarei com ele como o aplicativo do Twitter. Um golpe para a esquerda exibe as opções
Thomas Joulin 13/10
Sim, eu pensei sobre isso, então se eu realmente não puder fazer isso com um evento de imprensa longo, usarei o método swipe. Mas, talvez alguém na pilha de estouro fez isso ...
foog