鉴于苹果公司非常坚决的警告,您不必使用frame 属性,也不应该使用。在这种情况下你通常可以使用bounds
and center
达到你的结果。
在您的情况下,您可以忽略子视图的所有属性。假设你的子视图是viewForZoomingInScrollView
你可以使用scrollView的contentOffset
and zoomScale
特性
- (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);
}
}
从两者调用它scrollViewDidScroll
and scrollViewDidZoom
在你的scrollView委托中。这应该可以顺利工作,但如果您有疑问,您也可以通过子类化滚动视图并调用它来实现它layoutSubviews
。在他们的 PhotoScroller 示例中,Apple 通过覆盖来居中滚动视图的内容layoutSubviews
- 尽管令人抓狂的是他们忽略了自己的警告并调整子视图的框架属性来实现这一点。
update
上述方法消除了滚动视图达到极限时的“弹跳”。如果你想保留反弹,你可以直接改变视图的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);
}
update 2
从您更新的问题(带有代码)中,我可以看到这些解决方案都不能解决您的问题。似乎发生的情况是,偏移量越大,变焦运动就会变得越不稳定。偏移量为 100 点时,动作仍然相当流畅,但偏移量为 500 点时,动作就粗糙得令人无法接受。这部分与您的drawRect
例程,部分与滚动视图中进行的(太多)重新计算以显示正确的内容有关。所以我有另一个解决方案......
在你的 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 文档,构建层层次结构 http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/CoreAnimation_guide/BuildingaLayerHierarchy/BuildingaLayerHierarchy.html)
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
是一个可动画的属性,如果您需要的话,您还可以免费获得动画。
在你的情况下,我们只需要设置一次路径,所以all的工作在初始化时完成一次。
现在,您可以从scrollView委托方法中删除任何居中代码,不再需要它了。