基于StoryBoard自动布局--Cell自适应高度

基于StoryBoard自动布局–Cell自适应高度

AutoLayout StoryBoard Automatic Adaptation Height

AutoLayout是什么?

AutoLayout 是一种基于约束的描述性布局系统,一般来说,如果采用auto layout布局UI,代码里可以不用再出现setFame方法。描述性布局UI与生活更和谐,不是吗:】。更具体的在WWDC官方解释202 – Introduction to Auto Layout for iOS and OS X, 228 – Best Practices for Mastering Auto Layout, 232 – Auto Layout by Example。

起因

由于现在手机应用大统一的趋势,所以了解新的布局系统是很有必要的。但是在实践过程中由于不够细心而经常受挫,也放弃过自动布局,特别是在项目中经常出现的TableViewCell自适应高度,死嗑几天终于解开心中最大疑惑:】

  • 自动布局是否能像手写代码一样精确控制UI布局
  • 自动布局是否能像手写代码一样精确算出CELL高度
代码呈上

代码下载

自动布局核心概念

  • intrinsic size 固有高宽

    不是每个视图都有固有高宽。UILabel对比UIView在XIB中拖放布局时会有一个很好的感知,具有固有高宽的视图UILabel,不设置宽高时也不会出现miss constrainst红点,系统会根据要显示的字体确定一个首选高度,根据内容而确定一个首选宽度。详细说明可参照

    H:|-(177)-[Click Me] V:[Click Me]-(48)-| VFL语言标注,感觉更有描述性 : ]

    简单示例

  • Compression Resistance Priority / Content Hugging Priority

    这两个优先级隐性的存在每一个视图中,compression resistance 可认为视图不想被压缩,content Hugging 是不想被拉伸,它们都含有H/V两个方向。当设置高度或宽度优先级低于或等于隐性优先级时,视图就会首选intrinsic size进行布局[可参照]

    [Click Me(130@500)]

    这里写图片描述

实战

场景:我们需要3个UILabel,title,subTitle都是多行文本,设置numberOfLines = 0;price为单行文本,但是宽度可由价格数目伸展,所以将会导致title,subTitle被压缩

这里写图片描述

这里注意两点

  • Label没有写上数据,而是采用相同字段是为了能更好的处理优先级问题
  • 视图从上至下,顺序不一样也会导致约束不一样
大概思路
  • 由于高度自适应,所以在V方向依靠compress/hug,而不应该出现优先级更高的手动设置Height
  • 由于价格宽度变动原因,所以在H方向也应该能够compress/hug,而不应该全部设置Width

当采用默认优先级布局完之后,会发现3个Label在H方向出现Misplaced Views警告提示,告知我们布局与我们想要的不一致,点击price label如图提示
这里写图片描述

price label 在与其他2个label同优先级的情况下H方向被拉伸,按照我们思路price需要压缩,此时H方向的compress优先级降1,即749;其他2个label需要拉伸,此时H方向的hugging优先级降1,即250。此时再看警告没了。

如果还不够理解,试想想手写代码思路,首先先精确price的宽度,即压缩;然后计算其他2个label可拉伸的宽度

对于多行文本,我们还要显示选择title label,subtitle label 的首选宽度(Preferred Width)
这里写图片描述

当然在此要解决上面提到的第一个问题了,精确布局。按要求笑脸与父视图上下右边距10个像素,点击笑脸图,按住Option+光标移到父视图,确实边距是10个像素,注意了如果拖动创建约束,默认参照带Margin的父边沿,右键把Relative to margin勾选去掉,再设置constant 为10

这里写图片描述 ——->这里写图片描述——->这里写图片描述

或者用右下角pin创建约束,把constrain to margin勾选去掉

这里写图片描述

好了,到此解决心中第一个大疑问,精确布局边距:】

动态计算高度

由于autolayout是基于约束的布局,所以只有约束正确,才能精确算出高度,在此我们主要使用

 CGSize size =  [cell.contentView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];

除此以外还涉及到更新约束布局过程

