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?

Łukasz
źródło

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ą.

ohho
źródło
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.

JakubKnejzlik
źródło
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.

jeyben
źródło
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.

Magnus
źródło
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
źródło
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
}
parag
źródło
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 ☝️

Anup Gupta
źródło