동적 셀 높이를 가진 UITableView의 reloadData ()로 인해 스크롤이 급증합니다.


142

나는 이것이 일반적인 문제라고 생각하고 그것에 대한 일반적인 해결책이 있는지 궁금합니다.

기본적으로 내 UITableView에는 모든 셀에 대한 동적 셀 높이가 있습니다. UITableView 및 I의 맨 위에 있지 않으면 위로 tableView.reloadData()스크롤이 급증합니다.

나는 이것이 데이터를 다시로드했기 때문에 스크롤 할 때 UITableView가 가시성에 도달하는 각 셀의 높이를 다시 계산한다는 사실 때문이라고 생각합니다. 이를 완화하는 방법 또는 특정 IndexPath에서 UITableView의 끝까지 만 데이터를 다시로드하는 방법은 무엇입니까?

또한 맨 위로 스크롤 할 때 점프하지 않아도 문제가 없습니다. UITableViewCell 높이가 이미 계산 되었기 때문일 수 있습니다.


몇 가지 ... (1) 예를 사용하여 특정 행을 확실히 다시로드 할 수 있습니다 reloadRowsAtIndexPaths. 그러나 (2) "점프"는 무엇을 의미합니까? (3) 예상 행 높이를 설정 했습니까? (테이블을 동적으로 업데이트 할 수있는 더 나은 솔루션이 있는지 알아 내려고 노력했습니다.)
Lyndsey Scott

@LyndseyScott, 예, 예상 행 높이를 설정했습니다. 겁나는 것은 위로 스크롤 할 때 행이 위쪽으로 이동한다는 것을 의미합니다. 필자는 예상 행 높이를 128로 설정 한 다음 위로 스크롤하면 UITableView의 위의 모든 게시물이 더 작아서 높이가 줄어들어 테이블이 점프하기 때문이라고 생각합니다. reviewRowsAtIndexPaths를 xTableView의 행에서 마지막 행 까지 수행하려고 생각하고 있지만 새 행을 삽입하기 때문에 작동하지 않으므로 다시로드하기 전에 테이블 뷰의 끝이 무엇인지 알 수 없습니다. 자료.
David

2
@LyndseyScott 여전히 문제를 해결할 수 없습니다. 좋은 해결책이 있습니까?
rad

1
이 문제에 대한 해결책을 찾은 적이 있습니까? 동영상에서 볼 수있는 것과 동일한 문제가 발생했습니다.
user3344977

1
아래 답변 중 어느 것도 나를 위해 일하지 않았습니다.
Srujan Simha

답변:


221

점프를 방지하기 위해 셀이로드 될 때 셀의 높이를 저장하고 정확한 값을 제공해야합니다 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;
}

1
감사합니다, U 정말 너무 내 하루 objc에서 :) 작품 저장
아르 템 Z.

3
초기화하는 것을 잊지 마십시오 cellHeightsDictionary: cellHeightsDictionary = [NSMutableDictionary dictionary];
Gerharbo

1
estimatedHeightForRowAtIndexPath:double 값을 반환하면 *** Assertion failure in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:]오류 가 발생할 수 있습니다 . return floorf(height.floatValue);대신 수정하십시오 .
liushuaikobe

안녕하세요 @lgor, 같은 문제가 발생하여 솔루션을 구현하려고합니다. 내가 얻는 문제는 willDisplayCell 이전에 추정 된 HeightForRowAtIndexPath가 호출되므로 추정 된 HeightForRowAtIndexPath가 호출 될 때 셀 높이가 계산되지 않습니다. 도움이 필요하십니까?
Madhuri

1
@Madhuri 유효 높이는 "heightForRowAtIndexPath"로 계산해야합니다.이 값은 willDisplayCell 직전에 화면의 모든 셀에 대해 호출되며 나중에 추후에 RowRoweight (테이블 재로드시)에서 사용하기 위해 사전의 높이를 설정합니다.
Donnit

109

수락 된 답변의 신속한 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 
}

고마워요! 사실의 구현을 제거 할 수 있었 func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {으므로 필요한 모든 높이 계산을 처리합니다.
Natalia

지속적인 점프로 많은 시간을 겪은 후 UITableViewDelegate수업에 추가하는 것을 잊었다는 것을 알았습니다 . 위에 표시된 willDisplay기능이 포함되어 있기 때문에 해당 프로토콜을 준수해야합니다 . 나는 누군가와 같은 투쟁을 구할 수 있기를 바랍니다.
MJQZ1347

스위프트 답변 감사합니다. 필자의 경우 테이블보기가 맨 아래로 스크롤 될 때 다시로드 할 때 셀이 비정상적으로 작동하는 슈퍼 이상한 동작이 발생했습니다. 자체 크기 조정 셀이있을 때마다 지금부터 이것을 사용할 것입니다.
Trev14

스위프트 4.2에서 완벽하게 작동
Adam S.

생명의 은인. 데이터 소스에 더 많은 항목을 추가하려고 할 때 유용합니다. 새로 추가 된 셀이 화면 중앙으로 점프하지 않도록합니다.
Philip Borbon

38

점프는 예상 높이가 잘못 되었기 때문입니다. 예상 RowHeight가 실제 높이와 다를수록 테이블을 다시로드 할 때 특히 테이블이 더 아래로 스크롤 될 때 테이블이 더 많이 점프 할 수 있습니다. 테이블의 예상 크기가 실제 크기와 크게 다르기 때문에 테이블의 내용 크기와 오프셋을 조정해야하기 때문입니다. 따라서 추정 높이는 임의의 값이 아니라 높이가 될 것으로 생각되는 것과 비슷해야합니다. 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
}

