代碼適配Masonry使用的詳細介紹

原文轉自:http://www.cnblogs.com/wqcoder/p/5511676.html

Masonry簡介

Masonry是一個輕量級的佈局框架,它擁有自己的描述語法(採用更優雅的鏈式語法封裝)來自動佈局,具有很好可讀性且同時支持iOS和Max OS X等。
總之,對於側重寫代碼的coder,請你慢慢忘記Frame,喜歡Masonry

使用前的準備

若是你對於自動佈局很熟練的話,再接觸這個第三方Masonry很容易上手的,對UI界面顯示的控件的約束本質都是相同的,現在呢,我一般都是喜歡在控制器裏導入  #import "Masonry.h"之前再添加兩個宏,來提高App的開發效率。

//1. 對於約束參數可以省去"mas_"
#define MAS_SHORTHAND
//2. 對於默認的約束參數自動裝箱
#define MAS_SHORTHAND_GLOBALS

即:需要我們導入的框架與宏如下

//define this constant if you want to use Masonry without the 'mas_' prefix
#define MAS_SHORTHAND
//define this constant if you want to enable auto-boxing for default syntax
#define MAS_SHORTHAND_GLOBALS

#import "Masonry.h" //宏必須添加在頭文件前面

添加約束前提:被約束的必須有父控件,其中約束項都必須是UIView或子類的實例

約束的屬性

在此我就列舉幾個可能不太熟悉的吧

@property (nonatomic, strong, readonly) MASConstraint *leading;  //首部
@property (nonatomic, strong, readonly) MASConstraint *trailing; //尾部
@property (nonatomic, strong, readonly) MASConstraint *baseline; //文本基線

約束的三種方法

/**
 //這個方法只會添加新的約束
 [blueView mas_makeConstraints:^(MASConstraintMaker *make)  {

 }];

 // 這個方法會將以前的所有約束刪掉,添加新的約束
 [blueView mas_remakeConstraints:^(MASConstraintMaker *make) {

 }];

 // 這個方法將會覆蓋以前的某些特定的約束
 [blueView mas_updateConstraints:^(MASConstraintMaker *make) {

 }];
 */
