我在UIScrollView
内绘制了一个图表。它是使用UIView
的自定义子类作为其图层的一个大CATiledLayer
。
当我放大和缩小UIScrollView
时,我希望图表能够像我从viewForZoomingInScrollView
返回图表时那样动态调整大小。但是,Graph在新的缩放级别重绘自己,我想将变换比例重置为1x1,以便下次用户缩放时,变换从当前视图开始。如果我在scrollViewDidEndZooming
中将转换重置为Identity,则它在模拟器中工作,但会在设备上抛出EXC_BAD_ACCSES
。
这甚至都没有在模拟器上完全解决问题,因为下次用户放大时,变换会将自身重置为它所处的任何缩放级别,所以看起来如果我缩放到2x,例如,它突然达到了4倍。当我完成缩放时,它以正确的比例结束,但实际的缩放行为看起来很糟糕。
首先:如何让图形在缩放后以1x1的标准比例重绘自己,如何在整个过程中进行平滑缩放?
编辑:新发现
错误似乎是“[CALayer retainCount]: message sent to deallocated instance
”
我自己永远不会释放任何图层。在此之前,我甚至没有删除任何观点或任何内容。在缩放和旋转时抛出此错误。如果我在旋转之前删除对象并在之后重新添加它,它不会抛出异常。这不是缩放的选项。
答案 0 :(得分:41)
我无法帮助您解决崩溃问题,除了告诉您检查并确保您无意中在代码中的某个位置无意中自动释放视图或图层。我已经看到模拟器处理自动释放的时间与设备上的不同(最常见的是涉及线程时)。
视图缩放是我遇到的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是视图的float实例变量。然后我按如下方式实现缩放委托方法:
- (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
子类的上述代码仅对运行早于3.2的iPhone OS版本的设备是必需的。
答案 1 :(得分:13)
感谢以前的所有答案,这是我的解决方案 实施 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];
}
完美适合我。
BR,Eugene。
答案 2 :(得分:7)
我只是想将加载重置为默认缩放比例,因为当我缩放它时,scrollview下次具有相同的缩放比例,直到它被取消分配。
-(void) viewWillAppear:(BOOL)animated {
[self.scrollView setZoomScale:1.0f];
}
改变了比赛。
答案 3 :(得分:5)
我详细讨论了UIScrollView缩放在github.com/andreyvit/ScrollingMadness/的工作原理(以及原因)。
(将链接还包含了如何编程方式放大UIScrollView的描述,如何效仿图片库式传呼+变焦+滚动,实例项目和ZoomScrollView类,封装了一些神奇的放大。)
引用:
UIScrollView没有“当前缩放级别”的概念,因为它包含的每个子视图可能都有自己的当前缩放级别。请注意,UIScrollView中没有字段来保持当前缩放级别。但是我们知道有人存储缩放级别,因为如果你捏缩放子视图,然后将其变换重置为CGAffineTransformIdentity,然后再次捏,你会注意到子视图的先前缩放级别已经恢复。
实际上,如果你看一下反汇编,UIView就会存储它自己的缩放级别(在_gestureInfo字段指向的UIGestureInfo对象中)。它还有一组很好的未记录方法,如zoomScale
和setZoomScale:animated:
。 (请注意,它也有一堆与旋转相关的方法,也许我们很快就会获得旋转手势支持。)
但是,如果我们仅为缩放创建一个新的UIView并将我们的真实可缩放视图添加为其子视图,我们将始终以缩放级别1.0开始。我对程序化缩放的实现基于这个技巧。
答案 4 :(得分:3)
适用于iOS 3.2和4.0及更高版本的相当可靠的方法如下。您必须准备好以任何请求的比例提供可缩放视图(滚动视图的主要子视图)。然后在scrollViewDidEndZooming:
中,您将删除此视图的模糊缩放转换版本,并将其替换为绘制到新比例的新视图。
您将需要:
用于维持以前规模的ivar;我们称之为oldScale
一种识别viewForZoomingInScrollView:
和scrollViewDidEndZooming:
的可缩放视图的方法;在这里,我将应用一个标签(999)
创建可伸缩视图,标记它并将其插入滚动视图的方法(这里我称之为addNewScalableViewAtScale:
)。请记住,它必须根据比例执行所有操作 - 它会调整视图及其子视图或绘图的大小,并调整滚动视图的contentSize的大小以匹配。
minimumZoomScale和maximumZoomScale的常量。这些是必需的,因为当我们用详细的较大版本替换缩放视图时,系统会认为当前比例为1,因此我们必须欺骗它以允许用户缩小视图。
< / LI>很好。创建滚动视图时,可以按比例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;
答案 5 :(得分:2)
请注意,Brad提供的解决方案有一个问题:如果你继续以编程方式放大(让我们说双击事件)并让用户手动(捏合)缩小,经过一段时间后真实比例之间的差异并且UIScrollView跟踪的比例将变得太大,因此previousScale
在某些时候会失去浮动的精度,最终会导致不可预测的行为
答案 6 :(得分:0)
Swift 4。* 和 Xcode 9.3
将scrollView缩放比例设置为0会将scrollView重置为其初始状态。
self.scrollView.setZoomScale(0.0, animated: true)