I am trying to make a UITableView line up with the height sizing of paragraphs in a UITextView. Example: The timestamps to the left are what I am trying to do. I changed my code to use UIView's instead of TVcells to see what was wrong and you can see the orange view is overlapping the cyan one, meaning that the views don't actually line up but they overlap. NOTE: I am wanting to use the TableView not UIView's I am having trouble understanding how the text heights are calculated in iOS. I am using the below code to get the heights of each paragraph:
let liveParagraphView = textView.selectionRects(for: txtRange).reduce(CGRect.null) { $0.union($1.rect) }
After this I calculate the height of each then feed that into my UITableView heightForRowAt
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let models = getParagraphModel()
let height = models[indexPath.row].height
let finalHeight = models[indexPath.row].height
let heightValue = finalHeight
return CGFloat(heightValue);
}
Every line has different height values but even when using these values it's not lining up. The problem seems to be that every line calculates a Y Position which is not directly under the line before it. It's ON TOP OF!! Resulting in the UITableView not being alined when new cells are added and that 'overlay' of the selectionRects isn't taken into account. Am I correct by this? How could I go about achieving this?
Swift 5
Firstly you should set your textView (which is in the cell) dynamic height:
textView.translatesAutoresizingMaskIntoConstraints = true
textView.sizeToFit()
textView.isScrollEnabled = false
Then calculate your textView's number of lines in textDidChange etc. for update tableView's layout.
let numOfLines = (yourTextView.contentSize.height / yourTextView.font.lineHeight) as? Int
When textView's text one line down you should update tableView layout:
tableView.beginUpdates()
tableView.endUpdates()
And then you should set your tableView cell's intrinsicContentSize for dynamic rowHeight:
Set your cell's (which is the contains textView) layout without static height,
Set your tableView's rowHeight and estimatedRowHeight:
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 44 // whatever you want
So now you have tableView cell with dynamicHeight
Related
I know how to make a custom self sizing cell. But for some reason I'm facing challenges when trying to make a default one multi-line.
What I currently want is a cell which only has one label. So the default one with a built-in style "Basic" seems to be the best solution for something as simple as that. However it only shows 2 lines of text.
My current set-up: a static UITableView and a UITableViewController containing outlets to some of the cells that need to be configured.
Things I tried:
set number of lines to 0
set table view's row height to UITableViewAutomaticDimension
override heightForRowAtIndexPath so that it always returns UITableViewAutomaticDimension
call sizeToFit, setNeedsLayout, layoutIfNeeded on the cell and/or content view and/or text label
set custom cell height to 0 in storyboard
increase vertical and horizontal content hugging priorities for the label
EDIT:
I guess I wasn't really clear about what exactly is the problem. I'm not using a custom cell. I'm trying to get away with the basic one.
This means you can't add any constraints to its label. Sure, you can programmatically but since everything is managed internally for Apple's built-in styles it may result in a conflict.
Additional details:
At this point (as I mentioned above) I have a UITableViewController with outlets to specific cells: #IBOutlet weak var descriptionCell: UITableViewCell!
override func viewDidLoad() {
super.viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
}
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
Is it really possible? Since I already spent too much time trying to avoid making a custom cell I'll finally go make it. Anyway any solution is welcome.
It's no need to do following two.
call sizeToFit, setNeedsLayout, layoutIfNeeded on the cell and/or content view and/or text label
set custom cell height to 0 in storyboard
And you should check you label's constraints. For example, it should has fixed width at run time and has constraints with cell's top and bottom. So the cell will grow itself.
Try this approach:
Set number of lines to 0
Set table view's row height to UITableViewAutomaticDimension
Override heightForRowAtIndexPath so that it always returns UITableViewAutomaticDimension
NsLayConstraints for label:
top = cell.top
bottom = cell.bottom
leading = cell.leading.padding
trailing = cell.leading.padding (Padding is optional)
It works for me. All you need to do in your code is
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 44.0 // or whatever height is closest to what your cells will be
tableView.rowHeight = UITableViewAutomaticDimension
}
Everything you listed after "set table view's row height to UITableViewAutomaticDimension" is not necessary. Just set number of lines in your cell's label to 0 like you did and include the code above.
Check out the second UILabel inside of the pink UITableViewCell. As you can see, the text inside it is cut-off. I've been trying to get the height of this UILabel to correctly expand or shrink as a function of how much text is plugged into it - and its not working.
• I'm using AutoLayouts
• The UILabel is pinned on all 4 sides to the ContentView - which of course is inside the UITableViewCell
• The Cells are all Static cells by the way, not Prototype cells.
• Here's the code I'm using:
(in viewWillAppear)
descriptionLabel.text = " < a bunch of text goes here > "
descriptionLabelSize = descriptionLabel.sizeThatFits(descriptionLabel.bounds.size)
// here's a global var I use to store the height:
descriptionLabelHeight = descriptionLabelSize.height
then, in viewDidAppear (and FYI, I also tried putting the following in will and didLayoutSubviews and in all sorts of other permutations:)
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
var newFrame = descriptionLabel.frame
newFrame.size.height = descriptionLabelHeight
descriptionLabel.frame = newFrame
descriptionLabel.setNeedsLayout()
view.layoutIfNeeded()
}
Finally, in heightForRowAtIndexPath I use the global var to adjust the TableViewCell's height:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
switch indexPath.row {
case 0: return 50
case 1: return descriptionLabelHeight+20 // the + 20 is for padding & margin
default: return 50
}
}
The result is mixed: the height of the Label - and the Cell housing it - does adjust - but not enough. It always ends up being too short. And I don't mean that it just cuts off a couple of words, its way too short, cutting off multiple sentences.
But again: the height does adjust. And that's the weird part. Its working - just not enough.
Any tips would be greatly appreciated!
You need to tell table view to dynamically scale the cells based on content:
tableView.rowHeight = UITableViewAutomaticDimension
Then, you can remove your implementation of heightForRowAtIndexPath: - cells should scale automatically. Remember to setup layout constraints correctly though. Without it dynamic sizing won't work.
I followed this tutorial to create a dynamic height UITableViewCell.
My prototype cell has 1 label (pretty simple). That label has constraints set to "0" for all sides of the cell edges. That label has numberOfLines = 0 and wraps the word.
In my UITableView setup, I do this:
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 100.0 //some random number
Basically, everything works. Each cell has its own different height.
The problem arises when I update that label with different text. The height of the cell stays the same, and the text just gets truncated OR white space appears.
Is there a way to tell the cell to "redraw" itself so that it calculates the height again?
Ideally, I'd like to do this inside the UITableViewCell class, since that's where it receives a notification to update the label text.
You have to implements estimatedHeightForRowAtIndexPath and calculate your label's size.
for example :
override func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath
indexPath: NSIndexPath) -> CGFloat {
let model = YourModelArray[indexPath.row]
// Label fontName and size
let attributes = [
NSFontAttributeName : UIFont(name: "FontName-Regular", size: 16) as! AnyObject]
let nsStringText = model.text as! NSString
// Calculate the size here
//--------------------//
//--10--UILabel--10--//
//-----Element------//
//------------------//
let size = nsStringText.
boundingRectWithSize(CGSize(width: self.tableView.frame.width - 20,
height: 1000),
options:[NSStringDrawingOptions.UsesLineFragmentOrigin,
NSStringDrawingOptions.UsesFontLeading],
attributes: attributes, context: nil)
// Basically add other elements heights and vertical Spacing between them
return size.height + element.frame.height + verticalSpacingBetweenLabelAndElement
}
I have been struggling this issue for 3 days and still can not figure it out. I do hope anyone here can help me.
Currently, i have an UITableView with customized cell(subclass of UITableViewCell) on it. Within this customized cell, there are many UILabels and all of them are set with Auto Layout (pining to cell content view) properly. By doing so, the cell height could display proper height no matter the UILabel is with long or short text.
The problem is that when i try to set one of the UILabels (the bottom one) to be hidden, the content view is not adjusted height accordingly and so as cell.
What i have down is i add an Hight Constraint from Interface Builder to that bottom label with following.
Priority = 250 (Low)
Constant = 0
Multiplier = 1
This make the constrain with the dotted line. Then, in the Swift file, i put following codes.
override func viewDidLoad() {
super.viewDidLoad()
//Setup TableView
tableView.allowsMultipleSelectionDuringEditing = true
//For tableView cell resize with autolayout
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
}
func tableView(tableView: UITableView, willSelectRowAtIndexPath indexPath: NSIndexPath) -> NSIndexPath? {
let cell = tableView.cellForRowAtIndexPath(indexPath) as! RecordTableViewCell
cell.lbLine.hidden = !cell.lbLine.hidden
if cell.lbLine.hidden != true{
//show
cell.ConstrainHeightForLine.priority = 250
}else{
//not show
cell.ConstrainHeightForLine.priority = 999
}
//tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
return indexPath
}
The tricky thing is that when i call tableView.reloadRowAtIndexPaths(), the cell would display the correct height but with a bug that it has to be trigger by double click (selecting) on the same row rather than one click.
For this, i also try following code inside the willSelectRowAtIndexPath method, but none of them is worked.
cell.contentView.setNeedsDisplay()
cell.contentView.layoutIfNeeded()
cell.contentView.setNeedsUpdateConstraints()
Currently the result is as following (with wrong cell Height):
As showed in the Figure 2, UILabel 6 could be with long text and when i hide this view, the content view is still showing as large as it before hiding.
Please do point me out where i am wrong and i will be appreciated.
I finally change the code
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.None)
to the following
tableView.reloadData()
Then, it work perfectly.
However, i don't really know the exactly reason on it. Hope someone can still comment it out.
I have a table view with a bunch of cells (custom cell, which only has its content view).
In tableView:cellForRowAtIndexPath: I'm adding a predefined UIView (which has several subviews) to the content view of the custom cell. I set up all constraints for the UIView and its subviews before.
Last but not least, I set the vertical and horizontal constraints for the content view of my custom cell (superview) and the UIView, which was added before (subview).
The constraint strings look like this:
H:|[view]|
V:|[view]|
Unfortunately, I still get the default height for all table view cells. I'm wondering If there's a way to let auto layout do the calculation of the height automatically according to content size.
Check out my detailed answer to this question here: https://stackoverflow.com/a/18746930/796419
It takes a bit of work to set up, but you can absolutely have Auto Layout constraints driving a completely dynamic table view without a single hardcoded height (and let the constraint solver do the heavy lifting and provide you with the row height).
Auto Layout won't help with the cell height. You'll need to set that in tableView:heightForRowAtIndexPath. I guess you're probably asking this because your cell heights are variable, not fixed. i.e., they depend on the content.
To resolve that, pre-calculate the cell heights and store them in an array. Return the value for the appropriate indexPath in the tableView:heightForRowAtIndexPath method.
Be sure to calculate content sizes on the main thread, using sizeThatFits of UILabel classes and such like.
If your calculation is intensive, do the majority of it off main apart from the view related methods such as sizeThatFits.
I solved the problem by using CGSize size = [view systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]; in tableView:heightForRowAtIndexPath:.
To set automatic dimensions for 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)
Swift:
#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.