iOS 10.3 Label高度計算問題 (UITableView+FDTemplateLayoutCell)

1.前言

今天有用戶反饋說10.3的系統(tǒng),有些文字顯示不全,影響正式用戶我哪里敢怠慢。急速的更新手機(jī)系統(tǒng)進(jìn)行測試,發(fā)現(xiàn)真的是有問題,而且這個問題是UITableView+FDTemplateLayoutCell 引起的,計算的高度不準(zhǔn)確引起的。

2.問題發(fā)現(xiàn)

經(jīng)過測試發(fā)現(xiàn)問題出現(xiàn)在xib或者nib創(chuàng)建的cell 拉約束之后再用UITableView+FDTemplateLayoutCell 計算高度就會出現(xiàn)問題,用Masonry 配合UITableView+FDTemplateLayoutCell 使用沒問題(至少我的是沒有問題,當(dāng)然如果你的有問題也可以看下我列舉的解決辦法,相信有適合你的)

3.解決問題

經(jīng)過Gogle 發(fā)現(xiàn)目前為止很少有人提到這個問題(難道大家都沒發(fā)現(xiàn)還是大家的都沒問題,當(dāng)然也有可能沒用UITableView+FDTemplateLayoutCell 的),在UITableView+FDTemplateLayoutCell issues 和 Masonry issues 里面有很多提到這個問題的。究其原因好像iOS 10.3 會加一個寬一個高約束(Looks like iOS 10.3 has two additional constraints there for width/height),對Autolayout的約束有新的計算方式。

4.列舉下解決問題的方法

1.設(shè)置 label的 preferredMaxLayoutWidth

這個方法親測是可以的,但是有個問題nib 創(chuàng)建的cell 很多都不知道這個值到底是多少,就是知道也不能一個cell一個cell 的設(shè)置吧!至少我是不愿意 ,天啊幾十種cell?。‘?dāng)然有些人可能會圖省事,隨意設(shè)置一個吧,label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 80 這樣你測試的是會發(fā)現(xiàn)確實換行了,但是真的行嗎?label 的換行是要根據(jù)這個 preferredMaxLayoutWidth 類似計算文字高度的方法

- (CGRect)boundingRectWithSize:(CGSize)size options:(NSStringDrawingOptions)options attributes:(nullable NSDictionary<NSString *, id> *)attributes context:(nullable NSStringDrawingContext *)context NS_AVAILABLE(10_11, 7_0)

就像這個方法中size size的寬就是要設(shè)置label能夠顯示的寬,如果給的不對當(dāng)然計算出的高度也會不對,如果給的小了計算的高度就高,給的大了計算的高度就低

  1. 加 [cell layoutIfNeeded]

因為有時候我發(fā)現(xiàn)第一次label顯示的是沒問題的,但是刷新一下就不行了,所以我想到在刷新重新算高度之前刷新下約束,這樣就可以知道label的最大寬度限制了,當(dāng)然我也不想在所以的cell 里面處理 所以在UITableView+FDTemplateLayoutCell 里面做了些處理

- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id))configuration
  {
   if (!identifier) {
    return 0;
   }

// Fetch a cached template cell for `identifier`.
UITableViewCell *cell = [self fd_templateCellForReuseIdentifier:identifier];

// Manually calls to ensure consistent behavior with actual cells (that are displayed on screen).
[cell prepareForReuse];

// Customize and provide content for our template cell.
if (configuration) {
    configuration(cell);
}

CGFloat contentViewWidth = CGRectGetWidth(self.frame);

// If a cell has accessory view or system accessory type, its content view's width is smaller
// than cell's by some fixed values.
    if (cell.accessoryView) {
    contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
} else {
    static CGFloat systemAccessoryWidths[] = {
        [UITableViewCellAccessoryNone] = 0,
        [UITableViewCellAccessoryDisclosureIndicator] = 34,
        [UITableViewCellAccessoryDetailDisclosureButton] = 68,
        [UITableViewCellAccessoryCheckmark] = 40,
        [UITableViewCellAccessoryDetailButton] = 48
    };
    contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
}

CGSize fittingSize = CGSizeZero;
// If auto layout enabled, cell's contentView must have some constraints.
BOOL autoLayoutEnabled = cell.contentView.constraints.count > 0 && !cell.fd_enforceFrameLayout;

   if (autoLayoutEnabled) {
    // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
    // of growing horizontally, in a flow-layout manner.
    if (IOS_VERSION > 10.2) {
        [cell layoutIfNeeded];
    }
    NSLayoutConstraint *tempWidthConstraint =
    [NSLayoutConstraint constraintWithItem:cell.contentView
                                 attribute:NSLayoutAttributeWidth
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:nil
                                 attribute:NSLayoutAttributeNotAnAttribute
                                multiplier:1.0
                                  constant:contentViewWidth];
    [cell.contentView addConstraint:tempWidthConstraint];
    // Auto layout engine does its math
    fittingSize = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    [cell.contentView removeConstraint:tempWidthConstraint];
    
} else {
    
    // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself.
    // This is the same method used in iOS8 self-sizing cell's implementation.
    // Note: fitting height should not include separator view.
    SEL selector = @selector(sizeThatFits:);
    BOOL inherited = ![cell isMemberOfClass:UITableViewCell.class];
    BOOL overrided = [cell.class instanceMethodForSelector:selector] != [UITableViewCell instanceMethodForSelector:selector];
    if (inherited && !overrided) {
        NSAssert(NO, @"Customized cell must override '-sizeThatFits:' method if not using auto layout.");
    }
    fittingSize = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)];
}

