insertRowsAtIndexPaths in UITableView adding Duplicate Rows - ios

I'm using the insertRowsAtIndexPaths method to add new rows to my UITableView. There is a problem of duplicate rows being added.
For example, If I type "HI" and then touch return, a row with the text "HI" is added to the table. If I then go back and type "Hello" and press return, a row that says "HI" is added again. Here is some code:
func textFieldShouldReturn(textField: UITextField) -> Bool {
sendMessage(textField.text)
return true
}
func sendMessage(text:String){
var message = Message(text: text)
//global array that stores all the messages
self.messages.append(message)
let indexPathZero : NSIndexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.beginUpdates()
self.tableView.insertRowsAtIndexPaths(NSArray(object: indexPathZero) as [AnyObject], withRowAnimation: UITableViewRowAnimation.None)
self.tableView.endUpdates()
}
Thanks so much!
Edit: Here's the cellForRowAtIndexPath code
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell:GroupChatMessageCell = tableView.dequeueReusableCellWithIdentifier("cell", forIndexPath: indexPath) as! GroupChatMessageCell
cell.message.text = messages[indexPath.row].text
cell.contentView.transform = CGAffineTransformMakeScale (1,-1);
return cell
}

Seems you are appending new messages to the end of the array, but inserting new rows at the first row. So it asks for first array element in func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell
Can be fixed by changing self.messages.append(message) to self.messages.insert(message, atIndex: 0)

implement estimatedHeightForRowAtIndexPath do the trick !
-(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
return [self tableView:tableView heightForRowAtIndexPath:indexPath];
}
Enjoy ;)

Related

Swift indexPathsForSelectedRows returns nil

