UIScrollView缩小具有-ve原点的视图

时间:2013-05-03 14:52:27

标签: ios objective-c uiscrollview

我有一个UIScrollView。在这里我有一个UIView,其框架具有负原点 - 我需要限制滚动视图,以便您不能滚动整个视图..

我已在此滚动视图中实现了Zoom。

缩放滚动视图时,将根据比例调整Zoomable视图的大小。但它没有调整原点。

因此,如果我的视图的框架为 {0,-500},{1000,1000}

我缩小到0.5,这将为我提供 {0,-500},{500,500} 的新框架

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

我可以通过正确调整原点来解决scrollViewDidZoom方法中的问题。这确实有效,但缩放不顺畅。在此更改原点会导致它跳转。

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

  

警告:如果transform属性不是identity变换,那么   此属性的值未定义,因此应忽略。

不太清楚为什么会这样。

我接近这个问题了吗?解决问题的最佳方法是什么?

由于


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

在ViewController ..

- (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));        
    }
}

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

1 个答案:

答案 0 :(得分:10)

你不必使用框架属性 - 并且不应该,因为Apple非常坚定的警告。在这种情况下,您通常可以使用boundscenter来获得结果。

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

- (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达到了它的极限。如果您想保留反弹,可以直接改变视图的中心属性:

- (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中进行(太多)重新计算有关,以显示正确的内容。所以我有另一种解决方案......

在viewController中,将customView的bounds / frame origin设置为normal(0,0)。我们将使用图层来替代内容。您需要将QuartzCore框架添加到项目中,然后#import到您的自定义视图中。

在自定义视图中初始化两个CAShapeLayers - 一个用于框,另一个用于行。如果它们共享相同的填充和描边,则只需要一个CAShapeLayer(对于此示例,我更改了填充和描边颜色)。每个CAShapeLayer都带有它自己的CGContext,你可以用颜色,线宽等对每层初始化一次。然后为了制作一个CAShapelayer,你需要做的就是用CGPath设置它的path属性。

#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不需要做任何额外的工作。

  

与视图不同,超级图层不会自动剪切位于其边界矩形之外的子图层的内容。相反,超级层允许其子层默认显示为   (Apple Docs, Building a Layer Hierarchy

    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];
}

为每个形状图层设置贝塞尔曲线路径,然后将其传递到:

- (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的需求 - 如果更改路径属性,则只需重新绘制图层。即使您像调用drawRect一样经常更改路径属性,现在绘图应该更加高效。由于path是一个可动画的属性,如果需要,你也可以免费获得动画。

在您的情况下,我们只需要设置一次路径,因此在初始化时,所有工作都会完成一次。

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