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

Comments

  1. Joel Bernstein says:

    This is especially annoying with Core Animation layers, whose centers can be aligned based on constraint rules, so you don’t actually have a line of code assigning their position.

  2. Corey says:

    Ideally, you are not scaling images on device and creating them in photoshop to the exact size needed. Planning, of course, to line up on whole pixels. This makes your views render faster and eliminates the blurring.

  3. Zaph says:

    Rendering an UIImage that is an odd number of pixels wide into a UIImageView of exactly the same width and with an origin on integer values also causes pixel smearing. A workaround is to make the image 1 pixel wider.

  4. I had that problem with odd pixel sized UIImageViews I was using on a UIScrollView. Every time you scrolled the screen the app moved the UIImagViews so they appeared to stay still relative to the scrolling background. All the even pixel sized UIImageViews looked fine but the odd ones went blurry. If I hadn’t been lucky and had some even sized ones which worked I’d have just written it off as an un-fixable iPhone quirk!

  5. david says:

    thank your great job ,that be helpful for me!

  6. Marco says:

    @Mark: Thanks for sharing this! I spent hours trying to figure this one out before I finally Googled the right keywords and stumbled on your post.

    @Zaph: Thanks for your comment, my issue was actually with a 55 pixel height (width was even) and this was causing the issue. After resizing to 56 pixels, no more issue. I can’t believe how much time I lost on that stupid thing.

  7. Wayne says:

    Hi Mark;

    Thanks so much for the post! Like others this has been a thorn in my iPad project for weeks and I no idea to fix it. Although I didn’t use your code exactly I did use the following technique to “clear” up my problem.

    //set the centerpoint for the loadingview
    CGPoint centerpoint;

    centerpoint.x = [UIScreen mainScreen].bounds.size.width / 2;
    centerpoint.y = [UIScreen mainScreen].bounds.size.height / 2;

    loadingDrawView.center = centerpoint;

    //round up the frame position to prevent blurring
    loadingDrawView.frame = CGRectMake(round(loadingDrawView.frame.origin.x),
    round(loadingDrawView.frame.origin.y),
    loadingDrawView.frame.size.width,
    loadingDrawView.frame.size.height);

Trackbacks

  1. [...] UIView Has Blurred Subview | markjnet iPhone UIView Has Blurred Subview | markjnet As the box is sized with an even number of pixels, 174 x 82, setting the origin relative to the [...]

Speak Your Mind

*