I am working on a tableView, goal is to implement a expandible/collapsible tableView cells. The number of cells is completely dynamic.
I have things working, but with a small problem. I am using didSelectRowAtIndexPath to toggle expand/collapse cell. So what happens now is the toggle executes by tapping anywhere on the cell. :-(
Inside the cell, I have two UIViews (Header and Detail precisely). I need the toggle expand/collapse to work only when I tap on Header and not the whole cell.
For that, I am fetching the indexPath like here but that doesn't work since indexPathsForSelectedRows returns nil.
func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return myVouchers.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
if selectedIndex == indexPath.row {
return 150.0
}
else {
return 40.0
}
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
cell = tableView.dequeueReusableCellWithIdentifier("CollapsedCell", forIndexPath: indexPath) as! CollapsedCell
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action:#selector(VoucherDetailViewController.expandCell(_:)))
(cell as! CollapsedCell).headerView.addGestureRecognizer(tapGestureRecognizer)
return cell
}
func getIndexPathForSelectedCell() -> NSIndexPath? {
var indexPath: NSIndexPath?
if tableView.indexPathsForSelectedRows?.count > 0 { //**nil is returned here**
indexPath = (tableView.indexPathsForSelectedRows?[0])! as NSIndexPath
}
return indexPath
}
func expandCell(sender: UITapGestureRecognizer) {
if let myIndexPath = getIndexPathForSelectedCell() {
if selectedIndex == myIndexPath.row {
selectedIndex = -1
}
else {
selectedIndex = myIndexPath.row
}
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([myIndexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
}
}
Could someone give me a clue why the indexPathsForSelectedRows returns nil?
Many Thanks!
UPDATE 1:
Current Working demo
When I add my expand cell logic in didSelectRowAtIndexPath and remove my tapGestureRecognizer for the view inside the cell - it looks like this -
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
if selectedIndex == indexPath.row {
selectedIndex = -1
}
else {
selectedIndex = indexPath.row
}
self.tableView.beginUpdates()
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
self.tableView.endUpdates()
}
You got this bug because
self.tableView.reloadRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
reloads and deselect your rows.
You just select row, after that, you reload row (this action will deselect this row) and later you want to get a selected rows using
tableView.indexPathsForSelectedRows
but table view does not have any selected rows at this moment.
As a solution, you can store selected indexPath in new variable, and use this variable instead of
tableView.indexPathsForSelectedRows
From the indexPathsForSelectedRows documentation:
The value of this property is nil if there are no selected rows.
Therefore:
guard let selectedRows = tableView.indexPathsForSelectedRows else {
return nil
}
return selectedRows[0]
(no check for count is needed, the method never returns an empty array).
By the way, you could simplify the condition using:
return tableView.indexPathsForSelectedRows?.first
or, if your table does not support multiple selection, directly call:
return tableView.indexPathForSelectedRow
which is already there.
As Sulthan said try below code in place of reload
tableView.beginUpdates()
tableView.endUpdates()

Swift TableViewCell Array

My Situation: I want to save Data from an Array at Index X in an Row on Index X in Section 1.
My code is:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 1
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return setObjectToPass.count
}
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return "Section \(section)"
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cellEmpty = tableView.dequeueReusableCellWithIdentifier("LabelCell")
var countCell = 0
while countCell < setObjectToPass.count {
let indexPaths = NSIndexPath(forRow: countCell, inSection: 0)
let cell = tableView.dequeueReusableCellWithIdentifier( "LabelCell", forIndexPath: indexPaths)
cell.textLabel!.text = String(setObjectToPass[countCell])
print(cell)
countCell+=1
return cell
}
My Problem is that only the first index of the Array SetObjectToPass is passed and set into the Cell.text
while counter < fetchResult?.count {
let set = fetchResult![counter]
counter+=1;
setObject.append((set.reps?.integerValue)!)
}
You are implementing the tableView(_:cellForRowAtIndexPath:indexPath:) method wrongly.
Remember, every delegate method in UITableViewDelegate is like asking you a question. For example, numberOfSectionsInTableView(_:) is like asking you "How many sections do you want in your table view?". You answer the question by returning a value.
tableView(_:cellForRowAtIndexPath:indexPath:) is similar. It asks a question as well. It asks "What should I display in the table row at this index path?"
In your code, it seems like you want to give multiple answers - looping through the array and attempting to return multiple times. But it doesn't work that way, you can only give one answer.
In the first iteration of the while loop, the execution hits return and stopped. That's why you only see the first table cell.
Thus, you should change your code so that it only gives one answer to the question:
let cell = tableView.dequeueReusableCellWithIdentifier("LabalCell")
cell.textLabel?.text = String(setObjectsToPass[indexPath.row])
return cell
Don't use the loop in cellForRowAtIndexPath delegate method. cellForRowAtIndexPath method call each row based upon numberOfRowsInSection count rows, simply use indexpath.row, Use this code,
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cellEmpty = tableView.dequeueReusableCellWithIdentifier("LabelCell")
let cell = tableView.dequeueReusableCellWithIdentifier( "LabelCell", forIndexPath: indexPaths)
cell.textLabel!.text = setObjectToPass[indexPath.row] as? String
return cell
}
hope its helpful

Call 2 different custom UITableViewCells into UITableViewController

