Swift - autoresize UiTableViewCell to fit content [duplicate] - swift

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).

Related

UILabels and UICollectionView inside of UITableViewCell - need dynamic height

I have a custom UITableViewCell class that contains various UILabels, UIButtons, and a UITextField in the top and mid-sections, and then a UICollectionView at the bottom.
As a user enters more search terms into the UICollectionView, the collection view grows vertically, but I can't get my TableViewCell to adjust its height dynamically.
I have added constraints for every item in the TableViewCell so AutoLayout should work. I even set the heightForRowAt function to return UITableView.automaticDimension. But I'm apparently still missing something.
When the user adds/subtracts items to the collection view, how can I have the TableViewCell update its height dynamically?
I also tried the following block, but it doesn't work either.
override func systemLayoutSizeFitting(_ targetSize: CGSize, withHorizontalFittingPriority horizontalFittingPriority: UILayoutPriority, verticalFittingPriority: UILayoutPriority) -> CGSize {
collectionView.layoutIfNeeded()
collectionView.frame = CGRect(x: 0, y: 0, width: targetSize.width, height: targetSize.height + titleLabel.frame.height + filterButton.frame.height + collectionView.frame.height)
return collectionView.frame.size
}

Automatic height adjustment for static UITableViewCell doesn't work

I have a static UITableView and I want to set the row height for three of the cells dynamically. So in viewDidLoad() I implemented the following code:
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableView.automaticDimension
I also implemented the heightForRowAt method:
(The first two cells of the first section should have a fixed height)
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
if indexPath.section == 0 && indexPath.row == 0 {
return CGFloat(85)
} else if indexPath.section == 0 && indexPath.row == 1 {
return CGFloat(145)
}
return UITableView.automaticDimension
}
This the result which I'm currently getting:
I changed the lines of the labels to 0, too and the constraints of the labels inside the cells are 0, 12, 0, 12 (top, right, bottom, left).
Does anybody know, why the cell in section 3 doesn't display the data in the right way?
Edit:
(How it looks after the implementation of the suggestion above)
Because sizeToFit() did not work for you, we are going to try something a little more involved.
The cell in section 3 is displaying the data the right way. This is because UILabels don't automatically adjust their height to accommodate the text inside. Here's what you need to do:
1. Create a height constraint for your UILabel In your interface builder, add a constraint for the height of the UILabel in section 3's cell. Connect this height constraint to your view controller's class via an #IBOutlet:
class YourViewController: UIViewController {
#IBOutlet var cellLabel: UILabel!
#IBOutlet var cellLabelHeight: NSLayoutConstraint!
...
}
2. Add String extension that calculates height I am unsure of where/when you are setting the text of the UILabel in question, but I know you are doing this somewhere as you have described it as being "dynamic". Whenever you do set the text of the UILabel in question, you now also need to change the constant of the height constraint that we made in order to accommodate this text. So, we need to be able to calculate the height of the UILabel based on its width and font. We can add an extension to String in order to do this:
extension String {
func height(withConstrainedWidth width: CGFloat, font: UIFont) -> CGFloat {
let constraintRect = CGSize(width: width, height: .greatestFiniteMagnitude)
let boundingBox = self.boundingRect(with: constraintRect, options: .usesLineFragmentOrigin, attributes: [.font: font], context: nil)
return ceil(boundingBox.height)
}
}
3. Set the height constraint's constant based off the UILabel's text The final step is to set the height of the UILabel height constraint we made by using the extension we just created:
cellLabel.text = "DummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyDataDummyData"
//This will be called immediately after you set the text for the UILabel in question
cellLabelHeight.constant = cellLabel.text.height(withConstrainedWidth: cellLabel.frame.width, font: cellLabel.font)
The cell in section 3 is displaying the data the right way. Unless you tell it otherwise, a UILabel will not automatically adjust to accommodate the text within it.
What I need you to do is select the UILabel in question, then in the attributes inspector, set the Number of Lines to 0.
You also said that this UILabel is dynamic, meaning you are setting it's text somewhere in your code. Immediately after you set this UILabel's text, you are going to want to call myLabel.sizeToFit(). This should adjust the label's height to accommodate the text within.
If this doesn't work, I have another, more involved solution that should work for you.
Please look at the below;
Select your cellLabel and set the Lines value to 0:
Also apple says Self-Sizing
Summary :
lay out your table view cell’s content within the cell’s content view. To define the cell’s height, you need an unbroken chain of constraints and views (with defined heights) to fill the area between the content view’s top edge and its bottom edge. If your views have intrinsic content heights, the system uses those values. If not, you must add the appropriate height constraints, either to the views or to the content view itself.
Change the bottom constraint of the AuthorLabel from equal to Greater than or equal

