如何优化包含带有 CATiledLayer 层的子视图的 UIView 的绘制
我在 UIScrollView 中有一个 UIView,其中包含许多 UIView 子视图。每个子视图都有一个 CATiledLayer 层。此外,我还有一个放大镜功能,可以在其上下文中绘制容器 UIView(以及所有子视图)。相关代码:
这是放大镜的drawRect方法:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToMask( context , loupeRect, self.maskImage);
CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextFillRect(context, loupeRect);
CGContextSaveGState( context );
CGContextScaleCTM(context, gridScale, gridScale);
CGContextTranslateCTM(context, offset.x, offset.y);
CGRect rectToDraw = CGRectMake(-offset.x, -offset.y, 512, 512);
[appDelegate.gridViewController.objectContainerView drawInContext:context forRect:rectToDraw];
CGContextRestoreGState( context );
[overlayImage drawAtPoint:CGPointZero];
}
这是绘制子视图的容器UIView的drawInContext:forRect方法:
- (void)drawInContext:(CGContextRef)ctx forRect:(CGRect)rect {
CGRect newrect = CGRectMake(rect.origin.x-1024, rect.origin.y-1024, 2048, 2048);
for (UIView* v in [self subviews]) {
float viewscale = v.transform.a;
if (CGRectIntersectsRect(newrect,v.frame)) {
CGContextSaveGState(ctx);
CGContextScaleCTM(ctx, viewscale, viewscale);
CGContextTranslateCTM(ctx, v.frame.origin.x/viewscale, v.frame.origin.y/viewscale);
[v drawLayer:v.layer inContext:ctx];
CGContextRestoreGState(ctx);
}
}
[super drawLayer:self.layer inContext:ctx];
}
最后,这是带有CATiledLayer的子视图的drawRect方法:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat scale = CGContextGetCTM(context).a;
scale = (scale <= .125) ? .125 : (scale <= .250 ? .250 : (scale <= .5 ? .5 : 1));
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
CGSize tileSize = tiledLayer.tileSize;
tileSize.width /= scale;
tileSize.height /= scale;
int firstCol = floorf(CGRectGetMinX(rect) / tileSize.width);
int lastCol = floorf((CGRectGetMaxX(rect)-1) / tileSize.width);
int firstRow = floorf(CGRectGetMinY(rect) / tileSize.height);
int lastRow = floorf((CGRectGetMaxY(rect)-1) / tileSize.height);
for (int row = firstRow; row <= lastRow; row++) {
for (int col = firstCol; col <= lastCol; col++) {
UIImage *tile = [self tileForScale:scale row:row col:col];
CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
tileSize.width, tileSize.height);
tileRect = CGRectIntersection(self.bounds, tileRect);
[tile drawInRect:tileRect];
}
}
}
现在,一切都是这样工作的然而,当放大镜打开并移动时,应用程序的速度会显着减慢。问题是,每次移动放大视图时,都会调用其drawRect方法(因此它可以更新放大的内容),该方法随后调用容器UIView的drawInContext方法等等......导致所有CATiledLayers更新其图像每次移动放大镜时都会平铺。
正如您所看到的,我尝试在放大镜的上下文中绘制容器视图的较大部分,但这就是我陷入困境的地方。我看不出如何“缓冲”此容器视图的较大部分,因此当移动放大镜时,仅当要重绘的矩形超出“缓冲”矩形时,才会重绘子视图。
抱歉,如果代码很草率/新手 - 我正在其中寻求一些帮助。
谢谢!
I have a UIView within a UIScrollView that contains many UIView subviews. Each of these subviews has a CATiledLayer layer. In addition, I have a magnifying loupe functionality that draws the container UIView within its context (along with all the subviews). The relevant code:
This is drawRect method of the loupe:
- (void)drawRect:(CGRect)rect
{
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextClipToMask( context , loupeRect, self.maskImage);
CGContextSetFillColorWithColor(context, [[UIColor whiteColor] CGColor]);
CGContextFillRect(context, loupeRect);
CGContextSaveGState( context );
CGContextScaleCTM(context, gridScale, gridScale);
CGContextTranslateCTM(context, offset.x, offset.y);
CGRect rectToDraw = CGRectMake(-offset.x, -offset.y, 512, 512);
[appDelegate.gridViewController.objectContainerView drawInContext:context forRect:rectToDraw];
CGContextRestoreGState( context );
[overlayImage drawAtPoint:CGPointZero];
}
And this is the drawInContext:forRect method of the container UIView where the subviews are drawn:
- (void)drawInContext:(CGContextRef)ctx forRect:(CGRect)rect {
CGRect newrect = CGRectMake(rect.origin.x-1024, rect.origin.y-1024, 2048, 2048);
for (UIView* v in [self subviews]) {
float viewscale = v.transform.a;
if (CGRectIntersectsRect(newrect,v.frame)) {
CGContextSaveGState(ctx);
CGContextScaleCTM(ctx, viewscale, viewscale);
CGContextTranslateCTM(ctx, v.frame.origin.x/viewscale, v.frame.origin.y/viewscale);
[v drawLayer:v.layer inContext:ctx];
CGContextRestoreGState(ctx);
}
}
[super drawLayer:self.layer inContext:ctx];
}
And finally this is the drawRect method of the subviews with CATiledLayer:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat scale = CGContextGetCTM(context).a;
scale = (scale <= .125) ? .125 : (scale <= .250 ? .250 : (scale <= .5 ? .5 : 1));
CATiledLayer *tiledLayer = (CATiledLayer *)[self layer];
CGSize tileSize = tiledLayer.tileSize;
tileSize.width /= scale;
tileSize.height /= scale;
int firstCol = floorf(CGRectGetMinX(rect) / tileSize.width);
int lastCol = floorf((CGRectGetMaxX(rect)-1) / tileSize.width);
int firstRow = floorf(CGRectGetMinY(rect) / tileSize.height);
int lastRow = floorf((CGRectGetMaxY(rect)-1) / tileSize.height);
for (int row = firstRow; row <= lastRow; row++) {
for (int col = firstCol; col <= lastCol; col++) {
UIImage *tile = [self tileForScale:scale row:row col:col];
CGRect tileRect = CGRectMake(tileSize.width * col, tileSize.height * row,
tileSize.width, tileSize.height);
tileRect = CGRectIntersection(self.bounds, tileRect);
[tile drawInRect:tileRect];
}
}
}
Now, everything works that way I intended, however the app significantly slows down when the magnifying loupe is on and is being moved. The issue is that everytime the loupe view is moved, its drawRect method is called (so it can update the magnified contents), which subsequently calls the drawInContext method of the container UIView and so forth... resulting in all the CATiledLayers updating their image tiles everytime the loupe is moved.
As you can see I've attempted to draw a larger portion of the container view within the context of the loupe but this is where I'm stuck. I can't see how I can "buffer" a larger portion of this container view so when the loupe is moved, the subviews are redrawn only if the rect to be redrawn goes beyond the "buffered" rect.
Sorry if the code is sloppy/newby - I'm in middle of it and looking for some help.
Thanks!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以对放大镜/放大镜视图将显示的任何内容进行一次性预渲染吗?我不确定您尝试放大的显示是相对静态的还是不断变化的。
在我的应用程序中,用户点击屏幕来放置其他视图。当他们触摸&移动手指,放大镜视图会准确显示该项目的放置位置。此外,当他们积极使用放大镜时,背景图像不会改变(他们可能会在其他时间平移/缩放/移动视图,只是在放大镜恰好打开时除外)。
由于我放大的显示在放大过程中是静态的,因此我能够实现一个不会覆盖“绘制”方法的放大镜。下面是整个实现;它由 ViewController/touch 处理程序实例化并保存。
下面的技巧是截取整个窗口的屏幕截图并将其提供给放大镜的
layer.contents
。然后使用layer.contentsRect
进行裁剪、缩放和缩放。将屏幕截图放置在触摸点处。touchesBegin
调用[_magnifier magnify...
touchesMoved
调用[_magnifier touchPointMovedTo...
touchesEnded
调用[_magnifier removeFromSuperview];
MagnifierView.h
MagnifierView.m
Can you do a one time pre-render of anything the loupe/magnifier view will display? I'm not sure if the display you are trying to magnify is relatively static or constantly changing.
In my app the user taps the screen to place other views. As they touch & move their finger a magnifier view shows exactly where the item will be placed. Also, while they are actively using the magnifier the background image does not change (they may pan/zoom/move the views at other times just not while the magnifier happens to be on).
Since the display I'm magnifying is static during magnification, I was able to implement a loupe that does not override the 'draw' methods. Below is the entire implementation; it is instantiated and persisted by the ViewController/touch handler.
The trick below is to take a screenshot of the entire window and feed that to the loupe's
layer.contents
. Then use thelayer.contentsRect
to crop, zoom & position the screenshot at the touch point.touchesBegin
call[_magnifier magnify...
touchesMoved
call[_magnifier touchPointMovedTo...
touchesEnded
calls[_magnifier removeFromSuperview];
MagnifierView.h
MagnifierView.m