Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension

前端 未结 6 561
我在风中等你
我在风中等你 2020-12-04 05:49

I am building an app that has a feed view for user-submitted posts. This view has a UITableView with a custom UITableViewCell implementation. Insid

相关标签:
6条回答
  • 2020-12-04 06:04

    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)
    }
    
    0 讨论(0)
  • 2020-12-04 06:17

    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
    }
    
    0 讨论(0)
  • 2020-12-04 06:17

    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
        }
    }
    
    0 讨论(0)
  • 2020-12-04 06:20

    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  
    }
    
    0 讨论(0)
  • 2020-12-04 06:21

    @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

    1. first reload Tableview
    2. update constraint
    3. finally move to top Tableview

      tableView.reloadData()
      self.tableView.layoutIfNeeded()
      self.tableView.setContentOffset(CGPoint.zero, animated: true)
      
    0 讨论(0)
  • 2020-12-04 06:23

    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.

    0 讨论(0)
提交回复
热议问题