Arrastar e soltar básico no iOS

93

Desejo ter uma visão na qual existem veículos circulando que o usuário também pode arrastar e soltar. Qual você acha que é a melhor estratégia em grande escala para fazer isso? É melhor obter eventos de toque nas visualizações que representam os veículos ou em uma visualização maior? Existe um paradigma simples que você usou para arrastar e soltar e com o qual está satisfeito? Quais são as desvantagens das diferentes estratégias?

Lucas
fonte

Respostas:

126

Suponha que você tenha uma UIViewcena com uma imagem de fundo e muitas vehicles, você pode definir cada novo veículo como um UIButton(UIImageView provavelmente funcionará também):

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(imageTouch:withEvent:) forControlEvents:UIControlEventTouchDown];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button setImage:[UIImage imageNamed:@"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];

Então você pode mover o veículo para onde quiser, respondendo ao UIControlEventTouchDragInsideevento, por exemplo:

- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
    CGPoint point = [[[event allTouches] anyObject] locationInView:self.view];
    UIControl *control = sender;
    control.center = point;
}

É muito mais fácil para um veículo individual lidar com seus próprios arrastos do que gerenciar a cena como um todo.

Oh Ho
fonte
Parece ótimo e muito simples! Vou experimentar isso.
Lucas
10
Essa estratégia faria com que o arrastamento parasse se o usuário arrastasse o botão mais rápido do que a atualização da animação? Se o dedo saísse da caixa, ele deixaria de receber a imagemMoved: withEvent: calls, correto?
Tim
Como você poderia saber se a imagem para de se arrastar? Como você saberia que o dedo se moveu?
ymutlu
ohho - é possível deslocar a "alça" de arraste de uma imagem ou botão para que possa ser arrastado pelo canto superior direito ou superior esquerdo?
LJ Wilson
quando estou tentando obter uma exceção - [drag_move_textViewController imageTouch: withEvent:]: seletor não reconhecido enviado para a instância 0x4b4b1c0
iosDev
34

Além da resposta de ohho, tentei uma implementação semelhante, mas sem problema de arrastar "rápido" e centralizar para arrastar.

A inicialização do botão é ...

UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragInside];
[button addTarget:self action:@selector(imageMoved:withEvent:) forControlEvents:UIControlEventTouchDragOutside];
[button setImage:[UIImage imageNamed:@"vehicle.png"] forState:UIControlStateNormal];
[self.view addSubview:button];

e implementação do método:

- (IBAction) imageMoved:(id) sender withEvent:(UIEvent *) event
{
    UIControl *control = sender;

    UITouch *t = [[event allTouches] anyObject];
    CGPoint pPrev = [t previousLocationInView:control];
    CGPoint p = [t locationInView:control];

    CGPoint center = control.center;
    center.x += p.x - pPrev.x;
    center.y += p.y - pPrev.y;
    control.center = center;
}

Não digo que essa seja a solução perfeita para a resposta. Mesmo assim, achei esta a solução mais fácil para arrastar.

JakubKnejzlik
fonte
2
Em seu código de exemplo, você define UIControl * control = sender depois que ele é usado - isso não deveria ser definido antes?
Peter Johnson
@PeterJohnson: Na verdade, não importa neste caso. Não há uso anterior desta variável desde a linha que você mencionou :)
JakubKnejzlik
1
E sobre CGPoint pPrev = [t previousLocationInView: control]; CGPoint p = [t locationInView: controle]; Essas duas linhas anteriores parecem se referir a "controle"?
Peter Johnson
Oh Deus! Como posso perder isso? : -O ... Eu editei a resposta.
JakubKnejzlik
adiciono o construtor de interface de formulário de botão e uso autolayout para algum login, eu escondo o botão e mostro-o novamente quando faço isso, ele retorna ao lugar original como evitar este beviour
Amr Angry
2

Tive um caso em que gostaria de arrastar / soltar uiviews entre várias outras uiviews. Nesse caso, encontrei a melhor solução para rastrear os eventos de panorâmica em uma supervisão contendo todas as zonas de soltar e todos os objetos de empena de arrasto. O principal motivo para NÃO adicionar os ouvintes de evento às visualizações arrastadas reais foi que perdi os eventos de panorâmica em andamento assim que alterei a visualização pela visualização arrastada.

Em vez disso, implementei uma solução de arrastar e soltar bastante genérica que poderia ser usada em uma ou várias zonas para soltar.

