我有一个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);
}
然后在视图中......
- (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));
}
}
请注意,在较大的缩放比例下,跳跃更明显。在我的真实应用程序中,有更多的绘图和处理正在进行,跳跃在任何时候都更加明显。
鉴于Apple非常坚定的警告,您不必使用框架属性 - 也不应该使用框架属性。在这种情况下,您通常可以使用 bounds
和 center
实现你的结果。
在您的情况下,您可以忽略所有子视图的属性。假设您的子视图是 viewForZoomingInScrollView
你可以使用scrollView的 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通过覆盖来居中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);
}
更新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];
设置边界是关键位。与剪辑其子视图的视图不同,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];
}
为每个形状图层设置贝塞尔曲线路径,然后将其传递给:
- (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委托方法中删除任何居中代码,不再需要它。