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

Mon*_*ong 11 objective-c uiscrollview ios

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

我在这个scrollview中实现了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);
}
Run Code Online (Sandbox Code Playgroud)

然后在视图中......

- (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));        
    }
}
Run Code Online (Sandbox Code Playgroud)

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

fou*_*dry 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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

从两个scrollViewDidScrollscrollViewDidZoom您的scrollView委托中调用它.这应该可以顺利进行,但是如果你有疑问,你也可以通过子类化scrollView并使用它来调用它来实现它layoutSubviews.在他们的PhotoScroller示例中,Apple通过覆盖来集中scrollView的内容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);
    }
Run Code Online (Sandbox Code Playgroud)

更新2

从您更新的问题(带代码),我可以看到这些解决方案都没有解决您的问题.似乎正在发生的事情是,你的偏移量越大,变焦运动变得更加笨拙.如果偏移量为100个点,则动作仍然相当平滑,但是如果偏移量为500点,则会出现难以接受的粗糙现象.这部分与您的drawRect例程有关,部分与在scrollView中进行(太多)重新计算以显示正确的内容有关.所以我有另一种解决方案......

在viewController中,将customView的bounds/frame origin设置为normal(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];
Run Code Online (Sandbox Code Playgroud)

设置边界是关键位.与剪辑其子视图的视图不同,CALayers将超越其超级图层的界限.您将开始MIN_OFFSET_Y在视图顶部和MIN_OFFSET_X左侧绘制点.这允许您在scrollView的内容视图之外绘制内容,而scrollView不必执行任何额外的工作.

与视图不同,超级图层不会自动剪切位于其边界矩形之外的子图层的内容.相反,超级层允许其子层默认显示为完整.
(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];
}
Run Code Online (Sandbox Code Playgroud)

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

- (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];
}
Run Code Online (Sandbox Code Playgroud)

这样就不需要了drawRect- 如果更改路径属性,则只需重新绘制图层.即使您像调用drawRect一样经常更改路径属性,现在绘图应该更加高效.和path动画属性一样,如果需要,还可以免费获得动画.

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

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