Как оптимизировать рисование UIView, который содержит подпредставления со слоями CATiledLayer
У меня есть UIView в UIScrollView, который содержит много подпредставлений 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];
}
И это метод drawInContext:forRect контейнера UIView, где рисуются подпредставления:
- (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];
}
И, наконец, это метод drawRect подпредставлений с 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];
}
}
}
Теперь все работает так, как я задумал, однако приложение значительно замедляется, когда увеличительная лупа включена и перемещается. Проблема заключается в том, что каждый раз, когда перемещается представление лупы, вызывается его метод drawRect (чтобы он мог обновлять увеличенное содержимое), который впоследствии вызывает метод drawInContext контейнера UIView и т. Д., В результате чего все CATiledLayers обновляют свое изображение плитки каждый раз, когда лупа перемещается.
Как вы можете видеть, я попытался нарисовать большую часть вида контейнера в контексте лупы, но это то, где я застрял. Я не вижу, как я могу "буферизовать" большую часть этого контейнера, поэтому при перемещении лупы подпредставления перерисовываются, только если прямоугольник, который нужно перерисовать, выходит за пределы "буферизованного" прямоугольника.
Извините, если код неаккуратный / newby - я нахожусь в середине этого и ищу некоторую помощь.
Спасибо!
1 ответ
Можете ли вы сделать однократный предварительный рендеринг всего, что отобразит вид лупы / лупы? Я не уверен, является ли дисплей, который вы пытаетесь увеличить, относительно статичным или постоянно меняющимся.
В моем приложении пользователь нажимает на экран, чтобы разместить другие виды. Когда они касаются пальцами и двигают их, изображение лупы показывает, где именно будет расположен предмет. Кроме того, когда они активно используют лупу, фоновое изображение не меняется (они могут панорамировать / масштабировать / перемещать изображения в другое время, но только если лупа включена).
Поскольку экран, который я увеличиваю, является статичным во время увеличения, я смог реализовать лупу, которая не перекрывает методы "рисования". Ниже приводится полная реализация; он создается и сохраняется обработчиком ViewController / touch.
Хитрость заключается в том, чтобы сделать скриншот всего окна и передать его в лупу. layer.contents
, Затем используйте layer.contentsRect
обрезать, масштабировать и расположить скриншот в точке касания.
- От звонящего
touchesBegin
вызов[_magnifier magnify...
- И из
touchesMoved
вызов[_magnifier touchPointMovedTo...
- В заключение,
touchesEnded
звонки[_magnifier removeFromSuperview];
MagnifierView.h
#import <UIKit/UIKit.h>
@interface MagnifierView : UIView {
float _xscale;
float _yscale;
float _loupewidth;
float _loupeheight;
float _xzoom;
float _yzoom;
}
- (void) magnify:(CGPoint) touchPoint;
- (void) touchPointMovedTo:(CGPoint) touchPoint;
@end
MagnifierView.m
#import "MagnifierView.h"
#import <QuartzCore/QuartzCore.h>
#define kMagnifierDiameter 120
@implementation MagnifierView
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:CGRectMake(0, 0, kMagnifierDiameter, kMagnifierDiameter)];
if (self) {
self.layer.borderColor = [[UIColor darkGrayColor] CGColor];
self.layer.borderWidth = 3;
self.layer.cornerRadius = kMagnifierDiameter / 2;
self.layer.masksToBounds = YES;
UIImageView *crosshair = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"crosshairs.png"]];
[crosshair setCenter:self.center];
[self addSubview:crosshair];
[crosshair release];
}
return self;
}
- (void) magnify:(CGPoint)touchPoint
{
UIView *windowview = [[UIApplication sharedApplication] keyWindow];
/// grab a screenshot of the window (sloppy; goal is to get a CGImageRef to put into this view's layer)
UIGraphicsBeginImageContext(windowview.bounds.size);
CGContextRef context = UIGraphicsGetCurrentContext();
[windowview.layer renderInContext:context];
UIImage *screenshot = UIGraphicsGetImageFromCurrentImageContext(); /// autoreleased
UIGraphicsEndImageContext();
/// while we have the image size update our positioning and zooming numbers
_xscale = 1 / screenshot.size.width; /// layer units are 0.0 - 1.0 so scale the numbers to that range
_yscale = 1 / screenshot.size.height;
_loupewidth = _xscale * CGRectGetWidth(self.frame); /// scaled size of this view
_loupeheight = _yscale * CGRectGetHeight(self.frame);
_xzoom = (_xscale * 18); /// arbitrary 16; make sure it's in scaled units (negative to zoom out)
_yzoom = (_yscale * 18);
/// set our layer contents to the imageRef of the screenshot above (again, sloppy; can we skip UIImage altogether?)
CGImageRef imageRef = CGImageCreateWithImageInRect([screenshot CGImage], CGRectMake(0, 0, screenshot.size.width, screenshot.size.height));
self.layer.contents = (id)imageRef;
CGImageRelease(imageRef);
/// add us to the window view and move us
[windowview addSubview:self];
[self touchPointMovedTo:touchPoint];
}
- (void) touchPointMovedTo:(CGPoint)touchPoint
{
self.center = CGPointMake(touchPoint.x, touchPoint.y - 80); /// arbitrary 80 so we float 'above' your fat finger
/// touchPoint is the center; figure out the x,y (top,left)
float xcenter = (_xscale * touchPoint.x);
float x = xcenter - (_loupewidth / 2);
float ycenter = (_yscale * touchPoint.y);
float y = ycenter - (_loupeheight / 2);
/// with no additional adjustments this rect will just 'see through' to the screenshot
CGRect seethrough = CGRectMake(x , y, _loupewidth, _loupeheight);
/// RectInset with the zoom factor to scale up the contents
self.layer.contentsRect = CGRectInset(seethrough, _xzoom, _yzoom);
}
- (void)dealloc {
[super dealloc];
}
@end