programing

동적 셀 높이가 있는 UITableView의 reloadData()는 점프 스크롤을 유발합니다.

powerit 2023. 5. 23. 22:33
반응형

동적 셀 높이가 있는 UITableView의 reloadData()는 점프 스크롤을 유발합니다.

저는 이것이 공통적인 문제일 수도 있다고 생각하고 공통적인 해결책이 있는지 궁금합니다.

기본적으로, 내 UITableView에는 모든 셀에 대해 동적 셀 높이가 있습니다.및 IUI의 tableView.reloadData()위로 스크롤하면 점프가 됩니다.

이것은 제가 데이터를 다시 로드했기 때문이라고 생각합니다. 제가 위로 스크롤할 때마다 UITableView가 각 셀의 높이를 다시 계산하기 때문입니다.이를 완화하려면 어떻게 해야 합니까? 아니면 특정 IndexPath에서 UITableView 끝으로 데이터만 다시 로드하려면 어떻게 해야 합니까?

또한 맨 위까지 스크롤할 수 있게 되면 다시 아래로 스크롤한 다음 위로 스크롤할 수 있습니다. 점프하는 데 문제가 없습니다.이는 UITableViewCell 높이가 이미 계산되었기 때문일 가능성이 높습니다.

될 때 을 프를방 지점하때저셀합다니제값으로 지정해야 .tableView:estimatedHeightForRowAtIndexPath:

스위프트:

var cellHeights = [IndexPath: CGFloat]()

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

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return cellHeights[indexPath] ?? UITableView.automaticDimension
}

목표 C:

// declare cellHeightsDictionary
NSMutableDictionary *cellHeightsDictionary = @{}.mutableCopy;

// declare table dynamic row height and create correct constraints in cells
tableView.rowHeight = UITableViewAutomaticDimension;

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

// give exact height value
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
    NSNumber *height = [cellHeightsDictionary objectForKey:indexPath];
    if (height) return height.doubleValue;
    return UITableViewAutomaticDimension;
}

수락된 답변의 신속한 3가지 버전입니다.

var cellHeights: [IndexPath : CGFloat] = [:]


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

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return cellHeights[indexPath] ?? 70.0 
}

그 점프는 높이가 잘못 추정되었기 때문입니다.예상되는 Row 많 수 을 록 가 상 될 때 아래쪽으로 때 더 할 수 .높이가 실제 높이와 다를수록 테이블이 다시 로드될 때 더 많이 점프할 수 있습니다. 특히 테이블을 아래로 스크롤할수록 더 많이 점프할 수 있습니다.이는 테이블의 예상 크기가 실제 크기와 크게 달라 테이블의 내용 크기 및 오프셋을 조정해야 하기 때문입니다.따라서 예상되는 높이는 임의의 값이 아니라 여러분이 생각하는 높이에 가까워야 합니다.나는 또한 내가 설정했을 때 경험했습니다.UITableViewAutomaticDimension만약 당신의 세포가 같은 타입이라면.

func viewDidLoad() {
     super.viewDidLoad()
     tableView.estimatedRowHeight = 100//close to your cell height
}

만약 당신이 다른 부분에 다양한 세포들을 가지고 있다면, 나는 더 좋은 장소가 있다고 생각합니다.

func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
     //return different sizes for different cells if you need to
     return 100
}

@이 경우 Igor 답변은 잘 작동합니다.Swift-4그것의 규칙.

// declaration & initialization  
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]  

과 같은 UITableViewDelegate

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
  // print("Cell height: \(cell.frame.size.height)")
  self.cellHeightsDictionary[indexPath] = cell.frame.size.height
}

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

위의 해결 방법을 모두 시도해 보았지만 아무 것도 작동하지 않았습니다.

몇 시간을 보내고 가능한 모든 좌절을 겪은 후에, 이것을 고칠 방법을 생각해보세요.이 해결책은 생명의 구원자입니다!아주 잘 작동했어요!

스위프트 4

let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)

코드를 더 깨끗하게 보이게 하고 다시 로드할 때마다 이 모든 행을 쓰지 않도록 확장자로 추가했습니다.

extension UITableView {

    func reloadWithoutAnimation() {
        let lastScrollOffset = contentOffset
        beginUpdates()
        endUpdates()
        layer.removeAllAnimations()
        setContentOffset(lastScrollOffset, animated: false)
    }
}

드디어..

tableView.reloadWithoutAnimation()

또는 실제로 이 행을 추가할 수 있습니다.UITableViewCell awakeFromNib()방법

layer.shouldRasterize = true
layer.rasterizationScale = UIScreen.main.scale

그리고 정상적으로 하라.reloadData()

