ios UIScrollView 缩放后如何重置?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/448285/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 15:33:20  来源:igfitidea点击:

How do I reset after a UIScrollView zoom?

iphoneioscocoa-touchuiscrollviewzooming

提问by Ed Marty

I have a Graph being drawn inside a UIScrollView. It's one large UIViewusing a custom subclass of CATiledLayeras its layer.

我在一个UIScrollView. 它是一个大的UIView使用自定义子类CATiledLayer作为其层的。

When I zoom in and out of the UIScrollView, I want the graph to resize dynamically like it does when I return the graph from viewForZoomingInScrollView. However, the Graph redraws itself at the new zoom level, and I want to reset the transform scale to 1x1 so that the next time the user zooms, the transform starts from the current view. If I reset the transform to Identity in scrollViewDidEndZooming, it works in the simulator, but throws an EXC_BAD_ACCSESon the device.

当我放大和缩小 时UIScrollView,我希望图形像从viewForZoomingInScrollView. 但是,Graph 在新的缩放级别重新绘制自己,我想将变换比例重置为 1x1,以便下次用户缩放时,变换从当前视图开始。如果我将转换重置为 Identity in scrollViewDidEndZooming,它会在模拟器中工作,但会EXC_BAD_ACCSES在设备上抛出。

This doesn't even solve the issue entirely on the simulator either, because the next time the user zooms, the transform resets itself to whatever zoom level it was at, and so it looks like, if I was zoomed to 2x, for example, it's suddenly at 4x. When I finish the zoom, it ends up at the correct scale, but the actual act of zooming looks bad.

这甚至不能完全解决模拟器上的问题,因为下一次用户缩放时,变换将自身重置为它所在的任何缩放级别,因此看起来,如果我放大到 2 倍,例如,突然变成了 4 倍。当我完成缩放时,它以正确的比例结束,但实际的缩放行为看起来很糟糕。

So first: how do I allow the graph to redraw itself at the standard scale of 1x1 after zooming, and how do I have a smooth zoom throughout?

首先:如何让图形在缩放后以 1x1 的标准比例重新绘制,以及如何在整个过程中进行平滑缩放?

Edit:New findings The error seems to be "[CALayer retainCount]: message sent to deallocated instance"

编辑:新发现错误似乎是“ [CALayer retainCount]: message sent to deallocated instance

I'm never deallocating any layers myself. Before, I wasn't even deleting any views or anything. This error was being thrown on zoom and also on rotate. If I delete the object before rotation and re-add it afterward, it doesn't throw the exception. This is not an option for zooming.

我自己从不释放任何层。之前,我什至没有删除任何视图或任何内容。这个错误是在缩放和旋转时抛出的。如果我在旋转之前删除对象并在之后重新添加它,它不会抛出异常。这不是缩放选项。

回答by Brad Larson

I can't help you with the crashing, other than tell you to check and make sure you aren't unintentionally autoreleasing a view or layer somewhere within your code. I've seen the simulator handle the timing of autoreleases differently than on the device (most often when threads are involved).

除了告诉您检查并确保您不会在代码中的某处无意中自动释放视图或图层之外,我无法帮助您解决崩溃问题。我已经看到模拟器处理自动释放的时间与在设备上不同(通常是在涉及线程时)。

The view scaling is an issue with UIScrollViewI've run into, though. During a pinch-zooming event, UIScrollViewwill take the view you specified in the viewForZoomingInScrollView:delegate method and apply a transform to it. This transform provides a smooth scaling of the view without having to redraw it each frame. At the end of the zoom operation, your delegate method scrollViewDidEndZooming:withView:atScale:will be called and give you a chance to do a more high-quality rendering of your view at the new scale factor. Generally, it's suggested that you reset the transform on your view to be CGAffineTransformIdentityand then have your view manually redraw itself at the new size scale.

