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