Soft scroll animation NSScrollView scrollToPoint:

I want to create soft animation between transitions in just UI:

first slidesecond slidethird slide

which moves

view

When calling scrollToPoint: to move, to indicate that the transition is not animating. I am new to Cocoa programming (iOS is my background). And I don't know how to use .animator or NSAnimationContext correctly.

I also read the Core Animation manual, but did not find a solution.

Source may be available Git Hub Repository

Please, help!!!

+11
source share
5 answers

scrollToPoint is not animated. Only animated properties are animated, such as borders and position in NSAnimatablePropertyContainer. You do not need to do anything with CALayer: delete the wantLayer and CALayer elements. Then with the subsequent code, it is animated.

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

Swift 4 code for this answer

 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() } 
+4
source

Here is an extended version of 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() } } 
+1
source

I know this is a little off topic, but I wanted to have a similar method for scrolling to a rectangular box with animation, as in UIView scrollRectToVisible(_ rect: CGRect, animated: Bool) for my NSView . I was happy to find this post, but obviously the accepted answer does not always work correctly. It turns out that there is a problem with bounds.origin clip. If the size of the view changes (for example, by resizing the surrounding window), bounds.origin somehow shifts relative to the true beginning of the visible right-angled box in the y direction. I could not understand why and by how much. Well, there is also an expression in Apple docs that it should not directly manipulate the viewing of a clip, since its main purpose is to function as a scroll to view.

But I know the true origin of the visible region. This is part of documentVisibleRect clips. Therefore, I take this source to calculate the scrollable visibleRect source and shift the bounds.origin clip view by the same amount, and voila: this works even if the view size changes.

Here is my implementation of the new method of my 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 } } 

Note that I use inverted coordinates in my NSView so that it matches iOS behavior. By the way, the animation duration in the iOS version of scrollRectToVisible is 0.3 seconds.

https://www.fpposchmann.de/animate-nsviews-scrolltovisible/

+1
source

The suggested answers have a significant drawback: if the user tries to scroll during the current animation, the input will cause jitter, as the animation will continue forcibly until completion. If you set a very long animation duration, the problem will become apparent. Here is my use case: an animation of a scroll view to bind to a section title (while trying to scroll up):

enter image description here

I suggest the following subclass:

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

Overriding the regular scroll(_ clipView: NSClipView, to point: NSPoint) (called when the user scrolls) and manually scrolling inside the CATransaction using setDisableActions , we cancel the current animation. However, we do not call reflectScrolledClipView , instead we call super.scroll(clipView, to: point) , which will execute the other necessary internal procedures and then execute reflectScrolledClipView .

Above the class gives the best results:

enter image description here

+1
source

Source: https://habr.com/ru/post/956062/


All Articles