不过,视图缩放是UIScrollView我遇到的一个问题。在捏缩放事件期间,UIScrollView将采用您在viewForZoomingInScrollView:委托方法中指定的视图并对其应用转换。这种变换提供了视图的平滑缩放,而不必每帧重新绘制它。在缩放操作结束时,您的委托方法scrollViewDidEndZooming:withView:atScale:将被调用,并让您有机会以新的比例因子对视图进行更高质量的渲染。通常,建议您将视图上的变换重置为CGAffineTransformIdentity,然后让您的视图以新的大小比例手动重绘。

However, this causes a problem because UIScrollViewdoesn't appear to monitor the content view transform, so on the next zoom operation it sets the transform of the content view to whatever the overall scale factor is. Since you've manually redrawn your view at the last scale factor, it compounds the scaling, which is what you're seeing.

但是,这会导致一个问题,因为UIScrollView它似乎没有监视内容视图转换,因此在下一次缩放操作中,它将内容视图的转换设置为任何整体比例因子。由于您以最后一个比例因子手动重绘了视图,因此它会复合缩放,这就是您所看到的。

As a workaround, I use a UIView subclass for my content view with the following methods defined:

作为一种解决方法,我使用 UIView 子类作为我的内容视图,并定义了以下方法:

- (void)setTransformWithoutScaling:(CGAffineTransform)newTransform;
{
    [super setTransform:newTransform];
}

- (void)setTransform:(CGAffineTransform)newValue;
{
    [super setTransform:CGAffineTransformScale(newValue, 1.0f / previousScale, 1.0f / previousScale)];
}

where previousScale is a float instance variable of the view. I then implement the zooming delegate method as follows:

其中 previousScale 是视图的浮动实例变量。然后我实现缩放委托方法如下:

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale;
{
    [contentView setTransformWithoutScaling:CGAffineTransformIdentity];
// Code to manually redraw view at new scale here
    contentView.previousScale = scale;
    scrollView.contentSize = contentView.frame.size;
}

By doing this, the transforms sent to the content view are adjusted based on the scale at which the view was last redrawn. When the pinch-zooming is done, the transform is reset to a scale of 1.0 by bypassing the adjustment in the normal setTransform:method. This seems to provide the correct scaling behavior while letting you draw a crisp view at the completion of a zoom.

通过这样做,发送到内容视图的变换会根据视图上次重绘的比例进行调整。完成双指缩放后,通过绕过正常setTransform:方法中的调整,将变换重置为 1.0 的比例。这似乎提供了正确的缩放行为,同时让您在缩放完成时绘制清晰的视图。

UPDATE (7/23/2010): iPhone OS 3.2 and above have changed the behavior of scroll views in regards to zooming. Now, a UIScrollViewwill respect the identity transform you apply to a content view and only provide the relative scale factor in -scrollViewDidEndZooming:withView:atScale:. Therefore, the above code for a UIViewsubclass is only necessary for devices running iPhone OS versions older than 3.2.

更新(2010 年 7 月 23 日):iPhone OS 3.2 及更高版本更改了滚动视图在缩放方面的行为。现在, aUIScrollView将尊重您应用于内容视图的身份转换,并且仅提供-scrollViewDidEndZooming:withView:atScale:. 因此,上述UIView子类代码仅适用于运行早于 3.2 的 iPhone OS 版本的设备。

回答by dymv

Thanks to all the previous answers, and here is my solution.
Implement UIScrollViewDelegatemethods:

感谢之前的所有答案,这是我的解决方案。
实现UIScrollViewDelegate方法:

