[ios] UITableViewAutomaticDimension을 사용하여 UITableViewCell을 제자리에서 업데이트 한 후 불규칙한 스크롤

사용자가 제출 한 게시물에 대한 피드보기가있는 앱을 만들고 있습니다. 이보기에는 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확장되어 모든 주석이 표시되지만 애니메이션이 약간 불안정하고 셀 업데이트 애니메이션이 발생하는 동안 테이블 종류가 12 픽셀 정도 위아래로 스크롤됩니다.

크기 조정 중 점프하는 것은 약간 성가 시지만 내 주요 문제는 나중에 발생합니다. 이제 피드에서 아래로 스크롤하면 이전처럼 스크롤이 부드럽지만 댓글을 추가 한 후 크기를 조정 한 셀 위로 스크롤하면 피드가 피드 상단에 도달하기 전에 몇 번 뒤로 건너 뜁니다. 다음 iOS8과 같이 피드에 대한 자동 크기 조정 셀을 설정 했습니다.

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

을 제거하면 estimatedRowHeight셀 높이가 변경 될 때마다 표가 맨 위로 스크롤됩니다. 나는 지금 이것에 꽤 갇혀 있고 새로운 iOS 개발자로서 당신이 가질 수있는 팁을 사용할 수 있다고 느낍니다.



답변

다음은 이러한 종류의 문제를 해결하기 위해 찾은 최상의 솔루션입니다 (스크롤링 문제 + reloadRows + iOS 8 UITableViewAutomaticDimension);

tableView가 셀을 표시하므로 사전에 모든 높이를 유지하고 (사전에서) 업데이트하여 구성됩니다.

그런 다음 - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath메서드 에서 저장된 높이를 반환합니다 .

다음과 같이 구현해야합니다.

목표 -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];
}

스위프트 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)
}


답변

우리는 같은 문제가있었습니다. 이는 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
}


답변

dosdos 답변은 Swift 2 에서 나를 위해 일했습니다.

ivar 선언

var heightAtIndexPath = NSMutableDictionary()

func에서 viewDidLoad ()

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

그런 다음 다음 두 가지 방법을 추가하십시오.

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 솔루션이 잘 작동합니다.

하지만 추가해야 할 것이 있습니다

@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)
    


답변

나도 같은 문제에 직면했다. 해결 방법을 찾았지만 멍청이를 완전히 고치는 것은 아닙니다. 그러나 이전의 고르지 않은 스크롤에 비해 훨씬 나은 것 같습니다.

당신의에서 UITableView위임 방법 :cellForRowAtIndexPath:, 셀을 반환하기 전에 제약을 업데이트하려면 다음 두 가지 방법을 사용해보십시오. (신속한 언어)

cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()

편집 :tableView.estimatedRowHeight 더 부드러운 스크롤을 얻으려면 값을 가지고 놀아야 할 수도 있습니다 .


답변

팔로우 @dosdos 답변을 .

나는 또한 구현하는 것이 흥미로웠다. 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
    }
}


답변