自動布局基礎(chǔ)篇
關(guān)于自動布局的基本使用,參考網(wǎng)上的文章即可,如:
iOS開發(fā)-自動布局篇:史上最牛的自動布局教學(xué)!
自動布局進階篇
抗拉伸與抗壓縮
相信許多比較少使用自動布局的同學(xué)對下面的參數(shù)都感覺比較頭疼:

其實不難,請往下看:
Content Hugging Priority : 抗拉伸優(yōu)先級
最常見的情況是兩個label并排放置,并設(shè)置水平間距:

報錯了?莫慌,當(dāng)兩個label的文字長度加上水平間距不足以填滿父視圖時,需要設(shè)置抗拉伸優(yōu)先級。
解決辦法:假設(shè)我們不希望Label1被拉伸,則將其Hugging Priority值調(diào)大(默認(rèn)為251)

調(diào)整后,修改label1的文字,其長度隨之變化:



然后我們在調(diào)整的過程中會發(fā)現(xiàn):

又報錯了? 接著往下看:
Content Compression Resistance Priority : 抗壓縮優(yōu)先級
當(dāng)兩個label的文字長度加上水平間距超出了父視圖寬度時,需要設(shè)置抗壓縮優(yōu)先級。
解決辦法:假設(shè)我們希望Label1的內(nèi)容盡可能地展示完全,則將其Resistance Priority值調(diào)大(默認(rèn)為750)

調(diào)整后:

可以看到,Label2直接被壓沒了。
我們把Label2的Resistance Priority值調(diào)大:

調(diào)整后:

經(jīng)過上面的過程后,對于如何使用自動布局做出下圖的效果,是不是就心里有數(shù)了呢:

PS: 抗壓縮、抗拉伸優(yōu)先級同樣適用于與父視圖的約束優(yōu)先級(默認(rèn)值1000)
熟悉了xib中的優(yōu)先級設(shè)置后,在Masonry中對應(yīng)優(yōu)先級的思路相同,使用方法- (void)setContentCompressionResistancePriority:(UILayoutPriority)priority forAxis:(UILayoutConstraintAxis)axis;進行對應(yīng)設(shè)置(系統(tǒng)自帶方法,不是Masonry加的)。
內(nèi)容尺寸(Intrinsic Content Size)
某些用來展現(xiàn)內(nèi)容的用戶控件,例如文本控件UILabel、按鈕UIButton、圖片視圖UIImageView等,它們具有自身內(nèi)容尺寸(對于UIImageView,其自身內(nèi)容尺寸就是圖片(1倍圖)的尺寸)。
如何設(shè)置自定義控件的內(nèi)容尺寸?
我們可以通過重寫- (CGSize)intrinsicContentSize方法來指定內(nèi)容尺寸。
先來看看UIView默認(rèn)的返回值:
- (CGSize)intrinsicContentSize{
return CGSizeMake(UIViewNoIntrinsicMetric, UIViewNoIntrinsicMetric);
}
這個方法的作用是當(dāng)使用已有約束不能夠計算出內(nèi)容寬度/高度時,自動為視圖添加寬度/高度約束,其值為返回值中對應(yīng)的寬/高。
因此我們可以在這個方法中返回計算好的內(nèi)容大小。
我們在xib中也可以看到下面的參數(shù)(需要注意的是,在這里設(shè)置的size僅僅只用于xib中展示效果,實際運行時并沒有什么卵用):

知道了intrinsicContentSize的用法后,我們考慮一下下面的需求:
寫一個自定義的View,滿足下面的功能:
- 如果未使用約束,直接顯示frame的大小即可;
- 如果添加了能夠定位左上角點坐標(biāo)的約束,則使用默認(rèn)的內(nèi)容大小。
- 如果添加了能夠定位左上角點坐標(biāo)以及能夠計算出寬/高其中一個數(shù)值時,使用寬/高的另一個默認(rèn)值。
- 如果添加了能夠計算出全部frame值的約束,則不使用默認(rèn)內(nèi)容大小。
整體效果類似UILabel,但是這里簡化成固定的默認(rèn)尺寸。

這里給出一個簡單的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是自動布局調(diào)整后的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)) {
// 計算默認(rèn)內(nèi)容寬高的代碼
float width = 100;
float height = 20;
_defaultSize = CGSizeMake(width, height);
}
return _defaultSize;
}
@end
ScrollView的自動內(nèi)容大小
我們看下面的布局:

當(dāng)我們需要Label2可以顯示多行數(shù)據(jù)時,僅僅設(shè)置右邊距是不夠的:

我們可以讓Label2的寬度等于Scrollview的寬度減去左右邊距之和:

看看效果:

效果出來了,但是僅僅這樣我們發(fā)現(xiàn)不能上下滾動,因為沒有設(shè)置底部間距,scrollview無法計算出內(nèi)容高度,需要給最下面的view(Label2)添加底部間距:

看看效果:

下面考慮更進一步的需求:Label1也要可以顯示多行時的情況。
我們把Label1右邊與Label2對齊,然后改一下內(nèi)容文字:

OK大功告成。
PS:如果使用Tablview時用到Cell的自動高度(不管是用系統(tǒng)自帶還是UITableView+FDTemplateLayoutCell),也都需要設(shè)置好約束,讓它能夠計算出豎直方向的內(nèi)容高度(水平方向tableview不需要,collectionView需要),具體過程和上面類似。
自定義View與自動布局
我們看一個樣式:

要實現(xiàn)這一樣式的方式有很多種,這里我們考慮用UILabel的子類直接實現(xiàn)的情況(盡可能少的View):

UIView中有這一個類方法:+ (Class)layerClass;,該方法返回的就是view.layer的類型,因此我們可以使用自定義的Layer替換掉默認(rèn)的Layer,然后裁剪出對應(yīng)的形狀(原理比較簡單,直接上代碼了):
@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動態(tài)調(diào)整的界面,許多小伙伴都不知道該如何使用自動布局進行動畫而放棄,其實不難:
如果使用xib或者代碼創(chuàng)建,最簡單的方式就是將約束設(shè)置成對應(yīng)屬性,直接修改約束的constant即可。
如果使用Masonry創(chuàng)建布局,那么只需要使用下面的代碼就可以使用動畫效果了:
// 如果其約束還沒有生成的時候需要動畫的話,就請先強制刷新后才寫動畫,否則所有沒生成的約束會直接跑動畫
[view.superview layoutIfNeeded];
[UIView animateWithDuration:3 animations:^{
[view mas_updateConstraints:^(MASConstraintMaker *make) {
make.height.mas_equalTo(123);
}];
}];
// 強制繪制
[view.superview layoutIfNeeded];
記得動畫前后需要刷新。