UIScrollView 缩小具有 -ve 原点的视图
Posted
技术标签:
【中文标题】UIScrollView 缩小具有 -ve 原点的视图【英文标题】:UIScrollView zooming out of a view with a -ve origin 【发布时间】:2013-05-03 14:52:26 【问题描述】:我有一个 UIScrollView。在这个我有一个 UIView 有一个负原点的框架 - 我需要限制滚动视图,这样你就不能滚动整个视图..
我已经在这个滚动视图中实现了缩放。
缩放时,滚动视图将根据比例调整可缩放视图的大小。但它不会调整原点。
所以如果我有一个框架为 0, -500, 1000, 1000 的视图
我缩小到 0.5 的比例,这将给我一个 0, -500, 500, 500
的新框架显然这不好,整个视图被缩小到滚动视图之外。我希望框架为 0, -250, 500, 500
我可以通过正确调整原点在 scrollViewDidZoom 方法中修复一些问题。这确实有效,但缩放不平滑。在此处更改原点会导致它跳转。
我在 UIView 的文档中注意到它说(关于 frame 属性):
警告:如果变换属性不是恒等变换,则 此属性的值未定义,因此应忽略。
不太清楚为什么会这样。
我处理这个问题的方法有误吗?修复它的最佳方法是什么?
谢谢
以下是我正在使用的测试应用的一些源代码:
在 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));
请注意,这里的跳跃性在较大的缩放比例下更加明显。在我的真实应用程序中,有更多的绘图和处理在跳转,在任何时候都更加明显。
【问题讨论】:
关于变换和框架,文档太模糊了。 Frame 本质上是从边界、中心和应用变换(以及便利设置器)派生的。frame.size
将通过转换矩阵应用的水平和垂直缩放比例缩放bounds.size
(因此对于大小为 150x100 的视图,矩阵来自 CGAffineTransformMakeScale(3.0, 5.0)
,frame.size
将是 450x500)。对于旋转视图,frame.size
将是可以容纳旋转(和缩放)边界的最小直立矩形的大小,以便视图边界的角接触框架的边。
你是在模拟器还是在设备上测试?
我已经成功复制了您的生涩缩放。我最初的建议并没有解决这个问题。我没有看到它,因为它与偏移的大小有关,我正在测试一个较小的 100 点偏移,它是平滑的。我已经使用CAShapeLayer
制定了一个新的解决方案。请参阅我的更新答案。
感谢您的慷慨 - 这就是让我们解谜者继续前进的原因;-j
【参考方案1】:
您不必使用 frame 属性——也不应该使用,因为 Apple 发出了非常坚定的警告。在这种情况下,您通常可以使用bounds
和center
来实现您的结果。
在您的情况下,您可以忽略所有子视图的属性。假设您的子视图是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);
从您的 scrollView 委托中的 scrollViewDidScroll
和 scrollViewDidZoom
调用它。这应该可以顺利进行,但如果您有疑问,您也可以通过子类化 scrollView 并使用layoutSubviews
调用它来实现它。在他们的 PhotoScroller 示例中,Apple 通过覆盖 layoutSubviews
将滚动视图的内容居中 - 尽管令人抓狂的是,他们忽略了自己的警告并调整了子视图的 frame 属性来实现这一点。
更新
上述方法消除了滚动视图达到其限制时的“反弹”。如果你想保留反弹,你可以直接改变视图的中心属性:
- (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 的边界/框架原点设置为正常 (0,0)。我们将使用图层来抵消内容。您需要将 QuartzCore 框架添加到您的项目中,并将其#import 到您的自定义视图中。
在自定义视图中初始化两个 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];
设置界限是关键。与裁剪其子视图的视图不同,CALayers 将绘制超出其超层的边界。您将开始在视图顶部上方绘制MIN_OFFSET_Y
点,在左侧绘制MIN_OFFSET_X
。这允许您在滚动视图的内容视图之外绘制内容,而无需滚动视图做任何额外的工作。
与视图不同,超层不会自动剪切位于其边界矩形之外的子层的内容。相反,默认情况下,超级图层允许其子图层完整显示。 (Apple Docs, Building a Layer Hierarchy)
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 委托方法中删除任何居中代码,不再需要它。
【讨论】:
这不会阻止您通过滚动超出范围获得的“反弹”吗? @MongusPong - 是的。如果您想保持反弹,请改为更改视图的中心属性(请参阅我的更新) 试试这个吧。设置中心与设置框架的效果完全相同。缩放时视图会上下跳动,不流畅。 @MongusPong - 它对我来说很好,我会看看可能是什么原因 - 也许你可以更新你的问题以显示你的代码,以防有其他东西正在制作查看跳转? 谢谢,我需要一点时间来消化这个。初步测试看起来很有希望......希望今晚晚些时候能够敲定它。以上是关于UIScrollView 缩小具有 -ve 原点的视图的主要内容,如果未能解决你的问题,请参考以下文章