views:

965

answers:

5

I'm trying to add images to table cells in a grouped UITableView but the corners of the images are not clipped. What's the best way to go about clipping these (besides clipping them in Photoshop? The table contents are dynamic.)

Top left corner of image needs to be round

+1  A: 

There isn't a built-in standard way to do this, but it's not terribly hard to do in your own code. There are examples on how to round corners on an UIImage on the web, see for example http://blog.sallarp.com/iphone-uiimage-round-corners/.

calmh
Huh. I was hoping there would be a more built-in standard way like grabbing the shape mask of the cell itself. But yeah -- guess not.
John Williams
+3  A: 

This was my solution, which could use a little refactoring:

void addRoundedRectToPath(CGContextRef context, CGRect rect, float ovalWidth, float ovalHeight, BOOL top, BOOL bottom)
{
    float fw, fh;
    if (ovalWidth == 0 || ovalHeight == 0) {
        CGContextAddRect(context, rect);
        return;
    }
    CGContextSaveGState(context);
    CGContextTranslateCTM (context, CGRectGetMinX(rect), CGRectGetMinY(rect));
    CGContextScaleCTM (context, ovalWidth, ovalHeight);
    fw = CGRectGetWidth (rect) / ovalWidth;
    fh = CGRectGetHeight (rect) / ovalHeight;
    CGContextMoveToPoint(context, fw, fh/2);
    CGContextAddArcToPoint(context, fw, fh, fw/2, fh, 0);

    NSLog(@"bottom? %d", bottom);

    if (top) {
        CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 3);
    } else {
        CGContextAddArcToPoint(context, 0, fh, 0, fh/2, 0);
    }

    if (bottom) {
        CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 3);
    } else {
        CGContextAddArcToPoint(context, 0, 0, fw/2, 0, 0);
    }

    CGContextAddArcToPoint(context, fw, 0, fw, fh/2, 0);
    CGContextClosePath(context);
    CGContextRestoreGState(context);
}

- (UIImage *)roundCornersOfImage:(UIImage *)source roundTop:(BOOL)top roundBottom:(BOOL)bottom {
    int w = source.size.width;
    int h = source.size.height;

    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);

    CGContextBeginPath(context);
    CGRect rect = CGRectMake(0, 0, w, h);
    addRoundedRectToPath(context, rect, 4, 4, top, bottom);
    CGContextClosePath(context);
    CGContextClip(context);

    CGContextDrawImage(context, CGRectMake(0, 0, w, h), source.CGImage);

    CGImageRef imageMasked = CGBitmapContextCreateImage(context);
    CGContextRelease(context);
    CGColorSpaceRelease(colorSpace);

    return [UIImage imageWithCGImage:imageMasked];    
}

Implement those functions, then check the indexPath in the cellForRowAtIndexPath delegate method to determine which corner to round.

if (indexPath.row == 0) {
            cell.imageView.image = [self roundCornersOfImage:coverImage roundTop:YES roundBottom:NO];
        } else if (indexPath.row == [indexPath length]) {
            cell.imageView.image = [self roundCornersOfImage:coverImage roundTop:NO roundBottom:YES];
        } else {
            cell.imageView.image = coverImage;
        }
Kevin Cupp
Thanks, Kevin. I've refactored the code into a UIImage category so I don't have to copy and paste this all over the place.
John Williams
Great answer, thanks. One fix: imageMasked is being leaked. You need to assign the final UIImage to a variable, CGImageRelease(imageMasked) and then return the UIImage.
Ryan McCuaig
.... UIImage *image = [UIImage imageWithCGImage:imageMasked]; CGImageRelease(imageMasked); return image;} Thanks. This was what I was looking for.Now it's ok. Great.
Tharindu Madushanka
A: 

There is built-i way if you want to just display the rounded corners. Put the image in a UIImageView and then set the cornerRadius of the layer of the UIImageView. You will also need to tell the UIImageView to clip to bounds but that will give you rounded corners.

UIImageView *myImageView = [[UIImageView alloc] initWithImage:...];
[myImageView setClipsToBounds:YES];
[[myImageView layer] setCornerRadius:5.0f];
Marcus S. Zarra
OK, so I commented out Kevin's solution and tried this one. First, I had to add the QuartzCore framework -- setCornerRadius is in QuartzCore.Unfortunately, setCornerRadius rounds all four corners. If I want to match the shape of a grouped UITableViewCell.imageView, I need the top left of the image rounded on the top row -- but the others have to stay sharp. So while setCornerRadius is convenient, it's not suitable for an application where you have to pick and choose which corners to round.
John Williams
A: 

I was wondering how is the scrolling performance using addRoundedRectToPath, and QuartzCore layer property cornerRadius. Which one is better to use for good table view scrolling? Thanks, Oliver

Oliver
I don't know, Oliver. We use addRoundedRectToPath because it does something cornerRadius doesn't let us do -- specify which corners to round. That way we can make the image fit into the top and bottom rows of a grouped table's section. So it really shouldn't be a problem as long as we don't have a lot of sections.It should be relatively easy to try both methods in your application and run the app through Apple's profiling tools to see how it affects performance in your use case.
John Williams
+1  A: 

If you're happy to have all four image corners rounded, then you can just do the following when creating the cell:

cell.imageView.layer.masksToBounds = YES;
cell.imageView.layer.cornerRadius = 10.0;

If you also want to inset the image from the boundary, I described a simple category on UIImage to do it here.

Michael Tyson