it-swarm.com.ru

Перекрестная прокрутка после обновления UITableViewCell на месте с помощью UITableViewAutomaticDimension

Я создаю приложение, которое имеет вид канала для пользовательских сообщений. Это представление имеет UITableView с пользовательской реализацией UITableViewCell. Внутри этой ячейки у меня есть другая UITableView для отображения комментариев. Суть что-то вроде этого:

Feed TableView
  PostCell
    Comments (TableView)
      CommentCell
  PostCell
    Comments (TableView)
      CommentCell
      CommentCell
      CommentCell
      CommentCell
      CommentCell

Первоначальный канал будет загружен с 3 комментариями для предварительного просмотра, но если есть еще комментарии, или если пользователь добавляет или удаляет комментарий, я хочу обновить PostCell на месте внутри представления таблицы каналов, добавляя или удаляя CommentCells к комментариям таблица внутри PostCell. В настоящее время я использую следующий помощник для достижения этой цели:

// (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
}

Это завершает обновление просто отлично. Сообщение обновляется на месте с комментариями, добавленными или удаленными внутри PostCell, как и ожидалось. Я использую авторазмер PostCells в таблице каналов. Таблица комментариев PostCell расширяется, чтобы показать все комментарии, но анимация немного прерывистая, и таблица как бы прокручивается вверх и вниз на дюжину пикселей или около того, пока происходит анимация обновления ячейки.

Прыжки во время изменения размера немного раздражают, но моя главная проблема возникает позже. Теперь, если я прокручиваю вниз в ленте, прокрутка будет плавной, как и раньше, но если я прокручиваю вверх выше ячейки, которую я только что изменил после добавления комментариев, лента будет прыгать назад несколько раз, прежде чем она достигнет верхней части ленты. Я настраиваю iOS8 для автоматического определения размера ячеек в Feed как это:

// (FeedController.Swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560

Если я удаляю estimatedRowHeight, таблица просто прокручивается к вершине каждый раз, когда изменяется высота ячейки. Я чувствую себя застрявшим в этом сейчас, и, как новый разработчик iOS, могу использовать любые ваши советы.

71
Bryan Alger

Вот лучшее решение, которое я нашел, чтобы решить эту проблему (проблема с прокруткой + reloadRows + iOS 8 UITableViewAutomaticDimension);

Он состоит из сохранения всех высот в словаре и их обновления (в словаре), так как tableView будет отображать ячейку.

Затем вы вернете сохраненную высоту в методе - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath.

Вы должны реализовать что-то вроде этого:

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)
}
109
dosdos

У нас была такая же проблема. Это происходит из-за неправильной оценки высоты ячеек, из-за которой SDK вызывает неправильную высоту, которая вызывает скачок ячеек при прокрутке назад. В зависимости от того, как вы построили ячейку, лучший способ исправить это - реализовать метод UITableViewDelegate- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath

Пока ваша оценка довольно близка к реальному значению высоты ячейки, это почти отменит прыжки и рывки. Вот как мы это реализовали, вы получите логику:

- (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;
    }
}

Добавлена ​​поддержка Swift 2

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  
}
29
Gabriel Cartier

досдос ответ работал для меня в Swift 2

Объявить ивар

var heightAtIndexPath = NSMutableDictionary()

в func viewDidLoad ()

func viewDidLoad() {
  .... your code
  self.tableView.rowHeight = UITableViewAutomaticDimension
}

Затем добавьте следующие 2 метода:

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
}
18
Ranknoodle

Я тоже столкнулся с той же проблемой. Я нашел обходной путь, но он не полностью исправляет рывок. Но это выглядит намного лучше по сравнению с предыдущей изменчивой прокруткой.

В своем методе делегата UITableView:cellForRowAtIndexPath: попробуйте использовать следующие два метода, чтобы обновить ограничения перед возвратом ячейки. (Свифт язык)

cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()

Правка: Возможно, вам также придется поиграться со значением tableView.estimatedRowHeight, чтобы получить более плавную прокрутку.

2
Vishal Chandran

Следующий @dosdos answer.

Я также нашел интересным для реализации: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:

Специально для моего кода, где ячейка изменяется динамически Ограничения, в то время как ячейка уже отображается на экране. Подобное обновление словаря помогает во второй раз, когда отображается ячейка.

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
    }
}
1
Gabriel.Massana

Решение @dosdos работает нормально 

но есть кое-что, что вы должны добавить

следующий ответ @dosdos

Свифт 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)
}

затем используйте эти строки, когда захотите, для меня я использую их внутри textDidChange

  1. первая перезагрузка Tableview 
  2. ограничение обновления
  3. наконец-то перейти наверх Tableview

    tableView.reloadData()
    self.tableView.layoutIfNeeded()
    self.tableView.setContentOffset(CGPoint.zero, animated: true)
    
0
Basil