【OC梳理】自動佈局

自動佈局基礎篇

關於自動佈局的基本使用,參考網上的文章便可,如: iOS開發-自動佈局篇:史上最牛的自動佈局教學!bash

自動佈局進階篇

抗拉伸與抗壓縮

相信許多比較少使用自動佈局的同窗對下面的參數都感受比較頭疼:佈局

其實不難,請往下看: #####Content Hugging Priority : 抗拉伸優先級 最多見的狀況是兩個label並排放置,並設置水平間距:動畫

報錯了?莫慌,當兩個label的文字長度加上水平間距不足以填滿父視圖時,須要設置抗拉伸優先級。 解決辦法:假設咱們不但願Label1被拉伸,則將其Hugging Priority值調大(默認爲251)ui

調整後,修改label1的文字,其長度隨之變化:atom

而後咱們在調整的過程當中會發現:spa

又報錯了? 接着往下看: #####Content Compression Resistance Priority : 抗壓縮優先級 當兩個label的文字長度加上水平間距超出了父視圖寬度時,須要設置抗壓縮優先級。 解決辦法:假設咱們但願Label1的內容儘量地展現徹底,則將其Resistance Priority值調大(默認爲750)3d

調整後:code

能夠看到,Label2直接被壓沒了。 咱們把Label2的Resistance Priority值調大:cdn

調整後:blog

通過上面的過程後,對於如何使用自動佈局作出下圖的效果,是否是就內心有數了呢:

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];
複製代碼

記得動畫先後須要刷新。

相關文章
相關標籤/搜索