所以我得到了这个UITableView,它从内存中获取数据(已经预加载,滚动时没有请求,所有内容都在视图布局之前加载)。每个单元格的高度根据UITextView和Autolayout中的文本量动态计算。从Nib加载细胞并重复使用细胞正常工作(至少我希望如此)。我在计算行高时使用UITableViewAutomaticDimension,所以我不强制单元格布局两次,就像你在iOS 8之前那样做。
以下是填充单元格并计算高度的相关方法:
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kLoadingCell])
return kLoadingCellHeight;
else if ([cellType isEqualToString:kOfflineCell])
return kOfflineCellHeight;
else if ([cellType isEqualToString:kFootprintListHeaderCell])
return kHeaderCellHeight;
else if ([cellType isEqualToString:kFootprintCellUnsynced])
return kUnsyncedCellHeight;
else if ([cellType isEqualToString:kShowFullTripCell])
return kShowFullTripCellHeight;
else if ([cellType isEqualToString:kFootprintOnMapCell])
return kFootprintOnMapCellHeight;
else
{
return UITableViewAutomaticDimension;
}
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kLoadingCell])
return kLoadingCellHeight;
else if ([cellType isEqualToString:kOfflineCell])
return kOfflineCellHeight;
else if ([cellType isEqualToString:kFootprintListHeaderCell])
return kHeaderCellHeight;
else if ([cellType isEqualToString:kFootprintCellUnsynced])
return kUnsyncedCellHeight;
else if ([cellType isEqualToString:kShowFullTripCell])
return kShowFullTripCellHeight;
else if ([cellType isEqualToString:kFootprintOnMapCell])
return kFootprintOnMapCellHeight;
else
{
return UITableViewAutomaticDimension;
}
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
NSString *cellType = [self reuseIdentifierForIndexPath:indexPath];
if ([cellType isEqualToString:kLoadingCell])
{
UITableViewCell *loadingCell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:nil];
loadingCell.tag = kLoadingCellTag;
loadingCell.selectionStyle = UITableViewCellSelectionStyleNone;
loadingCell.backgroundColor = loadingCell.contentView.backgroundColor = [UIColor clearColor];
UIActivityIndicatorView *activityIndicatorView = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
activityIndicatorView.center = CGPointMake(tableView.frame.size.width / 2, 20);
[loadingCell.contentView addSubview:activityIndicatorView];
[activityIndicatorView startAnimating];
return loadingCell;
}
else if ([cellType isEqualToString:kOfflineCell])
{
FPOfflineCell *offlineCell = [tableView dequeueReusableCellWithIdentifier:kOfflineCell];
return offlineCell;
}
else if ([cellType isEqualToString:kFootprintListHeaderCell])
{
FPFootprintListHeaderCell *headerCell = [tableView dequeueReusableCellWithIdentifier:kFootprintListHeaderCell];
[headerCell.syncButton addTarget:self action:@selector(syncButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
return headerCell;
}
else if ([cellType isEqualToString:kFootprintCellUnsynced])
{
FPFootprintCellUnsynced *unsyncedCell = [tableView dequeueReusableCellWithIdentifier:kFootprintCellUnsynced];
unsyncedCell.footprint = self.map.footprintsNonSynced[[self unsyncedFootprintIndexForIndexPath:indexPath]];
return unsyncedCell;
}
else if ([cellType isEqualToString:kShowFullTripCell])
{
FPShowFullTripCell *showFullTripCell = [tableView dequeueReusableCellWithIdentifier:kShowFullTripCell];
return showFullTripCell;
}
else if ([cellType isEqualToString:kFootprintOnMapCell])
{
FPFootprintOnMapCell *footprintOnMapCell = [tableView dequeueReusableCellWithIdentifier:kFootprintOnMapCell];
footprintOnMapCell.footprint = self.map.footprints[0];
return footprintOnMapCell;
}
else
{
FPFootprint *footprint = self.map.footprints[[self footprintIndexForIndexPath:indexPath]];
FootprintCell *cell = [tableView dequeueReusableCellWithIdentifier:kFootprintCell];
cell.titleLabel.text = footprint.name;
cell.dateLabel.text = footprint.displayDate;
cell.textView.text = nil;
if (footprint.text && footprint.text.length > 0) {
if ([self.readmoreCache[@(footprint.hash)] boolValue]) {
cell.textView.text = footprint.text;
} else {
cell.textView.text = [footprint.text stringByAppendingReadMoreAndLimitingToCharacterCount:300 screenWidth:tableView.frame.size.width];
}
} else {
cell.hasText = NO;
}
cell.textView.markdownLinkTextViewDelegate = self;
[cell.textView setNeedsDisplay];
cell.isPrivate = footprint.isPrivate;
[cell.likesAndCommentsView setLikesCount:footprint.likes andCommentsCount:footprint.comments];
[cell.likesAndCommentsView setLiked:footprint.liked];
[cell.likesAndCommentsView.likeButton addTarget:self action:@selector(likeButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.likesAndCommentsView.likesTextButton addTarget:self action:@selector(likesTextButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.likesAndCommentsView.commentButton addTarget:self action:@selector(commentButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.likesAndCommentsView.commentsTextButton addTarget:self action:@selector(commentsTextButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.detailButton addTarget:self action:@selector(detailButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
[cell.translateButton addTarget:self action:@selector(translateButtonPressed:) forControlEvents:UIControlEventTouchUpInside];
if (footprint.canBeTranslated) {
cell.translationStatus = footprint.translationState;
if (footprint.translationState == FPFootprintTranslationStateTranslated) {
cell.translatedTextView.text = footprint.translatedText;
}
} else {
cell.translationStatus = FPFootprintTranslationStateNotAvailible;
}
cell.numberOfImages = 2;
return cell;
}
}
这是我的手机:
import UIKit
@objc class FootprintCell: UITableViewCell {
var translationStatus: FPFootprintTranslationState = .NotTranslated {
didSet {
translateButton.hidden = true
translateLoader.stopAnimating()
translatedTextView.hidden = true
translatedTextView.text = nil
translatedTextView.addConstraint(translatedTextViewHeightConstraint)
translationButtonHeightConstraint.constant = 0
loaderHeightConstraint.constant = 0
switch translationStatus {
case .NotAvailible:
break
case .NotTranslated:
translateButton.hidden = false
translationButtonHeightConstraint.constant = translationButtonHeightConstraintConstant
case .Translating:
translateLoader.startAnimating()
loaderHeightConstraint.constant = loaderHeightConstraintConstant
translatedTextView.text = nil
case .Translated:
translatedTextView.hidden = false
translatedTextView.removeConstraint(translatedTextViewHeightConstraint)
}
}
}
var isPrivate: Bool = false {
didSet {
privacyBar.hidden = !isPrivate
privacyIcon.image = UIImage(named: isPrivate ? "ic_lock" : "ic_globe")
}
}
var hasText: Bool = true {
didSet {
if hasText {
textView.removeConstraint(textViewHeightConstraint)
} else {
textView.addConstraint(textViewHeightConstraint)
}
}
}
var numberOfImages: Int = 0 {
didSet {
if numberOfImages == 0 {
imagesContainer.subviews.map { $0.removeFromSuperview() }
} else if numberOfImages == 2 {
twoImagesContainer = NSBundle.mainBundle().loadNibNamed("FootprintCellTwoImagesContainer", owner: nil, options: nil)[0] as? FootprintCellTwoImagesContainer
twoImagesContainer?.setTranslatesAutoresizingMaskIntoConstraints(false)
imagesContainer.addSubview(twoImagesContainer!)
let views = ["foo" : twoImagesContainer!] as [NSString : AnyObject]
imagesContainer.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("V:|[foo]|", options: .allZeros, metrics: nil, views: views))
imagesContainer.addConstraints(NSLayoutConstraint.constraintsWithVisualFormat("H:|[foo]|", options: .allZeros, metrics: nil, views: views))
}
}
}
@IBOutlet private(set) weak var titleLabel: UILabel!
@IBOutlet private(set) weak var dateLabel: UILabel!
@IBOutlet private(set) weak var textView: FPForwardingTextView!
@IBOutlet private(set) weak var likesAndCommentsView: FPLikesAndCommentsView!
@IBOutlet private weak var privacyBar: UIView!
@IBOutlet private weak var privacyIcon: UIImageView!
@IBOutlet private(set) weak var detailButton: UIButton!
@IBOutlet private(set) weak var translateButton: UIButton!
@IBOutlet private weak var translateLoader: UIActivityIndicatorView!
@IBOutlet private(set) weak var translatedTextView: FPForwardingTextView!
@IBOutlet private(set) weak var imagesContainer: UIView!
private(set) var twoImagesContainer: FootprintCellTwoImagesContainer?
@IBOutlet private weak var translationButtonHeightConstraint: NSLayoutConstraint!
@IBOutlet private weak var loaderHeightConstraint: NSLayoutConstraint!
@IBOutlet private var translatedTextViewHeightConstraint: NSLayoutConstraint!
@IBOutlet private var textViewHeightConstraint: NSLayoutConstraint!
private var translationButtonHeightConstraintConstant: CGFloat!
private var loaderHeightConstraintConstant: CGFloat!
override func awakeFromNib() {
super.awakeFromNib()
textView.contentInset = UIEdgeInsets(top: -10, left: -5, bottom: 0, right: 0)
textView.linkColor = UIColor(fromHexString: "0088CC")
translatedTextView.contentInset = UIEdgeInsets(top: -10, left: -5, bottom: 0, right: 0)
translatedTextView.linkColor = UIColor(fromHexString: "0088CC")
privacyBar.backgroundColor = UIColor(patternImage: UIImage(named: "ic_privacy_bar"))
translatedTextView.text = nil
translatedTextView.hidden = true
translateButton.hidden = true
translationButtonHeightConstraintConstant = translationButtonHeightConstraint.constant
loaderHeightConstraintConstant = loaderHeightConstraint.constant
hasText = true
}
func layoutMargins() -> UIEdgeInsets {
return UIEdgeInsetsZero
}
override func prepareForReuse() {
super.prepareForReuse()
numberOfImages = 0
translationStatus = .NotAvailible
hasText = true
}
}
FootprintCellTwoImagesContainer
和FPLikesAndCommentsView
是从Nib加载的,目前不包含任何图像或加载任何内容,只有一些Autolayout。
所以主要问题是即使整个tableView被加载并且每个单元格至少显示一次(因此应该有足够的单元格可以重复使用),在缓慢地向上或向下滚动单元格边框后,我得到一个小跳跃(像上下5个像素)。这种情况发生在每台设备上,即使在6 Plus上也是如此。
问题出在哪里?我希望它不是我在xibs中的约束,至少Interface Builder不会在那里发出警告......
答案 0 :(得分:1)
我不太确定UITableViewAutomaticDimension是用于表格单元格的。从文档......
当您希望UITableView选择默认值时,从UITableViewDelegate方法返回此值,该方法请求维度指标。例如,如果在tableView:heightForHeaderInSection:或tableView:heightForFooterInSection:中返回此常量,则UITableView使用的高度适合从tableView返回的值:titleForHeaderInSection:或tableView:titleForFooterInSection :(如果标题不是nil)。
没有提到tableview单元格。
所以我做了一个搜索,发现了这个...... more discussion on UITableViewAutomaticDimension...
它说的地方..
它不起作用。 UITableViewAutomaticDimension不用于设置行高。使用rowHeight并指定您的值或实现:
所以我认为你可能有这个错误。
答案 1 :(得分:-1)
OK之前的代码,原则。我有一个列中有4个标签的自定义单元格。 顶部标签(label1)始终具有文本,底部标签(label4)也始终具有文本。标签2和3可以包含文本,可以是一个,也可以是两个。 为了实现调整大小,我们使用了部分自动布局和部分委托方法(距离你的目标不远)
在“界面”构建器中,我们设置原型单元格的约束
Label1:领先,尾随,顶部,高度,宽度
标签2:前导,尾随,顶部,底部,高度,宽度
Label3:领先,尾随,顶部,底部,高度,宽度
Label4:领先,尾随,顶部,底部,高度,宽度
对于标签1和标签4(顶部和底部),我们将内容压缩电阻优先级垂直设置为“必需”(1000) 对于标签2和3 ,我们将内容压缩电阻优先级垂直设置为“低”(250)
这基本上意味着如果高度应该降低,首先折叠标签2和3以及折叠标签1和4。(您可能已经知道所有这些) 您应该没有任何警告,并且限制所有正确添加的内容。 (除非你知道它的作用,否则不要使用约束边距)
现在代码。
- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
// Calls the sizing method to return a calculated height.
return [self heightForBasicCellAtIndexPath:indexPath];
}
- (CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
//taken from interface builder. If all 4 labels have strings and not collapsed, this is the height the cell will be.
return 123.0f;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
// Yours has lots of case logic -
// Mine is similar but I configure the properties of the custom cell elsewhere mainly so it can be used for sizing.
[self configureCell:cell atIndexPath:indexPath];
return cell;
}
- (void)configureCell:(MyJobCell *)cell atIndexPath:(NSIndexPath *)indexPath {
// my data source
MyCase *aCase = [_fetchedResultsController objectAtIndexPath:indexPath];
// setting the labels to match the case from data
cell.label1.text = aCase.name;
cell.label2.text = aCase.address;
cell.label3.text = aCase.postcode;
cell.label4.text = aCase.caseDescription;
}
- (CGFloat)heightForBasicCellAtIndexPath:(NSIndexPath *)indexPath {
// In here I create a cell and configure it with a cell identifier
static MyJobCell *sizingCell = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sizingCell = [self.tableView dequeueReusableCellWithIdentifier:MyJobCellIdentifier];
});
// This configures the sizing cell labels with text values
[self configureCell:sizingCell atIndexPath:indexPath];
// This line calls the calculation. It fires the Auto Layout constraints on the cell,
// If label 2 and / or label 3 are empty, they will be collapsed to 0 height.
return [self calculateHeightForConfiguredSizingCell:sizingCell];
}
- (CGFloat)calculateHeightForConfiguredSizingCell:(MYJobCell *)sizingCell {
//Force the cell to update its constraints
[sizingCell setNeedsLayout];
[sizingCell layoutIfNeeded];
// Get the size of the 'squashed' cell and return it to caller
CGSize size = [sizingCell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
return size.height;
}
这是我向你展示工作方法的最好方法。您必须进行调整,以便逻辑地满足您拥有的所有不同类型的自定义单元格。 但除此之外,我认为这应该有所帮助。让我知道你是怎么过的。