Eu criei um exemplo simples que pode ser visto aqui: Arraste e solte uiviews entre várias outras uiviews

Se você decidir ir por outro caminho, tente usar uicontrol em vez de uibutton. Eles declaram os métodos addTarget e são muito mais fáceis de estender - portanto, fornecem estado e comportamento personalizados.

Jeyben
fonte
1

Na verdade, eu rastrearia o arrastar na própria visualização do veículo , em vez da visualização grande - a menos que haja um motivo específico para não fazê-lo.

Em um caso em que permito que o usuário coloque itens arrastando-os na tela. Nesse caso, experimentei fazer com que a vista superior e a vista secundária fossem arrastáveis. Descobri que é um código mais limpo se você adicionar algumas visualizações "arrastáveis" ao UIView e controlar como elas podem ser arrastadas. Usei um retorno de chamada simples para o UIView pai para verificar se o novo local era adequado ou não - para que eu pudesse indicar com animações.

Suponho que ter a trilha da vista superior arrastando também é bom, mas isso torna um pouco mais confuso se você gostaria de adicionar visualizações não arrastáveis ​​que ainda interagem com o usuário, como um botão.

Magnus
fonte
1
-(void)funcAddGesture
{ 

 // DRAG BUTTON
        UIPanGestureRecognizer *panGestureRecognizer;
        panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(dragButton:)];
        panGestureRecognizer.cancelsTouchesInView = YES;

        UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom];
        button.frame = CGRectMake(50, 100, 60, 60);
        [button addTarget:self action:@selector(buttontapEvent) forControlEvents:UIControlEventPrimaryActionTriggered];
        [button setImage:[UIImage imageNamed:@"button_normal.png"] forState:UIControlStateNormal];
        [button addGestureRecognizer:panGestureRecognizer];
        [self.view addSubview:button];
}


- (void)dragButton:(UIPanGestureRecognizer *)recognizer {

    UIButton *button = (UIButton *)recognizer.view;
    CGPoint translation = [recognizer translationInView:button];

    float xPosition = button.center.x;
    float yPosition = button.center.y;
    float buttonCenter = button.frame.size.height/2;

    if (xPosition < buttonCenter)
        xPosition = buttonCenter;
    else if (xPosition > self.view.frame.size.width - buttonCenter)
        xPosition = self.view.frame.size.width - buttonCenter;

    if (yPosition < buttonCenter)
        yPosition = buttonCenter;
    else if (yPosition > self.view.frame.size.height - buttonCenter)
        yPosition = self.view.frame.size.height - buttonCenter;

    button.center = CGPointMake(xPosition + translation.x, yPosition + translation.y);
    [recognizer setTranslation:CGPointZero inView:button];
}


- (void)buttontapEvent {

    NSLog(@"buttontapEvent");
}
Balaji G
fonte
1

a resposta do ohho funcionou bem para mim. Caso você precise da versão 2.x rápida:

override func viewDidLoad() {

    let myButton = UIButton(type: .Custom)
    myButton.setImage(UIImage(named: "vehicle"), forState: .Normal)

    // drag support
    myButton.addTarget(self, action:#selector(imageMoved(_:event:)), forControlEvents:.TouchDragInside)
    myButton.addTarget(self, action:#selector(imageMoved(_:event:)), forControlEvents:.TouchDragOutside)
}

private func imageMoved(sender: AnyObject, event: UIEvent) {
    guard let control = sender as? UIControl else { return }
    guard let touches = event.allTouches() else { return }
    guard let touch = touches.first else { return }

    let prev = touch.previousLocationInView(control)
    let p = touch.locationInView(control)
    var center = control.center
    center.x += p.x - prev.x
    center.y += p.y - prev.y
    control.center = center
}
parag
fonte
0

Para Swift 4 Maneira fácil e mais fácil. 😛

Etapa 1: conecte o botão do storyboard para visualizar o controlador. E definir todas as restrições básicas do AutoLayout

 @IBOutlet weak var cameraButton: UIButton!

Etapa 2: adicionar gesto panorâmico para o botão em viewDidLoad ()

self.cameraButton.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(self.panGestureHandler(panGesture:))))

Etapa 3:

Adicionar panGestureHandler

@objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {

         let location = recognizer.location(in: view)
        cameraButton.center = location

    }

E agora o seu botão de ação

@IBAction func ButtonAction(_ sender: Any) {

        print("This is button Action")
    }

insira a descrição da imagem aqui

Adicionar Veja o Resultado ☝️

Anup Gupta
fonte