Scorrimento con due dita con un UIScrollView

Ho un’app in cui la mia vista principale accetta entrambi i touchesBegan e touchesBegan , quindi accetta tocchi con un dito e trascina. Voglio implementare un UIScrollView e lo faccio funzionare, ma sovrascrive le drags, e quindi il mio contentView non le riceve mai. Mi piacerebbe implementare un UIScrollview , in cui un trascinamento di due dita indica una pergamena, e un evento di trascinamento con un dito viene passato alla vista del contenuto, quindi funziona normalmente. Devo creare la mia sottoclass di UIScrollView ?

Ecco il mio codice dalla mia appDelegate dove implemento UIScrollView .

 @implementation MusicGridAppDelegate @synthesize window; @synthesize viewController; @synthesize scrollView; - (void)applicationDidFinishLaunching:(UIApplication *)application { // Override point for customization after app launch //[application setStatusBarHidden:YES animated:NO]; //[window addSubview:viewController.view]; scrollView.contentSize = CGSizeMake(720, 480); scrollView.showsHorizontalScrollIndicator = YES; scrollView.showsVerticalScrollIndicator = YES; scrollView.delegate = self; [scrollView addSubview:viewController.view]; [window makeKeyAndVisible]; } - (void)dealloc { [viewController release]; [scrollView release]; [window release]; [super dealloc]; } 

Devi sottoclassi UIScrollView (ovviamente!). Quindi è necessario:

  • creare eventi con un solo dito per andare alla visualizzazione del contenuto (facile) e

  • crea eventi a due dita scorri la vista di scorrimento (potrebbe essere facile, potrebbe essere difficile, potrebbe essere imansible).

Il suggerimento di Patrick è generalmente soddisfacente: consenti alla sottoclass UIScrollView di conoscere la tua vista del contenuto, quindi i gestori di eventi touch controllano il numero di dita e inoltrano l’evento di conseguenza. Assicurati che (1) gli eventi che invii alla visualizzazione del contenuto non tornino a UIScrollView attraverso la catena di risposta (cioè assicurati di gestirli tutti), (2) rispetti il ​​stream usuale di eventi di touch (ad es. un certo numero di {touchesBegan, touchMoved, touchEnded}, finito con touchEnded o touchCancelled), specialmente quando si tratta di UIScrollView. # 2 può essere difficile.

Se decidi che l’evento è per UIScrollView, un altro trucco è rendere UIScrollView credibile che il tuo gesto con due dita sia in realtà un gesto con un dito (perché non è ansible scorrere UIScrollView con due dita). Prova a passare solo i dati da un dito a super (filtrando l’ (NSSet *)touches argomento – nota che contiene solo i tocchi modificati – e ignorando del tutto gli eventi per il dito sbagliato del tutto).

Se ciò non funziona, sei nei guai. Teoricamente puoi provare a creare tocchi artificiali per alimentare UIScrollView creando una class simile a UITouch. Il codice C sottostante non controlla i tipi, quindi forse il casting (YourTouch *) in (UITouch *) funzionerà e sarai in grado di ingannare UIScrollView nella gestione dei tocchi che non sono realmente accaduti.

Probabilmente vorrai leggere il mio articolo sui trucchi avanzati di UIScrollView (e vedere alcuni codici di esempio UIScrollView totalmente non collegati).

Naturalmente, se non riesci a farlo funzionare, c’è sempre la possibilità di controllare manualmente il movimento di UIScrollView o utilizzare una visualizzazione di scorrimento interamente personalizzata. C’è la class TTScrollView nella libreria Three20; non si sente bene all’utente, ma si sente bene con il programmatore.

In SDK 3.2 la gestione del touch per UIScrollView viene gestita tramite i Riconoscitori di gesti.

Se si desidera eseguire la panoramica con due dita anziché la panoramica con un dito di default, è ansible utilizzare il seguente codice:

 for (UIGestureRecognizer *gestureRecognizer in scrollView.gestureRecognizers) { if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { UIPanGestureRecognizer *panGR = (UIPanGestureRecognizer *) gestureRecognizer; panGR.minimumNumberOfTouches = 2; } } 

Per iOS 5+, l’impostazione di questa proprietà ha lo stesso effetto della risposta di Mike Laurence:

 self.scrollView.panGestureRecognizer.minimumNumberOfTouches = 2; 

Il trascinamento di un dito viene ignorato da panGestureRecognizer e quindi l’evento di trascinamento con un dito viene passato alla vista del contenuto.

Con iOS 3.2 e versioni successive è ora ansible effettuare facilmente lo scorrimento con due dita. Basta aggiungere un indicatore di movimento pan alla vista di scorrimento e impostarne il numero massimoNumero su 1. Reclamerà tutti gli scroll con un dito, ma consentirà 2+ rotoli per far passare la catena al riconoscitore di movimento pan integrato della vista di scorrimento (e quindi consentire il normale comportamento di scorrimento).

 UIPanGestureRecognizer *panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(recognizePan:)]; panGestureRecognizer.maximumNumberOfTouches = 1; [scrollView addGestureRecognizer:panGestureRecognizer]; [panGestureRecognizer release]; 

