UITableView in UITableViewControllers row-dividers off? - ios

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
}
}
}

Related

Dynamic height for UITableView ( Not Cell )

I'm trying to have a UITableView that lists all the different HomeKit devices a user has available.
Obviously there is no way to know how many devices they have, so I need to have the UITableView's height in the storyboard change.
I've tried this, which I call in the viewDidLoad() function:
func adjustHeightOfTableView() {
//getting the height of the tableview
var tableHeight = self.tableView.contentSize.height
//the height of the content inside the view
var maxHeight = self.tableView.superview?.frame.size.height
//if the height of the content is bigger then the height of the tableview
if (maxHeight! > tableHeight) {
tableHeight = maxHeight!
//set the tableview height to be the content height
}
//trying to reload the tableview height?
self.view.layoutIfNeeded()
self.tableView.reloadData()
}
I am trying to have some UI Elements under the tableview, and I want them to be a set space from the bottom of the tableview, but also have the tableview be the height that it needs to be, for whatever amount of cells there is.
But it's just not working.
If I'm doing anything wrong, or if anyone knows how to make this work, please let me know.
Thanks!
Note: For this approach you need to have static cell height or figure out a way to know before hand whats the total contentsize height
Assuming you are using constraints, create following constraints on your UITableView (apart from leading and trailing!)
Add a height constraint with a priority of 750 and a bottom spacing constraint of 0 to your super view that will be >= 0 and have a priority of 1000. Create outlet for this height constraint that you created in your UIViewController
Now,
func adjustHeightOfTableView() {
//set the height to be equal to the number of elements multiplied by the height of each cell.
//or use some logic that allows you to know what content size or space the cells will occupy!
tableViewHeightConstraint.constant = dataArray.count * rowHeight
view.layoutIfNeeded()
}
Now if your UITableView height is less than super view, no problems! But if it is greater than screen bounds, it will break the height constraint and become full screen and display the content normally as you expect a UITableView to!
Edit:
Even if you are using UIAutomaticRowDimensions what you can do is add constraints programmatically to your UITableView. i.e
Of course all your other views will still have a bottom constraint to your UITableView.
Create a UITableView in your storyboard with normal leading, trailing, top and bottom to the super view. Fetch the data. Get the contentSize for your UITableView and then remove the bottom constraint. Now add a height constraint that will be the minimum value of your UIScreen.main().bounds.size.height and contentSize.
you can use Automatic Dimensions if you are using autolayouts
in view didload:
let nib = UINib(nibName: "YOURCELLNIB", bundle: nil)
tableView.registerNib(nib, forCellReuseIdentifier: "REUSEIDENTIFIER")
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
Remove the function
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath)
In your code, you have:
tableHeight = maxHeight!
//set the tableview height to be the content height
But this does not change the table height - it only changes some variable that previously was assigned the value of the old table content height. Nowhere in your code do you actually do anything to change the table height.
One way to change the table height directly is to assign it a completely new frame with values from the old frame, except for the frame's height, which you calculate however you like.
Try something like this (adding whatever other logic you need):
oldFrame = self.tableView.frame
newHeight = rowCount * rowHeight
self.tableView.frame = CGRectMake(oldFrame.origin.x, oldFrame.origin.y, oldFrame.size.width, newHeight)
There is a workaround which can make it seems like the height changes according to the number of the cells.
set tableview height to a proper value when init.
UITableView.init(frame: CGRect.init(x: 0, y: 70, width: self.view.frame.width, height: self.view.frame.height - 350))
set the tableview background color white transparent.
pulldownTableView?.backgroundColor = UIColor.white.withAlphaComponent(0)
set tableFooterView.
pulldownTableView?.tableFooterView = UIView(frame: CGRect.zero)
Below is the result, there are two table in the img. I set the transparent for the front tableview, left img set the backgroundColor to white, right white transparent.
----------------------vs----------------

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 to Add Margin Between UITableViewCells Without Being Hacky

A lot of the solutions I've seen here include changing the cell's background to an image and using sections for rows rather than just rows themselves. I'm looking to have only two sections and have each cell expand in height on tap, so neither of those solutions would work.
I saw one solution includes setting the frame of the cell in the layoutSubviews() function like so:
override func layoutSubviews() {
super.layoutSubviews()
self.frame = CGRectOffset(self.frame, 0, 10);
}
When I do this however, it only gives margin to one cell and that's only when I tap on the cell.
Is there a surefire way to add spacing in between UITableViewCells without being hacky and breaking the cell layouts in the process?
I did this yesterday pretty easily with auto layout.
I set the background of the cell and it's content view to clear, then I created a new view and setup constraints all around it and put my labels inside of it. The height changes dynamically based on the label so I needed to use UITableViewAutomaticDimension for the row height and give it an estimated row height as well.
I don't see why this wouldn't work for expanding it on a tap as well, you just might have to reload the cell.
make the cell and it's contentView transparent
contentView addSubview customContentView and layout your cell on customContentView
customContentView pin to contentView top leading trailing with offset 0 but pin to bottom with offset 10 //the margin height