// Add 1px extra space for separator line if needed, simulating default UITableViewCell.
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
    fittingSize.height += 1.0 / [UIScreen mainScreen].scale;
}

if (autoLayoutEnabled) {
    [self fd_debugLog:[NSString stringWithFormat:@"calculate using auto layout - %@", @(fittingSize.height)]];
} else {
    [self fd_debugLog:[NSString stringWithFormat:@"calculate using frame layout - %@", @(fittingSize.height)]];
}
  return fittingSize.height;
   }

*重點在這里


1.png

當(dāng)然如果這個能滿足你也是很好的,但是卻滿足不了我的工程,這個是可以完全解決nib 創(chuàng)建的cell 的高度問題,但是純代碼用Masonry加約束的卻出現(xiàn)了問題,反而不能換行了,所以這個方法也不適合我,此路不通我再想他法繼續(xù)往下看

3.給cell.contentView 加左右約束

在這里我這樣理解的,既然xcode自動幫我們加的左右約束有問題,那我不用他的了, 我自己加

2.png

在同樣的地方替換掉layoutIfNeeded,換成加左右約束, 記得后面加上priorityLow ,這樣是避免跟cell 里面手動加的約束起沖突,這樣就技能滿足nib cell 也能滿足純代碼cell ,也不用一個cell 一個cell 的改。至此這個問題完美的解決了(我的問題是解決了,你的解決了嗎?歡迎留言共同探討,小牛路過,不喜勿噴?。?/a>

<strong>什么,你還懶得敲,要我發(fā)源碼!好吧忍不了你了<strong>
- (CGFloat)fd_heightForCellWithIdentifier:(NSString *)identifier configuration:(void (^)(id))configuration
{
if (!identifier) {
return 0;
}

// Fetch a cached template cell for `identifier`.
UITableViewCell *cell = [self fd_templateCellForReuseIdentifier:identifier];

// Manually calls to ensure consistent behavior with actual cells (that are displayed on screen).
[cell prepareForReuse];

// Customize and provide content for our template cell.
if (configuration) {
    configuration(cell);
}

CGFloat contentViewWidth = CGRectGetWidth(self.frame);

// If a cell has accessory view or system accessory type, its content view's width is smaller
// than cell's by some fixed values.
if (cell.accessoryView) {
    contentViewWidth -= 16 + CGRectGetWidth(cell.accessoryView.frame);
} else {
    static CGFloat systemAccessoryWidths[] = {
        [UITableViewCellAccessoryNone] = 0,
        [UITableViewCellAccessoryDisclosureIndicator] = 34,
        [UITableViewCellAccessoryDetailDisclosureButton] = 68,
        [UITableViewCellAccessoryCheckmark] = 40,
        [UITableViewCellAccessoryDetailButton] = 48
    };
    contentViewWidth -= systemAccessoryWidths[cell.accessoryType];
}

CGSize fittingSize = CGSizeZero;
// If auto layout enabled, cell's contentView must have some constraints.
BOOL autoLayoutEnabled = cell.contentView.constraints.count > 0 && !cell.fd_enforceFrameLayout;

if (autoLayoutEnabled) {
    // Add a hard width constraint to make dynamic content views (like labels) expand vertically instead
    // of growing horizontally, in a flow-layout manner.
    if (IOS_VERSION > 10.2) {
        [cell.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.mas_equalTo(0).priorityLow();
            make.right.mas_equalTo(0).priorityLow();
        }];
    }
    NSLayoutConstraint *tempWidthConstraint =
    [NSLayoutConstraint constraintWithItem:cell.contentView
                                 attribute:NSLayoutAttributeWidth
                                 relatedBy:NSLayoutRelationEqual
                                    toItem:nil
                                 attribute:NSLayoutAttributeNotAnAttribute
                                multiplier:1.0
                                  constant:contentViewWidth];
    [cell.contentView addConstraint:tempWidthConstraint];
    // Auto layout engine does its math
    fittingSize = [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    [cell.contentView removeConstraint:tempWidthConstraint];
    
} else {
    
    // If not using auto layout, you have to override "-sizeThatFits:" to provide a fitting size by yourself.
    // This is the same method used in iOS8 self-sizing cell's implementation.
    // Note: fitting height should not include separator view.
    SEL selector = @selector(sizeThatFits:);
    BOOL inherited = ![cell isMemberOfClass:UITableViewCell.class];
    BOOL overrided = [cell.class instanceMethodForSelector:selector] != [UITableViewCell instanceMethodForSelector:selector];
    if (inherited && !overrided) {
        NSAssert(NO, @"Customized cell must override '-sizeThatFits:' method if not using auto layout.");
    }
    fittingSize = [cell sizeThatFits:CGSizeMake(contentViewWidth, 0)];
}

// Add 1px extra space for separator line if needed, simulating default UITableViewCell.
if (self.separatorStyle != UITableViewCellSeparatorStyleNone) {
    fittingSize.height += 1.0 / [UIScreen mainScreen].scale;
}

if (autoLayoutEnabled) {
    [self fd_debugLog:[NSString stringWithFormat:@"calculate using auto layout - %@", @(fittingSize.height)]];
} else {
    [self fd_debugLog:[NSString stringWithFormat:@"calculate using frame layout - %@", @(fittingSize.height)]];
}

    return fittingSize.height;
 }

如果你的是其他版本的 自己對比找下,如果你是最新版的看下圖

22.png

方法


33.png

位置


44.png

?????????? 客官慢走,喜歡點贊唄 ??????????

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容