我想在简单的UI中在转换之间创建软动画:
查看移动
当移动视图的调用 scrollToPoint:指向该过渡没有动画时。 我是Cocoa编程的新手(iOS是我的背景)。我不知道如何正确使用.animator或NSAnimationContext。
此外,我阅读了核心动画指南但未找到解决方案。
上访问来源请帮助!!!
答案 0 :(得分:22)
scrollToPoint不可动画。只有动画属性,如NSAnimatablePropertyContainer中的边界和位置等动画属性。你不需要对CALayer做任何事情:删除wantsLayer和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];
}
答案 1 :(得分:4)
Swift 4代码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()
}
答案 2 :(得分:2)
建议的答案有一个很大的缺点:如果用户在进行中的动画期间尝试滚动,则输入将引起抖动,因为动画将强制继续进行直到完成。如果您设置了非常长的动画持续时间,该问题就很明显。这是我的用例,为滚动视图设置动画以捕捉到节标题(同时尝试向上滚动):
我提出了以下子类:
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
的{{1}}内手动执行滚动,我们取消了当前动画。但是,我们不调用setDisableActions
,而是调用reflectScrolledClipView
,它将执行其他必要的内部过程,然后执行super.scroll(clipView, to: point)
。
以上课程会产生更好的结果:
答案 3 :(得分:1)
这是Andrew's answer的Swift 4扩展版本
extension NSScrollView {
func scroll(to point: NSPoint, animationDuration: Double) {
NSAnimationContext.beginGrouping()
NSAnimationContext.current.duration = animationDuration
contentView.animator().setBoundsOrigin(point)
reflectScrolledClipView(contentView)
NSAnimationContext.endGrouping()
}
}
答案 4 :(得分:1)
我知道,这有点不切实际,但是我想有一种类似的方法来滚动到UIView's
的{{1}} scrollRectToVisible(_ rect: CGRect, animated: Bool)
中带有动画的矩形。我很高兴找到这篇文章,但显然接受的答案并不总是能正常工作。事实证明,剪辑视图的NSView
存在问题。如果要调整视图的大小(例如,通过调整周围窗口的大小),bounds.origin
将以某种方式相对于可见矩形的真实原点在y方向偏移。我不知道为什么以及要花多少钱。嗯,Apple文档中也有这样的说法:不要直接操作clipview,因为它的主要目的是在内部充当视图的滚动机。
但是我确实知道可见区域的真正起源。它是剪辑视图的bounds.origin
的一部分。因此,我使用该原点来计算visibleRect的滚动原点,然后将clipview的documentVisibleRect
偏移相同的数量,并且voilà:即使视图的大小被调整了,该方法也有效。
这是我对NSView的新方法的实现:
bounds.origin
请注意,我在 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
}
}
中使用了翻转坐标,使其与iOS行为匹配。
顺便说一句:iOS版本NSView
中的动画持续时间为0.3秒。