I've been scouring the web trying to figure this out but can't find a clear answer. Here's what's going on:
I've created a UITableViewController called MainTableViewController and I have two custom cells that live in that: "FirstCell" and "SecondCell". They both use that naming convention as their reuse identifiers. I've also created cell class files for each: "FirstTableViewCell" and "SecondTableViewCell" that I've assigned each cell accordingly. I've also created IBOutlets for all the objects in the cells within each UITableViewCell class file.
So in MainTableViewController I have no problem getting "FirstCell" loaded. I'm doing that like so:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
}
How do I now call "SecondCell"? I know I will need to change my return to 2. But then how do I change the dequeueReusableCellWithIdentifier and cellForRowAtIndexPath code to make both my cells work? "Second Cell" has a radically different layout with different objects defined.
try this
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return yourArrayCount
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
if indexPath.row % 2 == 0 {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("SecondCell") as! SecondTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
}
}
You just need to deque your second cell with the reuse identifier assigned. This is completely on your requirements when you want to use first and when second.
You should simply change the parameter value you send to dequeueReusableCellWithIdentifier based on [indexPath row]. If it's 0, use "firstCell" and if it's 1, use "secondCell" and create a cell for the matching object type.
here i see you have static text to show in the cell view. Usually we have NSArray of data to populate the data in tableview and we define the count in umberOfRowsInSection method
so it will be very easy if you know what data you want to display in which cell like that you can easily use If statement for selecting your desire table cellview
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return arrayCount
}
after checking count you can do this by if statement
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
NSUInteger row = [indexPath row];
if [yourArray objectAtIndex:row] =="Michael" {
let cell = tableView.dequeueReusableCellWithIdentifier("FirstCell") as! FirstTableViewCell
cell.friendfirstnameLabel.text = "Michael"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
} else {
let cell = tableView.dequeueReusableCellWithIdentifier("SecondCell") as! SecondTableViewCell
cell.friendfirstnameLabel.text = “Unknown"
cell.friendImageView.image = UIImage(named: "mikepic")
return cell
}
}

How to have one static cell in a dynamic tableview?

I would like to use a UITableView to have 2 static cells on top of a list of dynamic cells. As far as I understand, I have to use a dynamic prototype tableView. But I don't understand how to add 2 static cells and design them, eg. adding a textfield to the first and a label to the second.
What do I have to do in my storyboard? And what do I have to do inside the Controller? How can I differentiate the static from the dynamic cells?
EDIT:
I tried this for testing:
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("cardCell", forIndexPath: indexPath) as CardTableViewCell
//static cell
if (indexPath.row < 2) {
cell.dyn.text = "static \(indexPath.row)"
return cell;
}
// Configure the cell...
cell.dyn.text = "buh"
return cell
}
this results in this:
Later when I use real data I will miss the first 2 data rows...
Can I somehow "reset" the row counter after I created my static cells?
And how can I modify the 2 static cells? For adding a textfield and labels? Or do I have to do this programmatically?
I found help here: Mixing static and dynamic sections in a grouped table view
And my solution looks like this:
1.
Add and layout the static cells:
Give each cell a unique name and add them as outlet to the TableViewCell class
Adjust the code:
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return 3
}
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if (section == 2){ // my dynamic cell is index 2
return 5 // just for testing, add here yourrealdata.count
}
return 1 // for static content return 1
}
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var cell: CardTableViewCell!
if (indexPath.section == 0) {
cell = tableView.dequeueReusableCellWithIdentifier("static1", forIndexPath: indexPath) as CardTableViewCell
cell.cardSetName?.text = self.cardSetObject["name"] as String
}else if (indexPath.section == 1) {
cell = tableView.dequeueReusableCellWithIdentifier("static2", forIndexPath: indexPath) as CardTableViewCell // just return the cell without any changes to show whats designed in storyboard
}else if (indexPath.section == 2) {
cell = tableView.dequeueReusableCellWithIdentifier("cardCell", forIndexPath: indexPath) as CardTableViewCell
cell.dyn.text = "row \(indexPath.row)" // return test rows as set in numberOfRowsInSection
}
return cell;
}
End results will look like this:
I hope I can help someone with the same question :)
you could use something like this to use or display your static cell
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return numberOfDynamicCells + 1;
}
and in you cellForRowAtIndexPath datasource you may use something like this.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
if(indexPath.row == 0){
// go ahead to display your static Cell
}
else{
//go ahead to display your dynamic cells.
}
return yourCell;
}
here is code for swift.
func numberOfRowsInSection(_ section: Int) -> Int{
return numberOfDynamicCells + 1
}
and in you cellForRowAtIndexPath datasource you may use something like this.
func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?{
if indexPath.row = 0{
// go ahead to display your static Cell
}
else{
//go ahead to display your dynamic cells.
}
return yourCell;
}
Good Luck...
Yes you can by having static cells be IBOutlet properties and in the tableView(_:cellForRowAt:) you can return those properties for any index paths you want
Here's an example:
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
switch indexPath.row {
case 0:
return firstStaticCell
case 1:
return secondStaticCell
default:
let cell = tableView.dequeueReusableCell(withIdentifier: "DynamicCell", for: indexPath)
cell.textLabel?.text = "Dynamic \(indexPath.row + 1)"
return cell
}
}

Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension

