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!
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