Bartłomiej Semańczyk
Bartłomiej Semańczyk

Reputation: 61840

scrollToRowAtIndexPath always scrolls from the top instead from current offset

In my UITableView I use a method to scroll to the last cell:

WLNetworkClient.sharedClient().createCommentWIthText(commentTextView.text, forItem: item) { error in

    defer {
        UIAlertController.showAlertFromError(error)
    }

    if error == nil {
        self.fetchedResultsController.fetchRequest.fetchLimit += 1
        try! self.fetchedResultsController.performFetch()
        self.tableView.reloadData()
        self.scrollTableViewToBottom()
    }
}

private func scrollTableViewToBottom() {

    tableView.scrollToRowAtIndexPath(NSIndexPath(forRow: fetchedResultsController.fetchedObjects!.count - 1, inSection: 0), atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
}

but this is not working as it is expected, because ALWAYS table is scrolled from the top even I am on the second cell to last. How to fix this?

Upvotes: 3

Views: 1832

Answers (5)

Łukasz Gierałtowski
Łukasz Gierałtowski

Reputation: 607

Extending ZaEeM ZaFaR great answer:

Objective C

NSMutableDictionary *heightAtIndexPath;
heightAtIndexPath = [[NSMutableDictionary alloc] init];

- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSNumber *height = [heightAtIndexPath objectForKey:indexPath];
    if(height) {
        return height.floatValue;
    } else {
        return UITableViewAutomaticDimension;
    }
}

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
    [heightAtIndexPath setObject:@(cell.frame.size.height) forKey:indexPath];
}

Upvotes: 1

Géza Mikló
Géza Mikló

Reputation: 81

To be swiftier I would simplify the Swift 3 version introduced above:

    var heightAtIndexPath = [IndexPath: CGFloat]()

    func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
        if let height = self.heightAtIndexPath[indexPath] {
            return height
        }
        return UITableViewAutomaticDimension
    }

    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let height = cell.frame.size.height
        self.heightAtIndexPath[indexPath] = height
    }

Upvotes: 0

ZaEeM ZaFaR
ZaEeM ZaFaR

Reputation: 1536

dosdos answer worked for me in Swift 2 Adding with minor update

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 = NSMutableDictionary()

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    let height = self.heightAtIndexPath.object(forKey: indexPath)
    if ((height) != nil) {
        return CGFloat(height as! CGFloat)
    } else {
        return UITableViewAutomaticDimension
    }
}

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    let height = cell.frame.size.height
    self.heightAtIndexPath.setObject(height, forKey: indexPath as NSCopying)
}

Upvotes: 8

Rory McKinnel
Rory McKinnel

Reputation: 8014

This issue is covered here: How to tell when UITableVIew has completed ReloadData?

Change your code to:

self.tableView.reloadData()
self.tableView.layoutIfNeeded()
self.scrollTableViewToBottom()

The layoutIfNeeded forces the table reload to happen immediately. All explained in the link.

Upvotes: 0

tuledev
tuledev

Reputation: 10327

I think the problem is you scroll before the tableView update (by self.tableView.reloadData()). Try to delay the scrolling:

     dispatch_async(dispatch_get_main_queue()) {
         self.scrollTableViewToBottom()
    }

Or force the tableView to update:

    self.tableView.reloadData()
    self.tableView.layoutIfNeeded()
    self.scrollTableViewToBottom()

Upvotes: 0

Related Questions