Masonry學習筆記和使用技巧(cell高度自適應,多控件居中、等間距分布、UIScrollView約束問題)

masonry是基于UILayoutConstraint封裝的第三方布局框架,相比于UILayoutConstraint添加約束的代碼量,masonry運用鏈式編程的思想可謂是極簡單和優雅的。
本文所使用的所有代碼在這里

文章目錄機構結構:
1、不同方式代碼添加約束的比較
2、masonry的基本用法(添加/更新/重置約束、動畫效果)
3、masonry的使用技巧(多控件相對父控件居中、等間距分布,約束UIScrollView技巧)
4、UITableViewCel自適應高度介紹

一、幾種代碼添加約束的比較

首先先創建一個View視圖,并將視圖添加到父視圖上。

    // 創建一個子視圖,添加到父視圖上面
    UIView *view= [[UIView alloc] init];
    view.backgroundColor = [UIColor redColor];
    //給視圖添加約束之前必須先將該視圖添加到俯視圖上否則會crash
    [self.view addSubview:view];

接下來我們將通過三種方法給view添加約束,以達到距離self.view的頂部為100、左右各為20,高度為50.

1、系統方法
// 1、禁用autoresizing
    view.translatesAutoresizingMaskIntoConstraints = NO;
// 2、創建約束對象
    //頂部約束(基于父控件)
    NSLayoutConstraint *topCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1.0 constant:100];
    // 左邊約束(基于父控件)
    NSLayoutConstraint *leftCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1.0 constant:20];
    // 右邊約束(基于父控件)
    NSLayoutConstraint *rightCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1.0 constant:-20];
    // 高度約束(自身)
    NSLayoutConstraint *heightCos = [NSLayoutConstraint constraintWithItem:view attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:0 multiplier:1.0 constant:50];
// 3、 添加約束
    [self.view addConstraints:@[topCos,leftCos,rightCos]];
    [view addConstraint:redHeightCos];
2、VFL語言添加約束
//禁用autoresizing
    view.translatesAutoresizingMaskIntoConstraints = NO;
    // 水平方向
    NSArray *hCos = [NSLayoutConstraint constraintsWithVisualFormat:@"H:|-20-[view]-20-|" options:kNilOptions metrics:nil views:NSDictionaryOfVariableBindings(view)];
    [self.view addConstraints:hCos];
    //豎直方向
    NSArray *vCos = [NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(==50)]" options:kNilOptions metrics:nil views:NSDictionaryOfVariableBindings(view)];
    [self.view addConstraints:vCos];
3、masonry添加約束
    [view mas_makeConstraints:^(MASConstraintMaker *make) {
        // 相對于self.view頂部的約束
        make.top.equalTo(self.view).offset(100);
        make.right.equalTo(self.view).offset(-20);
        make.left.equalTo(@20);//方向上的約束默認相對于俯視圖
        make.height.equalTo(@50);
    }];
運行結果.png

從給出的三種添加約束的代碼中我們可以看出:
系統方法添加約束的代碼量很多且相似代碼占據絕大部分
VFL語句自我感覺隱晦難懂且使用字符串的方式不符合我們平時編程習慣。
masonry采用鏈式編程的思想,使用點語法進行方法調用簡單且靈活。
接下來我們就看看如何使用masonry

二、Masonry的基本用法

首先masonry有兩個宏
MAS_SHORTHAND,只要#define這個宏,就不用帶mas_前綴了,但是不建議去掉,比如view.mas_left->view.left,這樣容易和工程中其他view的category產生沖突。
MAS_SHORTHAND_GLOBALS,只要#define這個宏,qualTo就等價于mas_equalTo,后面就可以直接使用基本類型,而不需要轉換成NSNumber了,建議添加。

  • 添加約束
    redView是距離父視圖上下左右的約束均為20,blueView是redView的姿勢圖,寬高的redView一半,并且居中。
    orangeView、purpleView和greenView是blueView的子視圖,它們高度相等,左右間距均為10,上下間距均為15。
    UIView *redView = [UIView new];
    redView.backgroundColor = [UIColor redColor];
    [self.view addSubview:redView];
    UIEdgeInsets padding1 =  UIEdgeInsetsMake(20, 20, 20, 20);
    [redView mas_makeConstraints:^(MASConstraintMaker *make) {
//         方法一
//        make.right.equalTo( self.view.mas_right).offset(-20);
//        make.left.equalTo(self.view).offset(20);//默認相對方向為你想要設置的方向
//        make.top.equalTo(redView.superview).offset(20);
//        make.bottom.equalTo(-20);//默認相對視圖為父視圖
//         方法二
//        make.top.left.equalTo(20);// 可同時設置多個約束
//        make.right.bottom.equalTo(-20);
//        方法三
//        make.top.left.right.bottom.equalTo(self.view).insets(padding1);
//         方法四
        make.edges.equalTo(padding1);//設置內邊距
    }];

    UIView *blueView = [UIView new];
    blueView.backgroundColor = [UIColor blueColor];
    [redView addSubview:blueView];
    [blueView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(redView);
        make.size.equalTo(redView).multipliedBy(0.5);//父視圖寬度的一半
      // 等價于make.size.equalTo(redView).dividedBy(2.0);
    }];

    UIView *orangeView = [UIView new];
    orangeView.backgroundColor = [UIColor orangeColor];
    [blueView addSubview:orangeView];
    UIView *purpleView = [UIView new];
    purpleView.backgroundColor = [UIColor purpleColor];
    [blueView addSubview:purpleView];
    UIView *greenView = [UIView new];
    greenView.backgroundColor = [UIColor greenColor];
    [blueView addSubview:greenView];

    UIEdgeInsets padding2 = UIEdgeInsetsMake(15, 10, 15, 10);
    [orangeView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.left.equalTo(blueView).insets(padding2);
        make.right.equalTo(purpleView.mas_left).insets(padding2);
       //上面寫法等價于make.right.equalTo(purpleView.mas_left).offset(-10);
        make.width.equalTo(purpleView.mas_width);
        make.height.equalTo(@[purpleView,greenView]);//多個視圖同一類型約束相等可傳數組
        make.bottom.equalTo(greenView.mas_top).insets(padding2);
    }];
    [purpleView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.top.right.equalTo(blueView).insets(padding2);
    }];
    [greenView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.right.bottom.equalTo(blueView).insets(padding2);
    }];