How to update UITableview Header height while scrolling programatically

In My application I have top navigation bar and a tableview below the navigation bar. I have CollectionViewCell with two rows which added inside the UITableViewHeader programmatically. When ever I scroll the the TableView to top, i want the header to stop just below the navigation bar, and update the TableView Header height so I can show only one row. I just want to do an animation (like Shrinked)when the TableViewHeader sticks to the navigationbar the two collectionview rows should turn into one row by decreasing the Header Height. How can I do it programmatically
Below is my code for showing CustomHeaderView
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 183))
let headerCell = tableView.dequeueReusableCell(withIdentifier: kLastPlayedidentifier) as! LastPlayedTVC
headerCell.frame = headerView.frame
headerCell.category = lastPlayedData
headerView.addSubview(headerCell)
return headerView
}
Also i'm checking for the scroll position to set the tableview header height progmmatically which isn't successful for me.
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
if scrollView.contentOffset.y > 237 //This value is to check when the header reached the top position {
//Condition to check and animate the headerview height to make collectionview cell two rows into one rows.
}
How can I achieve the TableViewHeader height update when header sticks on top while scrolling.
Any help is appreciated.
What you are looking for is "sticky header"
and you want to change the header as well.
Sticky part is built in automatically I think if you just use UITableViewController(style: .plain), if that doesn't work for you, you can just google sticky header and there are lots of answers.
the part about changing the height or animating it. you are doing it right, just do something like:
// update your viewForHeader method to account for headerRows variable above
// update your viewForHeader method to account for headerRows variable above
// default 2, you modify this in your scroll
var headerRows = 2
func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let height = headerRows == 2 ? 183 : 91
let headerView = UIView.init(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: height))
let headerCell = tableView.dequeueReusableCell(withIdentifier: kLastPlayedidentifier) as! LastPlayedTVC
headerCell.frame = headerView.frame
headerCell.category = lastPlayedData
headerView.addSubview(headerCell)
return headerView
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
print(scrollView.contentOffset)
if scrollView.contentOffset.y > 237 {
updatedHeader.frame.size.height = 40
self.tableviewObj.tableHeaderView = updatedHeader
headerRows = 1 } else {
headerRows = 2
}
self.tableView.reloadSectionHeaders()
}
If you want to do some animating instead, what you would do is store reference to your headerView in a variable of your view controller and inside your scrollViewDidScroll animate it using UIView.animate{...}
hope this helps man.

Swift: Dynamic Cell in table view

I'm trying to make the height of table view cell = the content of the cell.
I implemented these 2 lines in the viewDidLoad():
tableView.estimatedRowHeight = 200.0
tableView.rowHeight = UITableViewAutomaticDimension
Still, the cell is changing its height!
TextView will not expand to fit the entire text by default because it has scrolling capabilities, for what you want you should disable scrolling in the textView.
Select the textView and in the Attributes Inspector tab scroll down and uncheck the "Scrolling Enabled"
It appears to me that your issue may be that the height of the UITextView is not explicitly stated. The natural behaviour of the text view is not to be a tall as it's content.
I would suggest adding a height constraint within interface builder, hooking it up to an outlet, and then within the cell layoutSubviews function calculating the height like so:
#IBOutlet var textViewHeightConstraint: NSLayoutConstraint!
func layoutSubviews() {
super.layoutSubviews()
let fixedWidth = textView.frame.size.width
textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = textView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
textViewHeightConstraint.constant = newSize.height
}
try to use heightForRowAt indexpath
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {return UITableViewAutomaticDimension}
+
override func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return UITableViewAutomaticDimension
}
In ViewDidLoad
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 200
And put your both Title Label and Text Label (replace Text View) in a UIView. and give constraints to UIView
1. trailing, leading, bottom and top space as ZERO
2. give fixed hight as 200 and change relation as Greater than or equal (>=)
Then give constrains to Title label
1. trailing, leading and top space as ZERO
2. give fixed hight as 20 (your choice)
Give constrains to Text Label
1. trailing, leading, bottom and top space as ZERO
2. give fixed hight as 180 and change relation as Greater than or equal (>=)

