2011-10-06 17 views
25

Krótko mówiąc, muszę wiedzieć dokładnie, kiedy przewijanie przestało się przewijać. "Zatrzymałem przewijanie", mam na myśli moment, w którym już się nie porusza i nie jest dotykany.Jak dokładnie wiedzieć, kiedy przewijanie UIScrollView zostało zatrzymane?

Pracowałem nad poziomą podklasą UIScrollView (dla iOS 4) z zakładkami wyboru w niej. Jednym z jego wymagań jest to, że przestaje przewijać poniżej pewnej prędkości, aby umożliwić szybszą interakcję z użytkownikiem. Powinien także zostać przyciągnięty do początku karty. Innymi słowy, gdy użytkownik zwolni przewijanie, a jego prędkość będzie niska, zostanie ona przyciągnięta do pozycji. Zaimplementowałem to i działa, ale jest w nim błąd.

Co mam teraz:

Scrollview jest jego własny delegat. przy każdym wywołaniu scrollViewDidScroll :, odświeża swoje zmienne związane Speed-:

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

następnie przechodzi do przystawki jeśli potrzebne:

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

Oto sposoby Delegat:

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

ten działa dobrze przez większość czasu, z wyjątkiem dwóch scenariuszy: 1. Gdy użytkownik przewinie się bez zwalniania palca i puści w przybliżeniu w nieruchomym momencie zaraz po przeprowadzce, często zaskakuje na swoją pozycję, jak to ma miejsce, ale wiele razy, nie robi tego. Zwykle wymaga kilku prób, aby to się stało. Nieparzyste wartości czasu (bardzo niskie) i/lub odległości (raczej wysokie) pojawiają się na zwolnieniu, powodując wysoką wartość prędkości, podczas gdy scrollView jest w rzeczywistości prawie lub całkowicie nieruchomy. 2. Gdy użytkownik dotknie przewijania, aby zatrzymać jego ruch, wygląda na to, że przewijanie ustawia contentOffset do poprzedniego miejsca. Ta teleportacja daje bardzo wysoką wartość prędkości. Można to naprawić, sprawdzając, czy poprzednia delta ma wartość bieżącą Dela * -1, ale wolałbym bardziej stabilne rozwiązanie.

Próbowałem użyć didEndDecelerating, ale gdy wystąpi usterka, nie zostanie wywołana. To prawdopodobnie potwierdza, że ​​jest już stacjonarne. Wydaje się, że nie ma żadnej delegowanej metody, która zostanie wywołana, gdy scrollview przestanie się całkowicie poruszać.

Jeśli chcesz zobaczyć glitch siebie, oto niektóre kod, aby wypełnić Scrollview z zakładkami:

@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 

krótszą wersję tego pytania: Jak rozpoznać, kiedy Scrollview zatrzymał przewijanie? didEndDecelerating: nie zostanie wywołany, gdy zwolnisz go nieruchomo, didEndDragging: dzieje się dużo podczas przewijania i sprawdzenie prędkości jest niewiarygodne z powodu tego dziwnego "skoku", który ustawia prędkość na coś losowego.

+0

Można połączyć 'didEndDragging' ze zdarzeniem dotykowym, tak aby po dotknięciu widoku użytkownik ustawiał flagę i resetował po zdjęciu palca. Następnie, dopóki flaga jest ustawiona, nie uruchamiasz powiązanej logiki w 'didEndDragging:'. – FreeAsInBeer

+0

Dziękuję za odpowiedź. Jednak nie jestem pewien, o czym powinna być mowa flaga, o której wspomniałeś. Wydaje mi się, że masz na myśli flagę pokazującą, kiedy raz przewinięto widok przewijania, ale nie wiem, w jaki sposób byłoby to przydatne, biorąc pod uwagę, że użytkownik wielokrotnie przewijał przewijanie podczas przewijania lub tylko w punkcie stacjonarnym bez podniósł palec. – Aberrant

Odpowiedz

30

znalazłem rozwiązanie:

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

nie wcześniej zauważyć, że ostatni kawałek, willDecelerate. Jest to wartość false, gdy scrollView jest nieruchomy po zakończeniu dotykania. W połączeniu z wyżej wymienioną kontrolą prędkości, mogę zrobić zdjęcie zarówno gdy jest wolne (i nie jest dotykane), jak i gdy jest nieruchome.

Dla każdego, kto nie robi żadnych przyciągania, ale musi wiedzieć, kiedy przewijanie zatrzymało się, didEndDecelerating zostanie wywołany na końcu ruchu przewijania. W połączeniu z czekiem na willDecelerate w didEndDragging, będziesz wiedzieć, kiedy przewijanie się zatrzymało.

+0

działa również, gdy przewijanie stronicowanej scrollView jest ustawione na Tak? oto mój wpis dotyczący mojego problemu. http://stackoverflow.com/questions/9337996/objective-c-uiscrollview-pagingenabled-when-next-page-enters-start-the-anima – SeongHo

+0

@ SeongHo Jestem prawie pewien, że tak. Obecnie nie jestem w stanie tego sprawdzić i nie będzie przez jakiś czas. – Aberrant

20

[Edytowana odpowiedź] Właśnie tego używam - obsługuje wszystkie skrzynie brzegowe. Aby zachować stan, potrzebujesz ivar, a jak pokazano w komentarzach, istnieją inne sposoby radzenia sobie z tym.

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

stworzyłem bardzo prosty projekt wykorzystuje powyższy kod tak, że kiedy człowiek współdziała z Scrollview (w tym WebView), hamuje proces intensywnej pracy, dopóki użytkownik nie zatrzyma interakcji z Scrollview ORAZ Scrollview ma przestałem przewijać. To jest jak 50 linii kodu: ScrollWatcher

+2

To, proszę pana, jest najczystszym rozwiązaniem problemu, który widziałem. Dziękuję Ci. – thomax

+0

W odpowiedzi na twoją modyfikację możesz polubić to: https://github.com/lmirosevic/GBToolbox/commit/87be97a5eff702ef9ef0361fcb0539f36eb4984f Po prostu wywołaj 'ExecuteAfterScrolling (^ {NSLog (@" ciężka praca ");});' i nie trzeba zadzierać ze stanami boolowskimi i zaśmiecać kod za pomocą warunkowych kontroli. – lms

+0

@lms Czy możesz wyjaśnić, dlaczego to działa? Wydaje się, że metoda jest uruchamiana w NSTmiercie –

2

Metody delegowania wymienione w tym poście odpowiedzi mi nie pomogły. Znalazłem inną odpowiedź, która wykrywa ostateczny koniec przewijania w scrollViewDidScroll: link jest here

+0

+1, pomogło mi to, dzięki – NAZIK

18

Oto jak połączyć scrollViewDidEndDecelerating i scrollViewDidEndDragging:willDecelerate wykonać jakąś operację, gdy przewijanie zakończy:

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

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

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

odpowiedziałem na to pytanie na moim blogu, który nakreśla jak to zrobić bez problemów.

Obejmuje "przechwytywanie delegata", aby wykonać kilka czynności, a następnie przekazanie wiadomości delegatów tam, gdzie były przeznaczone. Jest to konieczne, ponieważ istnieje kilka scenariuszy, gdzie pożary delegata jeżeli przemieszczane programowo, czy nie, lub oba, a więc tak, najlepiej jest po prostu spojrzeć na ten link poniżej:

How to know when a UIScrollView is scrolling

to trochę trudne , ale przygotowałem tam przepis i szczegółowo wyjaśniłem części.

Powiązane problemy