UILabel layer cornerRadius negatively impacting performance
I've created a document view which displays the page number in the corner. The page number is a uilabel with a semi-transparent background colour, and has a corner radius (using the cornerRadius
property of the view
's layer
). I've positioned this above a UIScrollView
. However, this makes scrolling jerky. If I remove the cornerRadius
, performance is good. Is there anything I can do about this? What would be a better solution? It seems to have been achieved in the UIWebView
without any performance issues.
Solution 1:
For labels, or views with rounded corners and or background colors and shadows on scrolling views the solution is pretty simple:
The biggest issue is from the masksToBounds layer option. This appears to tax performance significantly, however the label seems to need this ON to mask the background color to rounded corners. So to get around this you need to set the labels layer background color instead and switch off masksToBounds.
The second issue is that the default behavior is to redraw the view whenever possible which is totally unnecessary with static or slow changing items on scrolling views. Here we simply set layer.shouldRasterize = YES. This will allow CA to 'cache' a rasterized version of the view for quick drawing when scrolling (presumably with hardware acceleration).
You need to make sure your layer has an alpha channel otherwise rasterizing will affect the drawing of rounded corners. I've never had a problem as I have alpha set for my background colors, but you may need to check in your situation.
Here is a sample UILabel set up to work nicely on a scollview:
UILabel *lbl = [[UILabel alloc] initWithFrame:CGRectMake(4, 4, 40.0, 24.0)];
lbl.font = [UIFont fontWithName:@"Helvetica" size:14.0];
lbl.textAlignment = UITextAlignmentRight;
lbl.text = @"Hello World";
// Must set the label background to clear so the layer background shows
lbl.backgroundColor = [UIColor clearColor];
// Set UILabel.layer.backgroundColor not UILabel.backgroundColor otherwise the background is not masked to the rounded border.
lbl.layer.backgroundColor = [UIColor colorWithRed:1 green:0 blue:0 alpha:0.5].CGColor;
lbl.layer.cornerRadius = 8;
lbl.layer.borderColor = [UIColor blackColor].CGColor;
lbl.layer.borderWidth = 1;
// Huge change in performance by explicitly setting the below (even though default is supposedly NO)
lbl.layer.masksToBounds = NO;
// Performance improvement here depends on the size of your view
lbl.layer.shouldRasterize = YES;
lbl.layer.rasterizationScale = [UIScreen mainScreen].scale;
// self here is the child view in the scroll view
[self addSubview:lbl];
[lbl release];
I can fill the iPad 1 screen with views like this and still scroll smoothly :)
Solution 2:
I stumbled into this one too. cornerRadius (for UIButton and UIImageView) was killing my app performance. I have found no working solution that rounds UIButton with background image, so I had to write my own piece of code - first method. Second adds a round corner and border to a UIImageView. I think that code speaks for itself.
+(void)setBackgroundImage:(UIImage*)image forButton:(UIButton*)button withCornerRadius:(float)cornerRadius borderColor:(CGColorRef)borderColor andBorderWith:(float)borderWidth
{
UIImageView *tempImageView = [[UIImageView alloc] initWithFrame:button.frame];
UIGraphicsBeginImageContextWithOptions(tempImageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:tempImageView.bounds
cornerRadius:cornerRadius] addClip];
[image drawInRect:tempImageView.bounds];
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
[button setBackgroundImage:tempImageView.image forState:UIControlStateNormal];
button.layer.shouldRasterize = YES;
button.layer.borderColor = borderColor;
button.layer.borderWidth = borderWidth;
button.layer.cornerRadius = cornerRadius;
}
+(void)makeRoundedCornersForImageView:(UIImageView*)imgV withCornerRadius:(float)cornerRadius borderColor:(CGColorRef)borderColor andBorderWith:(float)borderWidth
{
UIImageView *tempImageView = [[UIImageView alloc] initWithFrame:imgV.frame];
UIGraphicsBeginImageContextWithOptions(tempImageView.bounds.size, NO, 1.0);
[[UIBezierPath bezierPathWithRoundedRect:tempImageView.bounds
cornerRadius:cornerRadius] addClip];
[imgV.image drawInRect:tempImageView.bounds];
tempImageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
imgV.image = tempImageView.image;
imgV.layer.shouldRasterize = YES;
imgV.layer.borderColor = borderColor;
imgV.layer.borderWidth = borderWidth;
imgV.layer.cornerRadius = cornerRadius;
}
Solution 3:
Like petert suggested, after seeing the 'related' posts in the side bar I decided to create my own image. I subclassed UIView and added a background image (for the background with rounded edges) and a standard textlabel on init. To create the background image I make a stretchable image using the CG drawing functions.
// create background image
CGFloat radius = frame.size.height / 2;
CGFloat imgSize = (radius*2)+1; // 1 pixel for stretching
UIGraphicsBeginImageContext(CGSizeMake(imgSize, imgSize));
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetAlpha(context, 0.5f);
CGContextSetLineWidth(context, 0);
CGContextSetFillColorWithColor(context, [UIColor blackColor].CGColor);
CGFloat minx = 0;
CGFloat midx = imgSize/2;
CGFloat maxx = imgSize;
CGFloat miny = 0;
CGFloat midy = imgSize/2;
CGFloat maxy = imgSize;
CGContextMoveToPoint(context, minx, midy);
CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
CGContextClosePath(context);
CGContextDrawPath(context, kCGPathFillStroke);
UIImage *viewImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
UIImage *stretchImage = [viewImage stretchableImageWithLeftCapWidth:radius topCapHeight:radius];
UIImageView *stretch = [[UIImageView alloc] initWithImage:stretchImage];
stretch.frame = self.bounds;
stretch.autoresizingMask = (UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth);
[self addSubview:stretch];
[self sendSubviewToBack:stretch];
[stretch release];
Solution 4:
I had a similar issue with UILabel
in a custom UITableViewCell
with rounded corners. To get smooth performance I 'made' images with rounded-corners to get around this (see).
Lots of other posts, including this, or this might help out.