ios 使用 UITableViewAutomaticDimension 将 UITableViewCell 更新到位后生涩滚动
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/27996438/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Jerky Scrolling After Updating UITableViewCell in place with UITableViewAutomaticDimension
提问by Bryan Alger
I am building an app that has a feed view for user-submitted posts. This view has a UITableView
with a custom UITableViewCell
implementation. Inside this cell, I have another UITableView
for displaying comments. The gist is something like this:
我正在构建一个应用程序,该应用程序具有用户提交的帖子的提要视图。此视图具有UITableView
自定义UITableViewCell
实现。在这个单元格内,我有另一个UITableView
用于显示评论。要点是这样的:
Feed TableView
PostCell
Comments (TableView)
CommentCell
PostCell
Comments (TableView)
CommentCell
CommentCell
CommentCell
CommentCell
CommentCell
The initial feed will download with 3 comments for previewing, but if there are more comments, or if the user adds or deletes a comment, I want to update the PostCell
in place inside of the feed table view by adding or removing CommentCells
to the comments table inside of the PostCell
. I am currently using the following helper to accomplish that:
初始提要将下载 3 条评论以供预览,但如果有更多评论,或者用户添加或删除评论,我想PostCell
通过添加或删除CommentCells
评论表来更新提要表视图中的就地的PostCell
。我目前正在使用以下帮助程序来完成该任务:
// (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
}
This accomplishes the update just fine. The post is updated in place with comments added or removed inside of the PostCell
as expected. I am using auto sizing PostCells
in the feed table. The comments table of the PostCell
expands to show all of the comments, but the animation is a bit jerky and the table sort of scrolls up and down a dozen pixels or so while the cell update animation takes place.
这样就可以很好地完成更新。帖子更新到位,PostCell
并按预期在内部添加或删除评论。我PostCells
在提要表中使用自动调整大小。的评论表PostCell
展开以显示所有评论,但动画有点生涩,表格在单元格更新动画发生时上下滚动十几个像素左右。
The jumping during resizing is a bit annoying, but my main issue comes afterwards. Now if I scroll down in the feed, the scrolling is smooth as before, but if I scroll up above the cell I just resized after adding comments, the feed will jump backwards a few times before it reaches the top of the feed. I setup iOS8
auto sizing cells for the Feed like this:
调整大小期间的跳跃有点烦人,但我的主要问题是随后出现的。现在,如果我在提要中向下滚动,滚动会像以前一样平滑,但是如果我在添加评论后刚刚调整大小的单元格上方向上滚动,提要会在到达提要顶部之前向后跳几次。我iOS8
为 Feed设置自动调整大小的单元格,如下所示:
// (FeedController.swift)
// tableView is the feed table containing PostCells
self.tableView.rowHeight = UITableViewAutomaticDimension
self.tableView.estimatedRowHeight = 560
If I remove the estimatedRowHeight
, the table just scrolls to the top anytime a cell height changes. I'm feeling pretty stuck on this now and as a new iOS developer, could use any tips you might have.
如果我删除estimatedRowHeight
,表格只会在单元格高度发生变化时滚动到顶部。我现在对此感到非常困惑,作为一名新的 iOS 开发人员,可以使用您可能拥有的任何技巧。
回答by dosdos
Here is the best solution I found to solve this kind of problem (scrolling problem + reloadRows + iOS 8 UITableViewAutomaticDimension);
这是我找到的解决此类问题的最佳解决方案(滚动问题 + reloadRows + iOS 8 UITableViewAutomaticDimension);
It consists by keeping every heights in a dictionary and updating them (in the dictionary) as the tableView will display the cell.
它包括将每个高度保存在字典中并更新它们(在字典中),因为 tableView 将显示单元格。
You will then return the saved height in - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
method.
然后,您将在- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
方法中返回保存的高度。
You should implement something like this :
你应该实现这样的东西:
Objective-C
目标-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
斯威夫特 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)
}
回答by Gabriel Cartier
We had the same problem. It comes from a bad estimation of the cell height that causes the SDK to force a bad height which will cause the jumping of cells when scrolling back up. Depending on how you built your cell, the best way to fix this is to implement the UITableViewDelegate
method - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
我们遇到了同样的问题。它来自对单元格高度的错误估计,导致 SDK 强制设置错误的高度,这将导致向上滚动时单元格的跳跃。根据您构建单元的方式,解决此问题的最佳方法是实现该UITableViewDelegate
方法- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
As long as your estimation is pretty close to the real value of the cell height, this will almost cancel the jumping and jerkiness. Here's how we implemented it, you'll get the logic:
只要您的估计非常接近单元格高度的实际值,这几乎可以消除跳跃和急动。这是我们如何实现它,你会得到逻辑:
- (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;
}
}
Added Swift 2 support
添加了 Swift 2 支持
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
}
回答by Ranknoodle
dosdos answer worked for me in Swift 2
dosdos 答案在Swift 2 中对我有用
Declare the ivar
声明 ivar
var heightAtIndexPath = NSMutableDictionary()
in func viewDidLoad()
在函数 viewDidLoad() 中
func viewDidLoad() {
.... your code
self.tableView.rowHeight = UITableViewAutomaticDimension
}
Then add the following 2 methods:
然后添加以下2个方法:
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:
快速 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
}
回答by Vishal Chandran
I was facing the same problem too. I did find a workaround, but it doesn't completely fix the jerk. But it seems to be a lot better compared to the previous choppy scrolling.
我也面临同样的问题。我确实找到了一种解决方法,但它并没有完全解决这个问题。但与之前的断断续续滚动相比,它似乎要好得多。
In your UITableView
delegate method :cellForRowAtIndexPath:
, try using the following two methods to update the constraints before returning the cell. (Swift language)
在您的UITableView
委托方法中:cellForRowAtIndexPath:
,尝试使用以下两种方法在返回单元格之前更新约束。(斯威夫特语言)
cell.setNeedsUpdateConstraints()
cell.updateConstraintsIfNeeded()
EDIT:You may also have to play around with the tableView.estimatedRowHeight
value to get a smoother scrolling.
编辑:您可能还需要调整该tableView.estimatedRowHeight
值以获得更平滑的滚动。
回答by Basil
@dosdos solution is working fine
@dosdos 解决方案工作正常
but there is something you should added
但你应该添加一些东西
following @dosdos answer
关注@dosdos 回答
Swift 3/4
斯威夫特 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)
}
then use this lines when ever you want , for me I use it inside textDidChange
然后在需要时使用此行,对我而言,我在textDidChange 中使用它
- first reload Tableview
- update constraint
finally move to top Tableview
tableView.reloadData() self.tableView.layoutIfNeeded() self.tableView.setContentOffset(CGPoint.zero, animated: true)
- 首先重新加载Tableview
- 更新约束
最后移到顶部 Tableview
tableView.reloadData() self.tableView.layoutIfNeeded() self.tableView.setContentOffset(CGPoint.zero, animated: true)
回答by Gabriel.Massana
Following @dosdosanswer.
继@dosdos回答。
I also found interesting to implement: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
我还发现实施很有趣: tableView(tableView: didEndDisplayingCell: forRowAtIndexPath:
Specially for my code, where the cell is changing Constraints dynamically while the cell is already displayed on screen. Updating the Dictionary like this helps the second time the cell is displayed.
特别是对于我的代码,当单元格已经显示在屏幕上时,单元格正在动态更改约束。像这样更新字典有助于第二次显示单元格。
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
}
}