Grundlegendes Drag & Drop in iOS


93

Ich möchte eine Ansicht haben, in der Fahrzeuge herumfahren, die der Benutzer auch per Drag & Drop verschieben kann. Was ist Ihrer Meinung nach die beste groß angelegte Strategie dafür? Ist es am besten, Berührungsereignisse aus den Ansichten der Fahrzeuge oder aus der größeren Ansicht abzurufen? Gibt es ein einfaches Paradigma für Drag & Drop, mit dem Sie zufrieden sind? Was sind die Nachteile verschiedener Strategien?


Antworten:


126

Angenommen, Sie haben eine UIViewSzene mit einem Hintergrundbild und vielen vehicles, können Sie jedes neue Fahrzeug als definieren UIButton(UIImageView wird wahrscheinlich auch funktionieren):

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

Dann können Sie das Fahrzeug bewegen, wo immer Sie möchten, indem Sie auf das UIControlEventTouchDragInsideEreignis reagieren , z.

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

Es ist für ein einzelnes Fahrzeug viel einfacher, mit seinen eigenen Schleppern umzugehen, verglichen mit der Verwaltung der gesamten Szene.


Sieht gut aus und ist sehr einfach! Ich werde das ausprobieren.
Luke

10
Würde diese Strategie dazu führen, dass das Ziehen unterbrochen wird, wenn der Benutzer die Schaltfläche schneller als die aktualisierte Animation zieht? Wenn ihr Finger außerhalb der Box wäre, würde er den Empfang von imageMoved: withEvent: -Anrufen beenden, richtig?
Tim

Wie können Sie wissen, ob das Bild nicht mehr gezogen wird? Woher wissen Sie, dass der Finger ausgezogen ist?
Ymutlu

ohho - ist es möglich, den ziehenden "Griff" für ein Bild oder eine Schaltfläche so zu versetzen, dass er an der oberen rechten oder oberen linken Ecke gezogen werden kann?
LJ Wilson

Wenn ich versuche, eine Ausnahme zu erhalten - [drag_move_textViewController imageTouch: withEvent:]: Nicht erkannter Selektor an Instanz 0x4b4b1c0 gesendet
iosDev

34

Zusätzlich zu Ohhos Antwort habe ich eine ähnliche Implementierung versucht, jedoch ohne das Problem des "schnellen" Ziehens und Zentrierens zum Ziehen.

Die Tasteninitialisierung ist ...

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

und Methodenimplementierung:

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

Ich sage nicht, dass dies die perfekte Lösung für die Antwort ist. Trotzdem fand ich dies die einfachste Lösung zum Ziehen.


2
In Ihrem Beispielcode definieren Sie UIControl * control = sender, nachdem es verwendet wurde - sollte das nicht früher definiert werden?
Peter Johnson

@ PeterJohnson: Tatsächlich spielt es in diesem Fall keine Rolle. Es gibt keine frühere Verwendung dieser Variablen seit der Zeile, die Sie erwähnt haben :)
JakubKnejzlik

1
Was ist mit CGPoint pPrev = [t previousLocationInView: control]; CGPoint p = [t locationInView: control]; Diese beiden früheren Zeilen scheinen sich beide auf "Kontrolle" zu beziehen?
Peter Johnson

Oh Gott! Wie kann ich das verpassen? : -O ... Ich habe die Antwort bearbeitet.
JakubKnejzlik

Ich füge den Button Form Interface Builder hinzu und verwende Autolayout für einige Anmeldungen. Ich verstecke die Schaltfläche und zeige sie erneut, wenn ich dies tue. Sie kehrt an den ursprünglichen Ort zurück, um dies zu verhindern
Amr Angry

2

Ich hatte einen Fall, in dem ich uiviews zwischen mehreren anderen uiviews ziehen / ablegen möchte. In diesem Fall habe ich die beste Lösung gefunden, um die Pan-Ereignisse in einer Superansicht zu verfolgen, die alle Drop-Zones und alle Drag-Gable-Objekte enthält. Der Hauptgrund dafür, dass die Ereignis-Listener NICHT zu den tatsächlich gezogenen Ansichten hinzugefügt wurden, war, dass ich die laufenden Pan-Ereignisse verloren habe, sobald ich die Übersicht für die gezogene Ansicht geändert habe.

Stattdessen habe ich eine eher generische Drag-Drop-Lösung implementiert, die für einzelne oder mehrere Drop-Zonen verwendet werden kann.

Ich habe ein einfaches Beispiel erstellt, das hier zu sehen ist: Ziehen Sie Drop-Uiviews zwischen mehrere andere Uiviews

Wenn Sie sich für einen anderen Weg entscheiden, verwenden Sie uicontrol anstelle von uibutton. Sie deklarieren die addTarget-Methoden und sind viel einfacher zu erweitern - geben daher einen benutzerdefinierten Status und ein benutzerdefiniertes Verhalten an.


1

Ich würde tatsächlich das Ziehen in der Fahrzeugansicht selbst verfolgen und nicht in der Großansicht - es sei denn, es gibt einen bestimmten Grund, dies nicht zu tun.

In einem Fall erlaube ich dem Benutzer, Elemente durch Ziehen auf dem Bildschirm zu platzieren. In diesem Fall habe ich damit experimentiert, dass sowohl die Draufsicht als auch die untergeordnete Ansicht ziehbar sind. Ich habe festgestellt, dass es sauberer Code ist, wenn Sie der UIView einige "ziehbare" Ansichten hinzufügen und festlegen, wie sie gezogen werden können. Ich habe einen einfachen Rückruf an die übergeordnete UIView verwendet, um zu überprüfen, ob der neue Speicherort geeignet ist oder nicht - damit ich mit Animationen angeben kann.

Das Ziehen der Draufsichtspur ist meiner Meinung nach genauso gut, aber das macht es etwas chaotischer, wenn Sie nicht ziehbare Ansichten hinzufügen möchten, die noch mit dem Benutzer interagieren, z. B. eine Schaltfläche.


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

Ohhos Antwort hat bei mir gut funktioniert. Falls Sie die schnelle 2.x-Version benötigen:

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

Für Swift 4 Einfacher und einfachster Weg. 😛

Schritt 1: Verbinden Sie Ihre Schaltfläche vom Storyboard mit dem View Controller. Und legen Sie die grundlegenden AutoLayout-Einschränkungen fest

 @IBOutlet weak var cameraButton: UIButton!

Schritt 2: Pan-Geste für Ihre Schaltfläche hinzufügen In viewDidLoad ()

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

Schritt 3:

Fügen Sie panGestureHandler hinzu

@objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {

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

    }

Und jetzt Ihre Button-Aktion

@IBAction func ButtonAction(_ sender: Any) {

        print("This is button Action")
    }

Geben Sie hier die Bildbeschreibung ein

Hinzufügen Siehe Ergebnis ☝️

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.