[英]UITableView dynamic cell heights only correct after some scrolling when resizing cell according to uiimage view
[英]UITableView dynamic cell heights only correct after some scrolling
我有一个UITableView
一个自定义UITableViewCell
在故事板使用自动布局定义。 单元格有几个多行UILabels
。
UITableView
似乎可以正确计算单元格高度,但对于前几个单元格,高度未在标签之间正确划分。 滚动一点后,一切都按预期工作(即使是最初不正确的单元格)。
- (void)viewDidLoad {
[super viewDidLoad]
// ...
self.tableView.rowHeight = UITableViewAutomaticDimension;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
// ...
// Set label.text for variable length string.
return cell;
}
有什么我可能会遗漏的,导致自动布局在前几次无法完成工作吗?
我创建了一个示例项目来演示这种行为。
我不知道这是否有明确记录,但是在返回单元格之前添加[cell layoutIfNeeded]
可以解决您的问题。
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
TableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"TestCell"];
NSUInteger n1 = firstLabelWordCount[indexPath.row];
NSUInteger n2 = secondLabelWordCount[indexPath.row];
[cell setNumberOfWordsForFirstLabel:n1 secondLabel:n2];
[cell layoutIfNeeded]; // <- added
return cell;
}
当其他类似的解决方案没有时,这对我有用:
override func didMoveToSuperview() {
super.didMoveToSuperview()
layoutIfNeeded()
}
这似乎是一个实际的错误,因为我非常熟悉 AutoLayout 以及如何使用 UITableViewAutomaticDimension,但是我仍然偶尔会遇到这个问题。 我很高兴我终于找到了一些可以作为解决方法的东西。
在cellForRowAtIndexPath
中添加[cell layoutIfNeeded]
不适用于最初滚动到视图外的单元格。
也没有以[cell setNeedsLayout]
开头。
您仍然需要滚动某些单元格并返回到视图中才能正确调整大小。
这非常令人沮丧,因为大多数开发人员都有动态类型、自动布局和自适应单元格正常工作——除了这个烦人的情况。 这个错误影响了我所有的“更高”的表视图控制器。
我在我的一个项目中也有同样的经历。
为什么会发生?
在 Storyboard 中设计的单元格为某些设备具有一定的宽度。 例如 400 像素。 例如,您的标签具有相同的宽度。 当它从故事板加载时,它的宽度为 400 像素。
这里有一个问题:
tableView:heightForRowAtIndexPath:
在单元tableView:heightForRowAtIndexPath:
之前调用它的子视图。
所以它计算了宽度为 400px 的标签和单元格的高度。 但是您在带有屏幕的设备上运行,例如 320px。 而这个自动计算的高度是不正确的。 仅仅因为单元格的layoutSubviews
仅在tableView:heightForRowAtIndexPath:
之后发生,即使您在layoutSubviews
手动为标签设置preferredMaxLayoutWidth
layoutSubviews
。
我的解决方案:
1) 子类化UITableView
并覆盖dequeueReusableCellWithIdentifier:forIndexPath:
。 将单元格宽度设置为等于表格宽度并强制单元格的布局。
- (UITableViewCell *)dequeueReusableCellWithIdentifier:(NSString *)identifier forIndexPath:(NSIndexPath *)indexPath {
UITableViewCell *cell = [super dequeueReusableCellWithIdentifier:identifier forIndexPath:indexPath];
CGRect cellFrame = cell.frame;
cellFrame.size.width = self.frame.size.width;
cell.frame = cellFrame;
[cell layoutIfNeeded];
return cell;
}
2) 子类UITableViewCell
。 为layoutSubviews
的标签手动设置preferredMaxLayoutWidth
。 您还需要手动布局contentView
,因为它不会在单元格框架更改后自动布局(我不知道为什么,但确实如此)
- (void)layoutSubviews {
[super layoutSubviews];
[self.contentView layoutIfNeeded];
self.yourLongTextLabel.preferredMaxLayoutWidth = self.yourLongTextLabel.width;
}
我有一个类似的问题,在第一次加载时,没有计算行高,但在滚动或转到另一个屏幕后,我回到这个屏幕行被计算。 在第一次加载时,我的项目是从 Internet 加载的,在第二次加载时,我的项目首先从 Core Data 加载并从 Internet 重新加载,我注意到行高是在从 Internet 重新加载时计算的。 所以我注意到在转场动画期间调用 tableView.reloadData() 时(推送和呈现转场的问题相同),没有计算行高。 所以我在视图初始化时隐藏了 tableview 并放置了一个活动加载器以防止对用户产生丑陋的影响,我在 300ms 后调用 tableView.reloadData ,现在问题解决了。 我认为这是一个 UIKit 错误,但这种解决方法可以解决问题。
我将这些行(Swift 3.0)放在我的项目加载完成处理程序中
DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(300), execute: {
self.tableView.isHidden = false
self.loader.stopAnimating()
self.tableView.reloadData()
})
这解释了为什么对于某些人来说,在 layoutSubviews 中放置一个 reloadData 可以解决问题
上述解决方案都不适合我,有效的是这个魔法秘诀:按以下顺序调用它们:
tableView.reloadData()
tableView.layoutIfNeeded() tableView.beginUpdates() tableView.endUpdates()
我的 tableView 数据是从 Web 服务填充的,在连接的回调中我写了上面几行。
我已经尝试了这个问题的大部分答案,但无法让其中任何一个起作用。 我发现的唯一功能解决方案是将以下内容添加到我的UITableViewController
子类中:
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
UIView.performWithoutAnimation {
tableView.beginUpdates()
tableView.endUpdates()
}
}
UIView.performWithoutAnimation
调用是必需的,否则您将在视图控制器加载时看到正常的表视图动画。
在我的情况下,UILabel 的最后一行在第一次显示单元格时被截断。 它发生得非常随机,正确调整大小的唯一方法是将单元格滚动到视图之外并将其带回来。 我尝试了到目前为止显示的所有可能的解决方案(layoutIfNeeded..reloadData),但没有任何效果对我有用。 诀窍是将“自动收缩”设置为最小字体比例(对我来说是 0.5)。 试一试
为表格视图自定义单元格中的所有内容添加约束,然后估计表格视图行高并将行高设置为在 viewdid 加载中的自动尺寸:
override func viewDidLoad() {
super.viewDidLoad()
tableView.estimatedRowHeight = 70
tableView.rowHeight = UITableViewAutomaticDimension
}
要修复该初始加载问题,请在自定义表格视图单元格中应用 layoutIfNeeded 方法:
class CustomTableViewCell: UITableViewCell {
override func awakeFromNib() {
super.awakeFromNib()
self.layoutIfNeeded()
// Initialization code
}
}
设置preferredMaxLayoutWidth
对我有帮助。 我加了
cell.detailLabel.preferredMaxLayoutWidth = cell.frame.width
在我的代码中。
另请参阅UILabel和http://openradar.appspot.com/17799811 中的单行文本需要两行。
在cellForRowAt
调用cell.layoutIfNeeded()
在 ios 10 和 ios 11 上对我cell.layoutIfNeeded()
,但在 ios 9 上不起作用。
为了在 ios 9 上也能完成这项工作,我调用了cell.layoutSubviews()
并且它成功了。
我尝试了此页面中的所有解决方案,但取消选中使用大小类然后再次检查它解决了我的问题。
编辑:取消选中大小类会导致故事板上出现很多问题,因此我尝试了另一种解决方案。 我在我的视图控制器的viewDidLoad
和viewWillAppear
方法中填充了我的表视图。 这解决了我的问题。
上述解决方案均无效,但以下建议组合有效。
必须在 viewDidLoad() 中添加以下内容。
DispatchQueue.main.async {
self.tableView.reloadData()
self.tableView.setNeedsLayout()
self.tableView.layoutIfNeeded()
self.tableView.reloadData()
}
上面的 reloadData、setNeedsLayout 和 layoutIfNeeded 组合有效,但其他组合无效。 不过,可能特定于项目中的单元格。 是的,必须两次调用 reloadData 才能使其工作。
还要在 viewDidLoad 中设置以下内容
tableView.rowHeight = UITableViewAutomaticDimension
tableView.estimatedRowHeight = MyEstimatedHeight
在 tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
cell.setNeedsLayout()
cell.layoutIfNeeded()
就我而言,单元格中的堆栈视图导致了问题。 这显然是一个错误。 一旦我删除它,问题就解决了。
我有调整标签大小的问题,所以我只需要做
chatTextLabel.text = chatMessage.message chatTextLabel?.updateConstraints() 设置好文字后
// 完整代码
func setContent() {
chatTextLabel.text = chatMessage.message
chatTextLabel?.updateConstraints()
let labelTextWidth = (chatTextLabel?.intrinsicContentSize().width) ?? 0
let labelTextHeight = chatTextLabel?.intrinsicContentSize().height
guard labelTextWidth < originWidth && labelTextHeight <= singleLineRowheight else {
trailingConstraint?.constant = trailingConstant
return
}
trailingConstraint?.constant = trailingConstant + (originWidth - labelTextWidth)
}
就我而言,我在其他周期进行更新。 所以 tableViewCell 高度在 labelText 设置后更新。 我删除了异步块。
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier:Identifier, for: indexPath)
// Check your cycle if update cycle is same or not
// DispatchQueue.main.async {
cell.label.text = nil
// }
}
只要确保您没有在表格视图的 'willdisplaycell' 委托方法中设置标签文本。 在 'cellForRowAtindexPath' 委托方法中设置标签文本以进行动态高度计算。
别客气 :)
问题是初始单元格在我们获得有效行高之前加载。 解决方法是在视图出现时强制重新加载表。
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
[self.tableView reloadData];
}
苹果偶尔出现奇怪的无能的一个持续例子,问题实际上持续了数年。
情况似乎确实如此
cell.layoutIfNeeded()
return cell
将修复它。 (当然,您会失去一些性能。)
这就是苹果的生活。
在我的情况下,单元格高度的问题发生在加载初始表格视图之后,并且发生了用户操作(点击单元格中具有更改单元格高度效果的按钮)。 除非我这样做,否则我一直无法让单元格改变其高度:
[self.tableView reloadData];
我确实尝试过
[cell layoutIfNeeded];
但这没有用。
在 Swift 3 中。每次更新可重用单元格的文本时,我都必须调用 self.layoutIfNeeded()。
import UIKit
import SnapKit
class CommentTableViewCell: UITableViewCell {
static let reuseIdentifier = "CommentTableViewCell"
var comment: Comment! {
didSet {
textLbl.attributedText = comment.attributedTextToDisplay()
self.layoutIfNeeded() //This is a fix to make propper automatic dimentions (height).
}
}
internal var textLbl = UILabel()
override func layoutSubviews() {
super.layoutSubviews()
if textLbl.superview == nil {
textLbl.numberOfLines = 0
textLbl.lineBreakMode = .byWordWrapping
self.contentView.addSubview(textLbl)
textLbl.snp.makeConstraints({ (make) in
make.left.equalTo(contentView.snp.left).inset(10)
make.right.equalTo(contentView.snp.right).inset(10)
make.top.equalTo(contentView.snp.top).inset(10)
make.bottom.equalTo(contentView.snp.bottom).inset(10)
})
}
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let comment = comments[indexPath.row]
let cell = tableView.dequeueReusableCell(withIdentifier: CommentTableViewCell.reuseIdentifier, for: indexPath) as! CommentTableViewCell
cell.selectionStyle = .none
cell.comment = comment
return cell
}
commentsTableView.rowHeight = UITableViewAutomaticDimension
commentsTableView.estimatedRowHeight = 140
我遇到了这个问题并通过将我的视图/标签初始化代码从tableView(willDisplay cell:)
到tableView(cellForRowAt:)
修复它。
对于仍然面临此问题的人,请尝试这条神奇的线条
tableview.scrollToRow(at: IndexPath(row: 0, section: 0), at: .top, animated: true)
只需确保在填充表后再调用数据,这样就可以确保第0部分和第0行始终可用,否则它会崩溃,你可以先检查它们的可用性,希望这会有所帮助。
我找到了一个很好的解决方法。 由于在单元格可见之前无法计算高度,因此您需要做的就是在计算单元格大小之前滚动到单元格。
tableView.scrollToRow(at: indexPath, at: .bottom, animated: false)
let cell = tableView.cellForRow(at: indexPath) ?? UITableViewCell()
let size = cell.systemLayoutSizeFitting(CGSize(width: frame.size.width, height: UIView.layoutFittingCompressedSize.height))
iOS 11+
表格视图默认使用估计高度。 这意味着 contentSize 与最初的估计值一样。 如果您需要使用 contentSize,您需要通过将 3 个估计高度属性设置为零来禁用估计高度: tableView.estimatedRowHeight = 0 tableView.estimatedSectionHeaderHeight = 0 tableView.estimatedSectionFooterHeight = 0
public func tableView(_ tableView: UITableView, estimatedHeightForHeaderInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
public func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
public func tableView(_ tableView: UITableView, estimatedHeightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNormalMagnitude
}
另一种解决方案
@IBOutlet private weak var tableView: UITableView! {
didSet {
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = UITableView.automaticDimension
}
}
extension YourViewController: UITableViewDelegate {
func tableView(_ tableView: UITableView, estimatedHeightForRowAt indexPath: IndexPath) -> CGFloat {
UITableView.automaticDimension
}
}
那你就不需要layoutSabviews了
覆盖 prepareForReuse() 并将标签设置为 nil 在您的单元格类中
override func prepareForReuse() {
super.prepareForReuse()
self.detailLabel.text = nil
self.titleLabel.text = nil
}
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
// call the method dynamiclabelHeightForText
}
使用上面的方法动态返回行的高度。 并为您使用的标签分配相同的动态高度。
-(int)dynamiclabelHeightForText:(NSString *)text :(int)width :(UIFont *)font
{
CGSize maximumLabelSize = CGSizeMake(width,2500);
CGSize expectedLabelSize = [text sizeWithFont:font
constrainedToSize:maximumLabelSize
lineBreakMode:NSLineBreakByWordWrapping];
return expectedLabelSize.height;
}
此代码可帮助您找到标签中显示的文本的动态高度。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.