在 UIScrollView 中为 zoomScale 设置动画时不需要的滚动
Posted
技术标签:
【中文标题】在 UIScrollView 中为 zoomScale 设置动画时不需要的滚动【英文标题】:Unwanted scrolling when animating zoomScale in UIScrollView 【发布时间】:2011-11-10 05:35:02 【问题描述】:执行摘要:
有时UIScrollView
会对contentOffset
的值进行不必要的更改,从而导致应用程序在正在查看的文档中显示错误的位置。不需要的更改与滚动视图的 zoomScale
的动画更改一起发生。
详情:
在UIScrollView
中使用CATiledLayer
缩小时遇到问题。 CATiledLayer
保存一个pdf,当contentOffset
在一定范围内时,当我缩小时,contentOffset
在缩放发生之前被更改(这是错误)。 contentOffset
似乎在 Apple 的代码中有所更改。
为了说明问题,我修改了 Apple 的示例应用程序 ZoomingPDFViewer。代码在github上:https://github.com/DirkMaas/ZoomingPDFViewer-bug
点击将导致zoomScale
更改为0.5,使用animateWithDuration
,从而缩小。如果UIScrollView
的contentOffset.y
小于约2700 或大于5900,则zoomScale
动画工作正常。如果点击发生在contentOffset.y
介于这两个值之间时,contentOffset.y
将跳转(未动画)到大约 2700,然后会出现zoomScale
动画,但同时会发生滚动,这样当动画完成了,contentOffset.y
应该在哪里。但跳跃从何而来?
例如,假设点击屏幕时contentOffset.y
是 2000:zoomScale
动画效果很好; contentOffset.y
没有改变。
但是如果在点击屏幕时contentOffset.y
为4000:contentOffset.y
将在没有动画的情况下跳转到大约2700,然后将从该点开始缩放和滚动并同时发生。动画完成后,看起来好像我们从 4000 处直接放大,所以我们最终在正确的位置,但行为是错误的。
关于用户界面的说明:
文字可以正常垂直滚动 文本可以通过正常的捏合方式放大和缩小 单击将导致zoomScale
设置为0.5;变化是动画的
我注意到如果zoomScale
大于 0.5,则跳跃不是那么大。另外,如果我使用setZoomScale:animated:
而不是animateWithDuration
,错误就会消失,但我不能使用它,因为我需要链接动画。
以下是我所做的总结(github 中的代码包括这些更改):
从下载 ZoomingPDFViewer http://developer.apple.com/library/ios/#samplecode/ZoomingPDFViewer/Introduction/Intro.html 并在 XCode 中打开 更改了构建设置 |架构 | Base SDK 到最新的 iOS (iOS 4.3) 更改了构建设置 | GCC 4.2 - 语言 |根据 Objective-C++ 编译源代码 从项目中删除了 TestPage.pdf 在项目中添加了“whoiam 5 24 cropped 3-2.pdf” 将PDFScrollView *scrollView;
添加到ZoomingPDFViewerViewController
类
将ZoomingPDFViewerViewController
中的loadView
更改为初始化scrollView
而不是sv
在 PDFScrollview.m 中将 viewDidLoad
、handleTapFrom:recognizer
和 zoomOut
添加到 ZoomingPDFViewerViewController
注释掉了 scrollViewDidEndZooming:withView:atScale
和 scrollViewWillBeginZooming:withView:
,因为它们在图片背景中做的事情会分散手头的问题
非常感谢您对我的包容,以及任何和所有的帮助!
【问题讨论】:
需要链接动画是什么意思?为什么你不能使用scrollViewDidEndZooming:withView:atScale
来通知你缩放何时完成?
@Michael Frederick -- 好点。这是一种解决方法,但我喜欢使用animateWithDuration:
而不是setZoomScale:Animated:
,因为除非我遗漏了什么,否则后者不允许控制持续时间,而且在我看来,块动画做得更好,将相关代码放在一起。另外,Apple 似乎鼓励我们使用块动画。所以也许我应该说“我不想使用setZoomScale:Animated:
”。感谢您的评论!
【参考方案1】:
关于缩放的最棘手的事情之一是它总是发生在一个称为锚点的点周围。我认为理解它的最好方法是想象一个坐标系叠加在另一个坐标系之上。假设 A 是您的外部坐标系,B 是内部坐标系(B 将是滚动视图)。当B的偏移量为(0,0),比例为1.0时,则点B(0,0)对应A(0,0),一般情况下B(x,y) = A(x,y )。
进一步,如果 B 的偏移量是 (xOff, yOff) 则 B(x,y) = A(x - xOff, y - yOff)。同样,这仍然假设缩放比例为 1.0。
现在,让偏移量再次为 (0,0) 并想象当您尝试缩放时会发生什么。屏幕上一定有一个点在缩放时不会移动,并且每隔一个点都会从该点向外移动。这就是锚点定义的内容。如果您的锚点是 (0,0),那么左下角的点将保持固定,而所有其他点都会向上和向右移动。在这种情况下,偏移量保持不变。
如果你的锚点是 (0.5, 0.5)(锚点是标准化的,即 0 到 1,所以 0.5 是一半),那么中心点保持固定,而所有其他点都向外移动。这意味着偏移量必须改变以反映这一点。如果它在 iPhone 上处于纵向模式并且您缩放到 2.0,则锚点 x 值将移动屏幕宽度的一半,320/2 = 160。
滚动视图内容视图在屏幕上的实际位置由偏移量和锚点定义。因此,如果您只是简单地更改下面的图层锚点而不对偏移量进行相应更改,您将看到视图似乎跳转到不同的位置,即使偏移量相同。
我猜这是这里的根本问题。当您为缩放设置动画时,Core Animation 必须选择一个新的锚点,以便缩放“看起来”正确。这也将更改偏移量,以使屏幕上的视图实际可见区域不会跳转。尝试在整个过程中的不同时间记录锚点的位置(它是在您使用 Views“层”属性访问的任何 View 的底层 CALayer 上定义的)。
另外,请参阅文档here 以获得漂亮的图片,并且可能比我在此处给出的情况描述要好得多:)
最后,这是我在应用程序中使用的一段代码,用于更改图层的锚点而不使其在屏幕上移动。
-(CGPoint)setNewAnchorPointWithoutMoving:(CGPoint)newAnchor
CGPoint currentAnchor = CGPointMake(self.anchorPoint.x * self.contentSize.width,
self.anchorPoint.y * self.contentSize.height);
CGPoint offset = CGPointMake((1 - self.scale) * (currentAnchor.x - newAnchor.x),
(1 - self.scale) * (currentAnchor.y - newAnchor.y));
self.anchorPoint = CGPointMake(newAnchor.x / self.contentSize.width,
newAnchor.y / self.contentSize.height);
self.position = CGPointMake(self.position.x + offset.x, self.position.y + offset.y);
return offset;
【讨论】:
谢谢,但我不认为是这样。代码不会直接或间接尝试更改anchorpoint
值,我在缩放前后记录了它,两次都是默认值(0.5、0.5)。此外,就其本质而言,anchorPoint
始终在屏幕上,可见,这意味着任何跳跃都将小于 1 个屏幕的距离,但我看到的跳跃会导致 contentOffset.y
改变数千 - 几个屏幕'距离。对我来说,这证明了 anchorPoint 不是问题,除非我误解了什么。【参考方案2】:
我一直在使用您发布的代码,我想我已经发现了发生了什么。当您在代码中执行块动画时:
[UIView animateWithDuration:4.0
animations:^
self.scrollView.zoomScale = 0.5;
completion:nil];
动画块实际上在动画开始时被调用。 Core Animation 在内部处理使它看起来像实际移动的层。开发中心有一个list of Animatable Properties,zoomScale不在其中。
当 zoomScale 发生变化时,scrollView 会自动更新其 contentOffset。因此,当动画开始时,您看到的跳跃是 contentOffset 正在为新的 zoomScale 调整。然后将图层动画到适当的缩放比例,但目标 contentOffset(即缩放结束时的 contentOffset 应该是什么)已经在缩放开始时设置。
这也是为什么缩放看起来是以屏幕外的某个点为中心的原因。您要么必须使用 setZoomScale:animated: 要么自己为图层和偏移设置动画...
【讨论】:
【参考方案3】:您应该使用 scrollViewDidEndZooming:withView:atScale 来通知您缩放何时完成。否则,我相信您将不得不删除 UIScrollView 的属性 zoomScale 的使用,而是完全手动实现缩放,例如http://jonathanwatmough.com/2008/12/implementing-tap-to-zoom-in-uiscrollview-on-an-iphone/.
【讨论】:
【参考方案4】:当我的 viewForZoomingInScrollView 函数直接返回滚动视图时,我遇到了很多缩放错误。
在我在包含所有内容的 scrollView 中设置一个视图并在 viewForZoomingInScrollView 中返回它之后,许多这些奇怪的行为都消失了。也许这也应该解决你的问题:
-(void) someIntializationFunction
[myScrollView addSubView:self.globalContainer];
// Now had everything in the container and not in the scrollView directly
....
- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView;
return self.globalContainer;
另外,我见过 zoomToRect: 函数比改变 scrollView 的 zoomScale 更可靠。这也会有所帮助,即使您需要进行更多计算...
【讨论】:
【参考方案5】:我认为您必须在每个滚动事件上自己操作 contentInset 和/或 contentOffset。操纵锚点将无济于事。看看this question 和我的回答。
【讨论】:
以上是关于在 UIScrollView 中为 zoomScale 设置动画时不需要的滚动的主要内容,如果未能解决你的问题,请参考以下文章
如何在 ios 中为 UIScrollView 设置对齐方式
如何使用自动布局在 UIScrollView 中为视图高度设置动画?
在 UIScrollview 中为 UIButton 添加 UIPangesture 以在所有视图上移动
ImageView 的边界在 ViewDidLayoutSubviews() 中为零 [重复]