Мягкая анимация прокрутки NSScrollView scrollToPoint:
Я хочу создать мягкую анимацию между переходами в просто UI:
![first slide]()
![second slide]()
![third slide]()
который перемещается
![view]()
При вызове scrollToPoint: для перемещения, чтобы указать, что переход не анимируется.
Я новичок в программировании Cocoa (iOS - мой фон). И я не знаю, как правильно использовать .animator или NSAnimationContext.
Также я прочитал руководство Core Animation, но не нашел решение.
Источник может быть доступен Git Репозиторий концентратора
Пожалуйста, помогите!!!
Ответы
Ответ 1
scrollToPoint не является анимированным. Анимируются только анимационные свойства, такие как границы и положение в NSAnimatablePropertyContainer. Вам не нужно ничего делать с CALayer: удаляйте элементы wantLayer и CALayer. Затем с последующим кодом он анимируется.
- (void)scrollToXPosition:(float)xCoord {
[NSAnimationContext beginGrouping];
[[NSAnimationContext currentContext] setDuration:5.0];
NSClipView* clipView = [_scrollView contentView];
NSPoint newOrigin = [clipView bounds].origin;
newOrigin.x = xCoord;
[[clipView animator] setBoundsOrigin:newOrigin];
[_scrollView reflectScrolledClipView: [_scrollView contentView]]; // may not bee necessary
[NSAnimationContext endGrouping];
}
Ответ 2
Swift 4 код этого ответа
func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
let clipView = scrollView.contentView
clipView.animator().setBoundsOrigin(toPoint)
scrollView.reflectScrolledClipView(scrollView.contentView)
NSAnimationContext.endGrouping()
}
Ответ 3
Вот расширенная версия Swift 4 Andrew answer
extension NSScrollView {
func scroll(to point: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(point)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
Ответ 4
Я знаю, что это немного не по теме, но я хотел иметь похожий метод для прокрутки к прямоangularьнику с анимацией, как в UIView
scrollRectToVisible(_ rect: CGRect, animated: Bool)
для моего NSView
. Я был счастлив найти этот пост, но, очевидно, принятый ответ не всегда работает правильно. Оказывается, что есть проблема с bounds.origin
клипа. Если размер вида изменяется (например, изменяя размеры окружающего окна), bounds.origin
каким-то образом сдвигается относительно истинного начала видимого прямоangularьника в направлении y. Я не мог понять, почему и на сколько. Что ж, в документах Apple есть также выражение о том, что он не должен напрямую манипулировать просмотром клипа, поскольку его основная цель - функционировать как прокрутка для просмотра.
Но я знаю истинное происхождение видимой области. Это часть клипов documentVisibleRect
. Поэтому я беру этот источник для вычисления прокручиваемого источника visibleRect и сдвигаю bounds.origin
просмотра клипа на ту же величину, и вуаля: это работает, даже если размер представления изменяется.
Вот моя реализация нового метода моего NSView:
func scroll(toRect rect: CGRect, animationDuration duration: Double) {
if let scrollView = enclosingScrollView { // we do have a scroll view
let clipView = scrollView.contentView // and thats its clip view
var newOrigin = clipView.documentVisibleRect.origin // make a copy of the current origin
if newOrigin.x > rect.origin.x { // we are too far to the right
newOrigin.x = rect.origin.x // correct that
}
if rect.origin.x > newOrigin.x + clipView.documentVisibleRect.width - rect.width { // we are too far to the left
newOrigin.x = rect.origin.x - clipView.documentVisibleRect.width + rect.width // correct that
}
if newOrigin.y > rect.origin.y { // we are too low
newOrigin.y = rect.origin.y // correct that
}
if rect.origin.y > newOrigin.y + clipView.documentVisibleRect.height - rect.height { // we are too high
newOrigin.y = rect.origin.y - clipView.documentVisibleRect.height + rect.height // correct that
}
newOrigin.x += clipView.bounds.origin.x - clipView.documentVisibleRect.origin.x // match the new origin to bounds.origin
newOrigin.y += clipView.bounds.origin.y - clipView.documentVisibleRect.origin.y
NSAnimationContext.beginGrouping() // create the animation
NSAnimationContext.current.duration = duration // set its duration
clipView.animator().setBoundsOrigin(newOrigin) // set the new origin with animation
scrollView.reflectScrolledClipView(clipView) // and inform the scroll view about that
NSAnimationContext.endGrouping() // finaly do the animation
}
}
Обратите внимание, что я использую перевернутые координаты в моем NSView
, чтобы он соответствовал поведению iOS.
Кстати, продолжительность анимации в версии для iOS scrollRectToVisible
составляет 0,3 секунды.
https://www.fpposchmann.de/animate-nsviews-scrolltovisible/
Ответ 5
Предложенные ответы имеют существенный недостаток: если пользователь пытается прокрутить во время текущей анимации, ввод будет вызывать дрожание, поскольку анимация будет продолжаться принудительно до завершения. Если вы установите очень большую продолжительность анимации, проблема станет очевидной. Вот мой пример использования: анимация вида прокрутки для привязки к заголовку раздела (при одновременной попытке прокрутки вверх):
![enter image description here]()
Я предлагаю следующий подкласс:
public class AnimatingScrollView: NSScrollView {
// This will override and cancel any running scroll animations
override public func scroll(_ clipView: NSClipView, to point: NSPoint) {
CATransaction.begin()
CATransaction.setDisableActions(true)
contentView.setBoundsOrigin(point)
CATransaction.commit()
super.scroll(clipView, to: point)
}
public func scroll(toPoint: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(toPoint)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
Переопределяя обычный scroll(_ clipView: NSClipView, to point: NSPoint)
(вызывается при прокрутке пользователя) и вручную выполняя прокрутку внутри CATransaction
с помощью setDisableActions
, мы отменяем текущую анимацию. Однако мы не вызываем reflectScrolledClipView
, вместо этого мы вызываем super.scroll(clipView, to: point)
, который выполнит другие необходимые внутренние процедуры, а затем выполнит reflectScrolledClipView
.
Выше класса дает лучшие результаты:
![enter image description here]()