2011-10-06 2 views
25

In breve, ho bisogno di sapere esattamente quando lo scrollview ha smesso di scorrere. Con lo "scrolling continuo", intendo il momento in cui non si muove più e non viene toccato.Come sapere esattamente quando lo scorrimento di UIScrollView si è fermato?

Ho lavorato su una sottoclasse UIScrollView orizzontale (per iOS 4) con schede di selezione al suo interno. Uno dei suoi requisiti è che smette di scorrere al di sotto di una determinata velocità per consentire l'interazione dell'utente più rapidamente. Dovrebbe anche scattare all'inizio di una scheda. In altre parole, quando l'utente rilascia la scrollview e la sua velocità è bassa, si blocca in una posizione. L'ho implementato e funziona, ma c'è un bug in esso.

quello che ho adesso:

Lo ScrollView è proprio delegato. ad ogni chiamata a scrollViewDidScroll :, rinfresca le sue variabili velocità relative:

-(void)refreshCurrentSpeed 
{ 
    float currentOffset = self.contentOffset.x; 
    NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; 

    deltaOffset = (currentOffset - prevOffset); 
    deltaTime = (currentTime - prevTime);  
    currentSpeed = deltaOffset/deltaTime; 
    prevOffset = currentOffset; 
    prevTime = currentTime; 

    NSLog(@"deltaOffset is now %f, deltaTime is now %f and speed is %f",deltaOffset,deltaTime,currentSpeed); 
} 

procede poi a scattare in caso di necessità:

-(void)snapIfNeeded 
{ 
    if(canStopScrolling && currentSpeed <70.0f && currentSpeed>-70.0f) 
    { 
     NSLog(@"Stopping with a speed of %f points per second", currentSpeed); 
     [self stopMoving]; 

     float scrollDistancePastTabStart = fmodf(self.contentOffset.x, (self.frame.size.width/3)); 
     float scrollSnapX = self.contentOffset.x - scrollDistancePastTabStart; 
     if(scrollDistancePastTabStart > self.frame.size.width/6) 
     { 
      scrollSnapX += self.frame.size.width/3; 
     } 
     float maxSnapX = self.contentSize.width-self.frame.size.width; 
     if(scrollSnapX>maxSnapX) 
     { 
      scrollSnapX = maxSnapX; 
     } 
     [UIView animateWithDuration:0.3 
         animations:^{self.contentOffset=CGPointMake(scrollSnapX, self.contentOffset.y);} 
         completion:^(BOOL finished){[self stopMoving];} 
     ]; 
    } 
    else 
    { 
     NSLog(@"Did not stop with a speed of %f points per second", currentSpeed); 
    } 
} 

-(void)stopMoving 
{ 
    if(self.dragging) 
    { 
     [self setContentOffset:CGPointMake(self.contentOffset.x, self.contentOffset.y) animated:NO]; 
    } 
    canStopScrolling = NO; 
} 

Questi sono i metodi delegato:

