自动布局基础篇
关于自动布局的基本使用, 参考网上的文章即可, 如: iOS 开发 - 自动布局篇: 史上最牛的自动布局教学! https://www.jianshu.com/p/f6cf9ef451d9
自动布局进阶篇
抗拉伸与抗压缩
相信许多比较少使用自动布局的同学对下面的参数都感觉比较头疼:
其实不难, 请往下看: #####Content Hugging Priority : 抗拉伸优先级 最常见的情况是两个 label 并排放置, 并设置水平间距:
报错了? 莫慌, 当两个 label 的文字长度加上水平间距不足以填满父视图时, 需要设置抗拉伸优先级. 解决办法: 假设我们不希望 Label1 被拉伸, 则将其 Hugging Priority 值调大(默认为 251)
调整后, 修改 label1 的文字, 其长度随之变化:
然后我们在调整的过程中会发现:
又报错了? 接着往下看: #####Content Compression Resistance Priority : 抗压缩优先级 当两个 label 的文字长度加上水平间距超出了父视图宽度时, 需要设置抗压缩优先级. 解决办法: 假设我们希望 Label1 的内容尽可能地展示完全, 则将其 Resistance Priority 值调大(默认为 750)
调整后:
可以看到, Label2 直接被压没了. 我们把 Label2 的 Resistance Priority 值调大:
调整后:
经过上面的过程后, 对于如何使用自动布局做出下图的效果, 是不是就心里有数了呢:
PS: 抗压缩, 抗拉伸优先级同样适用于与父视图的约束优先级(默认值 1000) 熟悉了 xib 中的优先级设置后, 在 Masonry 中对应优先级的思路相同, 使用方法 - (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis; 进行对应设置(系统自带方法, 不是 Masonry 加的).
内容尺寸(Intrinsic Content Size)
某些用来展现内容的用户控件, 例如文本控件 UILabel, 按钮 UIButton, 图片视图 UIImageView 等, 它们具有自身内容尺寸 (对于 UIImageView, 其自身内容尺寸就是图片(1 倍图) 的尺寸).
如何设置自定义控件的内容尺寸?
我们可以通过重写 - (CGSize)intrinsicContentSize 方法来指定内容尺寸. 先来看看 UIView 默认的返回值:
- - (CGSize)intrinsicContentSize{
- return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
- }
这个方法的作用是当使用已有约束不能够计算出内容宽度 / 高度时, 自动为视图添加宽度 / 高度约束, 其值为返回值中对应的宽 / 高. 因此我们可以在这个方法中返回计算好的内容大小.
我们在 xib 中也可以看到下面的参数(需要注意的是, 在这里设置的 size 仅仅只用于 xib 中展示效果, 实际运行时并没有什么卵用):
知道了 intrinsicContentSize 的用法后, 我们考虑一下下面的需求:
写一个自定义的 View, 满足下面的功能:
如果未使用约束, 直接显示 frame 的大小即可;
如果添加了能够定位左上角点坐标的约束, 则使用默认的内容大小.
如果添加了能够定位左上角点坐标以及能够计算出宽 / 高其中一个数值时, 使用宽 / 高的另一个默认值.
如果添加了能够计算出全部 frame 值的约束, 则不使用默认内容大小.
整体效果类似 UILabel, 但是这里简化成固定的默认尺寸.
这里给出一个简单的 demo 仅供参考:
- @interface DemoView()
- @property (nonatomic,assign) CGSize defaultSize;
- @end
- @implementation DemoView {
- BOOL _widthConstraintAdded;
- BOOL _heightConstraintAdded;
- float _widthConstrant;
- float _heightConstrant;
- }
- - (instancetype)initWithCoder:(NSCoder *)aDecoder{
- self = [super initWithCoder:aDecoder];
- if (self) {
- _widthConstraintAdded = _heightConstraintAdded = NO;
- _widthConstrant = self.defaultSize.width;
- _heightConstrant = self.defaultSize.height;
- }
- return self;
- }
- - (void)layoutSubviews{
- [super layoutSubviews];
- // 是否使用了自动布局
- if (self.constraints.count> 0) {
- // 判断是否添加了可以计算出宽度 / 高度的约束
- if (self.frame.size.width != _widthConstrant) {
- _widthConstraintAdded = YES;
- }
- if (self.frame.size.height != _heightConstrant) {
- _heightConstraintAdded = YES;
- }
- // 计算缺少的宽 / 高
- // 计算时可以使用 self.frame.size, 这个 size 是自动布局调整后的 size 了
- if (!_widthConstraintAdded) {
- // 计算宽度的代码...
- _widthConstrant = 10;
- }
- if (!_heightConstraintAdded) {
- // 计算高度的代码...
- _heightConstrant = 10;
- }
- // 添加约束
- if (!CGSizeEqualToSize(self.frame.size, [self intrinsicContentSize])) {
- [self invalidateIntrinsicContentSize];
- }
- }
- }
- - (CGSize)intrinsicContentSize{
- return CGSizeMake(_widthConstrant, _heightConstrant);
- }
- - (CGSize)defaultSize{
- if (CGSizeEqualToSize(_defaultSize, CGSizeZero)) {
- // 计算默认内容宽高的代码
- float width = 100;
- float height = 20;
- _defaultSize = CGSizeMake(width, height);
- }
- return _defaultSize;
- }
- @end
ScrollView 的自动内容大小
我们看下面的布局:
当我们需要 Label2 可以显示多行数据时, 仅仅设置右边距是不够的:
我们可以让 Label2 的宽度等于 Scrollview 的宽度减去左右边距之和:
看看效果:
效果出来了, 但是仅仅这样我们发现不能上下滚动, 因为没有设置底部间距, scrollview 无法计算出内容高度, 需要给最下面的 view(Label2)添加底部间距:
看看效果:
下面考虑更进一步的需求: Label1 也要可以显示多行时的情况. 我们把 Label1 右边与 Label2 对齐, 然后改一下内容文字:
OK 大功告成.
PS: 如果使用 Tablview 时用到 Cell 的自动高度(不管是用系统自带还是 UITableView+FDTemplateLayoutCell), 也都需要设置好约束, 让它能够计算出竖直方向的内容高度(水平方向 tableview 不需要, collectionView 需要), 具体过程和上面类似.
自定义 View 与自动布局
我们看一个样式:
要实现这一样式的方式有很多种, 这里我们考虑用 UILabel 的子类直接实现的情况(尽可能少的 View):
UIView 中有这一个类方法:+ (Class)layerClass;, 该方法返回的就是 view.layer 的类型, 因此我们可以使用自定义的 Layer 替换掉默认的 Layer, 然后裁剪出对应的形状(原理比较简单, 直接上代码了):
- @interface DemoLabelLayer : CAShapeLayer
- @end
- @implementation DemoLabelLayer
- - (void)layoutSublayers{
- [super layoutSublayers];
- [self setBackgroundPath];
- }
- - (void)setBackgroundPath{
- CAShapeLayer *shapeLayer = [CAShapeLayer layer];
- shapeLayer.fillColor = [UIColor whiteColor].CGColor;
- shapeLayer.fillRule = kCAFillRuleEvenOdd;
- shapeLayer.path = [self backgroundPathFrame:self.bounds].CGPath;
- self.mask = shapeLayer;
- }
- - (UIBezierPath *)backgroundPathFrame:(CGRect)frame{
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:frame byRoundingCorners:UIRectCornerTopRight|UIRectCornerBottomRight cornerRadii:CGSizeMake(self.cornerRadius, self.cornerRadius)];
- return path;
- }
- @end
- @interface DemoView()
- @end
- @implementation DemoView
- - (void)awakeFromNib{
- [super awakeFromNib];
- }
- - (void)layoutSubviews{
- [super layoutSubviews];
- DemoLabelLayer *layer = (DemoLabelLayer *)self.layer;
- layer.backgroundColor = self.backgroundColor.CGColor;
- layer.cornerRadius = self.frame.size.height/2.f;
- }
- + (Class)layerClass{
- return [DemoLabelLayer class];
- }
- @end
自动布局的动画
对于需要进行 frame 动态调整的界面, 许多小伙伴都不知道该如何使用自动布局进行动画而放弃, 其实不难:
如果使用 xib 或者代码创建, 最简单的方式就是将约束设置成对应属性, 直接修改约束的 constant 即可.
如果使用 Masonry 创建布局, 那么只需要使用下面的代码就可以使用动画效果了:
- // 如果其约束还没有生成的时候需要动画的话, 就请先强制刷新后才写动画, 否则所有没生成的约束会直接跑动画
- [view.superview layoutIfNeeded];
- [UIView animateWithDuration:3 animations:^{
- [view mas_updateConstraints:^(MASConstraintMaker *make) {
- make.height.mas_equalTo(123);
- }];
- }];
- // 强制绘制
- [view.superview layoutIfNeeded];
记得动画前后需要刷新.
来源: https://juejin.im/post/5c175620518825741e7c0f7f