問題描述
執行摘要:有時 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 設置動畫時不需要的滾動的文章就介紹到這了,希望我們推薦的答案對大家有所幫助,也希望大家多多支持html5模板網!