-(void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 
{ 
    canStopScrolling = NO; 
    [self refreshCurrentSpeed]; 
} 

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 
{ 
    canStopScrolling = YES; 
    NSLog(@"Did end dragging"); 
    [self snapIfNeeded]; 
} 

- (void)scrollViewDidScroll:(UIScrollView *)scrollView 
{ 
    [self refreshCurrentSpeed]; 
    [self snapIfNeeded]; 
} 

Questo funziona bene la maggior parte del tempo, tranne in due scenari: 1. Quando l'utente scorre senza rilasciare il suo dito e lascia andare a un tempismo stazionario vicino subito dopo lo spostamento, spesso scatta alla sua posizione come dovrebbe, ma molte volte no. Di solito ci vogliono alcuni tentativi per farlo accadere. I valori dispari per il tempo (molto basso) e/o la distanza (piuttosto alta) appaiono al rilascio, causando un valore di alta velocità mentre lo scrollView è, in realtà, quasi o interamente stazionario. 2. Quando l'utente tocca la vista a scorrimento per interrompere il movimento, sembra che la vista a scorrimento contenga lo contentOffset nel punto precedente. Questo teletrasporto produce un valore di velocità molto elevato. Questo potrebbe essere risolto controllando se il delta precedente è currentDelta * -1, ma preferirei una soluzione più stabile.

Ho provato a utilizzare didEndDecelerating, ma quando si verifica il problema tecnico, non viene chiamato. Questo probabilmente conferma che è già fermo. Non sembra esserci alcun metodo delegate che viene chiamato quando la scrollview smette di spostarsi completamente.

Se vuoi vedere il glitch da soli, ecco qualche codice per riempire lo ScrollView con le schede:

@interface UIScrollView <UIScrollViewDelegate> 
{ 
    bool canStopScrolling; 
    float prevOffset; 
    float deltaOffset; //remembered for debug purposes 
    NSTimeInterval prevTime; 
    NSTimeInterval deltaTime; //remembered for debug purposes 
    float currentSpeed; 
} 

-(void)stopMoving; 
-(void)snapIfNeeded; 
-(void)refreshCurrentSpeed; 

@end 


@implementation TabScrollView 

-(id) init 
{ 
    self = [super init]; 
    if(self) 
    { 
     self.delegate = self; 
     self.frame = CGRectMake(0.0f,0.0f,320.0f,40.0f); 
     self.backgroundColor = [UIColor grayColor]; 
     float tabWidth = self.frame.size.width/3; 
     self.contentSize = CGSizeMake(100*tabWidth, 40.0f); 
     for(int i=0; i<100;i++) 
     { 
      UIView *view = [[UIView alloc] init]; 
      view.frame = CGRectMake(i*tabWidth,0.0f,tabWidth,40.0f); 
      view.backgroundColor = [UIColor colorWithWhite:(float)(i%2) alpha:1.0f]; 
      [self addSubview:view]; 
     } 
    } 
    return self; 
} 

@end 

Una versione più breve di questa domanda: come sapere quando lo ScrollView smesso di scorrimento? didEndDecelerating: non viene chiamato quando lo si rilascia stazionario, didEndDragging: accade molto durante lo scorrimento e il controllo della velocità non è affidabile a causa di questo strano "salto" che imposta la velocità su qualcosa di casuale.

+0

È possibile combinare 'didEndDragging' con un evento touch, in modo che quando l'utente tocca la vista si imposta un flag e si ripristini quando si rimuove il dito. Quindi, fintanto che il flag è impostato non si esegue la logica correlata in 'didEndDragging:'. – FreeAsInBeer

+0

Grazie per la risposta. Tuttavia, non sono sicuro di quale dovrebbe essere la bandiera di cui parli. Mi sembra che intendi il flag quando la scrollview è stata rilasciata una volta, ma non riesco a capire come sarebbe utile, considerando che l'utente rilascia la scrollview più volte mentre scorre, o solo a un punto stazionario senza avere sollevò il dito. – Aberrant

risposta

30

ho trovato una soluzione:

-(void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 

non ho notato che l'ultimo pezzo prima, willDecelerate. È falso quando lo scrollView è fermo quando termina il tocco. In combinazione con il controllo della velocità di cui sopra, posso scattare entrambi quando è lento (e non viene toccato) o quando è fermo.

Per tutti coloro che non eseguono lo snap, ma devono sapere quando lo scorrimento è stato interrotto, verrà chiamato didEndDecelerating alla fine del movimento di scorrimento. Combinato con un controllo su willDecelerate in didEndDragging, saprai quando lo scorrimento è stato interrotto.

+0

funziona anche quando il pagingEnabled di scrollView è Sì? ecco il mio post riguardante il mio problema. http://stackoverflow.com/questions/9337996/objective-c-uiscrollview-pagingenabled-when-next-page-enters-start-the-anima – SeongHo

+0

@SeongHo Sono abbastanza sicuro che lo faccia. Al momento non sono in grado di controllarlo, e non lo sarà per un bel po '. – Aberrant

20

[Risposta Modificato] Questo è quello che uso - gestisce tutti i casi limite. Hai bisogno di un ivar per mantenere lo stato, e come mostrato nei commenti, ci sono altri modi per gestirlo.

- (void)scrollViewWillBeginDragging:(UIScrollView *)scrollView 
{ 
    //[super scrollViewWillBeginDragging:scrollView]; // pull to refresh 

    self.isScrolling = YES; 
    NSLog(@"+scrollViewWillBeginDragging"); 
} 

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView willDecelerate:(BOOL)decelerate 
{ 
    //[super scrollViewDidEndDragging:scrollView willDecelerate:decelerate]; // pull to refresh 

    if(!decelerate) { 
     self.isScrolling = NO; 
    } 
    NSLog(@"%@scrollViewDidEndDragging", self.isScrolling ? @"" : @"-"); 
} 
- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    self.isScrolling = NO; 
    NSLog(@"-scrollViewDidEndDecelerating"); 
} 

- (void)scrollViewDidScrollToTop:(UIScrollView *)scrollView 
{ 
    self.isScrolling = NO; 
    NSLog(@"-scrollViewDidScrollToTop"); 
} 

- (void)scrollViewDidEndScrollingAnimation:(UIScrollView *)scrollView 
{ 
    self.isScrolling = NO; 
    NSLog(@"-scrollViewDidEndScrollingAnimation"); 
} 

ho creato un molto semplice progetto utilizza il codice precedente, in modo che quando una persona interagisce con uno ScrollView (compreso un WebView) inibisce processo di lavoro intenso finché l'utente non interrompe l'interazione con lo ScrollView AND ScrollView ha smesso di scorrere. E 'come 50 righe di codice: metodi ScrollWatcher

+2

Questo, buon signore, è la soluzione più pulita al problema, che ho visto. Grazie. – thomax

+0

In risposta alla tua modifica, si potrebbe in questo modo: https://github.com/lmirosevic/GBToolbox/commit/87be97a5eff702ef9ef0361fcb0539f36eb4984f È sufficiente chiamare 'ExecuteAfterScrolling (^ {NSLog (@ "lavoro pesante");});' e non è necessario fare confusione con gli stati booleani e sporcare il codice con controlli condizionali. – lms

+0

@lms Puoi spiegare perché funziona? Sembra che il metodo sia appena eseguito in un NSTimer –

2

Delegato menzionati in questo post le risposte non mi ha aiutato. Ho trovato un'altra risposta che rilevano fine di scorrimento finale di scrollViewDidScroll: Link è here

+0

+1, questo mi ha aiutato, grazie – NAZIK

18

Ecco come combinare scrollViewDidEndDecelerating e scrollViewDidEndDragging:willDecelerate per eseguire alcune operazioni quando lo scorrimento è terminato:

- (void)scrollViewDidEndDecelerating:(UIScrollView *)scrollView 
{ 
    [self stoppedScrolling]; 
} 

- (void)scrollViewDidEndDragging:(UIScrollView *)scrollView 
        willDecelerate:(BOOL)decelerate 
{ 
    if (!decelerate) { 
     [self stoppedScrolling]; 
    } 
} 

- (void)stoppedScrolling 
{ 
    // done, do whatever 
} 
1

ho risposto a questa domanda sul mio blog che delinea come farlo senza problemi.

Coinvolge "l'intercettazione del delegato" per fare alcune cose, quindi per passare i messaggi delegati a dove erano destinati. Questo è necessario perché ci sono alcuni scenari in cui i fuochi delegato se si è trasferita a livello di codice, o meno, o di entrambi, e così in ogni caso, meglio guardare solo a questo link qui sotto:

How to know when a UIScrollView is scrolling

E 'un po' difficile , ma ho preparato una ricetta lì e ho spiegato le parti in dettaglio.