Why isn't my tableview row height being set properly? - ios

I am trying to make a table view with a dynamic cell containing an image view and 3 labels in Interface Builder, but for some reason the table view row height isn't being set properly and all the content is being cut off. This is the only code for it:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100.0
}
And my interface builder constraints are almost similar to the ones shown below from a Ray Wenderlich tutorial (http://www.raywenderlich.com/87975/dynamic-table-view-cell-height-ios-8-swift)
Here is a sample of the simulator output:
If anyone has any insight on how I can fix this, I'd be more than happy to hear you out. As you probably guessed, I am trying to make it so that the cell height expands according to the subtitle length (a la Twitter) but to no avail :( Thanks!
Update: It looks a bit better now but still not expanding as the UILabel expands :(

In the UIViewController which has your UITableView as a child view(or it can be UITableViewController) make your table view cell height "100" too.I think it is 44 now?
Also if it not set already, set your subtile's "Lines" property to "0" in Interface Builder to make it self-sizing.

You are using the estimatedrow height. There is an another delegate method for height that you have to use. It is just rowheight delegate. Use heightForRowAtItem index version of objective c in swift.

Solved it! My constraints were correct but for some odd reason the tableView wasn't updating. Adding the following code seemed to make it fine:
override func viewDidAppear(animated: Bool) {
tableView.reloadData()
}
If you have any better suggestions feel free!

Related

A mystery about iOS autolayout with table views and self-sizing table view cells

To help in following this question, I've put up a GitHub repository:
https://github.com/mattneub/SelfSizingCells/tree/master
The goal is to get self-sizing cells in a table view, based on a custom view that draws its own text rather than a UILabel. I can do it, but it involves a weird layout kludge and I don't understand why it is needed. Something seems to be wrong with the timing, but then I don't understand why the same problem doesn't occur for a UILabel.
To demonstrate, I've divided the example into three scenes.
Scene 1: UILabel
In the first scene, each cell contains a UILabel pinned to all four sides of the content view. We ask for self-sizing cells and we get them. Looks great.
Scene 2: StringDrawer
In the second scene, the UILabel has been replaced by a custom view called StringDrawer that draws its own text. It is pinned to all four sides of the content view, just like the label was. We ask for self-sizing cells, but how will we get them?
To solve the problem, I've given StringDrawer an intrinsicContentSize based on the string it is displaying. Basically, we measure the string and return the resulting size. In particular, the height will be the minimal height that this view needs to have in order to display the string in full at this view's current width, and the cell is to be sized to that.
class StringDrawer: UIView {
#NSCopying var attributedText = NSAttributedString() {
didSet {
self.setNeedsDisplay()
self.invalidateIntrinsicContentSize()
}
}
override func draw(_ rect: CGRect) {
self.attributedText.draw(with: rect, options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin], context: nil)
}
override var intrinsicContentSize: CGSize {
let measuredSize = self.attributedText.boundingRect(
with: CGSize(width:self.bounds.width, height:10000),
options: [.truncatesLastVisibleLine, .usesLineFragmentOrigin],
context: nil).size
return CGSize(width: UIView.noIntrinsicMetric, height: measuredSize.height.rounded(.up) + 5)
}
}
But something's wrong. In this scene, some of the initial cells have some extra white space at the bottom. Moreover, if you scroll those cells out of view and then back into view, they look correct. And all the other cells look fine. That proves that what I'm doing is correct, so why isn't it working for the initial cells?
Well, I've done some heavy logging, and I've discovered that at the time intrinsicContentSize is called initially for the visible cells, the StringDrawer does not yet correctly know its own final width, the width that it will have after autolayout. We are being called too soon. The width we are using is too narrow, so the height we are returning is too tall.
Scene 3: StringDrawer with workaround
In the third scene, I've added a workaround for the problem we discovered in the second scene. It works great! But it's horribly kludgy. Basically, in the view controller, I wait until the view hierarchy has been assembled, and then I force the table view to do another round of layout by calling beginUpdates and endUpdates.
var didInitialLayout = false
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
if !didInitialLayout {
didInitialLayout = true
UIView.performWithoutAnimation {
self.tableView.beginUpdates()
self.tableView.endUpdates()
}
}
}
The Mystery
Okay, so here are my questions:
(1) Is there a better, less kludgy workaround?
(2) Why do we need this workaround at all? In particular, why do we have this problem with my StringDrawer but not with a UILabel? Clearly, a UIlabel does know its own width early enough for it to give its own content size correctly on the first pass when it is interrogated by the layout system. Why is my StringDrawer different from that? Why does it need this extra layout pass?

Swift UITableView can't calculate content height properly

I'm having this weird issue with UITableView that can't calculate it's content's height properly.
I have custom UITableView class that is embedded in another custom UITableView, I want it to auto-adjust it's height to fit content so I have already:
override var contentSize: CGSize {
didSet {
self.invalidateIntrinsicContentSize()
}
}
override var intrinsicContentSize: CGSize {
self.layoutIfNeeded()
return self.contentSize
}
And now when I use:
self.estimatedRowHeight = UITableView.automaticDimension // non-zero value like 40 isn't working either
self.rowHeight = UITableView.automaticDimension
the output is the frame that is not full height, when I turn "Scrolling enabled" in this TableView it's scrollable with full content (don't want that):
Now when I change
self.estimatedRowHeight = UITableView.automaticDimension
to:
self.estimatedRowHeight = 0
the output is exactly what I would want to have except the content text is cut...
Here's my CommentCell:
Console isn't showing any errors with autolayout in any case.
Do you maybe know what's going on? I have spent literally days trying to get those comments to work and that's the last thing I need.
If you need any more info please just tell me.
Edit:
If i change estimatedRowHeight to a large number for example 500 I get loads of empty space under cells:
So it looks like TableView can't fix the cell height to content. Maybe this will help someone.
Maybe it's about the textfield inside the CellView. Did you set it's Layout to wraps?
Also I would try to set it's intrinsic size value to 'placeholder' inside the Size Inspector.