더 많은 방법을 사용하여 수정합니다.

뷰 컨트롤러의 경우:

var cellHeights: [IndexPath : CGFloat] = [:]


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

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return cellHeights[indexPath] ?? 70.0 
}

UITableView의 확장자로

extension UITableView {
  func reloadSectionWithoutAnimation(section: Int) {
      UIView.performWithoutAnimation {
          let offset = self.contentOffset
          self.reloadSections(IndexSet(integer: section), with: .none)
          self.contentOffset = offset
      }
  }
}

결과는

tableView.reloadSectionWithoutAnimation(section: indexPath.section)

저는 오늘 이것을 우연히 만나 다음을 관찰했습니다.

  1. iOS 8 뿐입니다.
  2. 오이드cellForRowAtIndexPath도움이 되지 않습니다.

해결책은 사실 매우 간단했습니다.

재정의estimatedHeightForRowAtIndexPath올바른 값을 반환하는지 확인합니다.

이것으로, 내 UITableViews에서 이상한 지렛대와 뛰어다니는 모든 것이 멈췄습니다.

참고: 저는 사실 제 세포의 크기를 알고 있습니다.가능한 값은 두 개뿐입니다.셀이 정말로 가변 크기라면, 당신은 캐시할 수 있습니다.cell.bounds.size.heighttableView:willDisplayCell:forRowAtIndexPath:

는 로제다음사특정행만다있수다로습니를 사용하여 특정 로드할 수 .reloadRowsAtIndexPathsex:

tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)

그러나 일반적으로 다음과 같이 테이블 셀 높이 변경을 애니메이션으로 만들 수도 있습니다.

tableView.beginUpdates()
tableView.endUpdates()

추정치 재정의행 높이(위치값이 높은 IndexPath 메서드(예: 300f)

이것으로 문제가 해결될 것입니다 :)

다음은 좀 더 짧은 버전입니다.

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension
}

iOS11에 도입된 버그가 있습니다.

그때가 당신이 할 때입니다.reloadcontentOffSet예기치 않게 변경됩니다.은 은실.contentOffset다시 로드한 후에는 변경되지 않아야 합니다.계산 착오로 인해 발생하는 경향이 있습니다.UITableViewAutomaticDimension

은 당신의 돈을 .contentOffSet다시 로드가 완료된 후 저장된 값으로 되돌립니다.

func reloadTableOnMain(with offset: CGPoint = CGPoint.zero){

    DispatchQueue.main.async { [weak self] () in

        self?.tableView.reloadData()
        self?.tableView.layoutIfNeeded()
        self?.tableView.contentOffset = offset
    }
}

어떻게 사용하나요?

someFunctionThatMakesChangesToYourDatasource()
let offset = tableview.contentOffset
reloadTableOnMain(with: offset)

이 답변은 여기서 도출되었습니다.

Swift4에서 저에게 도움이 된 것은 다음과 같습니다.

extension UITableView {

    func reloadWithoutAnimation() {
        let lastScrollOffset = contentOffset
        reloadData()
        layoutIfNeeded()
        setContentOffset(lastScrollOffset, animated: false)
    }
}

이 문제를 해결하기 위한 접근법 중 하나는

CATransaction.begin()
UIView.setAnimationsEnabled(false)
CATransaction.setCompletionBlock {
   UIView.setAnimationsEnabled(true)
}
tableView.reloadSections([indexPath.section], with: .none)
CATransaction.commit()

이 해결책들 중 어느 것도 저에게 효과가 없었습니다.스위프트 4와 X코드 10.1을 사용해 본 결과는 다음과 같습니다.

viewDidLoad()에서 테이블 동적 행 높이를 선언하고 셀에 올바른 제약 조건을 만듭니다...

tableView.rowHeight = UITableView.automaticDimension

또한 ViewDidLoad()에서 모든 테이블을 등록합니다. 다음과 같이 테이블 뷰에 셀 니브를 표시합니다.

tableView.register(UINib(nibName: "YourTableViewCell", bundle: nil), forCellReuseIdentifier: "YourTableViewCell")
tableView.register(UINib(nibName: "YourSecondTableViewCell", bundle: nil), forCellReuseIdentifier: "YourSecondTableViewCell")
tableView.register(UINib(nibName: "YourThirdTableViewCell", bundle: nil), forCellReuseIdentifier: "YourThirdTableViewCell")

