Auto height cell after add text - swift

I have tableView and when i type i want auto height cell. But i don't know how to fix it. My first idea was:
self.chatTableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text = new_char
self.chatTableView.cellForRowAtIndexPath(indexPath)?.sizeToFit()
self.chatTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
When i have this line with .sizeToFit() my result looks like this:
But when i comment this line:
self.chatTableView.cellForRowAtIndexPath(indexPath)?.textLabel?.text = new_char
self.chatTableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: false)
My result looks this:
Is any way to fix it ?

You have changed the height of your UITableViewCell directly. But the height of the cell is handled in the UITableView. To make this work you have to do two things:
1. Use self sizing cells:
Just add this two lines of code to your viewDidLoadmethod:
override func viewDidLoad() {
super.viewDidLoad()
...
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 40
}
The estimatedRowHeight is only used to calculate the scroll bar size and position so you just put the normal expected height of your cells here.
2. Reload the cell after you changed the text
After you changed the text of your cell's label, you have to tell the UITableView to reload this cell so that its height gets adjusted. You do this by calling this method:
func addSomeText() {
// add the text to your data source
tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
Obviously the indexPath is your cell's indexPath.
One important thing: You cannot add the new text directly to the cell. Instead you have to add it to your dataSource. Otherwise the text will be gone after the reload, because the cell gets its text from the dataSource when it is dequeued.

Related

Make UITableView Header auto-resize as the user types in cells

This UI lets users type in answers in the blanks (TableViewCells) and the header will preview the final answer with blanks filled in to the original question.
For example, the question prompt here should be "The elements that make up water molecules are (_____) and (_____)."
But the UI shows:
As you see, the header view is truncating the contents. I want the view to resize to fit whatever text is there, even as the user types and the ext gets longer. Here's the TableViewController code for creating the header view:
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
let cell = tableView.dequeueReusableCell(withIdentifier: "OPCell") as! WhatTheBlankOriginalPostTableViewCell
cell.OriginalPostLabel.text = WhatTheBlankUtility.PostText(post: (post?.content.text)!)
opCell = cell
let view = UIView()
view.addSubview(cell)
let margins = view.layoutMarginsGuide
cell.leadingAnchor.constraint(equalTo: margins.leadingAnchor, constant: 8).isActive = true
cell.trailingAnchor.constraint(equalTo: margins.trailingAnchor, constant: 8).isActive = true
cell.topAnchor.constraint(equalTo: margins.topAnchor, constant: 16).isActive = true
cell.bottomAnchor.constraint(equalTo: margins.bottomAnchor, constant: 16).isActive = true
return view;
}
Notice that I wrapped the header view (originally a cell, auto-resizing and worked fine! But whenever it updated the size - I used
Tableview.beginUpdates()
Tableview.endUpdates()
the whole tableview would jitter if you're too far scrolled down, and the header view would simply disappear if you scrolled too far down for say blank 20. The theory I saw was that because I returned a TableViewCell and not a UITableViewHeaderFooterView, when the tableview no longer thinks the cell is visible it gets destroyed) in a view. This is to solve the problem I mentioned in the parentheses. Forgive my poor organization of thoughts.
I also attempted to make the header view resize by pinning the cell inside to the view boundaries in the code (auto constraints). No luck - no changes to the size of the header view when the user types and the string gets longer.
If you create a new view and add constraints in your viewForHeaderInSection, it will result in weird behaviour. For starters, it will add new constraints every time viewForHeaderInSection is called. Since, the constraints are not being removed before being added in this method, it will result in repeating constraints and I can only imagine what a nightmare that would be. A simple solution is to just return cell.contentView in your viewForHeaderInSection method.

Swift, Auto Resize Custom Table View Cells

In my app, I have a table view with an image, label and text view in each cell. I would like to be able to auto-resize the cells depending on the amount of content in the text view. (The text view is the lower most text.)
So far, I have added the correct constraints; leading, trailing, top and bottom to the text view and have disabled scrolling and editing.
In my tableViewController.swift file, I have written this code:
override func viewWillAppear(_ animated: Bool) {
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
However, this is not working as when I add more text to the text view, it just cuts off.
Maybe this has something to do with the card like look, I have got a UIView in each cell acting as a card.
I honestly don't know what I am doing wrong
A picture is below of what my app looks like and if anyone could help that would be greatly appreciated
Check if your constraints are like this(based on your image) :
imageView : set to Top and Leading of your container, with fix height and width values.
label : you can set it to top and horizontal space of your image, with fix height and width as well.
textView : leading to image, top space to label, trailing and bottom to container.
And keep using
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
in your viewWillAppear()
Update for swift 4.2
Use:
UITableView.automaticDimension
Instead of:
UITableViewAutomaticDimension
Make sure that the content mode is set to Scale To Fill of your UITextView
Make sure that there are no height constraints for the UITextView and the card UIView
Try to add the estimated height into viewDidLoad:
override func viewDidLoad() {
tableView.estimatedRowHeight = 100
tableView.rowHeight = UITableViewAutomaticDimension
}
Maybe the AutoHeight is not working because of the UIView above the UITextView. Try to call the sizeToFit and layoutIfNeeded methods for the UIView in the cellForRowAt:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "Identifier", for: indexPath) as! CustomTableViewCell
cell.vwCard.sizeToFit()
cell.vwCard.layoutIfNeeded()
return cell
}
You can also try the sizeToFit and layoutIfNeeded as well for the UITextView.
Hope this works........
Set bottom of your textView with bottom of that white UIView and make sure that white UIView has left,right,top and bottom constraints :)
Same type of example is explained here programmatically....

How does changing the intrinsicContentSize of a UITableViewCell work?

