我有一个UIScrollView。在这里我有一个UIView,其框架具有负原点 - 我需要限制滚动视图,以便您不能滚动整个视图..
我已在此滚动视图中实现了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));
}
}
请注意,在较大的缩放比例下,跳跃更明显。在我真正的应用程序中,有更多的绘图和处理正在进行,跳跃在任何时候都更加明显。
答案 0 :(得分:10)
你不必使用框架属性 - 并且不应该,因为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);
}
}
从scrollView委托中的scrollViewDidScroll
和scrollViewDidZoom
调用它。这应该可以顺利进行,但如果您有疑问,也可以通过继承scrollView并使用layoutSubviews
调用它来实现它。在他们的PhotoScroller示例中,Apple通过覆盖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,你需要做的就是用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
。这允许您在scrollView的内容视图之外绘制内容,而scrollView不需要做任何额外的工作。
与视图不同,超级图层不会自动剪切位于其边界矩形之外的子图层的内容。相反,超级层允许其子层默认显示为 (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委托方法中删除任何居中代码,不再需要它。