Hiding UITableViewCell separator for individual cells - ios

I require a UITableViewCell separator for the majority of my cells, but some of them should be hidden.
The way this is set is self.tableView.separatorStyle, which would apply to every cell.
A heavy-handed work around is to disable it, and then draw it onto cells manually as a UIView, but I don't want to go there.
How can I otherwise remove the separator for individual cells?

You can have custom cell and add separator in this cell, this way you can manage your cell separator. Or you can addSubView a UIView for separator to your cell like iPatel's answer, but be careful for memory issue.

Use a different ReUseIdentifier for the cells which you don need separatorStyle.Don draw anything onto this cell.

Adding a custom view as a separator is a lot of work. Besides adding a property, setting constraints, etc, you need to worry about matching the color and line width of the default separators (1.0 / view.window.screen.scale so it's one pixel tall, not one point).
It's way easier to use the default separator view, and hide it by setting the insets. For example, turn on separators for the table, then in your UITableViewCell subclass do something like this:
var showsSeperator: Bool = true {
didSet {
let leftInset: CGFloat
if showsSeperator {
leftInset = layoutMargins.left
} else {
leftInset = 1000 // Out of site.
}
separatorInset = UIEdgeInsets(top: 0, left: leftInset, bottom: 0, right: 0)
}
}

Related

Autolayout ignores multi-line detailTextLabel when calculating UITableViewCell height (all styles)

So I'm trying to use the built-in UITableViewCell styles - specifically UITableViewCellStyleSubtitle - with a (single) line textLabel but multiline detailTextLabel. But the (auto) calculated cell height is consistently too short, and appears to ignore that there is more than 1 line of detail.
I've tried using numberOfLines=0, estimatedRowHeight, UITableViewAutomaticDimension, preferredMaxWidthLayout, etc, but in all the permutations the behavior - indeed for all the UITableViewCell styles - is it appears the UITableViewAutomaticDimension cell height calculation will correctly account for a multiline textLabel (yay!), but incorrectly assumes the detailTextlabel is at most single line (nay!). Consequently, cells with a multiline detailTextLabel are too short, and hence the cell content spills over the top and bottom of the cell.
I've posted a quick test app showing this behavior on GitHub here. Adding additional lines of text is fine - all the cell styles appropriately increase in height to accommodate - but adding additional lines of detail does nothing to change the cell height, and quickly causes the content to spill over; the text+detail are themselves laid out correctly, and together centered correctly over the middle of the cell (so in that sense layoutSubviews is working correctly), but the overall cell height itself is unchanged.
It almost seems like there are no actual top & bottom constraints between the cell.contentView and the labels, and instead the cell height is being calculated directly from the height of the (possibly multi-line) textLabel and (only single-line) detailTextLabel, and then everything is centered over the middle of the cell... Again, multiline textLabel is fine, and I'm doing nothing different between the textLabel and detailTextLabel, but only the former (correctly) adjusts the cell height.
So my question is, if it is possible to use the built-in UITableViewCell styles to reliably display multiline detailTextLabels, or is it simply not possible and you need to create a custom subclass instead? [or, almost equivalently, without having to override layoutSubviews in a subclass and rewire all the constraints manually].
[4 May 2016] Conclusion: as of iOS9 multi-line detailTextLabels dont work as expected with UITableViewAutomaticDimension; the cell will be consistently too short and the text/detail will spill over the top and bottom. Either you must manually compute the correct cell height yourself, or create and layout your own equivalent custom UITableViewCell subclass, or (see my answer below) subclass UITableViewCell and fix systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to return the correct height [recommended]
Further investigations (see UITableViewCellTest) indicate that when UITableViewAutomaticDimension is enabled the system calls -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to calculate the cell height, and that this pretty much ignores the height of the detailTextLabel in its computation (bug !?). As a result, for UITableViewCellStyleSubtitle the cell height is always going to be too short [a single-line detailTextLabel may not quite spill over the cell, but that's only because of the existing top and bottom margins], and for UITableViewCellStyleValue1 or UITableViewCellStyleValue2 the height will be too short whenever the detailTextLabel is taller (eg more lines) than the textLabel. This is all a moot point for UITableViewCellStyleDefault which has no detailTextLabel.
My solution was to subclass and fix with:
- (CGSize)systemLayoutSizeFittingSize:(CGSize)targetSize
withHorizontalFittingPriority:(UILayoutPriority)horizontalFittingPriority
verticalFittingPriority:(UILayoutPriority)verticalFittingPriority
{
// Bug finally fixed in iOS 11
if ([UIDevice.currentDevice.systemVersion compare:#"11" options:NSNumericSearch] != NSOrderedAscending) {
return [super systemLayoutSizeFittingSize:targetSize
withHorizontalFittingPriority:horizontalFittingPriority
verticalFittingPriority:verticalFittingPriority];
}
[self layoutIfNeeded];
CGSize size = [super systemLayoutSizeFittingSize:targetSize
withHorizontalFittingPriority:horizontalFittingPriority
verticalFittingPriority:verticalFittingPriority];
CGFloat detailHeight = CGRectGetHeight(self.detailTextLabel.frame);
if (detailHeight) { // if no detailTextLabel (eg style = Default) then no adjustment necessary
// Determine UITableViewCellStyle by looking at textLabel vs detailTextLabel layout
if (CGRectGetMinX(self.detailTextLabel.frame) > CGRectGetMinX(self.textLabel.frame)) { // style = Value1 or Value2
CGFloat textHeight = CGRectGetHeight(self.textLabel.frame);
// If detailTextLabel taller than textLabel then add difference to cell height
if (detailHeight > textHeight) size.height += detailHeight - textHeight;
} else { // style = Subtitle, so always add subtitle height
size.height += detailHeight;
}
}
return size;
}
And in the view controller:
- (void)viewDidLoad {
[super viewDidLoad];
self.tableView.estimatedRowHeight = 44.0;
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
You can pull the full subclass from here: MultilineTableViewCell
So far this fix appears to work well, and has let me successfully use the built-in UITableViewCellStyles with multiline text and details, in self-sizing cells with dynamic type support. This avoids the trouble (and mess) of manually computing the desired cell heights in tableView:heightForRowAtIndexPath:, or having to create custom cell layouts.
[(PARTLY)FIXED IN iOS11]
Apple finally fixed this bug in iOS11 (but apparantly only for UITableViewCellStyleSubtitle). I've updated my solution to only apply the necessary correction to pre-11 devices (otherwise you'll end up with extra space top and bottom of your cell!).
#tiritea 's answer in Swift 3 (Thanks again! :D)
// When UITableViewAutomaticDimension is enabled the system calls
// -systemLayoutSizeFittingSize:withHorizontalFittingPriority:verticalFittingPriority: to calculate the cell height.
// Unfortunately, it ignores the height of the detailTextLabel in its computation (bug !?).
// As a result, for UITableViewCellStyleSubtitle the cell height is always going to be too short.
// So we override to include detailTextLabel height.
// Credit: http://stackoverflow.com/a/37016869/467588
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
self.layoutIfNeeded()
var size = super.systemLayoutSizeFitting(targetSize, withHorizontalFittingPriority: horizontalFittingPriority, verticalFittingPriority: verticalFittingPriority)
if let textLabel = self.textLabel, let detailTextLabel = self.detailTextLabel {
let detailHeight = detailTextLabel.frame.size.height
if detailTextLabel.frame.origin.x > textLabel.frame.origin.x { // style = Value1 or Value2
let textHeight = textLabel.frame.size.height
if (detailHeight > textHeight) {
size.height += detailHeight - textHeight
}
} else { // style = Subtitle, so always add subtitle height
size.height += detailHeight
}
}
return size
}
It looks like Apple has resolved this bug in iOS 11.
Swift 3
After reading various answers, I have used following method for get ride of detail text label UITableViewAutomaticDimension issue . Use Basic style cell with title label only and use attributed string for Text and detail text view. Don't forget to Change tableview cell style from Subtitle to Basic.
func makeAttributedString(title: String, subtitle: String) -> NSAttributedString {
let titleAttributes = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .headline), NSForegroundColorAttributeName: UIColor.purple]
let subtitleAttributes = [NSFontAttributeName: UIFont.preferredFont(forTextStyle: .subheadline)]
let titleString = NSMutableAttributedString(string: "\(title)\n", attributes: titleAttributes)
let subtitleString = NSAttributedString(string: subtitle, attributes: subtitleAttributes)
titleString.append(subtitleString)
return titleString
}
How to use in cellforrowatindexpath
cell.textLabel?.attributedText = makeAttributedString(title: "Your Title", subtitle: "Your detail text label text here")
Add Following lines in viewdidload
YourTableView.estimatedRowHeight = UITableViewAutomaticDimension
YourTableView.rowHeight = UITableViewAutomaticDimension
YourTableView.setNeedsLayout()
YourTableView.layoutIfNeeded()
From my experience the built in cells don't support auto resize with constraints, I think the best solution is to create a custom cell, it really takes a couple of minutes and you don't need to override layoutSubview, it is really simple .
Just change the type of the cell in the IB to custom, drag a label , set constraints (in the IB), set number of rows , create a subclass, change the cells class in the IB to your subclass, create an outlet in the subclass and that's most of the work,
I am sure there are a lot of tutorials on the net you can follow.