I made a UITableView with cells that expand when you tap on them. It is modeled off the following project: https://github.com/rushisangani/TableViewCellExpand
Basically, the cell's container view has two subviews which act as containers for the expanded/contracted states - "front" and "back", each of which is constrained to the top, bottom, leading, and trailing edges of the cell's main content view. To make the cell expand or contract, I just toggle the isActive property on the bottom constraint of the front and back views. This works, but only if I reload the cell when it is tapped. If I just change the constraints and then try to call invalidateIntrinsicContentSize(), nothing happens.
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath)
{
// Expand/contract the cell and invalidate size (doesn't work)
// let cell = tableView.cellForRow(at: indexPath) as! ExpandingCell
// cell.tap()
// cell.invalidateIntrinsicContentSize()
// Keep track of the selected index and configure the expand/contract state when the cell is remade
tableView.deselectRow(at: indexPath, animated: false)
expandedIndexPath = indexPath
if(!expandedIndexPathArray.contains(indexPath)){
expandedIndexPathArray.append(indexPath)
}
else{
expandedIndexPathArray = expandedIndexPathArray.filter({$0 != indexPath})
}
// Whenever a cell's intrinsicContentSize changes, it must be reloaded
tableView.reloadRows(at: [indexPath], with: .none)
}
What's going on behind the scenes? Why can't the cell recalculate its size without being reloaded?
If you want to contract/expand cells based on their intrinsic content size, you need to do the following 3 steps:
Configure the table view:
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 44
For static table view controller, you will need to implement heightForRowAtIndexPath and return UITableViewAutomaticDimension.
Update cell constraints to reflect contracted/expanded state
Ask the table view to reflect changes:
tableView.beginUpdates()
tableView.endUpdates()

UITableViewController: Scrolling to bottom with dynamic row height starts animation at wrong position

I have a table view properly configured to have dynamic row heights based on Ray Wenderlich's guide found here:
I set up the constraints to have a clear line of constraints from the top to the bottom of the cell. I also set up content hugging and content compression resistance priorities and estimated row height.
This is the code I use to setup the table view:
func configureTableView() {
// its called on viewDidLoad()
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = 100.0
}
override func viewDidLoad() {
super.viewDidLoad()
configureTableView()
for i in 1...20 {
messages.append([
"title": "foo \(i)",
"message": "bla \(i)\nbla\nbla"
])
}
// this is because the actual row heights are not available until the next layout cycle or something like that
dispatch_async(dispatch_get_main_queue(), {self.scrollToBottom(false)})
}
func scrollToBottom(animated:Bool) {
let indexPath = NSIndexPath(forRow: self.messages.count-1, inSection: 0)
self.tableView.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: animated)
}
And this is how I add new rows:
#IBAction func addMore(sender:UIBarButtonItem) {
let message = [
"title": "haiooo",
"message": "silver"]
messages.append(message)
let indexPath = NSIndexPath(forRow: messages.count-1, inSection: 0)
tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Bottom)
scrollToBottom(true)
}
The setup with the default rows are fine. It add the rows and scrolls to the bottom as expected.
But when I add new rows after that, the scrolling seems to start above the last cell. As I add more cells, the offset seems to increase.
Here is a gif showing it happening: Imgur
It's certainly related to the scroll animation (not the insertRow animation) because it scrolls properly when the animation is turned off.
Changing the estimatedRowHeight makes difference on the scrolling offset, but I couldn't find a value that fixed it.
I also tried delaying up the scroll using dispatch_async but it didn't change anything.
Do you guys have any ideas?
Wow, that was a fun challenge. Thanks for posting the test project.
So it seems that after adding the new row there's something off with where the table view thinks it's scrolled to. Seems to me to be a bug in UIKit. So to work around that, I added some code to 'reset' the table view before applying the animation.
Here's what I ended up with:
#IBAction func addMore(sender:UIBarButtonItem) {
let message = [
"title": "haiooo",
"message": "silver"]
messages.append(message)
tableView.reloadData()
// To get the animation working as expected, we need to 'reset' the table
// view's current offset. Otherwise it gets confused when it starts the animation.
let oldLastCellIndexPath = NSIndexPath(forRow: messages.count-2, inSection: 0)
self.tableView.scrollToRowAtIndexPath(oldLastCellIndexPath, atScrollPosition: .Bottom, animated: false)
// Animate on the next pass through the runloop.
dispatch_async(dispatch_get_main_queue(), {
self.scrollToBottom(true)
})
}
I couldn't get it to work with insertRowsAtIndexPaths(_:withRowAnimation:), but reloadData() worked fine. Then you need the same delay again before animating to the new last row.

Swift 2 - Update UITableViewRow in auto layout to change its height on button tapped in cell

I am using self sizing cell method to auto height in my tableview cell as my description text is variable(can fit in 1 to 5 rows). It is working fine.
Self Sizing Cell iOS 8
I also want that on button click inside the cell text of the description should be cleared and the row height should decrease. For this I have implemented following delegate to update description text to empty and than call a delegate to my tableview to update the row. but instead of resizeing and emptying the text in the description the text just goes back to description.
var delegate:UpdateRowDelegate?
#IBAction func btnTestTapped(sender: AnyObject) {
self.descriptionLabel.text = "-"//!self.addressLabel.hidden
if delegate != nil {
delegate!.updateRow(self)
}
}
Protocol
protocol UpdateRowDelegate{
func updateRow(cell:UITableViewCell)
}
Update row delegate implemented in table View controller
func updateRow(cell: UITableViewCell) {
let indexPath = tableView.indexPathForCell(cell)
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([indexPath!], withRowAnimation: UITableViewRowAnimation.Fade)
self.tableView.endUpdates()
}
I am new to swift
When you do self.tableView.reloadRowsAtIndexPaths the cellForRowAtIndexPath will run again and it will probably set your description to the original text.