Dinamik hücre yüksekliklerine sahip UITableView'ın reloadData () yöntemi, bulanık kaymaya neden oluyor


142

Bunun ortak bir sorun olabileceğini hissediyorum ve bunun ortak bir çözümü olup olmadığını merak ediyordum.

Temel olarak, UITableView'ım her hücre için dinamik hücre yüksekliğine sahip. UITableView ve I üst kısmında değilse, tableView.reloadData()yukarı kaydırma gergin olur.

Bunun nedeni, veriyi yeniden yüklediğimden, yukarı kaydırdığım için UITableView'ın görünürlük haline gelen her hücre için yüksekliği yeniden hesapladığından kaynaklandığına inanıyorum. Bunu nasıl azaltabilirim veya yalnızca belirli bir IndexPath'ten UITableView'ün sonuna kadar Data'yı nasıl yeniden yükleyebilirim?

Dahası, en üste kadar ilerlemeyi başardığımda, aşağı ve sonra yukarı kaydırabilirim, zıplamadan sorun olmaz. Bunun nedeni büyük olasılıkla UITableViewCell yüksekliklerinin önceden hesaplanmış olmasıdır.


Birkaç şey ... (1) Evet, belirli satırları kullanarak kesinlikle yeniden yükleyebilirsiniz reloadRowsAtIndexPaths. Fakat (2) "gergin" ile ne demek istiyorsun ve (3) tahmini bir satır yüksekliği ayarladın? (Sadece tabloyu dinamik olarak güncellemenizi sağlayacak daha iyi bir çözüm olup olmadığını anlamaya
çalışın

@LyndseyScott, evet, tahmini bir satır yüksekliği ayarladım. Ürkekle, yukarı kaydırdığımda satırların yukarı doğru kaydığını kastediyorum. Bunun 128 satırlık bir satır yüksekliği ayarladığından ve sonra yukarı kaydırdığımda, yukarıdaki UITableView'daki tüm yayınlarımın daha küçük olduğundan, masamın atlamasına neden olan yüksekliği daralttığına inanıyorum. ReViewRowsAtIndexPaths satırdan xTableView içinde son satıra yapmayı düşünüyorum ... ama yeni satırlar eklediğim için, işe yaramaz, ben yeniden yüklemeden önce benim tablo görünümü sonunun ne olacağını bilmiyorum veri.
David

2
@LyndseyScott hala sorunu çözemiyorum, iyi bir çözüm var mı?
rad

1
Hiç bu soruna bir çözüm buldunuz mu? Videonuzdakiyle aynı sorunu yaşıyorum.
user3344977

1
Aşağıdaki cevapların hiçbiri benim için işe yaramadı.
Srujan Simha

Yanıtlar:


221

Atlamayı önlemek için, yüklendiğinde hücrelerin yüksekliklerini kaydetmeli ve tam olarak değer vermelisiniz tableView:estimatedHeightForRowAtIndexPath:

Swift:

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
}

Hedef 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
Teşekkürler, u gerçekten benim günümü kurtarmak :) Obc de çalışır
Artem Z.

3
cellHeightsDictionarycellHeightsDictionary = [NSMutableDictionary dictionary];
Başlamayı

1
estimatedHeightForRowAtIndexPath:çift ​​değer döndürürse *** Assertion failure in -[UISectionRowData refreshWithSection:tableView:tableViewRowData:]hataya neden olabilir . Bunu düzeltmek için return floorf(height.floatValue);.
liushuaikobe

Merhaba @lgor, aynı sorunu yaşıyorum ve çözümünüzü uygulamaya çalışıyorum. Ben alıyorum sorun tahmin edilirHeightForRowAtIndexPath willDisplayCell önce çağrılır, bu nedenle tahminiHeightForRowAtIndexPath çağrıldığında hücrenin yüksekliği hesaplanmaz. Herhangi bir yardım?
Madhuri

1
@Madhuri etkili yükseklikleri, willDisplayCell'den hemen önce ekrandaki her hücre için çağrılan "heightForRowAtIndexPath" içinde hesaplanmalıdır.
Donnit

109

Swift 3 kabul edilen cevap versiyonu.

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 
}