運行結果.png
  • 更新約束、重置約束
    更新約束:修改視圖已有約束,如果該視圖沒有該約束則會添加這個約束。
    給上面的blueVIew添加一個手勢方法,當被點擊時blueView的寬度變成父視圖的0.8倍,如下代碼:
[blueView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(updateConstraints:)]];
-(void)updateConstraints:(UITapGestureRecognizer *)tap{
    UIView *blueView = tap.view;
 [blueView mas_updateConstraints:^(MASConstraintMaker *make) {
    make.width.equalTo(blueView.superview).multipliedBy(0.8);
    }];
}
更新約束.png

重置約束:當我們想要通過更新現有約束無法滿足需求時,需要重新設置約束。
給原來的blueVIew重新添加一個手勢方法,當被點擊是blueView的位置距離底部和右邊為10,如下代碼:

[blueView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(remarkConstraints:)]];
- (void)remarkConstraints:(UITapGestureRecognizer *)tap{
    UIView *blueView = tap.view;
    [blueView mas_updateConstraints:^(MASConstraintMaker *make) {
        make.right.bottom.equalTo(blueView.superview).offset(-10);
        make.width.equalTo(blueView.superview).multipliedBy(0.5);
    }];
}
重置約束.png
  • 動畫效果約束
    masonry動畫效果很簡單,只需在動畫的block里view的父視圖調用layoutIfNeeded即可,但是在之前我們必須調用setNeedsUpdateConstraints和updateConstraintsIfNeeded告訴視圖需要更新約束。在上面代碼中添加如下代碼:
    // 告訴約束需要更新,但不會立即更新,
    [blueView.superview setNeedsUpdateConstraints];
    // 檢測當前視圖及其子視圖是否需要更新約束
    [blueView.superview updateConstraintsIfNeeded];
    [UIView animateWithDuration:3 animations:^{
        // 立即更新約束
        [blueView.superview layoutIfNeeded];
    }]
動畫.gif

三、Masonry的使用技巧

  • 多個控件相對于父控件居中
    思路:首先讓父控件居中,再根據子控件的size和間距拉伸父控件。水平居中分布代碼如下:(垂直居中可自己嘗試)
   UIView *supView = [UIView new];           
  supView.backgroundColor = [UIColor redColor];
   [self.view addSubview:supView];
   
   UIView *leftView = [UIView new];    
   leftView.backgroundColor = [UIColor orangeColor];
   [supView addSubview:leftView];
  
  UIView *rightView = [UIView new];
    rightView.backgroundColor = [UIColor greenColor];
    [supView addSubview:rightView];
    
    [supView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.center.equalTo(self.view);//設置父視圖居中
    }];
    
    [leftView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.top.bottom.equalTo(supView);//設置上左下對齊父視圖
        make.size.equalTo(CGSizeMake(80, 40));//設置尺寸
        make.right.equalTo(rightView.mas_left).offset(-10);//設置間距
    }]; 
    [rightView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.right.top.bottom.equalTo(supView);//設置右上下對齊父視圖 (配合leftView就可以確定superView的寬高了)
        make.size.equalTo(leftView);
    }];
9E81CAA3-35C8-4B75-93BE-6C4A28A8A0A8.png
38A50D79-AF24-4EF2-B1D6-AED550889440.png
  • 等間距view布局(view的寬度也是相同的)
    masonry給array擴展一些方法,可以對數組里的多個view同時設置約束,其中mas_distributeViewsAlongAxis就可以設置數組里view的間距問題,一種方法是指定間距拉伸view的寬度,另一種方法是指定view的寬度等分間距。


    8CD42876-F21D-4BCD-AC4F-CD54BF989B44.png

    給出代碼及注釋如下:

  • UIScrollView約束
    思路:給scrollView添加唯一的子視圖contentView,通過拉伸子視圖的size來確定scrollView的contentSize,代碼和實現見下方代碼和注釋:
-(void)scrollViewTest{
    UIScrollView *scrollView = [UIScrollView new];
    scrollView.backgroundColor = [UIColor redColor];
    [self.view addSubview:scrollView];
    [scrollView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self.view);
    }];
    
    UIView *contentView = [UIView new];
    contentView.backgroundColor = [UIColor lightGrayColor];
    [scrollView addSubview:contentView];
    
    [contentView mas_makeConstraints:^(MASConstraintMaker *make) {
        //設置邊距相對于scrollView的約束
        //(自己的見解:contentView的edges相對scrollView的edges的約束  contentView實際上被拉伸的寬高是相對于scrollView的contentSize的)
        make.edges.equalTo(scrollView);
        //因為上面的寬高是相對于contentSize的  所以為0  這里需要設置contentView的寬度約束后  scrollView的contentSize.width就會拉伸
        make.width.equalTo(scrollView);
    }];
    
    NSMutableArray *lastArray;
    NSMutableArray *array = [NSMutableArray array];
    for (NSInteger i = 0; i<99; i++) {
        UIView *view = [UIView new];
        view.backgroundColor = [UIColor colorWithRed:(arc4random()%255)/255.0 green:(arc4random()%255)/255.0 blue:(arc4random()%255)/255.0 alpha:1];
        [contentView addSubview:view];
        [array addObject:view];

        if ((i+1)%3 == 0) { //一行分三個  最后一行如果不足三個則忽略
            [array mas_distributeViewsAlongAxis:MASAxisTypeHorizontal withFixedItemLength:50 leadSpacing:20 tailSpacing:20];
            [array mas_makeConstraints:^(MASConstraintMaker *make) {
                make.height.equalTo(50);
                make.top.equalTo(lastArray.count?((UIView *)lastArray[0]).mas_bottom:contentView).offset(10);
            }];
            lastArray = array.mutableCopy;
            array = [NSMutableArray array];
        }
    }
    
    [contentView mas_makeConstraints:^(MASConstraintMaker *make) {
        // 這里設置contentView的底部約束等于最后一排視圖底部約束后  contentView的高度就確定了  scrollView的contentSize.height就會拉伸
        make.bottom.equalTo(((UIView *)lastArray[0]).mas_bottom);
    }];
}
運行結果

四、UITableViewCell自適應高度

UITableView算是我們開發中使用頻率最多的控件了,但是cell的高度的計算確實一直是我們頭疼的問題。我們最理想的狀態:cell的高度自己決定,而不需要我們進行計算。下面就介紹autoLayout是如何實現的自適應高度的

  • 系統方法。self-sizing cell,顧名思義,cell的高度自己決定,這正是我們想要的。
    如果約束正確,所有contentView的子視圖的約束可以撐開contentVIew,那么只需要設置:
   tableView.estimatedRowHeight = 100;//一個估算值
   tableView.rowHeight = UITableViewAutomaticDimension;//可以不設置,ios8之后默認值

只適用于iOS8及以上,且在數據量超大的時候,進行插入和刪除,都是很不流暢的,并且在調用scrollToRowAtIndexPath:方法時,甚至會出現白屏情況。當然這種方法針對一些常用場景,比如新聞列表、商品列表什么的,數據量沒那么大且不涉及到新增、刪除數據的時候,這種方法,還是蠻不錯的,寫起來很簡便。

  • UITableView+FDTemplateLayoutCell
    這是一個三方框架,同樣簡單的api,就可以達到自適應cell高度的效果。作者通過RunLoop進行緩存、預緩存,使tableView的滑動十分流暢。如果想要了解更多的可以看作者博客,寫的十分詳盡。
    !注意:使用這個框架時,cell的創建必須要以-registerClass:forCellReuseIdentifier: 或 -registerNib:forCellReuseIdentifier:其中之一的注冊方法創建的。具體使用方法見代碼:
-(UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath{
    TestCell *cell = [tableView dequeueReusableCellWithIdentifier:@"TestCell"];
    [cell configCellWithImageStr:@"logo" contentStr:_dataArray[indexPath.row]];
    return cell;
}
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath{
    return  [tableView fd_heightForCellWithIdentifier:@"TestCell" cacheByIndexPath:indexPath configuration:^(TestCell *cell) {
        // 這里調用cell子視圖賦值的方法
        [cell configCellWithImageStr:@"logo" contentStr:_dataArray[indexPath.row]];
    }];
}
-(void)configCellWithImageStr:(NSString *)imageStr contentStr:(NSString *)content{
    self.logoIv.image = [UIImage imageNamed:imageStr];
    self.contentLb.text = content;
}
自適應
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,406評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,034評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,413評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,449評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,165評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,559評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,606評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,781評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,327評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,084評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,278評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,849評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,495評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,927評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,172評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,010評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,241評論 2 375

推薦閱讀更多精彩內容