How do I center cells in table view?

I want to center my cells horizontally in my table view. Currently, they stick to the left. How do I go about doing this?
I've looked up various questions on SO and webpages, but they're all in Objective C, and I use Swift.
You shouldn't try to center the cells of your table view. You should allow Cocoa to manage the size of your cells and you should center your content within the content view of the cell.
There are 2 things you need to do:
First, you need to make sure you set the table style to Default: UITableViewCellStyleDefault. All other styles use a detailTextLabel in one way or another and you won't be able to set the textLabel's alignment property.
UITableViewCell(style: .Default, reuseIdentifier: CellIdentifier)
Then you can set the alignment of your cell's textLabel:
cell.textLabel.textAlignment = .Center
There are two approaches as listed below
1- you have to create cell w.r.t tableview width but drag your cell content in centre from nib.
2- You have to decrease tableview size so your cell fit with tableview.
if don't subclass the tableview cell, then subclass the UITableViewCell
and just override below method,
in CustomCell add below method
override func layoutSubviews()
{
super.layoutSubviews()
self.frame = CGRectMake(self.frame.origin.x + 100, self.frame.origin.y, 100 , 100);
//option 2
// self.bounds = CGRectMake( 100,0, 100 , 100);//calculate centre
}
in above u must use current frame react and calculate the centre according to your requirement, as u see i use the current frame of the cell if u ignore this, the x and y position set to zero the cell is always at the (0,0,width,height) of the tableview
and also also u can also use bounds it worked i comment it as option 2