Queste risposte sono un disastro in quanto puoi trovare la risposta corretta solo leggendo tutte le altre risposte e i commenti (la risposta più vicina ha fatto la domanda all’indietro). La risposta accettata è troppo vaga per essere utile e suggerisce un metodo diverso.

Sintetizzando, questo funziona

  // makes it so that only two finger scrolls go for (id gestureRecognizer in self.gestureRecognizers) { if ([gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]]) { UIPanGestureRecognizer *panGR = gestureRecognizer; panGR.minimumNumberOfTouches = 2; panGR.maximumNumberOfTouches = 2; } } 

Ciò richiede due dita per una pergamena. Ho fatto questo in una sottoclass, ma in caso contrario, basta sostituire self.gestureRecognizers con myScrollView.gestureRecognizers e sei a posto.

L’unica cosa che ho aggiunto è usare id per evitare un cast sgradevole 🙂

Funziona ma può diventare piuttosto complicato se vuoi che anche il tuo UIScrollView faccia lo zoom … i gesti non funzionano correttamente, dal momento che pizzicare-zoom e scorrere lo combattono. Aggiornerò questo se trovo una risposta adatta.

siamo riusciti a implementare funzionalità simili nella nostra app di disegno per iPhone sottoclassando UIScrollView e filtrando gli eventi in base al numero di tocchi in modo semplice e rude:

 //OCRScroller.h @interface OCRUIScrollView: UIScrollView { double pass2scroller; } @end //OCRScroller.mm @implementation OCRUIScrollView - (id)initWithFrame:(CGRect)aRect { pass2scroller = 0; UIScrollView* newv = [super initWithFrame:aRect]; return newv; } - (void)setupPassOnEvent:(UIEvent *)event { int touch_cnt = [[event allTouches] count]; if(touch_cnt<=1){ pass2scroller = 0; }else{ double timems = double(CACurrentMediaTime()*1000); pass2scroller = timems+200; } } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self setupPassOnEvent:event]; [super touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self setupPassOnEvent:event]; [super touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { pass2scroller = 0; [super touchesEnded:touches withEvent:event]; } - (BOOL)touchesShouldBegin:(NSSet *)touches withEvent:(UIEvent *)event inContentView:(UIView *)view { return YES; } - (BOOL)touchesShouldCancelInContentView:(UIView *)view { double timems = double(CACurrentMediaTime()*1000); if (pass2scroller == 0 || timems> pass2scroller){ return NO; } return YES; } @end 

ScrollView configurato come segue:

 scroll_view = [[OCRUIScrollView alloc] initWithFrame:rect]; scroll_view.contentSize = img_size; scroll_view.contentOffset = CGPointMake(0,0); scroll_view.canCancelContentTouches = YES; scroll_view.delaysContentTouches = NO; scroll_view.scrollEnabled = YES; scroll_view.bounces = NO; scroll_view.bouncesZoom = YES; scroll_view.maximumZoomScale = 10.0f; scroll_view.minimumZoomScale = 0.1f; scroll_view.delegate = self; self.view = scroll_view; 

il semplice touch non fa nulla (puoi gestirlo nel modo in cui ti serve), tocca con due dita scorre / zuma guarda come previsto. non viene utilizzato GestureRecognizer, quindi funziona da iOS 3.1

