简体   繁体   English

UIScrollView缩放后如何重置?

[英]How do I reset after a UIScrollView zoom?

I have a Graph being drawn inside a UIScrollView . 我在UIScrollView中绘制了一个Graph。 It's one large UIView using a custom subclass of CATiledLayer as its layer. 它是一个使用CATiledLayer的自定义子类作为其层的大型UIView

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 . 当我放大和缩小UIScrollView ,我希望图形能够像从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. 但是,Graph在新的缩放级别重绘自己,我想将变换比例重置为1x1,以便下次用户缩放时,变换从当前视图开始。 If I reset the transform to Identity in scrollViewDidEndZooming , it works in the simulator, but throws an EXC_BAD_ACCSES on the device. 如果我在scrollViewDidEndZooming中将转换重置为Identity,它可以在模拟器中工作,但会在设备上抛出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. 这甚至都没有在模拟器上完全解决问题,因为下次用户放大时,变换将自身重置为它所处的任何缩放级别,因此看起来,如果我缩放到2x,例如,它突然达到4倍。 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? 首先:如何让图形在缩放后以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. 这不是缩放的选项。

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. 但是,视图缩放是我遇到的UIScrollView一个问题。 During a pinch-zooming event, UIScrollView will take the view you specified in the viewForZoomingInScrollView: delegate method and apply a transform to it. 在缩放缩放事件期间, UIScrollView将采用您在viewForZoomingInScrollView: delegate方法中指定的视图并对其应用变换。 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. 在缩放操作结束时,将调用您的委托方法scrollViewDidEndZooming:withView:atScale:并使您有机会以新的比例因子对视图进行更高质量的渲染。 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. 通常,建议您将视图上的变换重置为CGAffineTransformIdentity ,然后让您的视图以新的大小比例手动重绘。

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. 但是,这会导致问题,因为UIScrollView似乎不会监视内容视图转换,因此在下一个缩放操作中,它会将内容视图的转换设置为整体比例因子。 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: 作为一种解决方法,我使用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. 其中previousScale是视图的float实例变量。 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. 当完成缩放变焦时,通过绕过正常setTransform:方法中的调整,将变换重置为1.0。 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. 更新(2010年7月23日):iPhone OS 3.2及更高版本更改了滚动视图在缩放方面的行为。 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: . 现在, UIScrollView将尊重您应用于内容视图的身份转换,并仅在-scrollViewDidEndZooming:withView:atScale:提供相对比例因子-scrollViewDidEndZooming:withView:atScale: . Therefore, the above code for a UIView subclass is only necessary for devices running iPhone OS versions older than 3.2. 因此, UIView子类的上述代码仅对运行早于3.2的iPhone OS版本的设备是必需的。

Thanks to all the previous answers, and here is my solution. 感谢以前的所有答案,这是我的解决方案。
Implement UIScrollViewDelegate methods: 实现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 is my subclass of UIView tmv是我的UIView的子类
  • tmvScrollView — outlet to UIScrollView tmvScrollView - UIScrollView的出口
  • set maximumZoomScale and minimumZoomScale before 之前设置maximumZoomScaleminimumZoomScale
  • create previousScale instance variable and set its value to 1.0f 创建previousScale实例变量并将其值设置为1.0f

Works for me perfectly. 完美适合我。

BR, Eugene. BR,Eugene。

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. 我只是想将加载重置为默认缩放比例,因为当我缩放它时,scrollview下次具有相同的缩放比例,直到它被取消分配。

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

Changed the game. 改变了游戏。

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. UIScrollView没有“当前缩放级别”的概念,因为它包含的每个子视图可能有自己的当前缩放级别。 Note that there is no field in UIScrollView to keep the current zoom level. 请注意,UIScrollView中没有字段来保持当前缩放级别。 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. 但是我们知道有人存储缩放级别,因为如果你捏缩放子视图,然后将其变换重置为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). 实际上,如果你看一下反汇编,UIView就会存储它自己的缩放级别(在_gestureInfo字段指向的UIGestureInfo对象中)。 It also has a set of nice undocumented methods like zoomScale and setZoomScale:animated: . 它还有一组很好的未记录的方法,如zoomScalesetZoomScale: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. 但是,如果我们创建一个仅用于缩放的新UIView并将我们的真实可缩放视图添加为其子视图,我们将始终以缩放级别1.0开始。 My implementation of programmatic zooming is based on this trick. 我对程序化缩放的实现基于这个技巧。

A fairly reliable approach, appropriate to iOS 3.2 and 4.0 and later, is as follows. 适用于iOS 3.2和4.0及更高版本的相当可靠的方法如下。 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. 然后在scrollViewDidEndZooming:您将删除此视图的模糊缩放转换版本,并将其替换为绘制到新比例的新视图。

You will need: 你会需要:

  • An ivar for maintaining the previous scale; 用于维持以前规模的ivar; let's call it oldScale 我们称之为oldScale

  • A way of identifying the scalable view, both for viewForZoomingInScrollView: and for scrollViewDidEndZooming: ; 一种识别可伸缩视图的方法,包括viewForZoomingInScrollView:scrollViewDidEndZooming: ; here, I'm going to apply a tag (999) 在这里,我要申请一个标签(999)

  • A method that creates the scalable view, tags it, and inserts it into the scroll view (here's I'll call it addNewScalableViewAtScale: ). 一种创建可伸缩视图,标记它并将其插入滚动视图的方法(这里我称之为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. 请记住,它必须根据比例执行所有操作 - 它会调整视图及其子视图或绘图的大小,并调整滚动视图的contentSize的大小以进行匹配。

  • Constants for the minimumZoomScale and maximumZoomScale. minimumZoomScale和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. 这些是必需的,因为当我们用详细的较大版本替换缩放视图时,系统会认为当前比例为1,因此我们必须欺骗它以允许用户缩小视图。

Very well then. 那好吧。 When you create the scroll view, you insert the scalable view at scale 1.0. 创建滚动视图时,可以按比例1.0插入可缩放视图。 This might be in your viewDidLoad , for example ( sv is the scroll view): 这可能在你的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;

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

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)

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM