20

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

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

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

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

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

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

4

7 回答 7

44

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

不过,视图缩放是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 及更高版本更改了滚动视图在缩放方面的行为。现在,aUIScrollView将尊重您应用于内容视图的身份转换,并且仅在-scrollViewDidEndZooming:withView:atScale:. 因此,上面的UIView子类代码仅对运行 iPhone OS 版本早于 3.2 的设备是必需的。

于 2009-01-16T19:29:19.393 回答
14

感谢所有先前的答案,这是我的解决方案。
实现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 的出口
  • 在之前设置maximumZoomScaleminimumZoomScale
  • 创建previousScale实例变量并将其值设置为 1.0f

完美地为我工作。

BR,尤金。

于 2011-08-02T16:33:33.377 回答
10

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

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

改变了游戏。

于 2015-06-03T06:53:00.010 回答
5

我在github.com/andreyvit/ScrollingMadness/详细讨论了 UIScrollView 缩放的工作原理(以及原因) 。

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

引用:

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

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

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

于 2009-05-07T23:04:08.860 回答
3

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

你会需要:

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

  • 一种识别可伸缩视图的方法,包括 forviewForZoomingInScrollView:和 for 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;
于 2010-11-12T05:33:14.393 回答
2

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

于 2009-07-28T03:43:41.040 回答
1

Swift 4.*Xcode 9.3

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

self.scrollView.setZoomScale(0.0, animated: true)
于 2018-06-29T05:21:45.860 回答