Как точно узнать, когда прокрутка UIScrollView остановилась?

Короче говоря, мне нужно точно знать, когда прокрутка перестала прокручиваться. Под «остановкой прокрутки» я подразумеваю момент, когда он больше не движется и к нему не прикасаются.

Я работал над горизонтальным подклассом UIScrollView (для iOS 4) с вкладками выбора в нем. Одним из его требований является прекращение прокрутки ниже определенной скорости для более быстрого взаимодействия с пользователем. Он также должен быть привязан к началу вкладки. Другими словами, когда пользователь отпускает режим прокрутки и его скорость низкая, он привязывается к определенной позиции. Я реализовал это, и он работает, но в нем есть ошибка.

Что у меня сейчас:

scrollview - это собственный делегат. при каждом вызове scrollViewDidScroll: он обновляет свои переменные, связанные со скоростью:

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

Затем при необходимости переходит к привязке:

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

Вот методы делегата:

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

Это работает в большинстве случаев, за исключением двух сценариев: 1. Когда пользователь прокручивает, не отпуская палец, и отпускает почти неподвижно сразу после движения, он часто фиксируется в своей позиции, как и предполагалось, но часто этого не происходит. Обычно требуется несколько попыток, чтобы это произошло. Нечетные значения для времени (очень низкое) и / или расстояния (довольно высокое) появляются при выпуске, вызывая высокое значение скорости, в то время как scrollView в действительности почти или полностью неподвижен. 2.Когда пользователь касается прокрутки, чтобы остановить ее движение, кажется, что прокрутка устанавливает contentOffset на предыдущее место. Эта телепортация приводит к очень высокому значению скорости. Это можно исправить, проверив, является ли предыдущая дельта currentDelta * -1, но я бы предпочел более стабильное решение.

Я пробовал использовать didEndDecelerating , но когда происходит сбой, он не вызывается. Это, вероятно, подтверждает, что он уже неподвижен. Кажется, что нет метода делегата, который вызывается, когда scrollview полностью перестает перемещаться.

Если вы хотите увидеть сбой самостоятельно, вот код для заполнения прокрутки вкладками:

@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

Более короткая версия этого вопроса: как узнать, когда прокрутка перестала прокручиваться? didEndDecelerating: не вызывается, когда вы отпускаете его в неподвижном состоянии, didEndDragging: часто происходит во время прокрутки, и проверка скорости ненадежна из-за этого нечетного «скачка», который устанавливает скорость на что-то случайное.

26
задан Aberrant 8 June 2014 в 05:18
поделиться