如何在UIScrollView缩放后进行重置?

21

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

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

这甚至在模拟器上也无法完全解决问题,因为下次用户缩放时,变换会重置为任何缩放级别,因此,例如,如果我缩放到2倍,则会突然变成4倍。当我完成缩放时,它最终会达到正确的比例,但缩放的实际过程看起来很糟糕。

因此,首先:如何允许图表在缩放后以标准比例1x1重新绘制自身,并如何进行平滑的缩放?

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

我从未自己释放任何层。 以前,我甚至没有删除任何视图或其他内容。这个错误在缩放和旋转时被抛出。如果我在旋转之前删除对象并在之后重新添加它,则不会引发异常。但对于缩放来说,这不是一个选择。

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。这似乎提供了正确的缩放行为,同时让您在缩放完成时绘制清晰的视图。

更新(7/23/2010):iPhone OS 3.2及以上版本已更改滚动视图在缩放方面的行为。现在,UIScrollView会尊重您对内容视图应用的恒等变换,并且仅在-scrollViewDidEndZooming:withView:atScale:中提供相对比例因子。因此,上述代码仅适用于运行早于3.2版本的iPhone OS设备的UIView子类。


1
你没有提供太多信息,但如果你没有手动隐藏或从层次结构中移除内容视图,我的猜测是它变得比GPU支持的纹理尺寸更大。如果视图变得比2048像素更宽或更高,则无法在iPhone和iPhone 3G上呈现(我不能代表3G S)。 - Brad Larson
@Brad Larson,即使在> 3.2版本中,您也应该“在视图上进行CGAffineTransformIdentity转换,然后手动重新绘制视图以适应新的大小比例”,对吗?否则,您的视图会变得模糊(对于文本)。 - Dan Rosenstark
@Yar - 是的,身份转换仍然需要应用,并重新渲染内容视图以进行清晰显示。早期的问题是UIScrollView忽略了当您重置内容视图上的变换时,并继续尝试基于绝对比例因子对其进行变换,因此我对此进行了解决。这在OS 3.2+中已得到纠正。 - Brad Larson
非常感谢@Brad Larson。我在http://stackoverflow.com/questions/3322547上提出了另一个问题,这就是我正在努力解决的问题。我已经取得了相当大的进展,这可能意味着我完全走错了路 :) - Dan Rosenstark
@BradLarson,请问您能帮我解决这个问题吗?http://stackoverflow.com/q/19856928/935381 - Hitarth
显示剩余5条评论

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的 outlet
  • 在创建之前设置maximumZoomScaleminimumZoomScale
  • 创建previousScale实例变量并将其值设置为1.0f

对我完美地工作。

BR,Eugene。


13
我只是想在加载时将缩放比例重置为默认值,因为当我缩放它时,scrollview在下一次具有相同的缩放比例,直到它被释放。
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];
}

改变了游戏。


5
我在github.com/andreyvit/ScrollingMadness/上详细讨论了UIScrollView缩放的工作方式和原因。

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

引用:

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

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

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


zoomScale是UIScrollView的一部分,而不是UIView。 - Jonny
1
我想你已经移动了你的滚动疯狂内容了?你能否更新链接(我还没有找到新的位置)。 - Benjohn
1
@Benjohn 嘿,它在好几年前就已经过时了。但现在你可以在UIScrollView上重置zoomScale,所以这个技巧不再需要(自iOS 3或4以来)。 - Andrey Tarantsov

4

Swift 4.*Xcode 9.3

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

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

你是不是想要设置为1.0呢? self.scrollViewForZoom.setZoomScale(1.0, animated: true) - iOS Flow
不,如果您想放大,请使用 self.scrollView.zoom(to: rectTozoom, animated: true),如果要缩小,则使用 self.scrollView.setZoomScale(0.0, animated: true)。 - Prashant Gaikwad

3
一个相当可靠的方法,适用于iOS 3.2和4.0及更高版本,如下所示。您必须准备好在任何请求的比例下提供可缩放视图(滚动视图的主要子视图)。然后在scrollViewDidEndZooming:中,您将删除此视图的模糊比例转换版本,并用绘制到新比例的新视图替换它。
你需要:
  • 一个实例变量来维护先前的比例; 让我们称之为oldScale

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

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

  • 最小缩放比例和最大缩放比例的常量。这是必需的,因为当我们用详细的较大版本替换缩放视图时,系统会认为当前比例为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;

2
请注意,Brad提供的解决方案有一个问题:如果您在编程缩放时(例如,在双击事件上),并允许用户手动缩小,那么经过一段时间后,真实比例和UIScrollView正在跟踪的比例之间的差异将会变得太大,因此previousScale最终将超出浮点精度范围,这将导致不可预测的行为。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接