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

Мягкая анимация прокрутки NSScrollView scrollToPoint:

Я хочу создать мягкую анимацию между переходами в просто UI:

first slidesecond slidethird slide

который перемещается

view

При вызове scrollToPoint: для перемещения, чтобы указать, что переход не анимируется. Я новичок в программировании Cocoa (iOS - мой фон). И я не знаю, как правильно использовать .animator или NSAnimationContext.

Также я прочитал руководство Core Animation, но не нашел решение.

Источник может быть доступен Git Репозиторий концентратора

Пожалуйста, помогите!!!

4b9b3361

Ответ 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