问题描述
执行摘要:有时 UIScrollView
会对 contentOffset
的值进行不必要的更改,从而导致应用程序在正在查看的文档中显示错误的位置.不需要的更改与滚动视图的 zoomScale
的动画更改一起发生.
详情:在 UIScrollView
中使用 CATiledLayer
缩小时遇到问题.CATiledLayer
保存一个pdf,当 contentOffset
在一定范围内时,当我缩小时, contentOffset
会改变(这是错误)在缩放发生之前.contentOffset
似乎在 Apple 的代码中有所更改.
为了说明问题,我修改了 Apple 的示例应用程序 ZoomingPDFViewer.代码在github上:https://github.com/DirkMaas/ZoomingPDFViewer-bugp>
点击将导致 zoomScale
使用 animateWithDuration
更改为 0.5,从而缩小.如果 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 中的代码包括这些更改):
- 从下载 ZoomingPDFViewerhttp://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:
因为它们在图像背景中做的事情会分散手头的问题
非常感谢您对我的包容,以及任何和所有的帮助!
关于缩放的最棘手的事情之一是它总是发生在一个称为锚点的点周围.我认为理解它的最好方法是想象一个坐标系叠加在另一个坐标系之上.假设 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 上定义).
另外,请参阅文档 这里 对于漂亮的图片,可能比我在这里给出的情况描述要好得多:)
最后,这是我在应用程序中使用的一段代码,用于更改图层的锚点而不使其在屏幕上移动.
-(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);返回偏移量;}
Executive Summary:
At times UIScrollView
makes an unwanted change to the value of contentOffset
, thus causing the app to display the wrong location in the document being viewed. The unwanted change happens in conjunction to an animated change to the scroll view's zoomScale
.
The Details:
I'm having trouble when zooming out with CATiledLayer
in a UIScrollView
. The CATiledLayer
holds a pdf, and when contentOffset
is within a certain range, when I zoom out, the contentOffset
is changed (that's the bug) before the zooming occurs. The contentOffset
seems to be changed in Apple's code.
To illustrate the problem, I modified Apple's sample app, ZoomingPDFViewer. The code is on github: https://github.com/DirkMaas/ZoomingPDFViewer-bug
A tap will cause zoomScale
to be changed to 0.5, using animateWithDuration
, thus zooming out. If the UIScrollView
's contentOffset.y
is less than about 2700 or greater than 5900, the zoomScale
animation works fine. If the tap happens when contentOffset.y
is between those two values, the contentOffset.y
will jump (not animated) to about 2700, and then the zoomScale
animation will occur, but scrolling will occur at the same time, so that when the animation is done, the contentOffset.y
is where it should be. But where does the jump come from?
For example, say the contentOffset.y
is 2000 when the screen is tapped: the zoomScale
animation works just fine; contentOffset.y
is not changed.
But if the contentOffset.y
is 4000 when the screen is tapped: the contentOffset.y
will jump, without animation, to about 2700, and then zooming and scrolling will begin from that point and occur at the same time. When the animation is done, it looks as if we zoomed straight back from 4000, so we end up in the right place, but the behavior is wrong.
A note on the UI:
- the text can be scrolled vertically in the normal way
- the text can be zoomed in and out by pinching in the normal way
- a single tap will cause the
zoomScale
to be set to 0.5; the change is animated
I've noticed that if zoomScale
is greater than 0.5, the jump is not so big. Also, if I use setZoomScale:animated:
instead of animateWithDuration
, the bug disappears, but I can't use it because I need to chain animations.
Here is a summary of what I did (the code in github includes these changes):
- Downloaded ZoomingPDFViewer from http://developer.apple.com/library/ios/#samplecode/ZoomingPDFViewer/Introduction/Intro.html and opened it in XCode
- Changed Build Settings | Architectures | Base SDK to Latest iOS (iOS 4.3) changed Build Settings | GCC 4.2 - Language | Compile Sources As to Objective-C++
- removed TestPage.pdf from the project
- added "whoiam 5 24 cropped 3-2.pdf" to the project in its place
- added
PDFScrollView *scrollView;
toZoomingPDFViewerViewController
class - changed
loadView
inZoomingPDFViewerViewController
to initializescrollView
instead ofsv
- added
viewDidLoad
,handleTapFrom:recognizer
andzoomOut
toZoomingPDFViewerViewController
in PDFScrollview.m - commented out
scrollViewDidEndZooming:withView:atScale
andscrollViewWillBeginZooming:withView:
because they do stuff in the image background that distracts from the issue at hand
Thanks so much for bearing with me, and any and all help!
One of the trickiest things to understand about zooming is that it always happens around a point called the Anchor Point. I think the best way to understand it is to imagine one coordinate system layered on top of another. Say A is your outer coordinate system, B is the inner (B will be the scrollview). When the offset of B is (0,0) and the scale is 1.0, then the point B(0,0) corresponds to A(0,0), and in general B(x,y) = A(x,y).
Further, if the offset of B is (xOff, yOff) then B(x,y) = A(x - xOff, y - yOff). Again, this is still assuming zoom scale is 1.0.
Now, let the offset be (0,0) again and imagine what happens when you try to zoom. There must be a point on the screen that doesn't move when you zoom, and every other point moves outward from that point. That is what the anchor point defines. If your anchor is (0,0) then the bottom left point will remain fixed while all other points move up and to the right. In this case the offset remains the same.
If your anchor point is (0.5, 0.5) (the anchor point is normalized, i.e. 0 to 1, so 0.5 is half way across), then the center point stays fixed while all other points move outward. This means the offset has to change to reflect that. If it's on an iPhone in portrait mode and you zoom to scale 2.0, the anchor point x value will move half the screen width, 320/2 = 160.
The actual position of the scroll views content view on screen is defined by BOTH the offset and the anchor point. So, if you simply change the layers anchor point underneath without making a corresponding change to the offset, you will see the view appear to jump to a different location, even though the offset is the same.
I am guessing that this is the underlying problem here. When you are animating the zoom, Core Animation must be picking a new anchor point so the zooming "looks" right. This will also change the offset so that the views actual visible region on screen doesn't jump. Try logging the location of the anchor point at various times throughout this process (it is defined on the underlying CALayer of any View which you access with a Views "layer" property).
Also, please see the documentation here for pretty pictures and likely a far better description of the situation than I've given here :)
Finally, here is a snippet of code I used in an app to change the anchor point of a layer without it moving on screen.
-(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;
}
这篇关于在 UIScrollView 中为 zoomScale 设置动画时不需要的滚动的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!