UIScrollView 缩放后如何重置?

发布于 2024-07-13 02:29:58 字数 760 浏览 6 评论 0原文

我在 UIScrollView 内绘制了一个图表。 它是一个大型 UIView,使用 CATiledLayer 的自定义子类作为其层。

当我放大和缩小 UIScrollView 时,我希望图形能够动态调整大小,就像我从 viewForZoomingInScrollView 返回图形时一样。 但是,图形会在新的缩放级别重新绘制自身,并且我想将变换比例重置为 1x1,以便下次用户缩放时,变换从当前视图开始。 如果我在 scrollViewDidEndZooming 中将转换重置为 Identity,它可以在模拟器中工作,但会在设备上抛出 EXC_BAD_ACCSES

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

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

编辑:新发现 错误似乎是“[CALayer keepCount]:消息发送到已释放的实例

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

I have a Graph being drawn inside a UIScrollView. It's one large UIView using a custom subclass of CATiledLayer as its layer.

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_ACCSES on the device.

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.

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?

Edit: New findings
The error seems to be "[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.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

ゝ杯具 2024-07-20 02:29:58

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

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

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

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

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

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

其中 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;
}

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

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

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 UIScrollView I've run into, though. During a pinch-zooming event, UIScrollView will 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 CGAffineTransformIdentity and then have your view manually redraw itself at the new size scale.

However, this causes a problem because UIScrollView doesn'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.

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

- (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:

- (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.

UPDATE (7/23/2010): iPhone OS 3.2 and above have changed the behavior of scroll views in regards to zooming. Now, a UIScrollView will 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 UIView subclass is only necessary for devices running iPhone OS versions older than 3.2.

殊姿 2024-07-20 02:29:58

感谢之前的所有回答,这是我的解决方案。
实现 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];
}
  • tmv 是 UIView tmvScrollView 的子类
  • - UIScrollView
  • 设置 ma​​ximumZoomScaleminimumZoomScale 的 出口
  • 创建 previousScale 实例变量并将其值设置为 1.0f

之前对我来说非常适合。

BR,尤金。

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

- (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];
}
  • tmv is my subclass of UIView
  • tmvScrollView — outlet to UIScrollView
  • set maximumZoomScale and minimumZoomScale before
  • create previousScale instance variable and set its value to 1.0f

Works for me perfectly.

BR, Eugene.

私藏温柔 2024-07-20 02:29:58

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

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.scrollView.setZoomScale(1.0, animated: false)
}

或者

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

改变了游戏。

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.

override func viewWillAppear(_ animated: Bool) {
    super.viewWillAppear(animated)
    self.scrollView.setZoomScale(1.0, animated: false)
}

OR

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

Changed the game.

标点 2024-07-20 02:29:58

我在 github.com/andreyvit/ScrollingMadness/

项目和封装了一些缩放魔法的 ZoomScrollView 类的描述。

(该链接还包含如何以编程方式缩放 UIScrollView、如何模拟照片库样式的分页+缩放+滚动、示例

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

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

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

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

(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.)

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.

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 zoomScale and setZoomScale:animated:. (Mind you, it also has a bunch of rotation-related methods, maybe we're getting rotation gesture support some day soon.)

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.

雪花飘飘的天空 2024-07-20 02:29:58

Swift 4.*Xcode 9.3

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

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

Swift 4.* and Xcode 9.3

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

self.scrollView.setZoomScale(0.0, animated: true)
风月客 2024-07-20 02:29:58

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

您将需要:

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

  • 一种识别可缩放视图的方法,适用于 viewForZoomingInScrollView:scrollViewDidEndZooming:; 在这里,我将应用一个标签 (999)

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

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

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

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

然后在您的 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;

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.

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.

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 (sv is the scroll view):

[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:

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;
桃扇骨 2024-07-20 02:29:58

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

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 previousScale will at some point fall out of float's precision which will eventually result in an unpredictable behaviour

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文