2
고마워, 내 tableView가 너무 까다로운 이유입니다.
루이스 드 데커

1
이전 답변이지만 2018 년 현재는 여전히 실용적입니다. 다른 모든 답변과 달리이 답변은 셀의 높이가 같거나 매우 유사한 경우 viewDidLoad에서 추정 RowHeigh를 한 번 설정하는 것을 제안합니다. 고맙습니다. BTW, 또는 esimatedRowHeight는 Size Inspector> Table View> Estimate의 Interface Builder를 통해 설정할 수 있습니다.
Vitalii

더 정확한 추정 높이를 제공하면 도움이되었습니다. 또한 멀티 섹션 그룹화 된 테이블 뷰 스타일을 가지고 있었고, 구현했다tableView(_:estimatedHeightForHeaderInSection:)
nteissler

25

이 경우 @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
}

6
이 솔루션을 사용하여 행 삽입 / 삭제를 처리하는 방법은 무엇입니까? 사전 데이터가 실제가 아니기 때문에 TableView가 점프합니다.
Alexey Chekanov

1
잘 작동합니다! 특히 행을 다시로드 할 때 마지막 셀에서.
Ning

19

위의 모든 해결 방법을 시도했지만 아무 효과가 없습니다.

몇 시간을 보내고 가능한 모든 좌절을 겪고 나서 이것을 고치는 방법을 알아 냈습니다. 이 솔루션은 구세주입니다! 매력처럼 일했다!

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


1
이것은 어떻게 다시로드합니까? 당신 그것을 부릅니다 . reloadWithoutAnimation그러나 그 reload부분 은 어디에 있습니까?
matt

@matt 당신은 tableView.reloadData()먼저 전화 한 다음 tableView.reloadWithoutAnimation()여전히 작동합니다.
Srujan Simha

큰! 위의 어느 것도 나를 위해 작동하지 않았습니다. 모든 높이와 예상 높이조차 완전히 동일합니다. 흥미 롭군
TY Kucuk

1
나를 위해 일하지 마십시오. tableView.endUpdates ()에서 충돌이 발생합니다. 누군가 나를 도울 수 있습니까!
Kakashi

12

더 많은 방법으로 문제를 해결합니다.

뷰 컨트롤러의 경우 :

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 reloadSectionWithouAnimation(section: Int) {
      UIView.performWithoutAnimation {
          let offset = self.contentOffset
          self.reloadSections(IndexSet(integer: section), with: .none)
          self.contentOffset = offset
      }
  }
}

결과는

tableView.reloadSectionWithouAnimation(section: indexPath.section)

1
나를위한 열쇠는 여기에서 그의 UITableView 확장을 구현하는 것이 었습니다. 매우 영리한. 감사 rastislv
BennyTheNerd 17:01에

완벽하게 작동하지만 단점이 하나뿐이므로 머리글, 바닥 글 또는 행을 삽입 할 때 애니메이션이 손실됩니다.
Soufian Hossam

reloadSectionWithouAnimation은 어디에서 호출됩니까? 예를 들어, 사용자는 Instagram과 같은 내 앱에 이미지를 게시 할 수 있습니다. 이미지 크기를 조정할 수는 있지만 대부분의 경우 테이블 셀을 스크롤하여 스크롤해야합니다. 테이블이 reloadData를 통과하면 셀이 올바른 크기가되기를 원합니다.
Luke Irvin

11

나는 오늘 이것을 만났고 관찰했다.

  1. 실제로 iOS 8 전용입니다.
  2. 재정의 cellForRowAtIndexPath는 도움이되지 않습니다.

수정은 실제로 매우 간단했습니다.

재정의 estimatedHeightForRowAtIndexPath하고 올바른 값을 반환하는지 확인하십시오.

이로 인해 UITableViews에서 모든 이상한 지터와 점프가 중단되었습니다.

참고 : 실제로 셀 크기를 알고 있습니다. 가능한 값은 두 가지뿐입니다. 셀이 실제로 가변 크기 인 경우 cell.bounds.size.heightfrom 을 캐시 할 수 있습니다tableView:willDisplayCell:forRowAtIndexPath:


2
높은 값 (예 : 300f
Flappy

1
@Flappy 그것은 당신이 제공하는 솔루션이 어떻게 작동하고 다른 제안 된 기술보다 짧은 지 흥미 롭습니다. 답변으로 게시하는 것을 고려하십시오.
Rohan Sanap 2014 년

9

실제로 다음을 사용하여 특정 행만 다시로드 할 수 있습니다 reloadRowsAtIndexPaths.

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

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

tableView.beginUpdates()
tableView.endUpdates()

beginUpdates / endUpdates 메소드를 시도했지만 테이블의 보이는 행에만 영향을 미칩니다. 스크롤 할 때 여전히 문제가 있습니다.
David

@David 아마도 예상 행 높이를 사용하고 있기 때문일 것입니다.
Lyndsey Scott 2016 년

EstimatedRowHeights를 제거하고 대신 beginUpdates 및 endUpdates로 바꿔야합니까?
David

@David 아무 것도 "대체"하지는 않지만 실제로는 원하는 동작에 달려 있습니다 ... 예상 행 높이를 사용하고 테이블의 현재 보이는 부분 아래에 인덱스를 다시로드하려면 다음과 같이 할 수 있습니다 나는 reloadRowsAtIndexPaths를 사용하여 말했다
Lyndsey Scott

reladRowsAtIndexPaths 메서드를 사용하는 데있어 내 문제 중 하나는 무한 스크롤을 구현한다는 것입니다. 따라서 reloadingData를 수행하면 dataSource에 15 행을 더 추가했기 때문입니다. 이것은 해당 행에 대한 indexPath가 아직 UITableView에 존재하지 않음을 의미합니다
David

3

약간 짧은 버전이 있습니다.

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

3

높은 값 (예 : 300f)으로 추정 된 HeightForRowAtIndexPath 메소드 대체

이것은 문제를 해결해야합니다 :)


2

버그가iOS11에 도입되었다고 생각 .

그때 reloadtableView 를 수행 contentOffSet하면 예기치 않게 변경됩니다. 실제로 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)

이 답변은 여기 에서 파생되었습니다


2

이것은 Swift4에서 나를 위해 일했습니다.

extension UITableView {

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

1

이 솔루션들 중 어느 것도 나를 위해 일하지 않았습니다. Swift 4 및 Xcode 10.1로 수행 한 작업 은 다음과 같습니다. ...

viewDidLoad ()에서 테이블 동적 행 높이를 선언하고 셀에 올바른 제한 조건을 작성하십시오.

tableView.rowHeight = UITableView.automaticDimension

또한 viewDidLoad ()에서 모든 tableView 셀 펜촉을 다음과 같이 tableview에 등록하십시오.

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

tableView 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
    } 

}

이제 tableView expectedHeightForRowAt의 각 셀에 대해 예상 행 높이를 제공하십시오. 최대한 정확하게 ...

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을 저장하고 설정할 필요가 없었습니다.


1

두 개의 다른 셀 높이가 있습니다.

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

내가 추가 한 후 estimatedHeightForRowAt을 더 이상 점프 없었다.

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

0

cell.layoutSubviews()셀을 반환하기 전에 전화를 겁니다 func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?. iOS8의 알려진 버그입니다.


0

당신은 다음을 사용할 수 있습니다 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

0

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

여기에 대한 답변에 따라 애니메이션과 관련이 있다는 단서가 있었으므로 테이블 뷰가 스택 뷰 안에 있었고 때로는 stackView.layoutIfNeeded()애니메이션 블록 내부에 호출되는 것을 알았습니다 . 마지막 해결책은 "필요하지 않은"레이아웃이 "필요하지 않은"경우에도 해당 컨텍스트에서 시각적 동작을 가지기 때문에 "실제로"필요하지 않은 한이 호출이 발생하지 않도록하는 것입니다.


0

나는 같은 문제가 있었다. 애니메이션없이 페이지 매김 및 데이터 다시로드가 있었지만 스크롤을 방지하는 데 도움이되지 않았습니다. 나는 다른 크기의 아이폰을 가지고 있는데, iphone8에서는 스크롤이 튀지 않았지만 iphone7 +에서는 깜짝 놀랐습니다.

viewDidLoad 함수 에 다음 변경 사항을 적용했습니다 .

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

내 문제가 해결되었습니다. 나는 그것이 당신에게도 도움이되기를 바랍니다.


0

내가 찾은이 문제를 해결하는 방법 중 하나는

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

-2

실제로 나는 당신 reloadRows이 점프 문제를 일으키는 것을 사용했는지 발견했다 . 그런 다음 다음 reloadSections과 같이 사용 하십시오.

UIView.performWithoutAnimation {
    tableView.reloadSections(NSIndexSet(index: indexPath.section) as IndexSet, with: .none)
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.