How to set height of UITableView cell dynamically using Autolayout? - ios

I had taken two view inside content view.
Height of "Post Container View" (Red background) is calculated dynamically as per height of label (All done using Autolayout).
Now I want that if height of "Post Container View" (Red background) will increase then height of cell view auto increase. I want to do this using autolayout.
I want to calculate height of UITableview cell using Autolayout. How to do it ?
Cell Height = Post Container View (Flexible as per label height)+ Image Container View Height (300 Fix)
I had seen this type of method, but dont know how to implement in my code ?
- (CGFloat)calculateHeightForConfiguredSizingCell:(UITableViewCell *)sizingCell
{
sizingCell.bounds = CGRectMake(0.0f, 0.0f, CGRectGetWidth(self.MyTableView.frame), CGRectGetHeight(sizingCell.bounds));
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height + 1.0f; // Add 1.0f for the cell separator height
}

To calculate height of cell :
CGFloat height ; // take global variable
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
height = cell.PostContainerView.frame.size.height ;
}
- (CGFloat)tableView:(UITableView*)tableView heightForRowAtIndexPath:(NSIndexPath*)indexPath
{
height = height + 300 //(300 is Fix height of Image Container View ) ;
return height ;
}

Do you want to calculate the height, or the tableView to calculate the height itself, considering your autolayout configuration ?
To do so, don't implement the delegate method heightForRowAtIndexPath but estimatedHeightForRowAtIndexPath instead (with whatever value you want for the moment).
The tableView will determine the height of the cell considering autolayout constraints applied on it.
Be warned that sometimes you need to call layoutIfNeeded on your cell, after you updated it. (for exemple in the cellForRowAtIndexPath)

Use this Extension class for string:
import UIKit
extension String {
func sizeOfString (font: UIFont, constrainedToWidth width: Double) -> CGSize {
return NSString(string: self).boundingRectWithSize(CGSize(width: width, height: DBL_MAX),
options: NSStringDrawingOptions.UsesLineFragmentOrigin,
attributes: [NSFontAttributeName: font],
context: nil).size
} }
In UITableView delegate calculate UILabel width:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let item = "hkxzghkfjkgjkfxkj jjhghdjajfjkshgjkhkkn jhkhhgdfgjkhsfjkdghhhsxzgnfshgfhk jhsfgfhjfhghj "
let widthOfLabel = Double(view.frame.size.width) - 30
let textHeight = item.sizeOfString(UIFont.systemFont(14), constrainedToWidth: widthOfLabel)
return (padding + textHeight.height)
}

Related

How can I adjust the UITableViewCell height to the content of UITextView that's inside?

In my story board I have a UITableView with dynamically generated UITableViewCells. Each cell contains 2 labels and 1 text view:
I have a code that adjust the size of the textfield to the amount of text:
let fixedWidth = cell.myComment.frame.size.width
cell.myComment.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
let newSize = cell.myComment.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = cell.myComment.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
cell.myComment.frame = newFrame;
and it works fine, when I set a background color of my textView to red I see:
and the cell itself - I'm setting the size in here:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
if indexPath.row == 0 {
return 100//this is my static cell
}
else {
return 117; //for now it's hardcoded - how can I set this value dynamically based on content?
}
}
So as I wrote in the comment above - how can I set the height of the cell dynamically based on the amount of text in the text view?
The key to getting self-sizing table cells (autolayout-based, which I recommend) is as follows:
Add your subviews to the contentView of the UITableViewCell
Provide constraints between your subviews and the contentView such that your subviews reach all edges of the table cell. In your case, this probably means aligning the leading, trailing, top, and bottom edges of your UITextView to the corresponding edges of the contentView.
Set the row height to UITableViewAutomaticDimension instead of a hardcoded CGFloat.
Somewhere in your controller, provide an estimation of the height with tableView.estimatedRowHeight = x (a hard coded constant is fine, this is for performance).

UITableView Cells with AutoLayout - Not smooth

