11

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

我已经在这个滚动视图中实现了缩放。

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

所以如果我有一个框架为{0, -500}, {1000, 1000}的视图

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

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

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

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

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

不太清楚为什么会这样。

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

谢谢


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

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

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

4

1 回答 1

10

你不必使用 frame 属性——也不应该使用,因为苹果公司非常坚定的警告。在这种情况下,您通常可以使用boundsandcenter来实现您的结果。

在您的情况下,您可以忽略所有子视图的属性。假设您的子视图是viewForZoomingInScrollView 您可以使用滚动视图的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);
        }
    }

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

更新

当滚动视图达到其限制时,上述方法消除了“反弹”。如果要保留反弹,可以直接更改视图的 center 属性:

- (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例程有关,部分与滚动视图中正在进行的(太多)重新计算以显示正确的内容有关。所以我有另一个解决方案……</p>

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

在自定义视图中初始化两个 CAShapeLayers - 一个用于盒子,另一个用于线条。如果它们共享相同的填充和描边,则您只需要一个 CAShapeLayer(在此示例中,我更改了填充和描边颜色)。每个 CAShapeLayer 都带有它自己的 CGContext,您可以使用颜色、线宽等对每个图层进行一次初始化。然后要让 CAShapelayer 进行绘制,您所要做的就是path使用 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这允许您在滚动视图的内容视图之外绘制内容,而滚动视图不必做任何额外的工作。

与视图不同,超层不会自动剪切位于其边界矩形之外的子层的内容。相反,默认情况下,超级图层允许其子图层完整显示
Apple Docs,构建层层次结构

    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 委托方法中删除任何居中代码,不再需要它。

于 2013-05-03T21:45:47.033 回答