- (UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView
{
    return tmv;
}

- (void)scrollViewDidEndZooming:(UIScrollView *)scrollView withView:(UIView *)view atScale:(float)scale
{
    CGPoint contentOffset = [tmvScrollView contentOffset];   
    CGSize  contentSize   = [tmvScrollView contentSize];
     CGSize  containerSize = [tmv frame].size;

    tmvScrollView.maximumZoomScale = tmvScrollView.maximumZoomScale / scale;
    tmvScrollView.minimumZoomScale = tmvScrollView.minimumZoomScale / scale;

    previousScale *= scale;

    [tmvScrollView setZoomScale:1.0f];

    [tmvScrollView setContentOffset:contentOffset];
    [tmvScrollView setContentSize:contentSize];
    [tmv setFrame:CGRectMake(0, 0, containerSize.width, containerSize.height)];  

    [tmv reloadData];
}
  • tmvis my subclass of UIView
  • tmvScrollView— outlet to UIScrollView
  • set maximumZoomScaleand minimumZoomScalebefore
  • create previousScaleinstance variable and set its value to 1.0f
  • tmv是我的 UIView 子类
  • tmvScrollView— UIScrollView 的出口
  • 之前设置maximumZoomScaleminimumZoomScale
  • 创建previousScale实例变量并将其值设置为 1.0f

Works for me perfectly.

非常适合我。

BR, Eugene.

BR,尤金。

回答by preetam

I was just looking to reset on load to default zoom scale, because when I zoom it, scrollview is having same zoom scale for next time until it gets deallocated.

我只是想在加载时重置为默认缩放比例,因为当我缩放它时,滚动视图下次具有相同的缩放比例,直到它被解除分配。

-(void) viewWillAppear:(BOOL)animated {
      [self.scrollView setZoomScale:1.0f];
}

Changed the game.

改变了比赛。

回答by Andrey Tarantsov

I have a detailed discussion of how (and why) UIScrollView zooming works at github.com/andreyvit/ScrollingMadness/.

我在github.com/andreyvit/ScrollingMadness/详细讨论了 UIScrollView 缩放如何(以及为什么)工作。

(The link also contains a description of how to programmatically zoom UIScrollView, how to emulate Photo Library-style paging+zooming+scrolling, an example project and ZoomScrollView class that encapsulates some of the zooming magic.)

(该链接还包含对如何以编程方式缩放 UIScrollView、如何模拟照片库样式的分页+缩放+滚动、一个示例项目和封装了一些缩放魔法的 ZoomScrollView 类的描述。)

Quote:

引用:

UIScrollView does not have a notion of a “current zoom level”, because each subview it contains may have its own current zoom level. Note that there is no field in UIScrollView to keep the current zoom level. However we know that someone stores that zoom level, because if you pinch-zoom a subview, then reset its transform to CGAffineTransformIdentity, and then pinch again, you will notice that the previous zoom level of the subview has been restored.

UIScrollView 没有“当前缩放级别”的概念,因为它包含的每个子视图可能都有自己的当前缩放级别。请注意, UIScrollView 中没有字段来保持当前缩放级别。但是我们知道有人存储了那个缩放级别,因为如果你捏缩放一个子视图,然后将它的变换重置为 CGAffineTransformIdentity,然后再次捏合,你会注意到子视图的先前缩放级别已经恢复。

Indeed, if you look at the disassembly, it is UIView that stores its own zoom level (inside UIGestureInfo object pointed to by the _gestureInfo field). It also has a set of nice undocumented methods like zoomScaleand setZoomScale:animated:. (Mind you, it also has a bunch of rotation-related methods, maybe we're getting rotation gesture support some day soon.)

事实上,如果你看看反汇编,它是 UIView 存储它自己的缩放级别(在 _gestureInfo 字段指向的 UIGestureInfo 对象内)。它还具有一组不错的未记录方法,例如zoomScalesetZoomScale:animated:。(请注意,它还有一堆与旋转相关的方法,也许我们很快就会获得旋转手势支持。)

However, if we create a new UIView just for zooming and add our real zoomable view as its child, we will always start with zoom level 1.0. My implementation of programmatic zooming is based on this trick.

然而,如果我们创建一个新的 UIView 只是为了缩放并将我们真正的可缩放视图添加为它的子视图,我们将始终从缩放级别 1.0 开始。我的程序化缩放实现就是基于这个技巧。

回答by matt

A fairly reliable approach, appropriate to iOS 3.2 and 4.0 and later, is as follows. You must be prepared to supply your scalable view (the chief subview of the scroll view) in any requested scale. Then in scrollViewDidEndZooming:you will remove the blurred scale-transformed version of this view and replace it with a new view drawn to the new scale.

一种适用于 iOS 3.2 和 4.0 及更高版本的相当可靠的方法如下。您必须准备好以任何请求的比例提供可缩放视图(滚动视图的主要子视图)。然后,scrollViewDidEndZooming:您将删除此视图的模糊比例转换版本,并将其替换为绘制到新比例的新视图。

You will need:

你会需要:

  • An ivar for maintaining the previous scale; let's call it oldScale

  • A way of identifying the scalable view, both for viewForZoomingInScrollView:and for scrollViewDidEndZooming:; here, I'm going to apply a tag (999)

  • A method that creates the scalable view, tags it, and inserts it into the scroll view (here's I'll call it addNewScalableViewAtScale:). Remember, it must do everything according to scale - it sizes the view and its subviews or drawing, and sizes the scroll view's contentSize to match.

  • Constants for the minimumZoomScale and maximumZoomScale. These are needed because when we replace the scaled view by the detailed larger version, the system is going to think that the current scale is 1, so we must fool it into allowing the user to scale the view back down.

  • 用于保持先前比例的 ivar;让我们称之为 oldScale

  • 一种识别可伸缩视图的方法,包括 forviewForZoomingInScrollView:和 for scrollViewDidEndZooming:;在这里,我要应用一个标签 (999)

  • 一种创建可伸缩视图、标记它并将其插入滚动视图的方法(这里我称之为addNewScalableViewAtScale:)。请记住,它必须根据比例执行所有操作 - 它调整视图及其子视图或绘图的大小,并调整滚动视图的 contentSize 以匹配。

  • minimumZoomScale 和 maximumZoomScale 的常量。这些是必需的,因为当我们用详细的更大版本替换缩放视图时,系统会认为当前的比例是 1,所以我们必须欺骗它允许用户缩小视图。

Very well then. When you create the scroll view, you insert the scalable view at scale 1.0. This might be in your viewDidLoad, for example (svis the scroll view):

那好吧。当您创建滚动视图时,您以 1.0 的比例插入可缩放视图。viewDidLoad例如,这可能在您的 中(sv是滚动视图):

[self addNewScalableViewAtScale: 1.0];
sv.minimumZoomScale = MIN;
sv.maximumZoomScale = MAX;
self->oldScale = 1.0;
sv.delegate = self;

Then in your scrollViewDidEndZooming:you do the same thing, but compensating for the change in scale:

然后在scrollViewDidEndZooming:你做同样的事情,但补偿规模的变化:

UIView* v = [sv viewWithTag:999];
[v removeFromSuperview];
CGFloat newscale = scale * self->oldScale;
self->oldScale = newscale;
[self addNewScalableViewAtScale:newscale];
sv.minimumZoomScale = MIN / newscale;
sv.maximumZoomScale = MAX / newscale;

回答by esad

Note that the solution provided by Brad has one problem though: if you keep programmatically zooming in (let's say on double tap event) and let user manually (pinch-out) zoom out, after some time the difference between the true scale and the scale UIScrollView is tracking will grow too big, so the previousScalewill at some point fall out of float's precision which will eventually result in an unpredictable behaviour

请注意,Brad 提供的解决方案有一个问题:如果您继续以编程方式放大(比方说双击事件)并让用户手动(放大)缩小,一段时间后真实比例和比例之间的差异UIScrollView 正在跟踪会变得太大,所以previousScale在某些时候会超出浮动的精度,这最终会导致不可预测的行为

回答by Prashant Gaikwad

Swift 4.*and Xcode 9.3

Swift 4.*Xcode 9.3

Setting scrollView zoom scale to 0 will reset your scrollView to it's initial state.

将 scrollView 缩放比例设置为 0 会将您的 scrollView 重置为其初始状态。

self.scrollView.setZoomScale(0.0, animated: true)