Podstawowe przeciąganie i upuszczanie w iOS


93

Chcę mieć widok, w którym poruszają się pojazdy, który użytkownik może również przeciągać i upuszczać. Jak myślisz, jaka jest najlepsza strategia na dużą skalę, aby to zrobić? Czy najlepiej jest odbierać zdarzenia dotykowe z widoków reprezentujących pojazdy, czy z większego widoku? Czy istnieje prosty paradygmat, którego używałeś do przeciągania i upuszczania, z którego jesteś zadowolony? Jakie są wady różnych strategii?


Odpowiedzi:


126

Załóżmy, że masz UIViewscenę z obrazem tła i wiele vehicles, możesz zdefiniować każdy nowy pojazd jako UIButton(UIImageView prawdopodobnie też zadziała):

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

Następnie możesz przemieścić pojazd w dowolne miejsce, odpowiadając na UIControlEventTouchDragInsidezdarzenie np:

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

Pojedynczy pojazd dużo łatwiej poradzi sobie z własnymi przeciągnięciami w porównaniu do zarządzania sceną jako całością.


Wygląda świetnie i bardzo prosto! Spróbuję tego.
Łukasz

10
Czy ta strategia spowodowałaby przerwanie przeciągania, gdyby użytkownik przeciągnął przycisk szybciej niż zaktualizowana animacja? Gdyby ich palec znalazł się poza pudełkiem, przestałby otrzymywać obraz. Przeniesiony: withEvent: wzywa, prawda?
Tim

Skąd możesz wiedzieć, czy obraz przestał się przeciągać? Skąd wiesz, że palec się wyprowadził?
ymutlu

ohho - czy można przesunąć "uchwyt" przeciągania dla obrazu lub przycisku, aby można go było przeciągać o prawy górny lub lewy górny róg?
LJ Wilson

kiedy próbuję uzyskać wyjątek - [drag_move_textViewController imageTouch: withEvent:]: nierozpoznany selektor wysłany do instancji 0x4b4b1c0
iosDev

34

Oprócz odpowiedzi Ohho próbowałem podobnej implementacji, ale bez problemu „szybkiego” przeciągania i centrowania do przeciągania.

Inicjalizacja przycisku to ...

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

i wdrożenie metody:

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

Nie mówię, że to idealne rozwiązanie na odpowiedź. Niemniej jednak uznałem to za najłatwiejsze rozwiązanie do przeciągania.


2
W przykładowym kodzie definiujesz UIControl * control = sender po jego użyciu - czy nie powinno to być zdefiniowane wcześniej?
Peter Johnson

@PeterJohnson: Właściwie to nie ma znaczenia w tym przypadku. Nie ma wcześniejszego użycia tej zmiennej od czasu wspomnianej linii :)
JakubKnejzlik

1
A co z CGPoint pPrev = [t previousLocationInView: control]; CGPoint p = [t locationInView: control]; Te dwa wcześniejsze wiersze wydają się odnosić do „kontroli”?
Peter Johnson,

O Boże! Jak mogłem to przegapić? : -O ... zredagowałem odpowiedź.
JakubKnejzlik

dodaję narzędzie do tworzenia interfejsu formularzy przycisków i używam automatycznego układu do logowania. ukrywam przycisk i pokazuję go ponownie, gdy to zrobię, wraca do pierwotnego miejsca, jak temu zapobiec
Amr Angry

2

Miałem przypadek, w którym chciałbym przeciągać / upuszczać uiviews między wieloma innymi uiviews. W takim przypadku znalazłem najlepsze rozwiązanie do śledzenia zdarzeń panoramy w superwidoku zawierającym wszystkie strefy upuszczania i wszystkie obiekty szczytu przeciągania. Głównym powodem, dla którego NIE dodawałem detektorów zdarzeń do rzeczywistych przeciągniętych widoków, było to, że straciłem trwające zdarzenia panoramowania, gdy tylko zmieniłem nadzór widoku po przeciągnięciu.

Zamiast tego zaimplementowałem raczej ogólne rozwiązanie typu „przeciągnij i upuść”, którego można używać w pojedynczych lub wielu strefach upuszczania.

Stworzyłem prosty przykład, który można zobaczyć tutaj: Przeciągnij upuść uiviews między wieloma innymi uiviews

Jeśli zdecydujesz się pójść w drugą stronę, spróbuj użyć uicontrol zamiast uibutton. Deklarują metody addTarget i są znacznie łatwiejsze do rozszerzenia - stąd dają niestandardowy stan i zachowanie.


1

W rzeczywistości śledziłbym przeciąganie samego widoku pojazdu , a nie dużego widoku - chyba że istnieje szczególny powód, aby tego nie robić.

W jednym przypadku, gdy pozwalam użytkownikowi umieszczać elementy, przeciągając je na ekranie. W takim przypadku eksperymentowałem zarówno z widokiem z góry, jak iz widokiem podrzędnym przeciągania . Zauważyłem, że kod jest bardziej przejrzysty, jeśli dodasz kilka widoków „do przeciągania” do UIView i poradzisz sobie, jak można je przeciągać. Użyłem prostego wywołania zwrotnego do nadrzędnego UIView, aby sprawdzić, czy nowa lokalizacja jest odpowiednia, czy nie - więc mogłem wskazać za pomocą animacji.

Wydaje mi się, że przeciąganie ścieżki widoku z góry jest równie dobre, ale to sprawia, że ​​jest nieco bardziej bałagan, jeśli chcesz dodać nieprzeciągalne widoki, które nadal wchodzą w interakcję z użytkownikiem, takie jak przycisk.


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

odpowiedź Ohho działała dobrze dla mnie. Jeśli potrzebujesz wersji Swift 2.x:

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

Dla Swift 4 Łatwy i najłatwiejszy sposób. 😛

Krok 1: Połącz swój przycisk ze scenorysu, aby wyświetlić kontroler. I ustaw wszystkie podstawowe ograniczenia autoukładu

 @IBOutlet weak var cameraButton: UIButton!

Krok 2: Dodaj gest Pan do przycisku W widokuDidLoad ()

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

Krok 3:

Dodaj panGestureHandler

@objc func panGestureHandler(panGesture recognizer: UIPanGestureRecognizer) {

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

    }

A teraz Twoja akcja przycisku

@IBAction func ButtonAction(_ sender: Any) {

        print("This is button Action")
    }

wprowadź opis obrazu tutaj

Dodaj Zobacz wynik ☝️

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.