I'm afraid there doesn't seem to be an easy way to do this. You will have to customize your UITableViewCell's, something like this works:
Set your tableView's style to grouped.
Set your TableView background color to clear color.
On your - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *) make the cell background clear and create a UIView with the desired rounded corners as the background. Something like this:
I wanted to achieve the same but with border around each section (line in iOS6). Since I didn't find an easy modification of suggested solutions I came up with my own. It is a modification of the answer @Roberto Ferraz gave in this topic.
I created a custom class that inherits from UITableViewCell. In it I added a container view with the appropriate size (in my case shrinked on both sides with 15px). Than in the class I did this:
- (void)layoutSubviews
{
[super layoutSubviews];
CGFloat cornerRadius = 10.0f;
self.vContainerView.layer.cornerRadius = cornerRadius;
self.vContainerView.layer.masksToBounds = YES;
self.vContainerView.layer.borderWidth = 1.0f;
if (self.top && self.bottom)
{
// nothing to do - cell is initialized in prepareForReuse
}
else if (self.top)
{
// cell is on top - extend height to hide bottom line and corners
CGRect frame = self.vContainerView.frame;
frame.size.height += cornerRadius;
self.vContainerView.frame = frame;
self.vSeparatorLine.hidden = NO;
}
else if (self.bottom)
{
// cell is on bottom - extend height and shift container view up to hide top line and corners
CGRect frame = self.vContainerView.frame;
frame.size.height += cornerRadius;
frame.origin.y -= cornerRadius;
self.vContainerView.frame = frame;
self.vSeparatorLine.hidden = YES;
}
else
{
// cell is in the middle - extend height twice the height of corners and shift container view by the height of corners - therefore hide top and bottom lines and corners.
CGRect frame = self.vContainerView.frame;
frame.size.height += (2 * cornerRadius);
frame.origin.y -= cornerRadius;
self.vContainerView.frame = frame;
self.vSeparatorLine.hidden = NO;
}
}
- (void)prepareForReuse
{
// establish original values when cell is reused
CGRect frame = self.vBorderView.frame;
frame.size.height = BORDER_VIEW_HEIGHT;
frame.origin.y = 0;
self.vBorderView.frame = frame;
self.vSeparatorLine.hidden = YES;
}
Then in your data source you do this:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
// only one cell in section - must be rounded on top & bottom
if (indexPath.row == 0 && indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1)
{
cell.top = YES;
cell.bottom = YES;
}
// first cell - must be rounded on top
else if (indexPath.row == 0)
{
cell.top = YES;
cell.bottom = NO;
}
// last cell - must be rounded on bottom
else if (indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1)
{
cell.top = NO;
cell.bottom = YES;
}
else
{
cell.top = NO;
cell.top = NO;
}
return cell;
}
And voila - you got rounded corners AND borders on your sections.
Hope this helps!
P.S. Made a few edits as I found some bugs in original code - mainly I didn't set all values in all cases which cause some very stunning efects when cells were reused :)
I have created a method called addRoundedCornersWithRadius:(CGFloat)radius ForCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath which will create rounded corners at the top and bottom of every section.
The benefit of using the maskView property of UITableViewCell is that when you select the cell the rounded corners are still visible.
The answer by @jvanmetre is great and it works. Building on top of it and as suggested by @SergiySalyuk in the comments. I've update the code to use UIBezierPath instead making it simpler to understand and slightly faster.
My version also fixes the separator bug and adds a selected background view that fits with the cell.
Remember to set your table view to no separator: tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
Objective-C
- (void)tableView:(UITableView*)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath*)indexPath {
// Set transparent background so we can see the layer
cell.backgroundColor = UIColor.clearColor;
// Declare two layers: one for the background and one for the selecetdBackground
CAShapeLayer *backgroundLayer = [CAShapeLayer layer];
CAShapeLayer *selectedBackgroundLayer = [[CAShapeLayer alloc] init];
CGRect bounds = CGRectInset(cell.bounds, 0, 0);//Cell bounds feel free to adjust insets.
BOOL addSeparator = NO;// Controls if we should add a seperator
// Determine which corners should be rounded
if (indexPath.row == 0 && indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1) {
// This is the only row in its section, round all corners
backgroundLayer.path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(7, 7)].CGPath;
} else if (indexPath.row == 0) {
// First row, round the top two corners.
backgroundLayer.path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight cornerRadii:CGSizeMake(7, 7)].CGPath;
addSeparator = YES;
} else if (indexPath.row == [tableView numberOfRowsInSection:indexPath.section]-1) {
// Bottom row, round the bottom two corners.
backgroundLayer.path = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(7, 7)].CGPath;
} else {
// Somewhere between the first and last row don't round anything but add a seperator
backgroundLayer.path = [UIBezierPath bezierPathWithRect:bounds].CGPath;// So we have a background
addSeparator = YES;
}
// Copy the same path for the selected background layer
selectedBackgroundLayer.path = CGPathCreateCopy(backgroundLayer.path);
// Yay colors!
backgroundLayer.fillColor = [UIColor colorWithWhite:1.f alpha:0.8f].CGColor;
selectedBackgroundLayer.fillColor = [UIColor grayColor].CGColor;
// Draw seperator if necessary
if (addSeparator == YES) {
CALayer *separatorLayer = [CALayer layer];
CGFloat separatorHeight = (1.f / [UIScreen mainScreen].scale);
separatorLayer.frame = CGRectMake(CGRectGetMinX(bounds)+10, bounds.size.height-separatorHeight, bounds.size.width-10, separatorHeight);
separatorLayer.backgroundColor = tableView.separatorColor.CGColor;
[backgroundLayer addSublayer:separatorLayer];
}
// Create a UIView from these layers and set them to the cell's .backgroundView and .selectedBackgroundView
UIView *backgroundView = [[UIView alloc] initWithFrame:bounds];
[backgroundView.layer insertSublayer:backgroundLayer atIndex:0];
backgroundView.backgroundColor = UIColor.clearColor;
cell.backgroundView = backgroundView;
UIView *selectedBackgroundView = [[UIView alloc] initWithFrame:bounds];
[selectedBackgroundView.layer insertSublayer:selectedBackgroundLayer atIndex:0];
selectedBackgroundView.backgroundColor = UIColor.clearColor;
cell.selectedBackgroundView = selectedBackgroundView;
}
func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath)
{
if (tableView == self.orderDetailsTableView)
{
//Top Left Right Corners
let maskPathTop = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.TopLeft, .TopRight], cornerRadii: CGSize(width: 5.0, height: 5.0))
let shapeLayerTop = CAShapeLayer()
shapeLayerTop.frame = cell.bounds
shapeLayerTop.path = maskPathTop.CGPath
//Bottom Left Right Corners
let maskPathBottom = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.BottomLeft, .BottomRight], cornerRadii: CGSize(width: 5.0, height: 5.0))
let shapeLayerBottom = CAShapeLayer()
shapeLayerBottom.frame = cell.bounds
shapeLayerBottom.path = maskPathBottom.CGPath
//All Corners
let maskPathAll = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.TopLeft, .TopRight, .BottomRight, .BottomLeft], cornerRadii: CGSize(width: 5.0, height: 5.0))
let shapeLayerAll = CAShapeLayer()
shapeLayerAll.frame = cell.bounds
shapeLayerAll.path = maskPathAll.CGPath
if (indexPath.row == 0 && indexPath.row == tableView.numberOfRowsInSection(indexPath.section)-1)
{
cell.layer.mask = shapeLayerAll
}
else if (indexPath.row == 0)
{
cell.layer.mask = shapeLayerTop
}
else if (indexPath.row == tableView.numberOfRowsInSection(indexPath.section)-1)
{
cell.layer.mask = shapeLayerBottom
}
}
}
working code for swift...
what actually we are doing is if section has only one row then we do it on all sides, if section has multiple rows then we do it top on first row and bottom at last row... the properties BottomLeft, BottomRight, topLeft, TopRight should be of type rect corner(Suggestions from xcode when you are typing... there is another property content corner with same name.. so check that )
After trying some of the answers here, I decided to go whole hog and implement an entire subclass on top of UITableView and UITableViewCell to replicate the rounded grouped table view style in iOS 7.
I had to subclass layoutSubviews in UITableView to relayout each cell and accessory view so they were no longer edge-to-edge.
I had to subclass UITableViewCell in order to to remove the top and bottom separator hairline views (but leaving the ones inside the section untouched).
I then created a custom UITableViewCell background view that could optionally have rounded corners on the top and bottom to be used for the first and last cells in each section. Those elements had to be CALayer to avoid UITableView's implicit behaviour of changing the color of the background views when a user taps the cell.
Because they are now CALayer instances that don't respond to layoutSubviews, I then had to do some Core Animation tinkering to ensure the top and bottom cells resized at the same speed as the other cells when the user rotates the device.
All in all, it's possible to do, but since it requires a fair bit of effort and costs a small amount of performance (as it's constantly fighting Apple's code trying to set everything back), it might best to file a radar with Apple requesting they officially expose that style. Until then, feel free to use my library. :)
In iOS 13 and up, this table style is finally made available by Apple, without having to re-engineer it, with the new UITableView.Style.insetGrouped table view style.
In Xcode 11 and up, this can be set in the interface builder settings for the table view, by selecting Inset Grouped for the Style: