UIScrollView以-ve原点缩小视图

我有一个UIScrollView。 在这个我有一个UIView有一个负面起源的框架 – 我需要限制滚动视图,以便您不能滚动整个视图..

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

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

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

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

显然这不好,整个视图是缩小的滚动视图。 我想框架是{0,-250},{500,500}

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

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

警告:如果transform属性不是标识转换,则此属性的值未定义,因此应予以忽略。

不太清楚这是为什么。

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

谢谢


以下是我使用的testing应用程序的一些源代码:

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

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

你不必使用框架属性 – 而且不应该,苹果公司的非常坚定的警告。 在这种情况下,您通常可以使用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中调用它。 这应该工作顺利,但如果你有疑虑,你也可以通过inheritancescrollView并用layoutSubviews调用它来实现它。 在他们的PhotoScroller示例中,Apple通过重写layoutSubviews中心scrollView的内容 – 尽pipe他们忽略了自己的警告,并调整了子视图的框架属性来实现这一点。

更新

上面的方法消除了“跳动”,因为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

从你更新的问题(用代码),我可以看到这些解决scheme都不能解决你的问题。 似乎发生的事情是,越大的偏移量,缩放运动变得越快。 在100点的偏移下,动作仍然非常平滑,但是有500点的偏移,这是不可接受的粗糙。 这部分与drawRect例程有关,部分与scrollView重新计算相关,以显示正确的内容。 所以我有另一个解决scheme…

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

在自定义视图中初始化两个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]; 

设置界限是关键的一点。 不同于将video剪辑为子视图的视图,CALayers将超越其超级界面的界限。 您将开始在视图顶部上方绘制MIN_OFFSET_Y个点,向左侧绘制MIN_OFFSET_Y 。 这允许您在scrollView的内容视图之外绘制内容,而不需要scrollView做任何额外的工作。

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

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

为每个形状图层设置一个贝塞尔path,然后将其传入:

 - (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是一个animation属性,如果你需要的话,你也可以免费获得animation。

在你的情况下,我们只需要设置一次path, 所有的工作都是在初始化时完成的。

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