How to support auto resizing UITableViewCell with a UILabel in it - ios

I am trying to figure out how get the correct height for my tableview cell that contains a uitextview so that all the content in the uitextview will be display. The textview is not editable and not scrollable, which means that the string for the uitextview is known. I tried simply returning the height of the tableview cell that is in the storyboard in heightForRowAtIndexPath but that cuts off the content if it is too large. I also tried calculating the height for the textView in heightForRowAtIndexPath but it throws an error at runtime:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let cellInfo = UserCellsArray[indexPath.section]
switch cellInfo {
case .userInfo: return 104
case .ubio:
let cell = tableView.dequeueReusableCellWithIdentifier(cellInfo.description, forIndexPath: indexPath) as! UserProfileTableViewCell //error
let oldHeight = cell.userBio.frame.size.height
cell.userBio.text = self.bio
var bodyframe = cell.userBio.frame
bodyframe.size = cell.userBio.contentSize
let newHeight = cell.userBio.contentSize.height
return tableView.rowHeight - oldHeight + newHeight
}
}

Here is what you need to do:
Need to use auto layout in your cell and set up appropriate constraints for UILabel. Since we want to resize the UILabel to fit the text set to it, we will set the number of lines value to 0. And then set the leading, trailing, bottom and top constraints to the UILabel. Please see screenshots here:
In your Table View controller > viewDidLoad call these two lines:
tableView.estimatedRowHeight = 50.0 //Give an approximation here
tableView.rowHeight = UITableViewAutomaticDimension
and also implement delegate method:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat{
return UITableViewAutomaticDimension
}

Related

UICollectionView inside UITableViewCell how to adjust tableview cell height based on screen size

I have the following layout
UITableView
UITableViewCell (class: CategoryCell)
Label
Button
UICollectionView
UICollectionViewCell (class: ItemCell)
UIImageView
UILabel
I am trying to make UICollectionView to always show 3 items no matter the screen size. I was able to get items to be shown based on screen width and was able to set the height. However, the height of table view does not change from inside CategoryCell. Here is the code:
// CategoryCell.swift
func awakeFromNib() {
super.awakeFromNib()
// Calculate width and height based on screen width
let screenWidth = UIScreen.main.bounds.width
let itemWidth = screenWidth / 3.0
let itemHeight = itemWidth / 0.75 // 3:4 aspect ratio
// Change height of table view cell
self.bounds.size.height = self.bounds.size.height - collectionView.bounds.size.height + itemHeight
// This is the line does nothing
// It should remove collectionView height from tableView (height of collectionView is set through autolayout) then add he new height to it.
// Set collection view height to equal to item height
collectionView.bounds.size.height = itemHeight
// set item height
let layout = collectionViewProducts.collectionViewLayout as! UICollectionViewFlowLayout
layout.itemSize = CGSize(width: itemWidth, height: itemHeight - 1.0)
// -1.0 because item/cell height needs to be SMALLER than collection view height
}
How can I change table view cell height from inside the cell class itself? My only guess is that I should not be doing these operations inside awakeFromNib but I am not able to find another function to call these from.
EDIT: I am using RxSwift, so my data source code is the following:
let observable = Observable.just(data)
observable.bindTo(tableView.rx.items(cellIdentifier: "CategoryCell", cellType: CategoryCell.self)) { (row, element, cell) in
cell.setCategory(category: element)
}.disposed(by: disposeBag)
tableView.rx.setDelegate(self).disposed(by: disposeBag)
You could implement UITableViewDelegate's heightForRowAt and return a value based on a variable. And you can set the variable wherever you do your UICollectionView itemHeight calculation. So, when you are done with the calculation, you should be able to do a table view data reload and the layout should update using the new itemHeight value.
I have not tested the code but the above should work. If you run into any issues, or I've misunderstood your requirements somehow, do let me know.
Provide the constraints to the collection view and make collectionviewcell outlet in tableviewcell page as well as collectionviewheight constraint outlet in the same tableviewcell page.
Like this in tableviewcell page:
class HomeServiceTableCell: UITableViewCell {
#IBOutlet weak var dealsCollectionView: UICollectionView!
#IBOutlet weak var dealsCollectionHeight: NSLayoutConstraint!
#IBOutlet weak var dealsImage: UIImageView!
// like this in the main page cellforRowAt indexPath function
func tableView (_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = homeServiceTable.dequeueReusableCell ( withIdentifier: "homeServiceTableCell6" ) as! homeServiceTableCell6
cell.dealsImage.image = UIImage(named: "deals.png")
cell.dealsCollectionView.tag = indexPath.row
cell.dealsCollectionView.delegate = self
cell.dealsCollectionView.dataSource = self
cell.dealsCollectionView.reloadData()
cell.dealsCollectionHeight.constant = cell.dealsCollectionView.collectionViewLayout.collectionViewContentSize.height
return cell
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
// lastly add this line in viewWillAppear function
override func viewWillAppear(_ animated: Bool) {
self.homeServiceTable.layoutSubviews()
}
}
You need to provide tableview's cell height for every cell and based on that you can calculate your collectionview cell's height. for dynamic height of tableview's cell you need to implement these method.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
or if you need fixed height then simply use these method and provide your cell height based on your calculation.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return fixedSize //e.g 100
}
try this hope it will help you.
Thank you #Prince and #Fahim. Your answers gave me an idea to move my row height logic to tableView delegate. Since, I am calculating the height based on screen width and aspect ratio. I just moved the same logic to tableView height:
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let screenWidth = UIScreen.main.bounds.size.width
// 3 items + 10px gap between cells
let itemWidth = (screenWidth / 3.0) - 20
// 3:4 aspect ratio + other elements
let itemHeight = (itemWidth) / 0.75 + 110
return itemHeight
}
For now this solution works normally for me, I would like to change it so it takes into account the cell itself.
I know it is weird to ask a question in the answer but it is related to this thread, so I think it is best to ask it here. How can I get the cell object from indexPath? Using tableView.cellForRow(at: indexPath) gives me bad access.