표의 View heightForRowAt에서 indexPath.row에서 각 셀의 높이와 동일한 반환 높이...

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {

    if indexPath.row == 0 {
        let cell = Bundle.main.loadNibNamed("YourTableViewCell", owner: self, options: nil)?.first as! YourTableViewCell
        return cell.layer.frame.height
    } else if indexPath.row == 1 {
        let cell = Bundle.main.loadNibNamed("YourSecondTableViewCell", owner: self, options: nil)?.first as! YourSecondTableViewCell
        return cell.layer.frame.height
    } else {
        let cell = Bundle.main.loadNibNamed("YourThirdTableViewCell", owner: self, options: nil)?.first as! YourThirdTableViewCell
        return cell.layer.frame.height
    } 

}

이제 표의 각 셀에 대한 예상 행 높이를 지정합니다. 추정 보기행 높이:가능한 한 정확하게...

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {

    if indexPath.row == 0 {
        return 400 // or whatever YourTableViewCell's height is
    } else if indexPath.row == 1 {
        return 231 // or whatever YourSecondTableViewCell's height is
    } else {
        return 216 // or whatever YourThirdTableViewCell's height is
    } 

}

그게 효과가 있을 겁니다

tableView.reloadData()를 호출할 때 contentOffset을 저장하고 설정할 필요가 없었습니다.

저는 두 개의 다른 세포 높이를 가지고 있습니다.

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
        return Helper.makeDeviceSpecificCommonSize(cellHeight)
    }

견적을 추가한 후높이ForRowAt, 점프는 더 이상 없었습니다.

func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
    let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
    return Helper.makeDeviceSpecificCommonSize(cellHeight)
}

나에게 작업 해결책은

UIView.setAnimationsEnabled(false)
    tableView.performBatchUpdates { [weak self] in
    self?.tableView.reloadRows(at: [indexPath], with: .none)
} completion: { [weak self] _ in
    UIView.setAnimationsEnabled(true)
    self?.tableView.scrollToRow(at: indexPath, at: .top, animated: true) // remove if you don't need to scroll
}

확장 가능한 세포를 가지고 있습니다.

통화시에 전화해 .cell.layoutSubviews()방으로 돌전기에가아감에서 셀을 에.func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?iOS8에서 알려진 버그입니다.

다음을 사용할 수 있습니다.ViewDidLoad()

tableView.estimatedRowHeight = 0     // if have just tableViewCells <br/>

// use this if you have tableview Header/footer <br/>
tableView.estimatedSectionFooterHeight = 0 <br/>
tableView.estimatedSectionHeaderHeight = 0

이러한 점프 동작이 있었고 처음에는 정확한 예상 헤더 높이를 설정하여 완화할 수 있었지만(헤더 뷰가 하나밖에 없었기 때문에) 점프가 헤더 내부에서 발생하기 시작하여 더 이상 전체 테이블에 영향을 주지 않았습니다.

여기에 있는 답변을 따라 애니메이션과 관련이 있다는 단서를 얻었습니다. 그래서 테이블 뷰가 스택 뷰 안에 있다는 것을 알게 되었고, 때때로 우리는 전화를 했습니다.stackView.layoutIfNeeded()애니메이션 블록 안에.저의 마지막 해결책은 레이아웃이 "필요하지 않은 경우"에도 해당 상황에서 시각적 동작을 수행하기 때문에 "필요한 경우"가 아닌 한 이 상담이 발생하지 않도록 하는 것이었습니다.

저도 같은 문제가 있었습니다.애니메이션 없이 페이지를 만들고 데이터를 다시 로드했지만 스크롤이 점프하는 것을 방지하는 데 도움이 되지 않았습니다.저는 다른 크기의 IP폰을 가지고 있습니다, iphone8에서는 스크롤이 뛰지 않았지만 iphone7+에서는 뛰었습니다.

viewDidLoad 기능에 다음과 같은 변경 사항을 적용했습니다.

    self.myTableView.estimatedRowHeight = 0.0
    self.myTableView.estimatedSectionFooterHeight = 0
    self.myTableView.estimatedSectionHeaderHeight = 0

그리고 내 문제는 해결되었습니다.당신에게도 도움이 되길 바랍니다.

저는 "hightForRowAt"에서 작동했습니다.

extension APICallURLSessionViewController: UITableViewDelegate {

func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
    print("Inside heightForRowAt")
    return 130.50
}
}

사실은 당신이 사용한다면 찾았습니다.reloadRows점프 문제를 일으킵니다.그렇다면 당신은 사용해봐야 합니다.reloadSections다음과 같이:

UIView.performWithoutAnimation {
    tableView.reloadSections(NSIndexSet(index: indexPath.section) as IndexSet, with: .none)
}

언급URL : https://stackoverflow.com/questions/28244475/reloaddata-of-uitableview-with-dynamic-cell-heights-causes-jumpy-scrolling

반응형