UITableView in UITableViewControllers row-dividers off?

I've got my custom UITableViewCell working now, with 'dynamic' height using Auto Layouts.
However, these row-dividers are kind of off.
It's a UITableViewController. The width of the image is the full width of the iPhone in the simulator.
Anyone have a clue? It's kind of a UITableViewController right of the shelf, not much code in it, mostly code for datasource/delegation.
To clearify I want the separators, but I want them equally indented on both sides. The default indention is fine, which is on the left side, but not the right side.
As Fogmeister mentioned, you could remove the separators entirely and just add a separator view on your custom table cells or you could extend the separators by setting the
cell.separatorInset = UIEdgeInsetsZero
cell.layoutMargins = UIEdgeInsetsZero
tableView.separatorInset = UIEdgeInsetsZero
tableView.layoutMargins = UIEdgeInsetsZero
note that this is only available for iOS 8 onwards.
These are called separators.
You can turned them off in Interface Builder as a property on the tableView.
Select the tableview and select the "None" property for the separator.
The default for the UITableViewCell separators is to be indented. However, by digging into your UITableViewCell's subviews, you can move and size the separator by altering its frame.
In your custom UITableViewCell class, override the layoutSubviews() method so you can grab the separator object as the cell's subviews are being laid out by iterating through your cell's subviews and check for a subview of the UITableViewCellSeparator type. If you want to make the separator span the entire cell's width, for example, change its frame's origin.x to 0 and make the separator the full width of the cell's contentView.
override func layoutSubviews() {
super.layoutSubviews()
for subview in self.subviews {
if subview.dynamicType == NSClassFromString("_UITableViewCellSeparatorView") {
var newFrame = subview.frame
newFrame.origin.x = 0
newFrame.size.width = self.contentView.frame.width
separator.frame = newFrame
}
}
}

