你不必使用 frame 属性——也不应该使用,因为苹果公司非常坚定的警告。在这种情况下,您通常可以使用bounds
andcenter
来实现您的结果。
在您的情况下,您可以忽略所有子视图的属性。假设您的子视图是viewForZoomingInScrollView
您可以使用滚动视图的contentOffset
和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
和scrollViewDidZoom
您的 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 委托方法中删除任何居中代码,不再需要它。