Expand UITableViewCell when size of contents changes - ios

I have a UITextView inside a static UITableViewCell constrained like this:
Picture
In the viewDidLoad() method of my table view class I want to be able to change the text of the UITextView, have the UITextView change size to fit the text (I have scrolling disabled on the UITextView), and then have the UITableViewCell still be constrained as I have intended it to be. This is my attempt to do so:
override func viewDidLoad() {
super.viewDidLoad()
self.tableView.delegate = self
self.aboutTextView.text = "Some Long String"
//implement self sizing cells
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 400.0
//set the frame of the UITextView to match the size of the text
let fixedWidth = aboutTextView.frame.size.width
let newSize = aboutTextView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.max))
var newFrame = aboutTextView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
aboutTextView.frame = newFrame;
//This is returning the new size correctly
//Reload the tableview, nothing happens, text view remains the same size as in the storyboard
self.tableView.reloadData()
}
Although the new frame is bigger than the default size in the storyboard, nothing happens when the tableView is reloaded. I have tried setNeedsLayout() on the UITextView with no luck as well. I also tried constraining the height of the UITextView and changing the constant of the height through an IBOutlet but then the constraints break for obvious reasons. Anybody know why my code isn't working? Any better method to do what I am trying to do?

All the code about the row height should go in your tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) function. That way, whenever the table is reloaded, the code will be executed again, changing the table height.
Edit: I completely misunderstood what you were doing. In the storyboard, try adding constraints to the UITextView within the cell and give it a constraint on the height, and then make sure that the cell also has a height constraint slightly larger than that of the UITextView.

Related

IOS Swift 5 Add Custom TableViewCell Separator for Dynamic Height TableView Cell

I am creating a dynamic height TableViewCell based on the contents of the Cell. I would like to create a custom separator between the cells. I have the following code in the TableViewCell swift file. It appears that the custom separator is created using the default height of the cell and not the calculated height of the cell based on the contents of the cell - which was calculated in the TableView heightForRowAt override function. As a result, the customer separator is rendered across the cell. It appears that the awakeFromNib is called before the height calculation. Any suggestions?
class FeedTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
// add a custom seperator between table view cells.
let screenSize = UIScreen.main.bounds
let separatorHeight = CGFloat(10.0)
let additionalSeparator = UIView.init(frame: CGRect(x: 0, y: self.frame.size.height-separatorHeight, width: screenSize.width, height: separatorHeight))
additionalSeparator.backgroundColor = UIColor(named: "TEXTVIEWCELL_SEPARATOR")
self.addSubview(additionalSeparator)
}
}
The awakeFromNib is called before the height calculation. While the solution works for fixed height TableViewCells, it won't work for variable height TableViewCells. The solution that I used was to create a view on the bottom of the TableViewCell, use constraints to pin it to the bottom of the TableViewCell. You can size the view to 10pt and of course set the color. I did this in StoryBoards, not programmatically.

UITableView Alignment with UITextView

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

iOS - Scroll TextView to top when it's in a tableview cell

I have a UITextView inside a custom TableView cell. I want the text view to scroll to the top by default. I usually do it this way in regular views:
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
let fixedWidth = bioView.frame.size.width
bioView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
let newSize = bioView.sizeThatFits(CGSize(width: fixedWidth, height: CGFloat.greatestFiniteMagnitude))
var newFrame = bioView.frame
newFrame.size = CGSize(width: max(newSize.width, fixedWidth), height: newSize.height)
bioView.frame = newFrame;
bioView.scrollRangeToVisible(NSRange(location:0, length:0))
}
bioView is my text view object. However, this doesn't work in my case since I can't reference the textview in the ViewController class and can only do so in the CustomCell class and that class cannot override viewDidLayoutSubviews. Any help?
Try this.
-(UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
CustomCell* cell = [tableView dequeueReusableCellWithIdentifier:#"CustomCell"];
[cell.textView scrollRangeToVisible:NSMakeRange(0, 0)];
return cell;
}
Swift 3.0:
let cell = tableView.dequeueReusableCell(withIdentifier: "CustomCell", for: indexPath) as! DemoCell
cell.textView.scrollRangeToVisible(NSRange(location:0, length:0))
It works! However, the table has to scroll for the changes to take effect, best way I found so far. Thanks everyone!
In your viewController which handles UITableViewDelegate events, override willDisplayCell method. Identify your textView from Cell and write the code to scroll.
I have tried doing the scrolling, either with scrollRangeToVisible() or setting contentOffset to .zero in awakeFromNib, willDisplayCell, and cellForRowAtIndexPath. These all get executed, but the scrolling is still reset (by AutoLayout?) some time subsequent to this.
For a cell that is on screen when the UITableView appears, performing the scroll in willDisplayCell does not "stick." It does work if the cell is scrolled away and scrolls back on screen.
For a cell that is already on screen that has a UITextView with "too much" text so that it scrolls out the bottom of the TextView, the only way I have found to make the text scroll to the top is to set either scrollRangeToVisible() or contentOffset in the viewDidAppear() of the UITableViewController. Even viewWillAppear() did not work.
One drawback is that this solution causes a jump in the scroll location after the TableView appears on screen, even in a performWithoutAnimation block.
You can put the code in viewDidLayoutSubviews, and it will work, but as mentioned this gets called several times, and the text will scroll back to the top with every orientation change, which may not be desirable.

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

UILabel inside UITableViewCell - height isn't adjusting correctly

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.

Resources