UIScrollView 缩小带有 -ve 原点的视图 [英] UIScrollView zooming out of a view with a -ve origin

查看:44
本文介绍了UIScrollView 缩小带有 -ve 原点的视图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个 UIScrollView.在这个我有一个 UIView,它有一个负原点的框架 - 我需要限制滚动视图,这样你就不能滚动整个视图..

I have a UIScrollView. In this I have a UIView which has a frame with a negative origin - I need to limit the scroll view so that you can't scroll around the entire view..

我在这个滚动视图中实现了缩放.

I have implemented Zoom in this scrollview.

缩放时,滚动视图将根据比例调整可缩放视图的大小.但它不会调整原点.

When Zooming the Scroll view will adjust the size of the Zoomable view according to the scale. BUT IT DOES NOT ADJUST THE ORIGIN.

因此,如果我有一个框架为 {0, -500}, {1000, 1000}

So if I have a view with a frame of {0, -500}, {1000, 1000}

我缩小到 0.5 的比例,这会给我一个 {0, -500}, {500, 500}

The I zoom out to a scale of 0.5, this will give me a new frame of {0, -500}, {500, 500}

显然这不好,整个视图都缩小了滚动视图.我希望框架为 {0, -250}, {500, 500}

Clearly this is not good, the entire view is zoomed out of the scrollview. I want the frame to be {0, -250}, {500, 500}

我可以通过正确调整原点来修复 scrollViewDidZoom 方法中的一些问题.. 这确实有效,但缩放不平滑.. 在此处更改原点会导致它跳跃.

I can fix things a bit in the scrollViewDidZoom method by adjusting the origin correctly.. This does work, but the zoom is not smooth.. Changing the origin here causes it to jump.

我在 UIView 的文档中注意到它说(关于框架属性):

I notice in the documentation for UIView it says (regarding the frame property):

警告:如果转换属性不是恒等转换,则此属性的值未定义,因此应忽略.

Warning: If the transform property is not the identity transform, the value of this property is undefined and therefore should be ignored.

不太清楚为什么会这样.

Not quite sure why that is.

我处理这个问题错了吗?修复它的最佳方法是什么?

Am I approaching this problem wrong? What is the best way to fix it?

谢谢

以下是我正在使用的测试应用程序的一些源代码:

Below is some source code from the test app I am using:

在视图控制器中..

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.bigView = [[BigView alloc] initWithFrame: CGRectMake(0, -400, 1000, 1000)];

    [self.bigScroll addSubview: bigView];
    self.bigScroll.delegate = self;
    self.bigScroll.minimumZoomScale = 0.2;
    self.bigScroll.maximumZoomScale = 5;
    self.bigScroll.contentSize = bigView.bounds.size;
}

-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView  {
    return bigView;
}

- (void)scrollViewDidZoom:(UIScrollView *)scrollView {    
//    bigView.frame = CGRectMake(0, -400 * scrollView.zoomScale,
//                               bigView.frame.size.width, bigView.frame.size.height);

    bigView.center = CGPointMake(500 * scrollView.zoomScale, 100 * scrollView.zoomScale);
}

然后在视图中...

- (void)drawRect:(CGRect)rect
{
    // Drawing code
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGContextSetFillColorWithColor(ctx, [UIColor whiteColor].CGColor);
    CGContextSetStrokeColorWithColor(ctx, [UIColor whiteColor].CGColor);
    CGContextFillRect(ctx, CGRectMake(100, 500, 10, 10));

    for (int i = 0; i < 1000; i += 100) {
        CGContextStrokeRect(ctx, CGRectMake(0, i, 1000, 3));        
    }
}

请注意,此处的跳跃在较大的缩放比例下更为明显.在我的真实应用程序中,在任何时候都有更多的绘图和处理在跳跃中更加明显.

Note that here the jumpiness is more apparent at larger zoom scales. In my real app where there is much more drawing and processing going on the jump is more apparent at all times.

推荐答案

您不必使用 frame 属性 - 并且不应该,鉴于 Apple 非常坚定的警告.在这种情况下,您通常可以使用 boundscenter 来实现您的结果.

You don't have to use the frame property - and should not, given Apple's very firm warning. In such cases you can usually use bounds and center to achieve your result.

在您的情况下,您可以忽略子视图的所有属性.假设您的子视图是 viewForZoomingInScrollView,您可以使用 scrollView 的 contentOffsetzoomScale 属性

In your case you can ignore all of the subview's properties. Assuming that your subview is the viewForZoomingInScrollView you can use the scrollView's contentOffset and zoomScale properties