Created with Raphaël 2.1.0 更新约束(setNeedsUpdateConstraints) 更新约束(setNeedsUpdateConstraints) 更新布局(setNeedsLayout) 更新布局(setNeedsLayout) 显示(setNeedsDisplay) 显示(setNeedsDisplay) frame(OSX)或bounds,center(iOS)--> 子视图--->父视图 父视图--->子视图 设置布局最终在显示器端显示--> 父视图--->子视图 <--更改位置(如在重写layoutSubviews)影响约束时

视图显示要依赖布局,布局要依赖约束,所以当你改成autoLayout布局UI时就决定了凡事都先考虑约束constrainst,改变之前先考虑frame的习惯,以上这些在WWDC中都有提及,也可以参照[objc中国]。

基于约束高度方法

一般情况下,通过约束计算视图高度使用方法systemLayoutSizeFittingSize,如示例返回tableviewcell的高度方法中写的:

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    TableViewCell *cell = cellCache;
    cell.imagev.image = _image;
    cell.indateImage.image = _indicateImage;
    cell.titleLb.text = _title;
    cell.subTitleLb.text = _subTitle;
    cell.priceLb.text = _price;

    cell.bounds = CGRectMake(0, 0, CGRectGetWidth(tableView.bounds), CGRectGetHeight(cell.bounds));

    [cell layoutIfNeeded];

    CGSize size = [cell systemLayoutSizeFittingSize:UILayoutFittingCompressedSize];
    NSLog(@"height is %f ",round(size.height));
    return  round(size.height);
}
  • 直接调用systemLayoutSizeFittingSize不就行了吗?
    numberOfLines ==0时,preferredMaxLayoutWidth影响UILabel的Size;想要在不同屏幕大小的时候都能精准获取UILabel的高度,由于preferredMaxLayoutWidth不会自动更新,所以在cell的重写layoutSubviews中手动更新

    -(void)layoutSubviews
    {
         [super layoutSubviews];
         [self.contentView layoutSubviews];
    
    if (self.titleLb.numberOfLines == 0 && self.titleLb.bounds.size.width != self.titleLb.preferredMaxLayoutWidth) {
        self.titleLb.preferredMaxLayoutWidth = self.titleLb.bounds.size.width;
    }
    if (self.subTitleLb.numberOfLines == 0 && self.subTitleLb.bounds.size.width != self.subTitleLb.preferredMaxLayoutWidth) {
        self.subTitleLb.preferredMaxLayoutWidth = self.subTitleLb.bounds.size.width;
    }
    }

    注意:在重写layoutSubviews中必须先调用父视图[super layoutSubviews],[self.contentView layoutSubviews]以获取子视图的frame,如果我们有做任何改动影响到约束时,得在最后加上[super layoutSubviews]用来更新约束,但是这里我们没有影响到约束,所以就没必要做这一步动作

  • 为什么还要更改cell的bounds?
    实际更新cell的bounds是要在返回- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
    之后,所以在返回高度的代理里面就需要手动设置,这样才会获取精确高度,而且要在[cell layoutIfNeeded]之前

  • 为什么说systemLayoutSizeFittingSize只适合一般情况?
    比如UITextView就是一个例外,它是继承于UIScrollView,看官方一句话先吧

    The size of the content inside of a scroll view is determined by the constraints of its descendants.
    至于什么是descendants constraints,可以认为是内容约束,textview里面的内容根本无法做约束嘛,当[textView systemLayoutSizeFittingSize:UILayoutFittingCompressedSize]自然返回0了。更准确的感知就是自己点击tableviewcell的contentview约束看看吧

这里写图片描述

可以使用sizeThatFits:计算textview高度,或boundingRectWithSize...计算textview.text的高度。

  • 提高tableview滚动性能我想针对这个场景提一点个人认识
    在iOS7提出的...estimatedHeightForRowAtIndexPath...代理方法返回估计高度值是官方推荐使用的,通常作法是返回一个常数,但是如果你所返回的高度与实际显示高度差到数量级别时,显示就会出现跳帧现象,特别是对于动态高度显示,这个返回值应该分情况以if...else方式提供比较精确的高度常量比较好。

诚心学习,诚心分享,请大家多多指教,谢谢!

你可能感兴趣的:(UI布局)