Ho un ulteriore miglioramento del codice sopra. Il problema era che anche dopo aver impostato setCanCancelContentTouches:NO Abbiamo il problema, che un gesto di zoom si interrompe con il contenuto. Non annullerà il touch del contenuto ma consentirà lo zoom nel frattempo. PER impedirlo, blocco lo zoom impostando il valore minimoZoomScale e massimoZoomScale sugli stessi valori ogni volta, il timer scatta.

Un comportamento abbastanza strano è che quando un evento con un dito viene annullato con un gesto di due dita entro il periodo di tempo consentito, il timer verrà ritardato. Viene sparato dopo che viene chiamato l’evento touchCanceled. Quindi abbiamo il problema, che proviamo a bloccare lo zoom anche se l’evento è già stato annullato e quindi disabilitare lo zoom per il prossimo evento. Per gestire questo comportamento, il metodo di callback del timer verifica se TouchesCanceled è stato chiamato prima. @implementation JWTwoFingerScrollView

 #pragma mark - #pragma mark Event Passing - (id)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { for (UIGestureRecognizer* r in self.gestureRecognizers) { if ([r isKindOfClass:[UIPanGestureRecognizer class]]) { [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2]; [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2]; zoomScale[0] = -1.0; zoomScale[1] = -1.0; } timerWasDelayed = NO; } } return self; } -(void)lockZoomScale { zoomScale[0] = self.minimumZoomScale; zoomScale[1] = self.maximumZoomScale; [self setMinimumZoomScale:self.zoomScale]; [self setMaximumZoomScale:self.zoomScale]; NSLog(@"locked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale); } -(void)unlockZoomScale { if (zoomScale[0] != -1 && zoomScale[1] != -1) { [self setMinimumZoomScale:zoomScale[0]]; [self setMaximumZoomScale:zoomScale[1]]; zoomScale[0] = -1.0; zoomScale[1] = -1.0; NSLog(@"unlocked %.2f %.2f",self.minimumZoomScale,self.maximumZoomScale); } } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"began %i",[event allTouches].count); [self setCanCancelContentTouches:YES]; if ([event allTouches].count == 1){ touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO]; [touchesBeganTimer retain]; [touchFilter touchesBegan:touches withEvent:event]; } } //if one finger touch gets canceled by two finger touch, this timer gets delayed // so we can! use this method to disable zooming, because it doesnt get called when two finger touch events are wanted; otherwise we would disable zooming while zooming -(void)firstTouchTimerFired:(NSTimer*)timer { NSLog(@"fired"); [self setCanCancelContentTouches:NO]; //if already locked: unlock //this happens because two finger gesture delays timer until touch event finishes.. then we dont want to lock! if (timerWasDelayed) { [self unlockZoomScale]; } else { [self lockZoomScale]; } timerWasDelayed = NO; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { // NSLog(@"moved %i",[event allTouches].count); [touchFilter touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"ended %i",[event allTouches].count); [touchFilter touchesEnded:touches withEvent:event]; [self unlockZoomScale]; } //[self setCanCancelContentTouches:NO]; -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@"canceled %i",[event allTouches].count); [touchFilter touchesCancelled:touches withEvent:event]; [self unlockZoomScale]; timerWasDelayed = YES; } @end 

Cattive notizie: iPhone SDK 3.0 e -touchesBegan: successive, non passare più i tocchi a -touchesBegan: e – touchesEnded: ** UIScrollview ** metodi sottoclass più. È ansible utilizzare i touchesShouldBegin touchesShouldCancelInContentView touchesShouldBegin e touchesShouldCancelInContentView metodi ” touchesShouldCancelInContentView che non è la stessa cosa.

Se vuoi davvero ottenere questi tocchi, avere un hack che permetta questo.

Nella sottoclass di UIScrollView sostituisci il metodo hitTest questo modo:

 - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *result = nil; for (UIView *child in self.subviews) if ([child pointInside:point withEvent:event]) if ((result = [child hitTest:point withEvent:event]) != nil) break; return result; } 

Questo ti passerà sottoclass questo touch, tuttavia non puoi annullare i tocchi alla super class UIScrollView .

Quello che faccio è avere il mio controller di visualizzazione per impostare la vista di scorrimento:

 [scrollView setCanCancelContentTouches:NO]; [scrollView setDelaysContentTouches:NO]; 