I have a UITableView with custom UITableViewCells. Each cell has a bunch of UIView, UIStackView, and UILabel subviews to build out the design I want.
Everything within the UITableViewCell is positioned using AutoLayout, and I allow the UITableView to automatically give height to the cells by doing:
tableView.estimatedRowHeight = 85
tableView.rowHeight = UITableViewAutomaticDimension
However, because of the complexity of the cell's subview layout, scrolling down the tableView is very laggy/choppy as each cell tries to estimate and build out it's correct height.
I read up on some articles that discuss these issues like:
https://medium.com/ios-os-x-development/perfect-smooth-scrolling-in-uitableviews-fd609d5275a5
This inspired me to try a different approach where I would calculate the height of each cell by adding up all the vertical space, and calculating dynamic string height by utilizing functions like the following, and then caching these heights in a dictionary of [IndexPath.row (Int), Row Height (CGFloat)].
extension String {
func heightWithConstrainedWidth(width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: CGFloat.max)
let boundingBox = self.boundingRectWithSize(constraintRect, options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: font], context: nil)
return boundingBox.height
}
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if let height = cellHeightCache[indexPath.row] {
return height
} else {
let data = dataItems[indexPath.row]
var height: CGFloat = 0
height += 20 // top padding above text
height += data.title.heightWithConstrainedWidth(tableView.frame.width - 72 * 2, font: UIFont.boldSystemFontOfSize(18)) // height of title
height += 20 // bottom padding below text
... // this goes on and on with if statements depending on if the data has a subtitle, image, other properties, etc to get the correct height for *that* cell
cellHeightCache[indexPath.row] = ceil(height)
return height
}
}
While this does work, now I am mixing frame calculations, tableView.frame.width - 72 * 2 with the AutoLayout of the cell's subviews to get the height. And because these are frame calculations, if the user rotates the device or on an iPad brings over the right split screen view, this clearly breaks. I could try and catch all these edge cases and recalculate when the user rotates/changes the size of the screen, but am I missing a way to calculate the height of these cells without using frames?
Is there anyway just using AutoLayout positioning I can calculate the height of the cells in the heightForRowAtIndexPath function?
Thanks

Changing the height of a dynamic-height UITableViewCell after it's already appeared

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
}

Get height of each rows of UITableView

Is there any way by which I can get row height of each row in a UITableView. I have a UITableView which contains data which is dynamic, and I am using
self.tableView.rowHeight = UITableViewAutomaticDimension
to make dynamic row heights.
But I want to change height of overall UITableView as well, for that I need height of all the rows after reload of data.
I tried using
self.tableView.layoutIfNeeded()
self.tableViewHeightContraint.constant = self.tableView.contentSize.height
But due to unknown reason it is giving me height less than what exactly it has, after I add 3 or 4 records in UITableView so thinking of other way around. To calculate height of each rows and then summing them up.
I am doing in Xcode 7.2, iOS 9 and Swift 2
var dictionaryOfCellHeight = [Int : CGFloat]()
var dataArray = [String]()
func tableView(tableView: UITableView!, heightForRowAtIndexPath indexPath: NSIndexPath!) -> CGFloat {
if dictionaryOfCellHeight[indexPath.row] == nil
{
var frame : CGRect = tableView.rectForRowAtIndexPath(indexPath)
if indexPath.row == dataArray.count
{
dictionaryOfCellHeight[indexPath.row] = frame.size.height
//calculate total height and reload table
}
else
{
dictionaryOfCellHeight[indexPath.row] = frame.size.height
}
}
}
in Swift 3.0
let myRowHeight = yourTableView.rowHeight
To change the height of Tableview , you don't need to calculate the height of all UITableViewCells and set to TableView.
Set the height of each UITableViewCell properly in heightForRowAtIndexPath method. This will automatically set the scrollable height to your Tableview
You can use this
CGRect frame = [tableView rectForRowAtIndexPath:indexPath];
NSLog(#"row height : %f", frame.size.height);
Using frame.size.height you can get height of particular row
This is because self.tableView.contentSize.height returns number of rows * estimatedRowHeight, which is not equivalent to actual height of the table view. What you need is to get the individual cell heights through visibleCells, then sum those up to get the table view's height.
Refer to this for the answer:
https://stackoverflow.com/a/40081129/4076956
Maybe the table is not resizing its content, so if you try using [self. tableView layoutSubviews]; to force the resizing, it may work. [self. tableView layoutIfNeeded]; not necessarily forces the update.
Functionally (one-liner) 👌
NOTE: my section array stores the rows in the .data var
let heightOfAllRows:CGFloat = sections.enumerated().map { arg -> [(section:Int,row:Int)] in
return arg.element.data.indices.map{ i in
(section:arg.offset,row:i)
}
}.flatMap{$0}.reduce(0){
return $0 + self.tableView(self, heightForRowAt: .init(row: $1.row, section: $1.section))
}

How to resize UITableView width to fit its content?

Is there possible way to adjust table view width by width of biggest text that should be set in the cellTitle label?
iOS 8 , SWIFT
public override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
// Set the popover tableview width to be max compressed size (assumes all cells visible)
let maxWidth = tableView.visibleCells.reduce(0.0) { (currentMax, cell) -> CGFloat in
return max(currentMax, cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).width)
}
var contentSize = tableView.contentSize
contentSize.width = maxWidth
tableView.contentSize = contentSize
tableView.bounds.size = contentSize
}
Set a width auto-layout constraint to the table view.
Set an outlet from the constraint to your code (ctrl+drag, just like a view).
When you have the size of the label (if the label also has auto-layout then you will have it at viewDidLayoutSubviews) set the width to the table like so:
- (void)viewDidLayoutSubviews {
self.myTableWidthConstraint.constant = self.myLabel.frame.size.width;
[self.myTableView updateConstraints];
[self.myTableView layoutIfNeeded];
}
You should be able to do that in the size inspector.

Resources