How to adjust label height and width in custom tableView Cell

I have a expandable tableView, in which when i expand a section, than there are three cell. On firth Cell there is only name and in second cell. It have a big content. Now I want to auto adjust this label height and width according to content.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tblView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! CustomTableViewCell
let dataArrayTblView = dataArrayForTableView
let titleName = dataArrayTblView.valueForKey("name")
let dueDate = dataArrayTblView.valueForKey("deadlinedate")
let description = dataArrayTblView.valueForKey("description")
cell.titleLabel.text = titleName[indexPath.row] as AnyObject! as! String!
cell.dueDateLabel.text = dueDate[indexPath.row] as? String
cell.descriptionLabel.text = description[indexPath.row] as? String
cell.descriptionLabel.sizeToFit()
cell.textLabel?.backgroundColor = UIColor.clearColor()
cell.selectionStyle = .None
return cell
}
But not getting complete content
Try to set this. It will automatically adjust the height of the row for you. If it is not working, then you have something wrong with your constraints inside your storyboard.
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return 40
}
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
You should use UITableViewAutomaticDimension as row height something like,
// this should be set in viewDidload
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 140
for that you must use autolayout and should set proper constraints to your label in cell.
Constraint should be in linear chain, I mean your label's top and bottom both constraint must be set and your label should resize according to content so your cell will resize accordingly!
You can refer Self-sizing Table View Cells by Raywenderlich !
Put below in viewDidLoad and set autolayout as per below screenshots.
tblview.rowHeight = UITableViewAutomaticDimension
tblview.estimatedRowHeight = 44
Screenshot 1
Screenshot 2
Screenshot 3
Screenshot 4
Use heightForRowAtIndexPath method to adjust height of row. Calculate
size of string with boundingRectWithSize this method. example:
Try This:
if let YOUR_STRING:NSString = str as NSString? {
let sizeOfString = ns_str.boundingRectWithSize(
CGSizeMake(self.titleLabel.frame.size.width, CGFloat.infinity),options: NSStringDrawingOptions.UsesLineFragmentOrigin, attributes: [NSFontAttributeName: lbl.font], context: nil).size
}

Change cell height by the content of the textView inside the cell

I have a Table View called todoTableView with cells that created by the user.
Each cell has Text View.
I want to change the height of the cell by the content of the Text View.
This is what I tried:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: TableViewCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath:indexPath) as! TableViewCell
cell.bounds.size.height = cell.textView.bounds.size.height
return cell
}
Bound Your textview with cell from all sides using marginal constraints.(Leading, Trailing, Top and Bottom constraints)
Disable textView Scrolling
In viewDidLoad() add the following.
tableView.estimatedRowHeight = 44.0
tableView.rowHeight = UITableViewAutomaticDimension
This will make your cell size according to your textview content size.
Have a look at result :
You don't need to write heightForRowAtIndexPath.
Irfan's answer didn't work for me.
It works after I go to Size Inspector and check "Automatic" for "Row Height".
You should also implement heightForRowAtIndexPath:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
{
return // Calculate your custom row height here.
}

Dynamic table view cell, change of subview height programmatically