Teşekkürler bu harika çalıştı! aslında benim uygulamamı kaldırabildim func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {, bu ihtiyacım olan tüm yükseklik hesaplamasını idare ediyor.
Natalia

Uzun saatler boyunca sürekli zıplama ile mücadele ettikten sonra dersime eklemeyi unuttuğumu anladım UITableViewDelegate. Yukarıda gösterilen willDisplayişlevi içerdiği için bu protokole uymak gerekir . Umarım birini aynı mücadeleden kurtarabilirim.
MJQZ1347

Swift yanıtı için teşekkür ederim. Benim durumumda tablo görünümü altına / yakın kaydırıldığında yeniden yükleme sırasında hücrelerin SÜPER garip davranışı vardı. Bundan sonra kendiliğinden büyüklükte hücrelerim olduğunda kullanacağım.
Trev14

Swift 4.2'de mükemmel çalışıyor
Adam S.

Bir hayat kurtarıcı. Veri kaynağına daha fazla öğe eklemeye çalışırken çok yardımcı olur. Yeni eklenen hücrelerin ekranın ortasına sıçramasını önler.
Philip Borbon

38

Atlama, kötü bir tahmini yükseklikten kaynaklanıyor. TahminiRowHeight, gerçek yükseklikten ne kadar farklı olursa, tablo yeniden yüklendiğinde özellikle o kadar aşağı kaydırıldığında zıplayabilir. Bunun nedeni, tablonun tahmini boyutunun gerçek boyutundan kökten farklı olması, tabloyu içerik boyutunu ve ofsetini ayarlamaya zorlamasıdır. Dolayısıyla, tahmin edilen yükseklik rastgele bir değer olmamalı, yüksekliğin ne olacağını düşündüğünüze yakın olmalıdır. UITableViewAutomaticDimension Hücreleriniz aynı tipte ise ben ayarladığımda da yaşadım

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

farklı bölümlerde çeşitli hücreler varsa daha iyi bir yer olduğunu düşünüyorum

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

2
teşekkür ederim, tam olarak neden tableView bu kadar gergin olduğunu.
Louis de Decker

1
Eski bir cevap, ancak 2018'den beri hala geçerli. Diğer tüm cevapların aksine, bu, hücrelerin aynı veya çok benzer yüksekliğe sahip olmasına yardımcı olan viewDidLoad'da bir kez tahminiRowHeigh ayarlanmasını önerir. Teşekkürler. BTW, alternatif olarak esimatedRowHeight, Boyut Denetçisi> Tablo Görünümü> Tahmin'de Arayüz Oluşturucu aracılığıyla ayarlanabilir.
Vitalii

daha doğru bir tahmini yükseklik sağladım. Ayrıca çok kesitli gruplandırılmış tablo görünümü stili vardı ve uygulamak zorunda kaldımtableView(_:estimatedHeightForHeaderInSection:)
nteissler

25

@Igor cevap bu durumda iyi çalışıyor,Swift-4kodu.

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

aşağıdaki yöntemlerle 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
Bu çözümü kullanarak satır ekleme / silme ile nasıl başa çıkılır? Sözlük verileri gerçek olmadığından TableView atlar.
Alexey Chekanov

1
harika çalışıyor! özellikle satır yeniden yüklerken son hücrede.
Ning

19

Yukarıdaki tüm geçici çözümleri denedim, ancak hiçbir şey işe yaramadı.

Saat harcadıktan ve olası tüm hayal kırıklıklarından geçtikten sonra, bunu düzeltmenin bir yolunu buldum. Bu çözüm hayat kurtarıcıdır! Bir cazibe gibi çalıştı!

Hızlı 4

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

Kodun daha temiz görünmesini sağlamak ve her yeniden yüklemek istediğimde tüm bu satırları yazmaktan kaçınmak için bir uzantı olarak ekledim.

extension UITableView {

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

en sonunda ..

tableView.reloadWithoutAnimation()

VEYA bu satırı aslında UITableViewCell awakeFromNib()yönteminize ekleyebilirsiniz

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

ve normal yap reloadData()


1
Bu nasıl yeniden yükleme yapar? Sen diyoruz bunu reloadWithoutAnimationancak nerede reloadparçası?
matt

@matt tableView.reloadData()önce arayabilirsin ve sonra tableView.reloadWithoutAnimation()hala çalışıyor.
Srujan Simha

Harika! Yukarıdakilerin hiçbiri benim için çalışmadı. Tüm yükseklikler ve tahmini yükseklikler bile tamamen aynıdır. İlginç.
TY Küçük

1
Benim için çalışma. TableView.endUpdates () 'de kilitlenir. Birisi bana yardım edebilir mi?
Kakashi

12

Bunu düzeltmek için daha fazla yol kullanıyorum:

Görünüm denetleyicisi için:

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 için uzantı olarak

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

Sonuç

tableView.reloadSectionWithouAnimation(section: indexPath.section)

1
Benim için önemli olan UITableView uzantısını burada uygulamaktı. Çok zeki. Teşekkür rastislv
BennyTheNerd

Mükemmel çalışır, ancak sadece bir dezavantajı vardır, üstbilgi, altbilgi veya satır eklerken animasyonu kaybedersiniz.
Soufian Hossam

ReloadSectionWithouAnimation nerede denir? Örneğin, kullanıcılar uygulamamda bir resim yayınlayabilir (Instagram gibi); Görüntüleri yeniden boyutlandırabilirim, ancak çoğu durumda bunun gerçekleşmesi için tablo hücresini kaydırmak zorundayım. Tablo reloadData geçtikten sonra hücre doğru boyutta olmasını istiyorum.
Luke Irvin

11

Bugün bununla karşılaştım ve gözlemledim:

  1. Gerçekten sadece iOS 8.
  2. Geçersiz kılma cellForRowAtIndexPathyardımcı olmaz.

Düzeltme aslında oldukça basitti:

Geçersiz kılın estimatedHeightForRowAtIndexPathve doğru değerleri döndürdüğünden emin olun.

Bununla, UITableViews'imdeki tüm garip titreme ve atlama durdu.

NOT: Aslında hücrelerimin boyutunu biliyorum. Sadece iki olası değer vardır. Hücreleriniz gerçekten değişken büyüklükte ise, o zaman önbelleğe isteyebilirsiniz cell.bounds.size.heightdantableView:willDisplayCell:forRowAtIndexPath:


2
Tahmin edilenHeightForRowAtIndexPath yönteminin yüksek bir değerle geçersiz kılınması düzeltildi, örneğin 300f
Flappy

1
@Seçtiğiniz çözümün nasıl çalıştığı ve önerilen diğer tekniklerden daha kısa olduğu ilginçtir. Cevap olarak göndermeyi düşünün.
Rohan Sanap

9

Aslında, yalnızca belirli satırları aşağıdaki gibi kullanarak yeniden yükleyebilirsiniz reloadRowsAtIndexPaths:

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

Ancak, genel olarak, tablo hücresi yükseklik değişikliklerini aşağıdaki gibi canlandırabilirsiniz:

tableView.beginUpdates()
tableView.endUpdates()

BeginUpdates / endUpdates yöntemini denedim, ancak bu yalnızca tablonun görünür satırlarını etkiler. Yukarı kaydırdığımda hala sorunum var.
David

@David Muhtemelen tahmini satır yükseklikleri kullandığınız için.
Lyndsey Scott

EstimatedRowHeights'ımdan kurtulmalı ve bunun yerine beginUpdates ve endUpdates ile değiştirmeli miyim?
David

@David Hiçbir şeyi "değiştiremezsiniz", ancak bu gerçekten istenen davranışa bağlıdır ... Tahmini satır yüksekliğini kullanmak ve tablonun geçerli görünür bölümünün altındaki dizinleri yeniden yüklemek istiyorsanız, bunu yapabilirsiniz Ben reloadRowsAtIndexPaths kullanarak dedim
Lyndsey Scott

ReladRowsAtIndexPaths yöntemini denemeyle ilgili sorunlarımdan biri, sonsuz kaydırma uyguladığımdır, bu yüzden Veriyi yeniden yüklediğimde dataSource'a sadece 15 satır daha ekledim. Bu, bu satırlar için indexPath'lerin UITableView'da henüz mevcut olmadığı anlamına gelir
David

3

İşte biraz daha kısa bir sürüm:

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

3

Tahmini değeri yüksek, örneğin 300f olan geçersizHeightForRowAtIndexPath yöntemini geçersiz kılma

Bu, sorunu çözmeli :)


2

İOS11'de tanıtıldığına inandığım bir hata var.

Bu reloadtabloyu yaptığınızda contentOffSetbeklenmedik şekilde değişir. Aslında contentOffsetbir yeniden yükleme sonrasında değişmemelidir. Bu yanlış hesaplamalar nedeniyle olma eğilimindedir.UITableViewAutomaticDimension

Yeniden contentOffSetyüklemeniz bittikten sonra, kaydetmeniz ve kaydettiğiniz değere ayarlamanız gerekir.

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

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

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

Nasıl kullanılır?

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

Bu cevap buradan türetildi


2

Bu Swift4'te benim için çalıştı:

extension UITableView {

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

1

Bu çözümlerin hiçbiri benim için işe yaramadı. İşte Swift 4 ve Xcode 10.1 ile yaptığım şey ...

ViewDidLoad () öğesinde, tablo dinamik satır yüksekliğini bildirin ve hücrelerde doğru kısıtlamalar oluşturun ...

tableView.rowHeight = UITableView.automaticDimension

Ayrıca viewDidLoad () 'da, tüm tableView hücre uçlarınızı tableview'e aşağıdaki gibi kaydedin:

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 öğesinde, indexPath.row dosyasındaki her bir hücrenin yüksekliğine eşit olan dönüş yüksekliği ...

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
    } 

}