UIView inside custom UITableViewCell wrong width / height

I ran into the following problem. I have a custom class UITableViewCell with a few UIViews inside, which act as container for chartviews.
The problem is if I do the following:
self.upperLeftChart = XYPieChart(frame: CGRectMake(0, 0, self.upperLeftContainer.frame.width, self.upperLeftContainer.frame.height))
self.upperLeftContainer.addSubview(self.upperLeftChart)
The Chartview is as big as the whole UITableViewCell instead of the size of my container.
If I print the size of my container
NSLog("\(self.upperLeftContainer.frame.width) / \(self.upperLeftContainer.frame.height)")
it prints: 320.0 / 568.0 which is wrong. My container is about a quarter of the whole cell.
I guess it hast something to do with my Autolayout + Constraints. If I set the size to 120x120px hardcoded, it works nicely.
Any trick to get the real width and height of UIView arranged with autolayout and constraints?
What you need to do is to override
override func layoutSubviews() {
super.layoutSubviews()
self.upperLeftCart.frame = CGRectMake(0, 0, self.upperLeftContainer.frame.width, self.upperLeftContainer.frame.height)
}
That way it will change frame of the view every time cell resizes

custom tableView's header view overlap the table view cells

I designed a custom view as my UITableView's header view. just like this
(I just put image link here instead of image since I don't have 10 reputations.)
http://i.stack.imgur.com/KhNbE.png
Then in my UITableViewController I use this view as tableHeaderView
override func viewDidLoad() {
tableView.tableHeaderView = headerView!
//...other things
}
I got text from a JSON to fulfill the ContentLabel. If the text is long, the headerView will overlap cells just like below image.(short text is OK)
http://i.stack.imgur.com/gtO2g.png
Section is visible but two lines of cell have been overlapped by the headerView.I'm not sure if I did wrong constraints or code on ContentLabel. Below is the code I configured the contentLabel in TopicHeaderView.swift
var content: String? {
didSet {
self.contentLabel.text = content!
self.setNeedsUpdateConstraints()
self.updateConstraintsIfNeeded()
self.setNeedsLayout()
self.layoutIfNeeded()
}
}
func setFrameHeight(height: CGFloat) {
var frame = self.frame
frame.size.height = height
self.frame = frame
}
override func layoutSubviews() {
super.layoutSubviews()
self.contentLabel.preferredMaxLayoutWidth = self.contentLabel.alignmentRectForFrame(contentLabel.frame).width
self.titleLabel.preferredMaxLayoutWidth = self.titleLabel.bounds.size.width
self.authorLabel.preferredMaxLayoutWidth = self.authorLabel.bounds.size.width
self.setFrameHeight(CGRectGetMaxY(contentLabel.frame) + 8)
}
I browsed similar questions in SO but seems I can't find a solution to fix my problem. Can anyone help on this?
EDITED:
I logged the origin CGPoint of my first tableView cell and headerView's height. It shows the right number which means the first cell is right next to the header view vertically. There is a 22 points gap because of the height of section of course.
headerheight:600.0
first cell's y: 622.0
So maybe it's the label problem that its height is too big to exceed the bounds of TableView headerView? I'm not sure.
EDITED:
Strange things happen. I logged the y value of headerView's bottom,contentLabel's bottom and first UITableViewCell's origin. Please see the image from the link in the question comment below(still need 10 reputation)
As you can see, from the value in console, the view sequence from top should be "contentLabel's bottom(value:224) - headerView's bottom bounds(value: 232) - first cell's origin(value:254)". But in simulator, the sequence is totally messed up.It turns "headerView's bottom bounds - first cell's origin - contentLabel's bottom"
I really appreciate if anyone can help on this.
Problem is, that UITableView does not automatically change positions of cells when its headerView's height changes. Thus you need to reload UITableView every time TopicHeaderView.content changes.
Select that header view, or imageView what you have there, and check Clip Subviews in Attributes Inspector tab.
This worked for me.

Resources