I am building an app that has a feed view for user-submitted posts. This view has a UITableView with a custom UITableViewCell implementation. Inside this cell, I have another UITableView for displaying comments. The gist is something like this:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
The initial feed will download with 3 comments for previewing, but if there are more comments, or if the user adds or deletes a comment, I want to update the PostCell in place inside of the feed table view by adding or removing CommentCells to the comments table inside of the PostCell. I am currently using the following helper to accomplish that:
// (PostCell.swift) Handle showing/hiding comments
func animateAddOrDeleteComments(startRow: Int, endRow: Int, operation: CellOperation) {
let table = self.superview?.superview as UITableView
// "table" is outer feed table
// self is the PostCell that is updating it's comments
// self.comments is UITableView for displaying comments inside of the PostCell
table.beginUpdates()
self.comments.beginUpdates()
// This function handles inserting/removing/reloading a range of comments
// so we build out an array of index paths for each row that needs updating
var indexPaths = [NSIndexPath]()
for var index = startRow; index <= endRow; index++ {
indexPaths.append(NSIndexPath(forRow: index, inSection: 0))
}
switch operation {
case .INSERT:
self.comments.insertRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .DELETE:
self.comments.deleteRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
case .RELOAD:
self.comments.reloadRowsAtIndexPaths(indexPaths, withRowAnimation: UITableViewRowAnimation.None)
}
self.comments.endUpdates()
table.endUpdates()
// trigger a call to updateConstraints so that we can update the height constraint
// of the comments table to fit all of the comments
self.setNeedsUpdateConstraints()
}
override func updateConstraints() {
super.updateConstraints()
self.commentsHeight.constant = self.comments.sizeThatFits(UILayoutFittingCompressedSize).height
}
This accomplishes the update just fine. The post is updated in place with comments added or removed inside of the PostCell as expected. I am using auto sizing PostCells in the feed table. The comments table of the PostCell expands to show all of the comments, but the animation is a bit jerky and the table sort of scrolls up and down a dozen pixels or so while the cell update animation takes place.
The jumping during resizing is a bit annoying, but my main issue comes afterwards. Now if I scroll down in the feed, the scrolling is smooth as before, but if I scroll up above the cell I just resized after adding comments, the feed will jump backwards a few times before it reaches the top of the feed. I setup iOS8 auto sizing cells for the Feed like this:
// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
If I remove the estimatedRowHeight, the table just scrolls to the top anytime a cell height changes. I'm feeling pretty stuck on this now and as a new iOS developer, could use any tips you might have.
Here is the best solution I found to solve this kind of problem (scrolling problem + reloadRows + iOS 8 UITableViewAutomaticDimension);
It consists by keeping every heights in a dictionary and updating them (in the dictionary) as the tableView will display the cell.
You will then return the saved height in - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath method.
You should implement something like this :
Objective-C
- (void)viewDidLoad {
[super viewDidLoad];
self.heightAtIndexPath = [NSMutableDictionary new];
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = [self.heightAtIndexPath objectForKey:indexPath];
if(height) {
return height.floatValue;
} else {
return UITableViewAutomaticDimension;
}
}
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
NSNumber *height = #(cell.frame.size.height);
[self.heightAtIndexPath setObject:height forKey:indexPath];
}
Swift 3
#IBOutlet var tableView : UITableView?
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
We had the same problem. It comes from a bad estimation of the cell height that causes the SDK to force a bad height which will cause the jumping of cells when scrolling back up. Depending on how you built your cell, the best way to fix this is to implement the UITableViewDelegate method - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
As long as your estimation is pretty close to the real value of the cell height, this will almost cancel the jumping and jerkiness. Here's how we implemented it, you'll get the logic:
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
// This method will get your cell identifier based on your data
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kFirstCellIdentifier])
return kFirstCellHeight;
else if ([cellType isEqualToString:kSecondCellIdentifier])
return kSecondCellHeight;
else if ([cellType isEqualToString:kThirdCellIdentifier])
return kThirdCellHeight;
else {
return UITableViewAutomaticDimension;
}
}
Added Swift 2 support
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
// This method will get your cell identifier based on your data
let cellType = reuseIdentifierForIndexPath(indexPath)
if cellType == kFirstCellIdentifier
return kFirstCellHeight
else if cellType == kSecondCellIdentifier
return kSecondCellHeight
else if cellType == kThirdCellIdentifier
return kThirdCellHeight
else
return UITableViewAutomaticDimension
}
dosdos answer worked for me in Swift 2
Declare the ivar
var heightAtIndexPath = NSMutableDictionary()
in func viewDidLoad()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Then add the following 2 methods:
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = self.heightAtIndexPath.objectForKey(indexPath)
if ((height) != nil) {
return CGFloat(height!.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
override func tableView(tableView: UITableView, willDisplayCell cell: UITableViewCell, forRowAtIndexPath indexPath: NSIndexPath) {
let height = cell.frame.size.height
self.heightAtIndexPath.setObject(height, forKey: indexPath)
}
SWIFT 3:
var heightAtIndexPath = [IndexPath: CGFloat]()
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.heightAtIndexPath[indexPath] ?? UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
self.heightAtIndexPath[indexPath] = cell.frame.size.height
}
#dosdos solution is working fine
but there is something you should added
following #dosdos answer
Swift 3/4
#IBOutlet var tableView : UITableView!
var heightAtIndexPath = NSMutableDictionary()
override func viewDidLoad() {
super.viewDidLoad()
tableView?.rowHeight = UITableViewAutomaticDimension
}
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
if let height = heightAtIndexPath.object(forKey: indexPath) as? NSNumber {
return CGFloat(height.floatValue)
} else {
return UITableViewAutomaticDimension
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
let height = NSNumber(value: Float(cell.frame.size.height))
heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}
then use this lines when ever you want , for me I use it inside textDidChange
first reload Tableview
update constraint
finally move to top Tableview
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)
I was facing the same problem too. I did find a workaround, but it doesn't completely fix the jerk. But it seems to be a lot better compared to the previous choppy scrolling.
In your UITableView delegate method :cellForRowAtIndexPath:, try using the following two methods to update the constraints before returning the cell. (Swift language)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDIT: You may also have to play around with the tableView.estimatedRowHeight value to get a smoother scrolling.
Following #dosdos answer.
I also found interesting to implement: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Specially for my code, where the cell is changing Constraints dynamically while the cell is already displayed on screen. Updating the Dictionary like this helps the second time the cell is displayed.
var heightAtIndexPath = [NSIndexPath : NSNumber]()
....
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = UITableViewAutomaticDimension
....
extension TableViewViewController: UITableViewDelegate {
//MARK: - UITableViewDelegate
func tableView(tableView: UITableView,
estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
let height = heightAtIndexPath[indexPath]
if let height = height {
return CGFloat(height)
}
else {
return UITableViewAutomaticDimension
}
}
func tableView(tableView: UITableView,
willDisplayCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
func tableView(tableView: UITableView,
didEndDisplayingCell cell: UITableViewCell,
forRowAtIndexPath indexPath: NSIndexPath) {
let height: NSNumber = CGRectGetHeight(cell.frame)
heightAtIndexPath[indexPath] = height
}
}

Resources