E nella vista di mio figlio ho un timer perché i tocchi di due dita di solito iniziano come un dito seguito rapidamente da due dita .:

 - (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // Hand tool or two or more touches means a pan or zoom gesture. if ((selectedTool == kHandToolIndex) || (event.allTouches.count > 1)) { [[self parentScrollView] setCanCancelContentTouches:YES]; [firstTouchTimer invalidate]; firstTouchTimer = nil; return; } // Use a timer to delay first touch because two-finger touches usually start with one touch followed by a second touch. [[self parentScrollView] setCanCancelContentTouches:NO]; anchorPoint = [[touches anyObject] locationInView:self]; firstTouchTimer = [NSTimer scheduledTimerWithTimeInterval:kFirstTouchTimeInterval target:self selector:@selector(firstTouchTimerFired:) userInfo:nil repeats:NO]; firstTouchTimeStamp = event.timestamp; } 

Se un secondo tocca Began: l’evento arriva con più di un dito, la visualizzazione a scorrimento è autorizzata a cancellare i tocchi. Quindi, se l’utente touchesCanceled: la panoramica utilizzando due dita, questa visualizzazione otterrebbe un touchesCanceled: . Messaggio inviato.

Questa sembra essere la migliore risorsa per questa domanda su Internet. Un’altra soluzione stretta può essere trovata qui .

Ho risolto questo problema in modo molto soddisfacente in un modo diverso, essenzialmente sostituendo il mio stesso riconoscitore di gesti all’equazione. Consiglio vivamente che chiunque stia cercando di ottenere l’effetto richiesto dal poster originale consideri questa alternativa rispetto alla sottoclass aggressiva di UIScrollView .

Il seguente processo fornirà:

  • Un UIScrollView contenente la tua vista personalizzata

  • Zoom e UIPinchGestureRecognizer con due dita (tramite UIPinchGestureRecognizer )

  • L’elaborazione dell’evento della tua vista per tutti gli altri tocchi

Innanzitutto, supponiamo di avere un controller di visualizzazione e la sua vista. In IB, rendere la vista una sottoview di una scrollView e regolare le regole di ridimensionamento della vista in modo che non ridimensionino. Negli attributi di scrollview, triggers tutto ciò che dice “rimbalza” e distriggersdelaysContentTouches “. Inoltre, è necessario impostare lo zoom min e max su un valore diverso da quello predefinito di 1.0 per, come dicono i documenti di Apple, questo è necessario per il funzionamento dello zoom.

Crea una sottoclass personalizzata di UIScrollView e crea questa scorciatoia come sottoclass personalizzata. Aggiungi una presa al tuo controller di visualizzazione per la scrollview e collegali. Ora sei completamente configurato.

Dovrai aggiungere il seguente codice alla sottoclass UIScrollView modo che passi in modo trasparente gli eventi di touch (temo che ciò potrebbe essere fatto in modo più elegante, magari ignorando del tutto la sottoclass):

 #pragma mark - #pragma mark Event Passing - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesBegan:touches withEvent:event]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { [self.nextResponder touchesEnded:touches withEvent:event]; } - (BOOL)touchesShouldCancelInContentView:(UIView *)view { return NO; } 

Aggiungi questo codice al tuo controller di visualizzazione:

 - (void)setupGestures { UIPinchGestureRecognizer *pinchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinchGesture:)]; [self.view addGestureRecognizer:pinchGesture]; [pinchGesture release]; } - (IBAction)handlePinchGesture:(UIPinchGestureRecognizer *)sender { if ( sender.state == UIGestureRecognizerStateBegan ) { //Hold values previousLocation = [sender locationInView:self.view]; previousOffset = self.scrollView.contentOffset; previousScale = self.scrollView.zoomScale; } else if ( sender.state == UIGestureRecognizerStateChanged ) { //Zoom [self.scrollView setZoomScale:previousScale*sender.scale animated:NO]; //Move location = [sender locationInView:self.view]; CGPoint offset = CGPointMake(previousOffset.x+(previousLocation.x-location.x), previousOffset.y+(previousLocation.y-location.y)); [self.scrollView setContentOffset:offset animated:NO]; } else { if ( previousScale*sender.scale < 1.15 && previousScale*sender.scale > .85 ) [self.scrollView setZoomScale:1.0 animated:YES]; } 

}

Si noti che in questo metodo ci sono riferimenti a un numero di proprietà che è necessario definire nei file di class del proprio controller di visualizzazione:

  • CGFloat previousScale;
  • CGPoint previousOffset;
  • CGPoint previousLocation;
  • CGPoint location;

Ok questo è tutto!

Purtroppo non ho potuto ottenere la scrollView per mostrare i suoi scrollers durante il gesto. Ho provato tutte queste strategie:

 //Scroll indicators self.scrollView.showsVerticalScrollIndicator = YES; self.scrollView.showsVerticalScrollIndicator = YES; [self.scrollView flashScrollIndicators]; [self.scrollView setNeedsDisplay]; 

Una cosa che mi è piaciuta di più è che se guardi l’ultima riga, noterai che afferra qualsiasi zoom finale che è intorno al 100% e lo arrotonda a quello. Puoi regolare il tuo livello di tolleranza; Avevo visto questo comportamento di zoom di Pages e ho pensato che sarebbe stato un bel touch.

Controlla la mia soluzione :

 #import “JWTwoFingerScrollView.h” @implementation JWTwoFingerScrollView - (id)initWithCoder:(NSCoder *)coder { self = [super initWithCoder:coder]; if (self) { for (UIGestureRecognizer* r in self.gestureRecognizers) { NSLog(@“%@”,[r class]); if ([r isKindOfClass:[UIPanGestureRecognizer class]]) { [((UIPanGestureRecognizer*)r) setMaximumNumberOfTouches:2]; [((UIPanGestureRecognizer*)r) setMinimumNumberOfTouches:2]; } } } return self; } -(void)firstTouchTimerFired:(NSTimer*)timer { [self setCanCancelContentTouches:NO]; } - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { [self setCanCancelContentTouches:YES]; if ([event allTouches].count == 1){ touchesBeganTimer = [NSTimer scheduledTimerWithTimeInterval:0.1 target:self selector:@selector(firstTouchTimerFired:) userInfo: nil repeats:NO]; [touchesBeganTimer retain]; [touchFilter touchesBegan:touches withEvent:event]; } } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { [touchFilter touchesMoved:touches withEvent:event]; } - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@“ended %i”,[event allTouches].count); [touchFilter touchesEnded:touches withEvent:event]; } -(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { NSLog(@“canceled %i”,[event allTouches].count); [touchFilter touchesCancelled:touches withEvent:event]; } @end 

Non ritarda il primo touch e non si ferma quando l’utente tocca con due dita dopo averne usato uno. Permette comunque di cancellare un evento one touch appena iniziato utilizzando un timer.

Sì, dovrai sottoclassi UIScrollView e sostituirai i suoi – touchesBegan: e -touchesEnded: metodi per passare i tocchi “su”. Questo probabilmente coinvolgerà anche la sottoclass che ha una variabile membro UIView modo che sappia cosa significa passare i ritocchi.

Inserisco questo nel metodo viewDidLoad e questo porta a termine la visualizzazione scroll gestendo il comportamento del pan a due touch e un altro gestore di pan pan che gestisce il comportamento del pan one-touch ->

 scrollView.panGestureRecognizer.minimumNumberOfTouches = 2 let panGR = UIPanGestureRecognizer(target: self, action: #selector(ViewController.handlePan(_:))) panGR.minimumNumberOfTouches = 1 panGR.maximumNumberOfTouches = 1 scrollView.gestureRecognizers?.append(panGR) 

e nel metodo handlePan, che è una funzione collegata al ViewController, c’è semplicemente un’istruzione print per verificare che il metodo venga inserito ->

 @IBAction func handlePan(_ sender: UIPanGestureRecognizer) { print("Entered handlePan numberOfTuoches: \(sender.numberOfTouches)") } 

HTH

La risposta di Kenshi in Swift 4

 for gestureRecognizer: UIGestureRecognizer in self.gestureRecognizers! { if (gestureRecognizer is UIPanGestureRecognizer) { let panGR = gestureRecognizer as? UIPanGestureRecognizer panGR?.minimumNumberOfTouches = 2 } }