Ich baue eine App, die eine Feedansicht für vom Nutzer eingereichte Beiträge enthält. Diese Ansicht hat eine UITableView
mit einer benutzerdefinierten UITableViewCell
-Implementierung. In dieser Zelle habe ich eine weitere UITableView
zum Anzeigen von Kommentaren. Der Gist ist ungefähr so:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
Der anfängliche Feed wird mit 3 Kommentaren für die Vorschau heruntergeladen. Wenn jedoch weitere Kommentare vorhanden sind oder der Benutzer einen Kommentar hinzufügt oder löscht, möchte ich die PostCell
in der Feedtabellenansicht aktualisieren, indem CommentCells
zu den Kommentaren hinzugefügt oder entfernt wird Tabelle innerhalb der PostCell
. Ich verwende derzeit den folgenden Helfer, um dies zu erreichen:
// (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
}
Damit ist das Update gut gelungen. Der Beitrag wird an Ort und Stelle aktualisiert, wobei Kommentare wie erwartet innerhalb der PostCell
hinzugefügt oder entfernt werden. Ich benutze die automatische Größenanpassung PostCells
in der Feed-Tabelle. Die Kommentartabelle von PostCell
wird erweitert, um alle Kommentare anzuzeigen, aber die Animation ist etwas ruckelig und die Tabelle blättert ein Dutzend Pixel auf und ab, während die Zellaktualisierungsanimation stattfindet.
Das Springen während der Größenänderung ist etwas ärgerlich, aber mein Hauptproblem kommt danach. Wenn ich nun im Feed nach unten scrolle, ist das Scrollen wie zuvor glatt, aber wenn ich über die Zelle nach oben scrolle, die ich gerade nach dem Hinzufügen von Kommentaren geändert habe, springt der Feed ein paar Mal zurück, bevor er den oberen Rand des Feeds erreicht. Ich iOS8
automatische Größenanpassung von Zellen für den Feed wie folgt:
// (FeedController.Swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
Wenn ich die Variable estimatedRowHeight
entferne, wird die Tabelle bei jeder Änderung der Zellenhöhe nach oben verschoben. Ich fühle mich jetzt ziemlich fest damit beschäftigt und könnte als neuer iOS-Entwickler alle möglichen Tipps verwenden.
Hier ist die beste Lösung, die ich gefunden habe, um diese Art von Problem zu lösen (Scrolling-Problem + reloadRows + iOS 8 UITableViewAutomaticDimension);
Es besteht darin, alle Höhen in einem Wörterbuch beizubehalten und sie zu aktualisieren (im Wörterbuch), da die tableView die Zelle anzeigt.
Die gespeicherte Höhe wird dann in der - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
-Methode zurückgegeben.
Sie sollten so etwas implementieren:
Ziel 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];
}
Swift 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)
}
Wir hatten das gleiche Problem. Dies beruht auf einer schlechten Schätzung der Zellenhöhe, die dazu führt, dass das SDK eine schlechte Höhe erzwingt, die beim Zurückblättern der Zellen zum Springen der Zellen führt. Je nachdem, wie Sie Ihre Zelle aufgebaut haben, können Sie dies am besten beheben, indem Sie die UITableViewDelegate
-Methode - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
implementieren.
Solange Ihre Schätzung dem tatsächlichen Wert der Zellenhöhe ziemlich nahe kommt, wird das Springen und Ruckeln fast aufgehoben. So haben wir es implementiert, Sie erhalten die Logik:
- (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;
}
}
Unterstützung für Swift 2 hinzugefügt
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
}
antwort von dosdos arbeitete für mich in Swift 2
Deklarieren Sie den Ivar
var heightAtIndexPath = NSMutableDictionary()
in func viewDidLoad ()
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Fügen Sie dann die folgenden zwei Methoden hinzu:
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
}
Ich stand auch vor dem gleichen Problem. Ich habe zwar eine Problemumgehung gefunden, aber der Ruck ist nicht vollständig behoben. Aber es scheint viel besser zu sein als beim vorherigen abgehackten Scrollen.
Verwenden Sie in Ihrer UITableView
-Delegatenmethode :cellForRowAtIndexPath:
die folgenden zwei Methoden, um die Einschränkungen zu aktualisieren, bevor Sie die Zelle zurückgeben. (Schnelle Sprache)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDIT: Möglicherweise müssen Sie auch mit dem tableView.estimatedRowHeight
-Wert herumspielen, um einen reibungsloseren Bildlauf zu erzielen.
Folgende @dosdos Antwort.
Ich fand auch interessant zu implementieren: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Speziell für meinen Code, bei dem die Zelle Einschränkungen dynamisch ändert, während die Zelle bereits auf dem Bildschirm angezeigt wird. Das Aktualisieren des Wörterbuchs auf diese Weise hilft beim zweiten Anzeigen der Zelle.
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
}
}
@dosdos-Lösung funktioniert einwandfrei
aber es gibt etwas, das Sie hinzufügen sollten
folgende @dosdos Antwort
Swift 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)
}
dann diese Zeilen verwenden, wann immer Sie wollen, für mich verwende ich es in textDidChange
endlich zum oberen Tableview wechseln
tableView.reloadData()
self.tableView.layoutIfNeeded()
self.tableView.setContentOffset(CGPoint.zero, animated: true)