Подтвердить что ты не робот

Как правильно настроить UIScrollView contentOffset

У меня есть подкласс UIScrollView. Его содержимое многократно используется - около 4 или 5 просмотров используются для отображения сотен элементов (при повторном прокрутке скрытых объектов и переходе на другую позицию, когда это необходимо для их просмотра)

Что мне нужно: возможность автоматического прокрутки моего прокрутки до любой позиции. Например, в моем представлении прокрутки отображается 4-й, 5-й и 6-й элементы, и когда я нажимаю какую-то кнопку, ему нужно прокрутить до 30-го элемента. Другими словами, мне нужно стандартное поведение для UIScrollView.

Это отлично работает:

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

но мне нужна настройка. Например, измените продолжительность анимации, добавьте некоторый код для выполнения в конце анимации.

Очевидное решение:

[UIView animateWithDuration:3 delay:0 options:UIViewAnimationOptionBeginFromCurrentState animations:^{
    [self setContentOffset:CGPointMake(index*elementWidth, 0)];
} completion:^(BOOL finished) {
    //some code
}];

но у меня есть некоторые действия, связанные с событием прокрутки, и теперь все они находятся в блоке анимации, и это заставляет все подкадры также оживлять (благодаря нескольким многократно используемым элементам все они оживляют не то, как я хочу)

Вопрос: Как создать пользовательскую анимацию (на самом деле мне нужна пользовательская продолжительность, действия на конец и параметр BeginFromCurrentState) для смещения содержимого БЕЗ анимирования всего кода, связанный с событием scrollViewDidScroll?

UPD: Благодаря Andrew answer (первая часть) я решил проблему с анимацией внутри scrollViewDidScroll:

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [UIView performWithoutAnimation:^{
        [self refreshTiles];
    }];
}

Но scrollViewDidScroll должен (для моих целей) исполняет каждый кадр анимации, как это было в случае

[self setContentOffset:CGPointMake(index*elementWidth, 0) animated:YES];

Однако теперь он выполняется только один раз при запуске анимации.

Как я могу это решить?

4b9b3361

Ответ 1

Попробовал ли вы такой же подход, но с отключенной анимацией в scrollViewDidScroll?

В iOS 7 вы можете попробовать обернуть свой код в scrollViewDidScroll в

[UIView performWithoutAnimation:^{
       //Your code here
    }];

в предыдущих версиях iOS, вы можете попробовать:

  [CATransaction begin];
    [CATransaction setDisableActions:YES];
     //Your code here
    [CATransaction commit];

Update:

К сожалению, там, где вы попали в трудную часть всего этого. setContentOffset: вызывает делегата только один раз, что эквивалентно setContentOffset:animated:NO, который снова вызывает его только один раз.

setContentOffset:animated:YES вызывает делегата, поскольку анимация изменяет границы прокрутки и вы хотите этого, но вы не хотите предоставленную анимацию, поэтому единственный способ, который я могу придумать, - это постепенно менять contentOffset прокрутки, так что система анимации не просто переходит к окончательному значению, как в данный момент.

Для этого вы можете посмотреть анимацию ключевых кадров, например, для iOS 7:

[UIView animateKeyframesWithDuration:duration delay:delay options:options animations:^{
    [UIView addKeyframeWithRelativeStartTime:0.0 relativeDuration:0.5 animations:^{
       [self setContentOffset:CGPointMake(floorf(index/2) * elementWidth, 0)];
    }];
    [UIView addKeyframeWithRelativeStartTime:0.5 relativeDuration:0.5 animations:^{
        [self setContentOffset:CGPointMake(index*elementWidth, 0)];
    }];
} completion:^(BOOL finished) {
    //Completion Block
}];

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

В предыдущих версиях iOS вам придется отказаться от CoreAnimation для анимации ключевого кадра, но это в основном то же самое с немного другим синтаксисом.

Способ 2: Вы можете попробовать опросить презентационный слой scrollview для любых изменений с помощью таймера, который вы начинаете в начале анимации, так как, к сожалению, свойства presentationLayer не являются наблюдаемыми KVO. Или вы можете использовать needsDisplayForKey в подклассе слоя для получения уведомления, когда границы меняются, но для этого потребуется некоторая работа по настройке, и это вызывает перерисовку, что может повлиять на производительность.

Способ 3: Было бы точно проанализировать, что происходит с scrollView при анимированном YES, и попытаться перехватить анимацию, которая будет установлена ​​в scrollview и изменить ее параметры, но так как это было бы самым взломанным, прерывистым из-за изменений Apple и самого сложного метода, я выиграл Входите в это.

Ответ 2

Хороший способ сделать это с помощью библиотеки AnimationEngine. Это очень маленькая библиотека: шесть файлов, еще три, если вы хотите демпфировать поведение spring.

За кулисами он использует CADisplayLink для запуска блока анимации после каждого кадра. Вы получаете чистый блок-синтаксис, который прост в использовании, и куча interpolation и облегчающие функции, которые экономят ваше время.

Анимация contentOffset:

startOffset = scrollView.contentOffset;
endOffset = ..

// Constant speed looks good...
const CGFloat kTimelineAnimationSpeed = 300;
CGFloat timelineAnimationDuration = fabs(deltaToDesiredX) / kTimelineAnimationSpeed;

[INTUAnimationEngine animateWithDuration:timelineAnimationDuration
                                   delay:0
                                  easing:INTULinear
                              animations:^(CGFloat progress) {
                                    self.videoTimelineView.contentOffset = 
                                         INTUInterpolateCGPoint(startOffset, endOffset, progress);
                              }
                             completion:^(BOOL finished) {
                                   autoscrollEnabled = YES;
                             }];

Ответ 3

Попробуй это:

UIView.animate(withDuration: 0.6, animations: {
        self.view.collectionView.contentOffset = newOffset
        self.view.layoutIfNeeded()
    }, completion: nil)