UITableView still leaves space on top even after disabling contentInset Xcode 9.4.1

I'm trying to place a table view inside of a basic view controller without any padding on the top and nothing I've tried has worked, no matter what I do there is a gap up top. I placed the following code in my viewDidLoad() for the view controller:
if #available(iOS 11, *) {
tableView.contentInsetAdjustmentBehavior = .never
} else {
self.automaticallyAdjustsScrollViewInsets = false
}
I've gone into storyboard and disabled those settings as well manually:
I do have custom cells I'm not sure if that matters, I do add insetting to the cells but even when I remove that code I have that gap.
Here's the code I customized for my cells:
override func layoutSubviews() {
super.layoutSubviews()
contentView.frame = UIEdgeInsetsInsetRect(contentView.frame, UIEdgeInsetsMake(cellSpacingHeight, 0, 0, 0))
}
I've spent a couple hours googling around and I can't figure it out. I'm using the latest swift and Xcode to build this. I even tried to print the values for the tableView's content inset and it came up as all 0s. Does anyone know why I still have the offset or inset up top in my table view?
Wow okay so I finally figured it out, I had set the table view style to Grouped at some point instead of Plain. Once I made it plain, everything worked, I hope this might help someone else in this position!

How to prevent UILabel to fill the entire screen?

I am trying to display a UILabel that may take up multiple lines but I'm having problem with how the height is resized.
Here is what it looks when I have text over a single line, displaying correctly:
When the text spans multiple lines however this happens:
Here's the interface builder settings I'm using:
Ideally I'd like the text view to remain at athe top of the screen and just take up as much space as it needs to diaplay the text but I really can't tell where I am going wrong.
The text view is a bit tricky to handle with automatic layout. If possible use an UILabel. If not then there are several issues with the text view and the most manageable solution is to add the height constraint which is then manipulated in the code.
The height of the text view content can be determined as:
let height = textView.sizeThatFits(textView.frame.size).height
It is also possible to use
let height = textView.contentSize.height
But the results are sometimes incorrect.
You do need to then set the delegate for the text view so that on change you will refresh the size of the text view.
Well you did give it permission to do so based on your constraints. Any height > 0 as long as it's 20 from the top margin. Since you don't have any other views to base your height off of you can hook up an outlet to your label and use this:
#IBOutlet weak var label: UILabel!
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
label.sizeToFit()
}
Uncheck the "Preferred Width" explicit checkbox(In Size Inspector)
Remove the height constraint on you UILabel.
It will definitely work.

UITableViewCell + Dynamic Height + Auto Layout

I came from this great answer: Using Auto Layout in UITableView for dynamic cell layouts & variable row heights
I've implemented the things described in that answer but I'm facing with a little different scenario. I haven't one UILabel but instead I have a dynamic list of UILabels.
I've created an image showing some different cases of what the table view should look:
At the current state of the repo the cell doesn't grow vertically to fit the cell's contentView.
UPDATE
REPO: https://github.com/socksz/DynamicHeightCellAutoLayout
If you try to get the project from the repo and run it, you can see exactly what is the problem I'm referring. I can't get what is missing for let it works.
The problem here is with the third party component you are using, FXLabel, not with any of the code around table views or Auto Layout in them. In order to support Auto Layout, custom subclasses of UIView must implement the -[intrinsicContentSize] method appropriately, and then call -[invalidateIntrinsicContentSize] when something changes it.
In this case, FXLabel appears to be relying on its superclass implementation (UILabel) for the above methods, and since UILabel was not designed to handle variable line spacing in the way that FXLabel implements it, it doesn't know the correct intrinsicContentSize to return, and therefore the Auto Layout calculations are wrong (in this case, since the intrinsic content size is too small). Check out the "Enabling Custom Views for Auto Layout" section of this excellent obcj.io article for more details.
Now the good news is that as of iOS 6, you should be able to accomplish this using an attributed string in a standard UILabel. Check out the Stack Overflow answer here.
If for some reason you really like FXLabel, perhaps you could open an issue on the GitHub project (or try and fix it yourself and submit a pull request).
To set automatic dimension for row height & estimated row height, ensure following steps to make, auto dimension effective for cell/row height layout.
Assign and implement dataSource and delegate
Assign UITableViewAutomaticDimension to rowHeight & estimatedRowHeight
Implement delegate/dataSource methods (i.e. heightForRowAt and return a value UITableViewAutomaticDimension to it)
-
#IBOutlet weak var table: UITableView!
override func viewDidLoad() {
super.viewDidLoad()
// Don't forget to set dataSource and delegate for table
table.dataSource = self
table.delegate = self
// Set automatic dimensions for row height
// Swift 4.2 onwards
table.rowHeight = UITableView.automaticDimension
table.estimatedRowHeight = UITableView.automaticDimension
// Swift 4.1 and below
table.rowHeight = UITableViewAutomaticDimension
table.estimatedRowHeight = UITableViewAutomaticDimension
}
// UITableViewAutomaticDimension calculates height of label contents/text
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
// Swift 4.2 onwards
return UITableView.automaticDimension
// Swift 4.1 and below
return UITableViewAutomaticDimension
}
For label instance in UITableviewCell
Set number of lines = 0 (& line break mode = truncate tail)
Set all constraints (top, bottom, right left) with respect to its superview/ cell container.
Optional: Set minimum height for label, if you want minimum vertical area covered by label, even if there is no data.

Resources