- (void) setMinOffsets:(UIScrollView*)scrollView
    {
        CGFloat minOffsetX = MIN_OFFSET_X*scrollView.zoomScale;
        CGFloat minOffsetY = MIN_OFFSET_Y*scrollView.zoomScale;

        if ( scrollView.contentOffset.x < minOffsetX
          || scrollView.contentOffset.y < minOffsetY ) {

            CGFloat offsetX = (scrollView.contentOffset.x > minOffsetX)?
                               scrollView.contentOffset.x : minOffsetX;

            CGFloat offsetY = (scrollView.contentOffset.y > minOffsetY)?
                               scrollView.contentOffset.y : minOffsetY;

            scrollView.contentOffset = CGPointMake(offsetX, offsetY);
        }
    }

从您的 scrollView 委托中的 scrollViewDidScrollscrollViewDidZoom 调用它.这应该可以顺利运行,但如果您有疑问,您也可以通过子类化 scrollView 并使用 layoutSubviews 调用它来实现它.在他们的 PhotoScroller 示例中,Apple 通过覆盖 layoutSubviews 将 scrollView 的内容居中 - 尽管令人发疯的是他们忽略了自己的警告并调整子视图的 frame 属性来实现这一点.

Call it from both scrollViewDidScroll and scrollViewDidZoom in your scrollView delegate. This should work smoothly, but if you have doubts you can also implement it by subclassing the scrollView and invoking it with layoutSubviews. In their PhotoScroller example, Apple centers a scrollView's content by overriding layoutSubviews - although maddeningly they ignore their own warnings and adjust the subview's frame property to achieve this.

更新

上述方法消除了滚动视图达到其限制时的反弹".如果你想保留反弹,你可以直接改变视图的中心属性:

The above method eliminates the 'bounce' as the scrollView hits it's limits. If you want to retain the bounce, you can directly alter the view's center property instead:

- (void) setViewCenter:(UIScrollView*)scrollView
    {
        UIView* view = [scrollView subviews][0];
        CGFloat centerX = view.bounds.size.width/2-MIN_OFFSET_X;
        CGFloat centerY = view.bounds.size.height/2-MIN_OFFSET_Y;

        centerX *=scrollView.zoomScale;
        centerY *=scrollView.zoomScale;

        view.center = CGPointMake(centerX, centerY);
    }

更新 2

从您更新的问题(带代码)来看,我可以看到这些解决方案都不能解决您的问题.似乎正在发生的事情是,您的偏移量越大,缩放运动变得越急促.偏移 100 点时,动作仍然非常流畅,但是偏移 500 点时,它的粗糙程度令人无法接受.这部分与您的 drawRect 例程有关,部分与在 scrollView 中进行的(过多)重新计算以显示正确的内容有关.所以我有另一个解决方案......

From your updated question (with code), I can see that neither of these solutions fix you problem. What seems to be happening is that the greater you make your offset, the jerkier the zoom movement becomes. With an offset of 100points the action is still quite smooth, but with an offset of 500points, it is unacceptably rough. This is partly related to your drawRect routine, and partly related to (too much) recalculation going on in the scrollView to display the right content. So I have another solution…

在您的 viewController 中,将您的 customView 的边界/框架原点设置为正常 (0,0).我们将使用图层来偏移内容.您需要将 QuartzCore 框架添加到您的项目中,并将其#import 到您的自定义视图中.

In your viewController, set your customView's bounds/frame origin to the normal (0,0). We will offset the content using layers instead. You will need to add the QuartzCore framework to your project, and #import it into your custom view.

在自定义视图中初始化两个 CAShapeLayers - 一个用于框,另一个用于线条.如果它们共享相同的填充和描边,你只需要一个 CAShapeLayer(在这个例子中,我改变了你的填充和描边颜色).每个 CAShapeLayer 都带有它自己的 CGContext,你可以用颜色、线宽等每层初始化一次.

In the custom view initialise two CAShapeLayers - one for the box, the other for the lines. If they share the same fill and stroke you would only need one CAShapeLayer (for this example I changed your fill and stroke colors). Each CAShapeLayer comes with it's own CGContext, which you can initialise once per layer with colors, linewidths etc. Then to make a CAShapelayer do it's drawing all you have to do is set it's path property with a CGPath.

#import "CustomView.h"
#import <QuartzCore/QuartzCore.h>

@interface CustomView()
@property (nonatomic, strong) CAShapeLayer* shapeLayer1;
@property (nonatomic, strong) CAShapeLayer* shapeLayer2;
@end

