Как оптимизировать отрисовку 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 обновляют свое изображение плитки каждый раз при перемещении лупы.

Как видите, я попытался нарисовать большую часть представления контейнера в контексте лупы, но здесь я застрял. Я не вижу, как я могу «буферизовать» большую часть этого представления контейнера, поэтому при перемещении лупы подвиды перерисовываются только в том случае, если перерисовываемый прямоугольник выходит за пределы «буферизованного» прямоугольника.

Извините, если код неаккуратный / новенький - я нахожусь в середине и ищу помощь.

Спасибо!


person canfan    schedule 07.02.2011    source источник
comment
У меня такая же проблема @canfan. Как тебе это удалось?   -  person Zack Kaytranada    schedule 20.10.2015


Ответы (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
person Douglas    schedule 01.07.2011
comment
Код работает хорошо, Дуглас, за исключением того, что просматриваемый прямоугольник - это не совсем та область, на которую пользователь указывает пальцем. Я не думаю, что x и y рассчитываются нормально. Я был бы очень признателен за помощь. - person Zack Kaytranada; 20.10.2015