美文网首页ios2017专题:临时区
iOS 10.3 Label高度计算问题 (UITableVie

iOS 10.3 Label高度计算问题 (UITableVie

作者: 陈雨尘 | 来源:发表于2017-04-07 17:06 被阅读1472次

1.前言

今天有用户反馈说10.3的系统,有些文字显示不全,影响正式用户我哪里敢怠慢。急速的更新手机系统进行测试,发现真的是有问题,而且这个问题是UITableView+FDTemplateLayoutCell 引起的,计算的高度不准确引起的。

2.问题发现

经过测试发现问题出现在xib或者nib创建的cell 拉约束之后再用UITableView+FDTemplateLayoutCell 计算高度就会出现问题,用Masonry 配合UITableView+FDTemplateLayoutCell 使用没问题(至少我的是没有问题,当然如果你的有问题也可以看下我列举的解决办法,相信有适合你的)

3.解决问题

经过Gogle 发现目前为止很少有人提到这个问题(难道大家都没发现还是大家的都没问题,当然也有可能没用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.设置 label的 preferredMaxLayoutWidth

这个方法亲测是可以的,但是有个问题nib 创建的cell 很多都不知道这个值到底是多少,就是知道也不能一个cell一个cell 的设置吧!至少我是不愿意 ,天啊几十种cell啊!当然有些人可能会图省事,随意设置一个吧,label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 80 这样你测试的是会发现确实换行了,但是真的行吗?label 的换行是要根据这个 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的宽就是要设置label能够显示的宽,如果给的不对当然计算出的高度也会不对,如果给的小了计算的高度就高,给的大了计算的高度就低

  1. 加 [cell layoutIfNeeded]

因为有时候我发现第一次label显示的是没问题的,但是刷新一下就不行了,所以我想到在刷新重新算高度之前刷新下约束,这样就可以知道label的最大宽度限制了,当然我也不想在所以的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

当然如果这个能满足你也是很好的,但是却满足不了我的工程,这个是可以完全解决nib 创建的cell 的高度问题,但是纯代码用Masonry加约束的却出现了问题,反而不能换行了,所以这个方法也不适合我,此路不通我再想他法继续往下看

3.给cell.contentView 加左右约束

在这里我这样理解的,既然xcode自动帮我们加的左右约束有问题,那我不用他的了, 我自己加

2.png

在同样的地方替换掉layoutIfNeeded,换成加左右约束, 记得后面加上priorityLow ,这样是避免跟cell 里面手动加的约束起冲突,这样就技能满足nib cell 也能满足纯代码cell ,也不用一个cell 一个cell 的改。至此这个问题完美的解决了(我的问题是解决了,你的解决了吗?欢迎留言共同探讨,小牛路过,不喜勿喷!)

<strong>什么,你还懒得敲,要我发源码!好吧忍不了你了<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

😝😝😝😝😝 客官慢走,喜欢点赞呗 😝😝😝😝😝

相关文章

网友评论

  • 爱自由鹏:我没用是xib/nib,用的纯代码+masonry也有问题,设置了label的preferredMaxLayoutWidth就可以了,谢谢!
    另,GitHub上的代码我用cocoapod安装1.6,结果拉取的代码没有变,在github上手动下载的代码是OK的,你是没有发布吗?(用cocoapod管理的1.6不对)
  • 扬生哥哥: 兄弟,textview 上面计算不准的话有解决方法么?
  • 冷酷的心:contentView里如果包含了需要换行的UILabel,超过100个字符,计算就不准了,请验证
    爱自由鹏:@冷酷的心 github的demo和cocoapod管理的1.6不一样,cocoapod还是原来的代码,github的demo是正确的,他应该是没有发布
    冷酷的心: @陈雨尘 问题找到了,使用pod安装的代码有问题,pod里1.6版本的代码和github里的不一样,github的demo没问题,谢谢
    陈雨尘:@冷酷的心 我这边测试的是可以的哦,你的不行吗 ,能发个Dome 过来吗
  • 布鲁克先生:完美解决了我的问题,万分感谢啊
    陈雨尘:不客气,共同学习哈
  • RhythmMaster:6666,看了这个才知道问题所在,xib不能用啊,我直接给contentView来个约束就没问题了
  • 西西西瓜:遇到了,这个坑爹的bug了。。。。
    西西西瓜:@陈雨尘 能把你的UITableView+FDTemplateLayoutCell 发给我吗。。。放git好吗
    西西西瓜:@陈雨尘 你的 UITableView+FDTemplateLayoutCell 是哪个版本的啊,怎么我的和你的代码不一样
    陈雨尘:哈哈 ,遇到问题才能让我们成长
  • c7fbb79c1b88:经过测试还是需要设置UILabel的preferredMaxLayoutWidth的属性,否则上下会留空白。
    当你的XXXCell.xib文件是基于iPhone7 Simulator尺寸布局的,运行于iPhone7 Plus Simulator模拟器就会出现上下留白,系统是以iPhone7 Simulator宽度作为换行的宽度,到了iPhone7 Plus Simulator,自然上下留空白了。希望能解决这个问题,iOS10.3之前没有这个问题。
    c7fbb79c1b88:@陈雨尘 一切正常的情况下当然不管是7还是7P都是正常的,比如10.3之前都是正常的。现在我的意思是在10.3的时候使用UITableView+FDTemplateLayoutCell然后根据你上文仅仅加上以下代码还是有问题的(上下有空白),
    if (IOS_VERSION > 10.2) {
    [cell.contentView mas_makeConstraints:^(MASConstraintMaker *make) {
    make.left.mas_equalTo(0).priorityLow();
    make.right.mas_equalTo(0).priorityLow();
    }];
    }

    在7P上运行还有约束冲突,
    "<NSLayoutConstraint:0x6080000990f0 UITableViewCellContentView:0x7fb604f0d500.width == 414>",
    "<NSLayoutConstraint:0x600000097430 UITableViewCellContentView:0x7fb604f0d500.width == 320>"

    如果把cell的XIB的宽度手动拉到414,那么5S、7、7P显示都正常,但是还是会有约束冲突

    我的一个例子你看下(分别在iOS的5S、7、7P下测试),https://github.com/yansun2006/TestLayoutCellBugProj
    陈雨尘:不会的 ,Auto Layout 不会因为你是用的7加的约束,到7P 上显示的时候就不行了,如果这样的话还这么算的上是屏幕适配呢,其实你加好约束的话这个宽度肯定是也是对应的,只是在nib 创建的 时候,刷新之后xcode没有在次去获取约束信息从而计算这个宽度而已,所以你只要想办法让他重新计算下这个宽度就行了(例如我上面所说的)。当然 preferredMaxLayoutWidth 设置这个也是可以的,只要你需要改的地方少,完全没问题。
  • f49607ee0f86:好厉害奥
  • d40f8aff5031:辛亏及时提醒,太赞了,谢啦
  • 21e0c0f0da08:有用有用,谢谢了
  • ff8e77440971:哈哈棒👍🏻

本文标题:iOS 10.3 Label高度计算问题 (UITableVie

本文链接:https://www.haomeiwen.com/subject/giphattx.html