Drag and Drop di base in iOS


93

Voglio avere una vista in cui ci sono veicoli in circolazione che l'utente può anche trascinare e rilasciare. Quale pensi sia la migliore strategia su larga scala per farlo? È meglio ottenere eventi tattili dalle viste che rappresentano i veicoli o da una vista più ampia? C'è un semplice paradigma che hai usato per il drag and drop di cui sei soddisfatto? Quali sono gli svantaggi delle diverse strategie?


Risposte:


126

Supponiamo di avere una UIViewscena con un'immagine di sfondo e molte vehicles, puoi definire ogni nuovo veicolo come UIButton(probabilmente funzionerà anche UIImageView):

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

Quindi puoi spostare il veicolo dove vuoi, rispondendo UIControlEventTouchDragInsideall'evento, ad esempio:

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

È molto più facile per il singolo veicolo gestire i propri trascina, confrontando la gestione della scena nel suo complesso.


Sembra fantastico e molto semplice! Lo proverò.
Luca

10
Questa strategia provocherebbe l'interruzione del trascinamento se l'utente trascinasse il pulsante più velocemente dell'animazione aggiornata? Se il loro dito fosse fuori dagli schemi, smetterebbe di ricevere l'immagineMoved: withEvent: calls, corretto?
Tim

Come puoi sapere se l'immagine smette di trascinare? Come sapresti che il dito si è spostato?
ymutlu

ohho - è possibile spostare la "maniglia" di trascinamento per un'immagine o un pulsante in modo che possa essere trascinato dall'angolo superiore destro o superiore sinistro?
LJ Wilson,

quando provo a ottenere un'eccezione - [drag_move_textViewController imageTouch: withEvent:]: selettore non riconosciuto inviato all'istanza 0x4b4b1c0
iosDev

34

Oltre alla risposta di ohho ho provato un'implementazione simile, ma senza problemi di trascinamento "veloce" e centratura per trascinare.

L'inizializzazione del pulsante è ...

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 implementazione del metodo:

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

Non dico che questa sia la soluzione perfetta per la risposta. Tuttavia ho trovato questa la soluzione più semplice per il trascinamento.


2
Nel tuo codice di esempio, definisci UIControl * control = sender dopo che è stato utilizzato: non dovrebbe essere definito in precedenza?
Peter Johnson

@PeterJohnson: In effetti non importa in questo caso. Non vi è alcun utilizzo precedente di questa variabile dalla riga che hai menzionato :)
JakubKnejzlik

1
Che dire di CGPoint pPrev = [t previousLocationInView: control]; CGPoint p = [t locationInView: control]; Queste due righe precedenti sembrano entrambe riferirsi a "controllo"?
Peter Johnson

Oh mamma! Come posso perderlo? : -O ... ho modificato la risposta.
JakubKnejzlik

aggiungo il generatore dell'interfaccia del modulo del pulsante e utilizzo il layout automatico per alcuni accessi nascondo il pulsante e lo mostro di nuovo quando lo faccio in modo che ritorni al posto originale come prevenire questo comportamento
Amr Angry

2

Ho avuto un caso in cui vorrei trascinare / rilasciare uivview tra più altre uivview. In quel caso ho trovato la soluzione migliore per tracciare gli eventi di pan in una super view contenente tutte le zone di rilascio e tutti gli oggetti drag gable. La ragione principale per NON aggiungere i listener di eventi alle effettive viste trascinate era che ho perso gli eventi di pan in corso non appena ho cambiato la superview per la vista trascinata.

Invece ho implementato una soluzione di trascinamento piuttosto generica che potrebbe essere utilizzata su zone di rilascio singole o multiple.

Ho creato un semplice esempio che può essere visto qui: trascina un'uivview tra più altre visualizzazioni

Se decidi di andare dall'altra parte, prova a usare uicontrol invece di uibutton. Dichiarano i metodi addTarget e sono molto più facili da estendere, quindi forniscono stato e comportamento personalizzati.


1

Traccerei effettivamente il trascinamento sulla visuale del veicolo stessa, piuttosto che sulla visuale ampia, a meno che non ci sia un motivo particolare per non farlo.

In un caso in cui consento all'utente di posizionare gli elementi trascinandoli sullo schermo. In quel caso ho sperimentato sia con la vista dall'alto che con le viste figlio trascinabili. Ho trovato che è un codice più pulito se si aggiungono alcune viste "trascinabili" a UIView e si gestisce come possono essere trascinate. Ho usato una semplice richiamata al genitore UIView per verificare se la nuova posizione era adatta o meno, così ho potuto indicare con animazioni.

Avere il trascinamento della traccia della vista dall'alto immagino sia altrettanto buono, ma questo rende leggermente più disordinato se si desidera aggiungere visualizzazioni non trascinabili che interagiscono ancora con l'utente, come un pulsante.


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

1

la risposta di ohho ha funzionato bene per me. Nel caso in cui sia necessaria la versione 2.x rapida:

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
}


0

Per Swift 4 Modo facile e più semplice. 😛

Passaggio 1: collega il tuo pulsante dallo storyboard al controller di visualizzazione. E impostare tutti i vincoli di layout automatico di base

 @IBOutlet weak var cameraButton: UIButton!

Passaggio 2: aggiungi il gesto di panoramica per il pulsante in viewDidLoad ()

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

Passaggio 3:

Aggiungi panGestureHandler

@objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {

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

    }

E ora l'azione del tuo pulsante

@IBAction func ButtonAction(_ sender: Any) {

        print("This is button Action")
    }

inserisci qui la descrizione dell'immagine

Aggiungi Vedi il risultato ☝️

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.