How to increase the UITableView separator height?

I want more space(10px) between each cell. How can I do this?
And I have added this code
tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
The best way for me, just add this in cellForRowAtIndexPath or in willDisplayCell
CGRect sizeRect = [UIScreen mainScreen].applicationFrame;
NSInteger separatorHeight = 3;
UIView * additionalSeparator = [[UIView alloc] initWithFrame:CGRectMake(0,cell.frame.size.height-separatorHeight,sizeRect.size.width,separatorHeight)];
additionalSeparator.backgroundColor = [UIColor grayColor];
[cell addSubview:additionalSeparator];
For Swift 3.0:
let screenSize = UIScreen.main.bounds
let separatorHeight = CGFloat(3.0)
let additionalSeparator = UIView.init(frame: CGRect(x: 0, y: self.frame.size.height-separatorHeight, width: screenSize.width, height: separatorHeight))
additionalSeparator.backgroundColor = UIColor.gray
self.addSubview(additionalSeparator)
You should add this to cell's method awakeFromNib() to avoid re-creation.
I have seen many clunky solutions like subclassing UITableView with hidden cells, and other less optimal ones incl. in this thread.
When initializing the UITableView, Set the rowHeight property of UITableView to a height that equals = cell height + desired separator/space height.
Do not use standard UITableViewCell class though, instead, subclass the UITableViewCell class and override its layoutSubviews method. There, after calling super (don't forget that), set the height of the cell itself to desired height.
BONUS UPDATE 18/OCT/2015:
You can be a bit smart about this. The solution above basically puts the "separator" at the bottom of the cell. What really happens is, the row height is managed by the TableViewController but the cell is resized to be a bit lower. This results in the separator/empty space being at the bottom. But you can also centre all the subviews vertically so that you leave the same space at the top and the bottom. For example 1pt and 1pt.
You can also create isFirst, isLast convenience properties on your cell subclass. You would set these to yes in the cellForRowAtIndexPath.
This way you can handle the edge cases for top and bottom separators inside the layoutSubviews method as this would have access to these properties.
This way you can handle the edge cases for top or bottom - because sometimes the design department wants N+1 separators while the number of cells is only N. So you have to either deal with the top one or the boot one in a special way. But it's best do this inside cells instead tableViewHeader or TableViewFooter.
I don't think it's possible using standard API. I guess you would need to subclass the UITableViewCell and add a view that simulates a separator at the bottom of the cell.
You may want to check this question, it seems related and has some sample code:
iPhone + UITableView + place an image for separator
In Swift
The easiest and shortest way for me was to add the snippet below in cellForRowAtIndexPath or in willDisplayCell:
override func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath)
{
let additionalSeparatorThickness = CGFloat(3)
let additionalSeparator = UIView(frame: CGRectMake(0,
cell.frame.size.height - additionalSeparatorThickness,
cell.frame.size.width,
additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.redColor()
cell.addSubview(additionalSeparator)
}
this is quite old. Nevertheless I will post my approach.
Simply increase your cell height a bit and assign a mask layer to the cell, like that:
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "...", for: indexPath)
// Configure the cell...
let maskLayer = CAShapeLayer()
let bounds = cell.bounds
maskLayer.path = UIBezierPath(roundedRect: CGRect(x: 2, y: 2, width: bounds.width-4, height: bounds.height-4), cornerRadius: 5).cgPath
cell.layer.mask = maskLayer
return cell
}
So in this example my seperator height will be 4.
Have fun!
You can do this entirely in the storyboard. Here is how:
go to the storyboard and select the tableview
Show the Size Inspector and from there set row height to say 140.
then show the Attributes Inspector and from there set your separator to Single Line and Style Plain and choose a color
then in the storyboard (or in Document Outline) select the cell
and again in the Size Inspector, under the Table View Cell, set custom Row Height to say 120.
That’s all. Your separator will be 20 units tall.
Kinda old thread, but since I only found hacky solutions in this thread,
here the solution that worked best for me (without additional UIView in every cell)
Swift 3:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
//configure your cell...
cell.layer.shadowColor = UIColor.red.cgColor
cell.layer.shadowOffset = CGSize(width: 0, height: 1)
cell.layer.shadowOpacity = 1
cell.layer.shadowRadius = 0
cell.layer.masksToBounds = false
return cell
}
EDIT: Unfortunately this does not work if you scroll up in a table. I leave the answer here anyway, since it might be a solution if your table has limited content.
See Shadow on a UITableViewCell disappears when scrolling for more info.
For a table cell with height of 50 and a space of 5 pix between the rows. Width is 320.
Define the background of the cells to be clear:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
cell.backgroundColor = [UIColor clearColor];
}
Set the height of the cells, this is the size of the row PLUS the delimiter:
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
return 55;
}
And define in cellForRowAtIndexPath a box, with the size of the row (MINUS delimiter) to draw in the background color:
UILabel *headerBackgroundLabel = [[UILabel alloc] initWithFrame:CGRectMake(0,0,320,50)];
backgroundBox.backgroundColor = [UIColor grayColor];
[cell addSubview:backgroundBox];
I do it a much simpler and more flexible way. Some may call it a hack. I call it pragmatic.
I hide the standard UITableViewSeparator. I then add a subview to my cell, using auto layout pin it to the top. Fix the height to what I desire. Pin it to the edges with a margin either side. Change it's background colour. I have a plain separator with the height i desire.
You may question how efficient this is having another UIView in the cell hierarchy. Is it really going to make a noticeable difference? Probably not - you've just taken the standard separator out of the table hierarchy anyway.
Swift 4
It's not possible to make the default separator higher. Instead you need to add a subview that will look as a separator to each cell (and optionally make the cell higher). You can do it for example in cellForRowAtIndexPath or in a UITableViewCell subclass.
In case you allow to select the cell, you need to add the subview for selected state as well, otherwise the separator would disappear when the cell is selected. That's why selectedBackgroundView is also configured.
Add this into your UITableViewController subclass:
override func viewDidLoad() {
super.viewDidLoad()
tableView.separatorStyle = .none
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath)
cell.backgroundView = UIView(backgroundColor: .white)
cell.backgroundView?.addSeparator()
cell.selectedBackgroundView = UIView(backgroundColor: .blue)
cell.selectedBackgroundView?.addSeparator()
// configure the cell
return cell
}
Add this extensions into the same file at the bottom:
private extension UIView {
convenience init(backgroundColor: UIColor) {
self.init()
self.backgroundColor = backgroundColor
}
func addSeparator() {
let separatorHeight: CGFloat = 2
let frame = CGRect(x: 0, y: bounds.height - separatorHeight, width: bounds.width, height: separatorHeight)
let separator = UIView(frame: frame)
separator.backgroundColor = .gray
separator.autoresizingMask = [.flexibleTopMargin, .flexibleWidth]
addSubview(separator)
}
}
Here's an option that might work for some people
cell.layer.borderColor = UIColor.white.cgColor
cell.layer.borderWidth = 4.0
cell.layer.masksToBounds = true
The easier and safest solution to this problem is to turn off the table separator and use a UITableViewCell as a separator of variable height. Sure, you'll have to do some index math to figure out where items are, but really it's odd / even.
It won't break and you get the benefit of recyclable cells (no extraneous views to clean up).
First make tableview separator none from the storyboard. Then add UILabel/UIView at bottom of cell of height(you needed) using storyboard or Xib
For Swift 4
Implemented King-Wizard's solution to Swift 4:
public func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let additionalSeparatorThickness = CGFloat(4)
let additionalSeparator = UIView(frame: CGRect(x: 0,
y: cell.frame.size.height - additionalSeparatorThickness, width: cell.frame.size.width, height: additionalSeparatorThickness))
additionalSeparator.backgroundColor = UIColor.groupTableViewBackground
cell.addSubview(additionalSeparator)
}
This is the easiest solution I've found:
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
" "
}
then just set the height to whatever you want:
tableView.sectionHeaderHeight = 30.0
I came across a way that has allowed me to effectively change the gap between cells.
In Interface builder I set the row height to be 46.
In the cellForRowAtIndexPath method of my TableView delegate I set the frame of the cell to be a smaller value.
cell.frame=CGRectMake(44,0,tableView.bounds.size.width,44)
This gives me a cell with a height of 44 that fits the width of the tableView but the space provided for the row will be 46 as defined in IB.
I was filling the cell programmatically anyway so this suited me fine.
You should implement
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
delegate method. and return 100.0 there.