Disable separator for a single cell

What I'd like to do is remove the separator for a single cell in a tableview. I'd like to keep the others ones as they are but I can't seem to find a way to do it.
I thought I could adjust the insets but from what I can see, these aren't the insets that I am trying to modify.
One easy way to get the same effect, but not answering exactly your question, is to disable UITableView's separators and adding a 1px-height UIView to the prototype cells you want to have it.
Yes, you can set cell separator inset in willDisplayCell.
if (indexPath.row == HideTheRowSeparator) {
cell.separatorInset = UIEdgeInsetsMake(0, tableView.bounds.size.width, 0, 0);
}

UITableView + Add content offset at top

I need to add some blank space to the top of my UITableView that does not affect the size of the content area. Shifting the content down or adding a blank cell is NOT what I want to do. Instead I just want an offset.
How?
I'm not sure if I'm following you but I think I'm having the same predicament. In my case I must give some space to the ADBannerView at the top of the screen so what I did was in the viewDidLoad method I added:
[self.tableView setContentInset:UIEdgeInsetsMake(50,0,0,0)];
the values it takes are UIEdgeInsetsMake(top,left,bottom,right).
Alternatively the same with Swift:
self.tableView.contentInset = UIEdgeInsetsMake(50, 0, 0, 0)
Swift 4.2:
self.tableView.contentInset = UIEdgeInsets(top: 50, left: 0, bottom: 0, right: 0)
Swift 5.1
add the following in viewDidLoad
tableView.contentInset.top = 100
Really that's all there is to it.
override func viewDidLoad() {
super.viewDidLoad()
tableView.contentInset.top = 100
}
You can add an "empty" header view to the table... this would give the initial appearance of the table to have an offset, but once you started scrolling the offset would be gone. NOt sure that's what you want.
If you need a permanent offset and are not already using section headers, then you could create the offset similarly to above by making custom views for the section headers, especially if you just have one section, this could give you the look of a permanent offset.
I can post sample code if it sounds like either of those are what you are looking for.
I combined Jigzat's answer with:
[self.tableView scrollRectToVisible:CGRectMake(0, 0, 320, 1) animated:NO];
in
- (void)viewDidLoad
so the first cell isn't at the top.
Sounds like you want to wrap a 'View' around your UITableView. If you have a UITableViewController in IB the UITableView will automatically be set to the view of UITableViewController. You change view property to a normal UIView and add your UITableView in there and give it a offset.
---Edit---
I just read my post and thought it made little sense :) When you create a UITableViewController you get this (in pseudo code):
UITableViewController.view = UITableView
This means that the actual table will take up the whole space and you cannot even add other views. So you need to change the
UITableViewController.view = UIView
and add your table to that UIView
I combined this answer with this one:
https://stackoverflow.com/a/9450345/1993937
To make the tableView appear at the top of the content inset, so the space at the top isn't cut off by having the tableView scrolled down slightly when the view initially appears. (18 is my top gap)
[self.tableView setContentInset:UIEdgeInsetsMake(18,0,0,0)];
[self.tableView setContentOffset:
CGPointMake(0, -self.songListTable.contentInset.top) animated:YES];

Resources