Şimdi, tablodaki her hücre için tahmini bir satır yüksekliği verin (TahminiHeightForRowAt). Mümkün olduğunca doğru olun ...

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
    } 

}

İşe yarayacak ...

TableView.reloadData () öğesini çağırırken contentOffset'i kaydetmem ve ayarlamam gerekmiyordu


1

2 farklı hücre yüksekliğim var.

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

Eklediğim sonra estimatedHeightForRowAt , artık atlama yoktu.

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

0

cell.layoutSubviews()Hücreyi geri getirmeden önce aramayı deneyin func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?. İOS8'de bilinen bir hata.


0

Şunlarda aşağıdakileri kullanabilirsiniz: 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

Bu atlama davranışım vardı ve başlangıçta tam olarak tahmin edilen üstbilgi yüksekliğini ayarlayarak hafifletmeyi başardım (çünkü yalnızca 1 olası üstbilgi görünümüm vardı), ancak daha sonra tüm tabloları etkilemeden özel olarak üstbilgilerin içinde gerçekleşmeye başladı .

Buradaki cevapları takiben, animasyonlarla ilgili ipucuna sahiptim, bu yüzden tablo görünümünün bir yığın görünümü içinde olduğunu ve bazen stackView.layoutIfNeeded()bir animasyon bloğunun içinde arayacağımızı buldum . Son çözümüm, "gerçekten" gerekmedikçe bu çağrının gerçekleşmediğinden emin olmaktı, çünkü "gerekliyse" düzeni, "gerekmediğinde" bile bu bağlamda görsel davranışlara sahipti.


0

Aynı sorunu yaşadım. Sayfalandırma ve animasyon olmadan verileri yeniden yükleme vardı ama atlama önlemek için kaydırma yardımcı olmadı. IPhone'ların farklı boyutu var, kaydırma iphone8'de gergin değildi, ancak iphone7 +

ViewDidLoad işlevinde aşağıdaki değişiklikleri uyguladım :

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

ve sorunum çözüldü. Umarım sana da yardımcı olur.


0

Bulduğum bu sorunu çözme yaklaşımlarından biri

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

-2

Aslında reloadRowsbir atlama sorununa neden kullanıyorsanız buldum . O zaman böyle kullanmaya çalışmalısınız reloadSections:

UIView.performWithoutAnimation {
    tableView.reloadSections(NSIndexSet(index: indexPath.section) as IndexSet, with: .none)
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.