`

常見約束的各種類型

/**
 1.尺寸:width、height、size
 2.邊界:left、leading、right、trailing、top、bottom
 3.中心點:center、centerX、centerY
 4.邊界:edges
 5.偏移量:offset、insets、sizeOffset、centerOffset
 6.priority()約束優先級(0~1000),multipler乘因數, dividedBy除因數
 */

Masonry約束易忽略的技術點

使用Masonry不需要設置控件的translatesAutoresizingMaskIntoConstraints屬性爲NO;
防止block中的循環引用,使用弱引用(這是錯誤觀點),在這裏block是局部的引用,block內部引用self不會造成循環引用的
__weak typeof (self) weakSelf = self;(沒必要的寫法)

Masonry約束控件出現衝突的問題

當約束衝突發生的時候,我們可以設置view的key來定位是哪個view
redView.mas_key = @"redView";
greenView.mas_key = @"greenView";
blueView.mas_key = @"blueView";
若是覺得這樣一個個設置比較繁瑣,怎麼辦呢,Masonry則提供了批量設置的宏MASAttachKeys
MASAttachKeys(redView,greenView,blueView); //一句代碼即可全部設置

約束iconView距離各個邊距爲30

    [iconView makeConstraints:^(MASConstraintMaker *make) {
      make.edges.equalTo(self.view).insets(UIEdgeInsetsMake(30, 30, 30, 30));
    }];

equalTo 和 mas_equalTo的區別

#define mas_equalTo(...) equalTo(MASBoxValue((__VA_ARGS__)))
#define mas_greaterThanOrEqualTo(...) greaterThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_lessThanOrEqualTo(...) lessThanOrEqualTo(MASBoxValue((__VA_ARGS__)))
#define mas_offset(...) valueOffset(MASBoxValue((__VA_ARGS__)))

得出結論:mas_equalTo只是對其參數進行了一個BOX(裝箱) 操作,目前支持的類型:數值類型(NSNumber)、 點(CGPoint)、大小(CGSize)、邊距(UIEdgeInsets),而equalTo:這個方法不會對參數進行包裝。

//常見約束的寫法 這裏太簡單了 ,就不備註了
[iconView makeConstraints:^(MASConstraintMaker *make) {
        make.right.equalTo(self.view).offset(-30);
        make.top.equalTo(self.view).offset(30);
        make.height.width.equalTo(100); //== make.size.equalTo(100);

        //make.size.mas_equalTo(self.view).multipliedBy(0.5);
        //make.top.greaterThanOrEqualTo(self.view).offset(padding);
    }];
[redView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.equalTo(superview.mas_top).with.offset(10);  //with 增強可讀性
        make.left.equalTo(greenView.mas_right).and.offset(10); //and 增強可讀性
        make.bottom.equalTo(blueView.mas_top).offset(-10);
        make.right.equalTo(superview.mas_right).offset(-10);
        make.width.equalTo(greenView.mas_width);

        make.height.equalTo(@[greenView, blueView]); //約束參數相同可以通過數組
    }];

更新約束的問題

例如:控制器有個按鈕,若是點擊按鈕,則按鈕本身的大小、位置會隨機改變

  • 監聽按鈕點擊
    [self.btn addTarget:self action:@selector(didClickBtn:) forControlEvents:UIControlEventTouchUpInside];
  • 處理事件

    (void) didClickBtn:(UIButton *)button {
      self.btnSize = CGSizeMake(self.btnSize.width * 1.3, self.btnSize.height * 1.3); //設置一個屬性(btnSize)保存其大小的變化
    
      //1.告知需要更新約束,但不會立刻開始,系統然後調用updateConstraints
      [self setNeedsUpdateConstraints];
    
      //2.告知立刻更新約束,系統立即調用updateConstraints
      [self updateConstraintsIfNeeded];
    
      //3.這裏動畫當然可以取消,具體看項目的需求
      //系統block內引用不會導致循環引用,block結束就會釋放引用對象
      [UIView animateWithDuration:0.4 animations:^{
          [self layoutIfNeeded]; //告知頁面立刻刷新,系統立即調用updateConstraints
      }];
    }
  • 蘋果官方建議:添加/更新約束在這個方法(updateConstraints)

// this is Apple's recommended place for adding/updating constraints
- (void)updateConstraints {
   //更新約束
    [self.btn updateConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self);

        make.width.equalTo(@(self.buttonSize.width)).priorityLow();
        make.height.equalTo(@(self.buttonSize.height)).priorityLow();

        make.width.lessThanOrEqualTo(self);
        make.height.lessThanOrEqualTo(self);
    }];

    //according to apple super should be called at end of method
    //最後必須調用父類的更新約束
    [super updateConstraints];
}
  • 設置requiresConstraintBasedLayout爲YES
+ (BOOL)requiresConstraintBasedLayout{
    return YES ; //重寫這個方法 若視圖基於自動佈局的
}

重置約束的問題

對於控件的重新約束,則之前的約束都是無效的,步驟都更新約束一樣的,只是在updateConstraints方法內的約束方法改爲了remakeConstraints,直接貼代碼吧(仍以按鈕爲例,其他原理都是相同的)

//首先監聽按鈕
[self.btn addTarget:self action:@selector(didClickBtn:) forControlEvents:UIControlEventTouchUpInside];

//處理事件
- (void) didClickBtn :(UIButton *)button{
   (...) //觸發條件
    [self setNeedsUpdateConstraints]; 

    [self updateConstraintsIfNeeded];

   /**
     *   動畫展示變化 - 這句代碼可有可無,參考項目具體的需求
     *   [UIView animateWithDuration:0.4 animations:^{
     *         [self layoutIfNeeded];
     *   }];
     */
}

//重置約束
- (void)updateConstraints {
    [self.btn remakeConstraints:^(MASConstraintMaker *make) {
      .....
    }];
    [super updateConstraints]; 
}

+ (BOOL)requiresConstraintBasedLayout{
    return YES ; //重寫這個方法 若視圖基於自動佈局的
}

多個(2個以上)控件的等間隔排序顯示

首先介紹2個函數
/**
     *  axisType         軸線方向
     *  fixedSpacing     間隔大小
     *  fixedItemLength  每個控件的固定長度/寬度
     *  leadSpacing      頭部間隔
     *  tailSpacing      尾部間隔
     *
     */
//1. 等間隔排列 - 多個控件間隔固定,控件長度/寬度變化
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
withFixedSpacing:(CGFloat)fixedSpacing leadSpacing:(CGFloat)leadSpacing
tailSpacing:(CGFloat)tailSpacing;

//2. 等間隔排列 - 多個固定大小固定,間隔空隙變化
- (void)mas_distributeViewsAlongAxis:(MASAxisType)axisType
withFixedItemLength:(CGFloat)fixedItemLength
leadSpacing:(CGFloat)leadSpacing
tailSpacing:(CGFloat)tailSpacing;
//首先添加5個視圖
 NSMutableArray *array = [NSMutableArray new];
    for (int i = 0; i < 5; i ++) {
        UIView *view = [UIView new];
        view.backgroundColor = [UIColor greenColor];
        [self addSubview:view];
        [array addObject:view]; //保存添加的控件
    }

//水平方向控件間隔固定等間隔
[array mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedSpacing:15 leadSpacing:10 tailSpacing:10];
            [array makeConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(50);
                make.height.equalTo(70);
            }];

//水平方向寬度固定等間隔
[array mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:70 leadSpacing:10 tailSpacing:10];
            [array makeConstraints:^(MASConstraintMaker *make) { //數組額你不必須都是view 
                make.top.equalTo(50);
                make.height.equalTo(70);
            }];

水平方向等間隔.png

水平方向控件寬度固定等間隔.png

多行label的約束問題

對於UILabel文字內容多的問題,個人覺得Masonry約束設置的非常簡單優雅,在此非常感謝Masonry的作者@Robert Payne

    //創建label
    self.label = [UILabel new];
    self.label.numberOfLines = 0;
    self.label.lineBreakMode = NSLineBreakByTruncatingTail;
    self.label.text = @"有的人,沒事時喜歡在朋友圈裏到處點贊,東評論一句西評論一句,比誰都有存在感。等你有事找他了,他就立刻變得很忙,讓你再也找不着。真正的朋友,平常很少聯繫。可一旦你遇上了難處,他會立刻回覆你的消息,第一時間站出來幫你。所謂的存在感,不是你有沒有出現,而是你的出現有沒有價值。存在感,不是刷出來的,也不是說出來的。有存在感,未必是要個性鋒芒畢露、甚至鋒利扎人。翩翩君子,溫潤如玉,真正有存在感的人,反而不會刻意去強調他的存在感。他的出現,永遠都恰到好處。我所欣賞的存在感,不是長袖善舞巧言令色,而是對他人的真心關照;不是鋒芒畢露計較勝負,而是讓人相處得舒服;不是時時刻刻聒噪不休,而是關鍵時刻能挺身而出。別總急着出風頭,希望你能有恰到好處的存在感。";
    [self addSubview: self.label];

    [self.label makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.equalTo(10);
        make.right.equalTo(-10);
    }];

//添加約束
- (void)layoutSubviews {
    //1. 執行 [super layoutSubviews];
    [super layoutSubviews];

    //2. 設置preferredMaxLayoutWidth: 多行label約束的完美解決
   self.label.preferredMaxLayoutWidth = [UIScreen mainScreen].bounds.size.width - 20;

    //3. 設置preferredLayoutWidth後,需要再次執行 [super layoutSubviews]; 
    //其實在實際中這步不寫,也不會出錯,官方解釋是說設置preferredLayoutWidth後需要重新計算並佈局界面,所以這步最好執行
    [super layoutSubviews];
}

多行label約束.png

UIScrollView的問題

原理同自動佈局一樣 UIScrollView上添加UIView
UIView上添加需要顯示的控件 UIScrollView滾動高度取決於顯示控件的總高度
對子控件做好約束,可達到控制UIView的大小

    //創建滾動視圖
    UIScrollView *scrollView = [UIScrollView new];
    self.scrollView = scrollView;

    scrollView.backgroundColor = [UIColor grayColor];
    [self addSubview:scrollView];

    [self.scrollView makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self);
    }];

    [self setUpContentView]; //添加內容視圖

- (void)setUpContentView {
    //約束UIScrollView上contentView
    UIView *contentView = [UIView new];
    [self.scrollView addSubview:contentView];

    [contentView makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.scrollView);
        make.width.equalTo(self.scrollView); //此處必填 - 關鍵點
    }];

    //添加控件到contentView,約束原理與自動佈局相同
    UIView *lastView;
    CGFloat height = 30;
    for (int i = 0; i <1 5; i ++) {
        UIView *view = UIView.new;
        view.backgroundColor = [UIColor colorWithRed:arc4random() % 255 / 256.0  green:arc4random() % 255 / 256.0 blue:arc4random() % 255 / 256.0 alpha:1.0];
        [contentView addSubview:view];

        [view makeConstraints:^(MASConstraintMaker *make) {
            make.top.equalTo(lastView ? lastView.bottom : @0);
            make.left.equalTo(0);
            make.width.equalTo(contentView.width);
            make.height.equalTo(height);
        }];

        height += 30;
        lastView = view;
    }

    [contentView makeConstraints:^(MASConstraintMaker *make) {
        make.bottom.equalTo(lastView.bottom);
    }];
}

scrollView的約束設置.png

Masonry源碼GitHub地址下載:Masonry
終於寫完了,有什麼理解不對的直接說...今晚還有巴薩、尤文的歐冠,真心傷不起。。。





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章