Ich bin der Meinung, dass dies ein allgemeines Problem sein könnte, und fragte mich, ob es eine gemeinsame Lösung dafür gab.
Grundsätzlich verfügt mein UITableView über dynamische Zellenhöhen für jede Zelle. Wenn ich mich nicht an der Spitze der UITableView befinde und ich tableView.reloadData()
, wird das Scrollen nach oben sprunghaft.
Ich glaube, das liegt an der Tatsache, dass UITableView die Höhe für jede in Sichtbarkeit kommende Zelle neu berechnet, weil ich Daten neu geladen habe, während ich hoch scrolle. Wie kann ich das abmildern, oder wie kann ich ReloadData nur von einem bestimmten IndexPath bis zum Ende der UITableView laden?
Wenn ich es schaffe, ganz nach oben zu scrollen, kann ich nach unten und dann nach oben scrollen, kein Problem ohne Springen. Dies ist höchstwahrscheinlich darauf zurückzuführen, dass die Höhen von UITableViewCell bereits berechnet wurden.
Um Sprünge zu vermeiden, sollten Sie die Höhe der Zellen beim Laden speichern und einen genauen Wert in tableView:estimatedHeightForRowAtIndexPath
angeben:
// declare cellHeightsDictionary
NSMutableDictionary *cellHeightsDictionary;
// initialize in code (thanks to @Gerharbo)
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;
}
Swift 3-Version der akzeptierten Antwort.
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
}
Der Sprung ist wegen einer schlechten geschätzten Höhe. Je mehr die geschätzteRowHeight von der tatsächlichen Höhe abweicht, desto mehr springt die Tabelle, wenn sie neu geladen wird, insbesondere, je weiter unten gescrollt wurde. Dies liegt daran, dass die geschätzte Größe der Tabelle radikal von der tatsächlichen Größe abweicht, sodass die Tabelle die Größe und den Versatz des Inhalts anpassen muss. Die geschätzte Höhe sollte also kein Zufallswert sein, sollte aber nahe an dem liegen, was Ihrer Meinung nach die Höhe ist Sein. Ich habe auch erfahren, wenn ich UITableViewAutomaticDimension
Setze, wenn Ihre Zellen dann vom gleichen Typ sind
func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 100//close to your cell height
}
wenn Sie unterschiedliche Zellen in verschiedenen Abschnitten haben, denke ich, ist der bessere Ort
func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
//return different sizes for different cells if you need to
return 100
}
@IgorAntwort funktioniert in diesem Fall gut, Swift-4
-Code davon.
// declaration & initialization
var cellHeightsDictionary: [IndexPath: CGFloat] = [:]
in folgenden Methoden von 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 UITableViewAutomaticDimension
}
Ich bin heute darauf gestoßen und habe beobachtet:
cellForRowAtIndexPath
hilft nicht.Das Update war eigentlich ziemlich einfach:
Überschreiben Sie estimatedHeightForRowAtIndexPath
und stellen Sie sicher, dass die korrekten Werte zurückgegeben werden.
Damit ist alles seltsame Wackeln und Springen in meinen UITableViews gestoppt.
HINWEIS: Ich weiß eigentlich, wie groß meine Zellen sind. Es gibt nur zwei mögliche Werte. Wenn Ihre Zellen wirklich variable Größe haben, möchten Sie möglicherweise den cell.bounds.size.height
von tableView:willDisplayCell:forRowAtIndexPath:
zwischenspeichern.
Sie können tatsächlich nur bestimmte Zeilen mit reloadRowsAtIndexPaths
laden, zB:
tableView.reloadRowsAtIndexPaths(indexPathArray, withRowAnimation: UITableViewRowAnimation.None)
Im Allgemeinen können Sie jedoch auch die Änderungen der Tabellenzellhöhe wie folgt animieren:
tableView.beginUpdates()
tableView.endUpdates()
Ich habe alle oben genannten Problemumgehungen ausprobiert, aber nichts hat funktioniert.
Nachdem ich Stunden verbracht hatte und alle möglichen Frustrationen durchgemacht hatte, fand ich einen Weg, dies zu beheben. Diese Lösung ist ein Lebensretter! Lief wie am Schnürchen!
Swift 4
let lastContentOffset = tableView.contentOffset
tableView.beginUpdates()
tableView.endUpdates()
tableView.layer.removeAllAnimations()
tableView.setContentOffset(lastContentOffset, animated: false)
Ich habe es als Erweiterung hinzugefügt, damit der Code sauberer aussieht und nicht jedes Mal, wenn ich neu laden möchte, alle Zeilen geschrieben wird.
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
beginUpdates()
endUpdates()
layer.removeAllAnimations()
setContentOffset(lastScrollOffset, animated: false)
}
}
endlich ..
tableView.reloadWithoutAnimation()
Ich benutze mehr Möglichkeiten, um das Problem zu beheben:
Für den View Controller:
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
}
als Erweiterung für UITableView
func reloadSectionWithouAnimation(section: Int) {
UIView.performWithoutAnimation {
let offset = self.contentOffset
self.reloadSections(IndexSet(integer: section), with: .none)
self.contentOffset = offset
}
}
Das Ergebnis ist
tableView.reloadSectionWithouAnimation(section: indexPath.section)
Hier ist eine etwas kürzere Version:
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return self.cellHeightsDictionary[indexPath] ?? UITableViewAutomaticDimension
}
Dieser hat in Swift4 für mich gearbeitet:
extension UITableView {
func reloadWithoutAnimation() {
let lastScrollOffset = contentOffset
reloadData()
layoutIfNeeded()
setContentOffset(lastScrollOffset, animated: false)
}
}
Ich habe 2 verschiedene Zellenhöhen.
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
return Helper.makeDeviceSpecificCommonSize(cellHeight)
}
Nachdem ich geschätztHoheForRowAt hinzugefügt habe, gab es kein Springen mehr.
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
let cellHeight = CGFloat(checkIsCleanResultSection(index: indexPath.row) ? 130 : 160)
return Helper.makeDeviceSpecificCommonSize(cellHeight)
}
In ViewDidLoad()
können Sie Folgendes verwenden:
tableView.estimatedRowHeight = 0 // if have just tableViewCells <br/>
// use this if you have tableview Header/footer <br/>
tableView.estimatedSectionFooterHeight = 0 <br/>
tableView.estimatedSectionHeaderHeight = 0
Keine dieser Lösungen funktionierte für mich. Hier ist was ich mit Swift 4 & Xcode 10.1 gemacht habe ...
In viewDidLoad () deklarieren Sie die dynamische Zeilenhöhe der Tabelle und erstellen Sie korrekte Einschränkungen in Zellen ...
tableView.rowHeight = UITableView.automaticDimension
Registrieren Sie auch in viewDidLoad () alle Ihre tableView-Zellenspitzen wie folgt in 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")
Geben Sie in tableView heightForRowAt die Höhe jeder Zelle in indexPath.row zurück.
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
}
}
Geben Sie nun eine geschätzte Zeilenhöhe für jede Zelle in tableView EstimHeightForRowAt an. Seien Sie genau wie Sie können ...
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
}
}
Das sollte funktionieren...
Beim Aufruf von tableView.reloadData () musste ich contentOffset nicht speichern und festlegen.
Überschreiben der EstimatedHeightForRowAtIndexPath-Methode mit einem hohen Wert, z. B. 300f
Dies sollte das Problem beheben :)
Es gibt einen bug, von dem ich glaube, dass er in iOS11 eingeführt wurde.
Wenn Sie eine reload
ausführen, wird die tableView contentOffSet
unerwartet geändert. Tatsächlich sollte sich contentOffset
nach einem Neuladen nicht ändern. Dies geschieht aufgrund von Fehlberechnungen von UITableViewAutomaticDimension
Sie müssen Ihre contentOffSet
speichern und nach dem Laden wieder auf Ihren gespeicherten Wert setzen.
func reloadTableOnMain(with offset: CGPoint = CGPoint.zero){
DispatchQueue.main.async { [weak self] () in
self?.tableView.reloadData()
self?.tableView.layoutIfNeeded()
self?.tableView.contentOffset = offset
}
}
Wie benutzt du es?
someFunctionThatMakesChangesToYourDatasource()
let offset = tableview.contentOffset
reloadTableOnMain(with: offset)
Diese Antwort wurde von hier abgeleitet.
Versuchen Sie, cell.layoutSubviews()
aufzurufen, bevor Sie die Zelle in func cellForRowAtIndexPath(_ indexPath: NSIndexPath) -> UITableViewCell?
zurückgeben. Es ist ein bekannter Fehler in iOS8.