Fixing Blurry Subviews

The app I'm currently working on uses a small panel with extra info in a small font drawn over the top of an image. It's implemented of course as a UIView nested as the subview in another UIView. My code loads the info panel view from a separate xib file and positions it in the main view with some code like this:

	NSArray *nibContent = [[NSBundle mainBundle] loadNibNamed:@"Box"
                                                     owner:self options:NULL];
	UIView* overlayBox1 = [nibContent objectAtIndex:0];
	[worksheet addSubview:overlayBox1];
	overlayBox1.center = worksheet.center;
	overlayBox1.frame = CGRectOffset(overlayBox1.frame, 0, -140);

It didn't quite work though, because the detail view came out really blurry. It didn't look blurry in Interface Builder, but both on the simulator and the iPhone it looked terrible, see below. I spent hours changing font properties and colors, thinking it was some weird text anti-aliasing gone wrong, but it always came out blurry. Can you guess the problem? It's one of those things that will probably stump you until you've run across it yourself...

blurryview1

The problem is Core Graphics awesome power and screen co-ordinates that are floats, not integers. Core Graphics lets you compose your views from graphics not to 100% scale, it will scale everything for you, and it can even position views with sub-pixel accuracy, rendering 'between' each pixel with an anti-alias like effect. This is possible because view coordinates are specified with CGRect, which uses CGPoint, where x and y are floats, not ints.

The problem occurs when you have views that are supposed to be drawn normally, ie at 100% scale and at exact pixels. In the code snippet above the subview overlayBox1 is being positioned using its center. Center is a convenience method implemented as a property, it is really setting the frame origin. As the box is sized with an even number of pixels, 174 x 82, setting the origin relative to the center results in an origin at half pixels: 73.5, 69.5. Core Graphics goes ahead and renders at the half pixel position, but as the view is drawn at 100% it means every pixel in the view lies at half pixels on the screen, so each pixel is smeared between 4 neighboring pixels resulting in a big blur.

This is easily fixed, by positioning the view to whole pixels:

	overlayBox2.center = worksheet.center;
	CGRect overlay2Frame = overlayBox2.frame;
	overlay2Frame.origin.x = round(overlay2Frame.origin.x);
	overlay2Frame.origin.y = round(overlay2Frame.origin.y);
	overlayBox2.frame = overlay2Frame;

If you want to play around with this you can download the full example: blurryview.zip

#import "BlurryViewViewController.h"

@implementation BlurryViewViewController

// The designated initializer. Override to perform setup that is required before the view is loaded.
- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
    if (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) {
        // Custom initialization
    }
    return self;
}

// Implement viewDidLoad to do additional setup after loading the view, typically from a nib.
- (void)viewDidLoad {
	[super viewDidLoad];

	NSArray *nibContent = [[NSBundle mainBundle] loadNibNamed:@"Box" owner:self options:NULL];
	UIView* overlayBox1 = [nibContent objectAtIndex:0];
	[worksheet addSubview:overlayBox1];
	overlayBox1.center = worksheet.center; //WARNING! blur ahead.
	overlayBox1.frame = CGRectOffset(overlayBox1.frame, 0, -140);

	nibContent = [[NSBundle mainBundle] loadNibNamed:@"Box" owner:self options:NULL];
	UIView* overlayBox2 = [nibContent objectAtIndex:0];
	[worksheet addSubview:overlayBox2];
	overlayBox2.center = worksheet.center;
	CGRect overlay2Frame = overlayBox2.frame;
	overlay2Frame.origin.x = round(overlay2Frame.origin.x);
	overlay2Frame.origin.y = round(overlay2Frame.origin.y);
	overlayBox2.frame = overlay2Frame;

	NSLog(@"Worksheet1 origin: %f,%f",overlayBox1.frame.origin.x,overlayBox1.frame.origin.y);
}

- (void)didReceiveMemoryWarning {
	// Releases the view if it doesn't have a superview.
    [super didReceiveMemoryWarning];

	// Release any cached data, images, etc that aren't in use.
}

- (void)viewDidUnload {
	// Release any retained subviews of the main view.
	// e.g. self.myOutlet = nil;
}

- (void)dealloc {
    [super dealloc];
}

@end