@implementation CustomView

    #define MIN_OFFSET_X 100
    #define MIN_OFFSET_Y 500

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self initialiseLayers];
    }
    return self;
}


- (void) initialiseLayers
{
    CGRect layerBounds  = CGRectMake( MIN_OFFSET_X,MIN_OFFSET_Y
                          , self.bounds.size.width + MIN_OFFSET_X
                          , self.bounds.size.height+ MIN_OFFSET_Y);

    self.shapeLayer1 = [[CAShapeLayer alloc] init];
    [self.shapeLayer1 setFillColor:[UIColor clearColor].CGColor];
    [self.shapeLayer1 setStrokeColor:[UIColor yellowColor].CGColor];
    [self.shapeLayer1 setLineWidth:1.0f];
    [self.shapeLayer1 setOpacity:1.0f];

    self.shapeLayer1.anchorPoint = CGPointMake(0, 0);
    self.shapeLayer1.bounds = layerBounds;
    [self.layer addSublayer:self.shapeLayer1];

设置界限是关键.与裁剪其子视图的视图不同,CALayers 将绘制超出其超级层的边界.您将开始在视图顶部上方绘制 MIN_OFFSET_Y 点,并在左侧绘制 MIN_OFFSET_X 点.这允许您在 scrollView 的内容视图之外绘制内容,而无需 scrollView 做任何额外的工作.

Setting the bounds is the critical bit. Unlike views, which clip their subviews, CALayers will draw beyond the bounds of their superlayer. You are going to start drawing MIN_OFFSET_Y points above the top of your View and MIN_OFFSET_X to the left. This allows you to draw content beyond your scrollView's content view without the scrollView having to do any extra work.

与视图不同,超级图层不会自动裁剪位于其边界矩形之外的子图层的内容.取而代之的是,默认情况下,超级图层允许完整显示其子图层.
(Apple 文档,构建层层次结构)

    self.shapeLayer2 = [[CAShapeLayer alloc] init];

    [self.shapeLayer2 setFillColor:[UIColor blueColor].CGColor];
    [self.shapeLayer2 setStrokeColor:[UIColor clearColor].CGColor];
    [self.shapeLayer2 setLineWidth:0.0f];
    [self.shapeLayer2 setOpacity:1.0f];

    self.shapeLayer2.anchorPoint = CGPointMake(0, 0);
    self.shapeLayer2.bounds = layerBounds;
    [self.layer addSublayer:self.shapeLayer2];

    [self drawIntoLayer1];
    [self drawIntoLayer2];
}

为每个形状图层设置一个贝塞尔曲线路径,然后传入:

Set a bezier path for each shape layer, then pass it in:

- (void) drawIntoLayer1 {

    UIBezierPath* path = [[UIBezierPath alloc] init];
    [path moveToPoint:CGPointMake(0,0)];

    for (int i = 0; i < self.bounds.size.height+MIN_OFFSET_Y; i += 100) {
        [path moveToPoint:
                CGPointMake(0,i)];
        [path addLineToPoint:
                CGPointMake(self.bounds.size.width+MIN_OFFSET_X, i)];
        [path addLineToPoint:
                CGPointMake(self.bounds.size.width+MIN_OFFSET_X, i+3)];
        [path addLineToPoint:
                CGPointMake(0, i+3)];
        [path closePath];
    }

    [self.shapeLayer1 setPath:path.CGPath];
}

- (void) drawIntoLayer2 {
    UIBezierPath* path = [UIBezierPath bezierPathWithRect:
            CGRectMake(100+MIN_OFFSET_X, MIN_OFFSET_Y, 10, 10)];
    [self.shapeLayer2 setPath:path.CGPath];
}

这消除了对 drawRect 的需要——如果你改变了 path 属性,你只需要重绘你的图层.即使您像调用 drawRect 那样频繁地更改 path 属性,绘图现在也应该显着提高效率.由于 path 是一个可设置动画的属性,如果需要,您还可以免费获得动画.

This obviates the need for drawRect - you only need to redraw your layers if you change the path property. Even if you do change the path property as often as you would call drawRect, the drawing should now be significantly more efficient. And as path is an animatable property, you also get animation thrown in for free if you need it.

在你的情况下,我们只需要设置一次路径,所以所有的工作在初始化时完成一次.

In your case we only need to set the path once, so all of the work is done once, on initialisation.

现在您可以从 scrollView 委托方法中删除任何居中代码,不再需要它.

Now you can remove any centering code from your scrollView delegate methods, it isn't needed any more.

这篇关于UIScrollView 缩小带有 -ve 原点的视图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