I have a UITableViewCell with a custom view cell.
In this view cell, I have a simple UIView called imgWrapper where I added constraints as follows:
width = 50
height = 50
leading to superview = 20
top to superview = 20
bottom to superview = 20
Those are the only constraints in there. And I left the hugging and compression to the default ones.
In my code I've set this:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 90
}
Then in in my rowAtIndex...I have this:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: LogCustomCell = tableView.dequeueReusableCellWithIdentifier("logCustomCell", forIndexPath: indexPath) as! LogCustomCell
var imgWrapperHeight: CGFloat = log.big ? 100 : 50
cell.imgWrapperHeight.frame.size.height = imgWrapperHeight
return cell
}
Once I compile and run it. All the cells are the same size.
Notes:
I checked if log.big was true/false and it does change.
I've also tried to do CGRect(x,y,width,height) but also didn't work.
I know I can do heightForRowAtIndexPath but I want to do animations and I know we can do something like this for labels (see printscreen) which makes the tableView know the height without defining it:
Any ideas what I'm doing wrong?
You need to put your height logic into the UITableViewDelegate method called tableView:heightForRowAtIndexPath, something like the following:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return (true ? 100 : 50) + 2 * 20
}
PS. I've written this in Swift 2, thus the overridekeyword.
Delegate method "heightForRowAtIndexPath" will do it for you. You can know cell index for which you are returning height from indexPath.row and hence return height accordingly.
i.e
if indexPath.row == 0
{
return 70
}
else if indexPath.row == 1
{
return 100
}
add
self.automaticallyAdjustsScrollViewInsets = false
before these two lines
tableView.estimatedRowHeight = 50
tableView.cellHeight = UITableViewCellAutomaticDimension
Solutions 1:
As mentioned by the users. You can set the row height in your UITableViewController like so:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
return (true ? 100 : 50) + 2 * 20
}
Solution 2:
Set the height constraint on the element that will determine the height of cell. Then create an outlet in your VC for NSLayoutConstraint
While you are setting the content of the cell, you can do:
Ex:
#IBOutlet var imgWrapperHeightConstraint: NSLayoutConstraint!
...
override func tableView(tableView: UITableView, cellAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
imgWrapperHeightConstraint.constant = 50 // Or whatever value you want
}

iOS8 implementing heightForRowAtIndexPath with dynamic heights

Long story short, iOS8 has a bug with UITableViewAutomaticDimension
So, you can't just call your tableView.rowHeight = UITableViewAutomaticDimension followed by the estimatedRowHeight because when reloading data someway through, you end up getting very jumpy, weird looking scrolling.
The way to mitigate this is apparently to return what the height of the cell is in func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat
Since you can't call cellForRowatIndexPath: in heightForRowAtIndexPath:, I basically copied my cellForRowAtIndexPath into the other method and changed it up for height. I can't seem to get it working though because my UILabel (the one with dynamic height in the cell), conforms to one line and just truncates instead of going on for x lines until all the content is displayed.
Note, postBody is the text that has a dynamic height.
My cellForRowAtIndexPath:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: UITableViewCell = self.feed.dequeueReusableCellWithIdentifier("post") as UITableViewCell
var userName = cell.viewWithTag(1)! as UILabel
var timestamp = cell.viewWithTag(2)! as UILabel
var postBody = cell.viewWithTag(5)! as UILabel
var post = self.posts[indexPath.row]
userName.text = post.name
timestamp.text = post.timestamp
postBody.text = post.body
return cell
}
my heightForRowAtIndexPath:
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var cell: UITableViewCell = self.feed.dequeueReusableCellWithIdentifier("post") as UITableViewCell
var userName = cell.viewWithTag(1)! as UILabel
var timestamp = cell.viewWithTag(2)! as UILabel
var postBody = cell.viewWithTag(5)! as UILabel
var post = self.posts[indexPath.row]
userName.text = post.name
timestamp.text = post.timestamp
postBody.text = post.body
cell.setNeedsLayout()
cell.layoutIfNeeded()
return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height
}
Why doesn't it work? With the UITableViewAutomaticDimension I got it working and conforming to the constraints I put through auto-layout, but can't get it here.
The jumpy scrolling you describe isn't a bug - it happens because your estimated height isn't close enough to your actual height.
Don't dequeue a cell in your heightForRowAtIndexPath method. Basically, you're dequeueing a cell which will never be displayed since you're using it for height calculation.
You have a few options:
Improve your estimated height prediction.
In heightForRowAtIndexPath, you can use auto-layout to calculate your actual height by creating a cell instance that isn't dequeued from the table view. You can do this by:
creating your cells programmatically,
creating your cells in a .xib,
copying of your entire view controller and extract one cell from it programmatically
copying your cell in the storyboard and then extracting it via a property

Resources