在UIScrollView中设置zoomScale动画时不需要的滚动

执行摘要:有时UIScrollView会对contentOffset的值进行不必要的更改,从而导致应用程序在正在查看的文档中显示错误的位置。 不必要的更改与滚动视图的zoomScale的动画更改一起发生。

详细信息:UIScrollView使用CATiledLayer缩小时遇到问题。 CATiledLayer包含一个pdf,当contentOffset在一定范围内时,当我缩小时,在缩放发生之前更改了contentOffset (这就是错误)。 在Apple的代码中似乎更改了contentOffset

为了说明这个问题,我修改了Apple的示例应用程序ZoomingPDFViewer。 代码在github上: https : //github.com/DirkMaas/ZoomingPDFViewer-bug

点按将使用animateWithDurationzoomScale更改为0.5,从而缩小。 如果UIScrollViewcontentOffset.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 ,则该bug会消失,但我无法使用它,因为我需要链接动画。

以下是我所做的总结(github中的代码包含这些更改):

  • 从http://developer.apple.com/library/ios/#samplecode/ZoomingPDFViewer/Introduction/Intro.html下载了ZoomingPDFViewer并在XCode中打开它
  • 更改了构建设置| 架构| 基础SDK到最新的iOS(iOS 4.3)改变了Build Settings | GCC 4.2 – 语言| 编译源代码如Objective-C ++
  • 从项目中删除了TestPage.pdf
  • 将“whoiam 5 24播种3-2.pdf”添加到该项目中
  • 添加了PDFScrollView *scrollView;ZoomingPDFViewerViewController
  • 更改了ZoomingPDFViewerViewController loadView以初始化scrollView而不是sv
  • 在PDFScrollview.m中添加了viewDidLoadhandleTapFrom:recognizerZoomingPDFViewerViewController到ZoomingPDFViewerViewController
  • 注释掉了scrollViewDidEndZooming:withView:atScalescrollViewWillBeginZooming:withView:因为它们在图像背景中做的东西分散了手头的问题

非常感谢您对我的支持,以及任何和所有的帮助!

关于缩放的最棘手的事情之一是它总是发生在一个叫做锚点的点附近。 我认为理解它的最好方法是想象一个坐标系统层叠在另一个上面。 说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“layer”属性访问的任何View的基础CALayer上定义的)。

此外,请参阅此处的文档,了解漂亮的图片,可能比我在这里给出的情况更好的描述:)

最后,这里是我在应用程序中使用的一段代码,用于更改图层的锚点,而不会在屏幕上移动。

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

我一直在玩你发布的代码,我想我已经发现了正在发生的事情。 当您在代码中执行块动画时:

 [UIView animateWithDuration:4.0 animations:^ { self.scrollView.zoomScale = 0.5; } completion:nil]; 

实际上动画块在动画开始时被调用。 核心动画内部处理层,使其看起来像实际移动。 开发人员中心有一个动画属性列表,其中不包含zoomScale。

当zoomScale更改时,scrollView会自动更新其contentOffset。 因此,当动画开始时,您看到的跳转是针对新的zoomScale调整的contentOffset。 然后将图层设置为适当的缩放比例,但目标contentOffset(即缩放结束时contentOffset应该是什么)已经在缩放开始时设置。

这也是为什么缩放看起来像是以屏幕上的一个点为中心的原因。 你要么必须使用setZoomScale:animated:或者自己动画图层和偏移量…

您应该使用scrollViewDidEndZooming:withView:atScale来通知您缩放何时完成。 否则我相信您将不得不删除UIScrollView属性zoomScale的使用,而是完全手动实现缩放,例如http://jonathanwatmough.com/2008/12/implementing-tap-to-zoom-in-uiscrollview-on -an-iphone / 。

当我的viewForZoomingInScrollView函数直接返回scrollview时,我有许多bug缩放。

在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更可靠。 这也有帮助,即使你有更多的计算要做…

我认为你必须在每个scroll事件上自己操作contentInset和/或contentOffset。 操纵锚点无济于事。 看看这个问题和我的答案。