Как правильно настроить 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];
Однако теперь он выполняется только один раз при запуске анимации.
Как я могу это решить?
Ответы
Ответ 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)