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