iOS 開(kāi)發(fā)實(shí)踐之 AutoLayout

本文是博主 iOS 開(kāi)發(fā)實(shí)踐系列中的一篇,主要講述 iOS 中 Auto Layout(自動(dòng)布局)在實(shí)際項(xiàng)目中的使用。

Auto Layout 在 2012 年的 iOS 6 中發(fā)布,距今已經(jīng) 2 年多了,如果從 2011 年在 Mac OS X 上發(fā)布的 Auto Layout 開(kāi)始算起,已經(jīng)超過(guò) 3 年了。如果你的簡(jiǎn)歷上寫(xiě)著 2 年以上工作經(jīng)驗(yàn),而竟然不會(huì)使用 Auto Layout,真有點(diǎn)不可思議。

本文將會(huì)通過(guò)若干個(gè) Demo 進(jìn)行講解,通過(guò)實(shí)踐來(lái)理解 Auto Layout 到底是什么,該如何使用(包括在 Xib 中使用以及手動(dòng)編碼)。

Auto Layout 是什么?

我的理解:Auto Layout 是一種基于約束的布局系統(tǒng),它可以根據(jù)你在元素(對(duì)象)上設(shè)置的約束自動(dòng)調(diào)整元素(對(duì)象)的位置和大小。

官方的說(shuō)明:

Auto Layout 是一個(gè)系統(tǒng),可以讓你通過(guò)創(chuàng)建元素之間關(guān)系的數(shù)學(xué)描述來(lái)布局應(yīng)用程序的用戶界面?!?a target="_blank" rel="nofollow">Auto Layout Guide》

Auto Layout 是一種基于約束的,描述性的布局系統(tǒng)。——《Taking Control of Auto Layout in Xcode 5 - WWDC 2013

這里有幾個(gè)關(guān)鍵字:

元素(Element)

低頭看看你電腦的鍵盤(pán),你可以把每一個(gè)按鍵當(dāng)做一個(gè)元素;對(duì)于 iOS 系統(tǒng)來(lái)說(shuō),你可以把桌面上每一個(gè)應(yīng)用圖標(biāo)當(dāng)做一個(gè)元素;對(duì)于某一款 iOS 應(yīng)用來(lái)說(shuō),你可以把視圖中的每一個(gè)子視圖當(dāng)做一個(gè)元素。

事實(shí)上,你也可以把整個(gè)鍵盤(pán)、桌面或者視圖當(dāng)做一個(gè)元素。

關(guān)系(Relation)

元素之間可以有關(guān)系。例如在鍵盤(pán)上?Q?鍵和?W?鍵之間有關(guān)系。是什么關(guān)系呢?有很多,例如?Q鍵在?W?鍵的左邊,W?鍵在?Q?鍵的右邊,Q?鍵和?W?鍵之間相距 0.5 厘米等等。

不理解?試著把鍵盤(pán)想象成?View,把按鍵想象成?Button,再思考一遍。

約束(Constraint)

元素之間關(guān)系的限制。約束是 Auto Layout 系統(tǒng)中最重要的概念。我們上面提到的?左邊、右邊以及?相距 0.5 厘米?等這些都是約束,它們限制了元素之間的關(guān)系。

描述(Description)

定義約束來(lái)限制元素之間的關(guān)系。描述定義了元素之間的關(guān)系及約束。

繼續(xù)用鍵盤(pán)舉例,Q?鍵的長(zhǎng)寬均為 1 厘米,左邊距離鍵盤(pán)的左邊緣 10 厘米,上邊距離鍵盤(pán)的頂部 5 厘米。這句話就可以定位?Q?鍵在鍵盤(pán)中的位置,很輕松就可以計(jì)算出?Q?鍵的?frame?為?{{10.0, 5.0}, {1.0, 1.0}}。

現(xiàn)在?Q?鍵的坐標(biāo)已經(jīng)確定,那么?W?鍵的坐標(biāo)可以這樣描述:頂部和?Q?鍵對(duì)齊,大小和?Q?鍵相等,位于?Q?鍵右側(cè) 0.5 厘米處。仔細(xì)想想,這句話中包含了元素間的關(guān)系,關(guān)系間的約束,可以直接計(jì)算出?W?鍵的?frame。

忘掉傳統(tǒng)的 Springs & Struts 布局方式

事實(shí)上如果你用傳統(tǒng)的設(shè)置 frame 的布局方式的思維來(lái)理解上面的?Q?鍵和?W?鍵的布局也說(shuō)的通。

因?yàn)樵?Auto Layout 中,當(dāng)你描述完之后, Auto Layout 會(huì)自動(dòng)幫你計(jì)算出 frame。換句話說(shuō),你的描述告訴了 Auto Layout 如何幫你計(jì)算出 frame。所以,你也可以理解為你間接的設(shè)置了 frame。為什么要這么做呢?為什么不直接設(shè)置 frame?這是因?yàn)槭褂?Auto Layout 有很多好處:

多數(shù)情況下旋轉(zhuǎn)屏幕不用再做額外的處理

更容易適配不同尺寸的屏幕

上手后布局非常簡(jiǎn)單容易,布局邏輯更清晰

Auto Layout 和傳統(tǒng)布局很大的不同之處在于它是一種相對(duì)的布局方式。怎么理解這句話?上面提到

W?鍵位于?Q?鍵右側(cè) 0.5 厘米處。

傳統(tǒng)的布局無(wú)法直接表示,你必須把這種布局手動(dòng)轉(zhuǎn)換為傳統(tǒng)布局代碼。例如上面的?Q?鍵和?W?鍵的傳統(tǒng)布局代碼看起來(lái)可能是這樣:

q.frame = CGRectMake(CGRectGetMinX(keyBoard.frame) + 10.f, CGRectGetMinY(keyBoard.frame) + 5.f, 1.f, 1.f);

w.frame = CGRectMake(CGRectGetMaxX(q.frame) + 0.5f, CGRectGetMinY(q.frame), CGRectGetWidth(q.frame), CGRectGetHeight(q.frame));

使用 Auto Layout 的布局代碼看起來(lái)像這樣:

// 偽代碼

q.width = 1.f;

q.height = 1.f;

q.left = keyboard.left + 10.f;

q.top = keyboard.top + 5.f;

w.top = q.top;

w.width = q.width;

w.height = q.height;

w.left = q.right + .5f;

Auto Layout 不僅能輕松表示這種布局,而且相對(duì)于傳統(tǒng)的布局更清晰簡(jiǎn)潔易懂,還免費(fèi)附贈(zèng)很多優(yōu)點(diǎn),有什么理由不使用 Auto Layout 呢?

實(shí)踐中我發(fā)現(xiàn)對(duì)于很多新手來(lái)說(shuō),Auto Layout 這種布局方式比較容易理解接受,相反很多對(duì)傳統(tǒng)布局很熟練的人卻不太容易理解,總是用傳統(tǒng)布局的思維來(lái)思考,所以如果可能的話,我建議你暫時(shí)忘掉傳統(tǒng)的布局方式。

Autoresizing Mask

事實(shí)上我不打算講這個(gè)東西,以及它和 Auto Layout 的區(qū)別和聯(lián)系。如果你不知道,對(duì)學(xué)習(xí) Auto Layout 不會(huì)有什么影響。

你唯一需要注意的是在使用 Auto Layout 時(shí),首先需要將視圖的?translatesAutoresizingMaskIntoConstraints?屬性設(shè)置為?NO。這個(gè)屬性默認(rèn)為?YES,如果你是使用 Xib 的話,這個(gè)屬性會(huì)自動(dòng)幫你設(shè)置為?NO。當(dāng)它為?YES?時(shí),運(yùn)行時(shí)系統(tǒng)會(huì)自動(dòng)將 Autoresizing Mask 轉(zhuǎn)換為 Auto Layout 的約束,這些約束很有可能會(huì)和我們自己添加的產(chǎn)生沖突。

Auto Layout 基礎(chǔ)知識(shí)

無(wú)論是在 Xib 中還是代碼中使用 Auto Layout,你都需要了解 Auto Layout 的一些必要知識(shí)。這些你現(xiàn)在不理解沒(méi)有關(guān)系,后面我們會(huì)詳細(xì)講述。

約束 (Constraint)

Auto Layout 中約束對(duì)應(yīng)的類(lèi)為?NSLayoutConstraint,一個(gè)?NSLayoutConstraint?實(shí)例代表一條約束。

NSLayoutConstraint?有兩個(gè)方法,第一個(gè)是

+ (id)constraintWithItem:(id)view1

? ? ? ? ? ? ? attribute:(NSLayoutAttribute)attribute1

? ? ? ? ? ? ? relatedBy:(NSLayoutRelation)relation

? ? ? ? ? ? ? ? ? toItem:(id)view2

? ? ? ? ? ? ? attribute:(NSLayoutAttribute)attribute2

? ? ? ? ? ? ? multiplier:(CGFloat)multiplier

? ? ? ? ? ? ? ? constant:(CGFloat)constant;

不要被這個(gè)方法的參數(shù)嚇到,實(shí)際上它只做一件事,就是讓?view1?的某個(gè)?attribute?等于?view2的某個(gè)?attribute?的?multiplier?倍加上?constant,

這里的?attribute可以是上下左右寬高等等。

精簡(jiǎn)后就是下面這個(gè)公式:

view1.attribute1 = view2.attribute2 × multiplier + constant

還有一個(gè)參數(shù)是?relation,這是一個(gè)關(guān)系參數(shù),它標(biāo)明了上面這個(gè)公式兩邊的關(guān)系,它可以是小于等于 (≤),等于 (=)和大于等于 (≥)。上面的公式假定了這個(gè)參數(shù)傳入的是?=,根據(jù)參數(shù)的不同,公式中的關(guān)系符號(hào)也不同。

需要注意的是,≤?或?≥?優(yōu)先會(huì)使用?=?關(guān)系,如果?=?不能滿足,才會(huì)使用?<?或?>。例如設(shè)置一個(gè)?≥ 100?的關(guān)系,默認(rèn)會(huì)是 100,當(dāng)視圖被拉伸時(shí),100 無(wú)法被滿足,尺寸才會(huì)變得更大。

例子:

1、我們要實(shí)現(xiàn)一個(gè)如下圖的布局。

查看圖片

布局代碼如下:

UIView *view = [UIView new];

[view setBackgroundColor:[UIColor redColor]];

[self.view addSubview:view];

CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);

// 使用 Auto Layout 布局

[view setTranslatesAutoresizingMaskIntoConstraints:NO];

// `view` 的左邊距離 `self.view` 的左邊 50 點(diǎn).

NSLayoutConstraint *viewLeft = [NSLayoutConstraint constraintWithItem:view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeLeading

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? relatedBy:NSLayoutRelationEqual

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:self.view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeLeading

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constant:CGRectGetMinX(viewFrame)];

// `view` 的頂部距離 `self.view` 的頂部 100 點(diǎn).

NSLayoutConstraint *viewTop = [NSLayoutConstraint constraintWithItem:view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeTop

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? relatedBy:NSLayoutRelationEqual

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:self.view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeTop

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constant:CGRectGetMinY(viewFrame)];

// `view` 的寬度 是 60 點(diǎn).

NSLayoutConstraint *viewWidth = [NSLayoutConstraint constraintWithItem:view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeWidth

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? relatedBy:NSLayoutRelationGreaterThanOrEqual

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:nil

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeNotAnAttribute

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constant:CGRectGetWidth(viewFrame)];

// `view` 的高度是 60 點(diǎn).

NSLayoutConstraint *viewHeight = [NSLayoutConstraint constraintWithItem:view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeHeight

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? relatedBy:NSLayoutRelationGreaterThanOrEqual

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:nil

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeNotAnAttribute

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constant:CGRectGetHeight(viewFrame)];

// 把約束添加到父視圖上.

[self.view addConstraints:@[viewLeft, viewTop, viewWidth, viewHeight]];

實(shí)現(xiàn)一個(gè)如此簡(jiǎn)單的布局竟然要寫(xiě)這么多的代碼,這顯然難于推廣使用。于是 UIKit 團(tuán)隊(duì)發(fā)明了另外一種更簡(jiǎn)便的表達(dá)方式進(jìn)行布局,這個(gè)我們后面再講,現(xiàn)在先看看這段代碼。

首先我把?view?的?translatesAutoresizingMaskIntoConstraints?設(shè)為了?NO,禁止將 Autoresizing Mask 轉(zhuǎn)換為約束。

然后在設(shè)置?viewLeft?這個(gè)約束時(shí),attribute?參數(shù)使用了?NSLayoutAttributeLeading?而不是?NSLayoutAttributeLeft,這兩個(gè)參數(shù)值都表示左邊,但它們之間的區(qū)別在于?NSLayoutAttributeLeft?永遠(yuǎn)表示左邊,但?NSLayoutAttributeLeading?是根據(jù)習(xí)慣區(qū)分的,例如在某些文字從右向左閱讀的地區(qū),例如阿拉伯,NSLayoutAttributeLeading?表示右邊。換句話說(shuō),NSLayoutAttributeLeading?是表示文字開(kāi)始的方向。在英文、中文這種從左往右閱讀的文字中它表示左邊,在像阿拉伯語(yǔ)、希伯來(lái)語(yǔ)這種從右往左閱讀的文字中它表示右邊。通常情況下,除非你明確要限制在左邊,否則你都應(yīng)該使用?NSLayoutAttributeLeading?表示左邊。相對(duì)的,表示右邊也類(lèi)似這樣。這對(duì)于我們的本地化工作有很大的幫助。

然后在設(shè)置?viewWidth?和?viewHeight?這兩個(gè)約束時(shí),relatedBy?參數(shù)使用的是?NSLayoutRelationGreaterThanOrEqual?而不是?NSLayoutRelationEqual。

因?yàn)?Auto Layout 是相對(duì)布局,所以通常你不應(yīng)該直接設(shè)置寬度和高度這種固定不變的值,除非你很確定視圖的寬度或高度需要保持不變。

如果一定要設(shè)置高度或?qū)挾龋貏e是寬度,在沒(méi)有顯式地設(shè)置內(nèi)容壓縮優(yōu)先級(jí)(Content Hugging Priority,后面會(huì)講到)和內(nèi)容抗壓縮優(yōu)先級(jí)(Content Compression Resistance Priority,后面會(huì)講到)的情況下,盡量不要使用?NSLayoutRelationEqual?這種絕對(duì)的關(guān)系,這會(huì)帶來(lái)許多潛在的問(wèn)題:

根據(jù)內(nèi)容決定寬度的視圖,當(dāng)內(nèi)容改變時(shí),外觀尺寸無(wú)法做出正確的改變

在本地化時(shí)過(guò)長(zhǎng)的文字無(wú)法顯示,造成文字切斷,或文字過(guò)短,寬度顯得過(guò)寬,影響美觀

添加了多余的約束時(shí),約束之間沖突,無(wú)法顯示正確的布局

所帶來(lái)的問(wèn)題不僅僅局限與這幾條,這里只是簡(jiǎn)單列出幾條。

如何正確的設(shè)置寬度或高度?給出一些 Tips:

如果寬度和高度布局可以改變,使用固有內(nèi)容尺寸(Intrinsic Content Size,后面會(huì)講到)設(shè)置約束(即 size to fit size)。

如果寬度和高度布局不可以改變,改變約束的關(guān)系為?≥。

調(diào)整壓縮優(yōu)先級(jí)和內(nèi)容抗壓縮優(yōu)先級(jí)

最后我把所有約束都添加到了?view?的父視圖?self.view?上。view?的約束為什么不添加到自身而添加到別的視圖上去呢?這是由于約束是根據(jù)視圖層級(jí)自下而上更新的,也就是從子視圖到父視圖。所以 Auto Layout 添加約束有一套自己的規(guī)則,如下:

查看圖片

兩個(gè)不同層級(jí)間視圖的約束,添加到它們最近的共同的父視圖上

查看圖片

兩個(gè)有層級(jí)關(guān)系的視圖的約束,添加到層次較高的視圖上(父視圖)上

查看圖片

因?yàn)槲覀儗儆谧詈笠环N情況,所以子視圖?view?的約束添加到了父視圖?self.view?上。

接下來(lái)是第二個(gè)方法

+ (NSArray *)constraintsWithVisualFormat:(NSString *)format

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? options:(NSLayoutFormatOptions)opts

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? metrics:(NSDictionary *)metrics

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? views:(NSDictionary *)views;

這個(gè)方法是我們實(shí)際編程中最常用的方法。它會(huì)根據(jù)我們指定的參數(shù)返回一組約束。 這個(gè)方法很重要,所以我會(huì)詳細(xì)解釋每個(gè)參數(shù)的用途。

format

這個(gè)參數(shù)存放的是布局邏輯,布局邏輯是使用?可視化格式語(yǔ)言 (VFL)?編寫(xiě)的。實(shí)際編程中我們也是使用?VFL?編寫(xiě)布局邏輯,因?yàn)榈谝粋€(gè)方法明顯參數(shù)過(guò)多,一個(gè)簡(jiǎn)單的布局要寫(xiě)很多代碼。

上一個(gè)布局使用?VFL?來(lái)重構(gòu)的話,代碼如下:

....

[view setTranslatesAutoresizingMaskIntoConstraints:NO];

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];

嘩,代碼量減少了很多。首先我們使用?NSDictionaryOfVariableBindings(...)?宏創(chuàng)建了一個(gè)字典?views,這個(gè)宏會(huì)自動(dòng)把傳入的對(duì)象的鍵路徑作為字典的鍵,把對(duì)象作為字典的值。所以?views字典的內(nèi)容就像這樣:

{@"self.view": self.view, @"view", view}

VFL?就是這兩句:

H:|-50-[view(>=150)]

V:|-100-[view(>=150)]

第一句是在水平方向布局,表示?view?左邊距離父視圖左邊 50 點(diǎn),寬度至少 150 點(diǎn)。(水平方向是寬度)

第二句是在垂直方向上布局,表示?view?頂部距離父視圖頂部 100 點(diǎn),寬度至少 150 點(diǎn)。(垂直方向是高度)

分解說(shuō)明如下:

H?/?V?表示布局方向。H?表示水平方向(Horizontal),V?表示垂直方向(Vertical),方向后要緊跟一個(gè)?:,不能有空格。

|?表示父視圖。通常出現(xiàn)在語(yǔ)句的首尾。

-?有兩個(gè)用途,單獨(dú)一個(gè)表示標(biāo)準(zhǔn)距離。這個(gè)值通常是 8 ;兩個(gè)中間夾著數(shù)值,表示使用中間的數(shù)值代替標(biāo)準(zhǔn)距離,如第一句的?-50-,就是使用 50 來(lái)代替標(biāo)準(zhǔn)距離。

[]?表示對(duì)象,括號(hào)中間需要填上對(duì)象名,對(duì)象名必須是我們傳入的?views?字典中的鍵。對(duì)象名后可以跟小括號(hào)?(),小括號(hào)中是對(duì)此對(duì)象的尺寸和優(yōu)先級(jí)約束。水平布局中尺寸是寬度,垂直布局中尺寸是高度。如第一句中的?(>=150)?就是對(duì)?view?尺寸的約束,因?yàn)槭撬椒较虿季郑运硎緦挾却笥诨虻扔?150 點(diǎn)。而 150 前面的?>=?就是我們上面第一個(gè)方法中提到的關(guān)系參數(shù)。至于為什么這里使用?>=,上面已經(jīng)解釋過(guò)了。括號(hào)中可以包含多條約束,如果我們想再加一條約束,保證?view?的寬度最大不超過(guò) 200 點(diǎn),我們可以這樣寫(xiě):H:|-50-[view(>=150,<=200)]< code="">。還可以添加優(yōu)先級(jí)約束,這個(gè)我們后面再講。

VFL?語(yǔ)法有幾點(diǎn)需要注意:

布局語(yǔ)句中不能包含空格

和關(guān)系一樣,沒(méi)有?>、<?這種約束

然后下面是一些例子,增加你對(duì)?VFL?語(yǔ)法的理解。

例一:

我們?cè)?view?右側(cè)添加另一個(gè)視圖?view2,效果如圖:

查看圖片

代碼如下:

UIView *view = [UIView new];

[view setBackgroundColor:[UIColor redColor]];

[self.view addSubview:view];

UIView *view2 = [UIView new];

[view2 setBackgroundColor:[UIColor blueColor]];

[self.view addSubview:view2];

[view setTranslatesAutoresizingMaskIntoConstraints:NO];

[view2 setTranslatesAutoresizingMaskIntoConstraints:NO];

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view]-[view2(>=50)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view2(>=50)]" options:0 metrics:nil views:views]];

我們講講最后的兩條新的?VFL?語(yǔ)句:

H:[view]-[view2(>=50)]

從開(kāi)始的?H:?我們可以判斷出這是水平方向的布局,換句話說(shuō)就是設(shè)置視圖的?x?和?width。接著的?[view],說(shuō)明后面的所有視圖都是在?view?的右側(cè);接著是?-,說(shuō)明后一個(gè)視圖和?view?之間有一個(gè)標(biāo)準(zhǔn)距離的間距;也就是說(shuō) x 等于?view?的右側(cè)再加上標(biāo)準(zhǔn)距離,即?CGRectGetMaxX(view) + 標(biāo)準(zhǔn)距離。最后是?[view2(>=50)],這里可以看出后一個(gè)視圖是?view2,并且它的寬度不小于 50 點(diǎn)。整一句翻譯成白話就是說(shuō):在水平方向上,view2?在?view?右側(cè)的標(biāo)準(zhǔn)距離位置處,并且它的寬度不小于 50 點(diǎn)。

V:|-100-[view2(>=50)]

從開(kāi)始的?V:?我們可以判斷出這是垂直方向的布局,換句話說(shuō)就是設(shè)置視圖的?y?和?height。接著的?|?說(shuō)明是后一個(gè)視圖是相對(duì)于父視圖進(jìn)行布局;接著是?-100-,說(shuō)明垂直方向和父視圖(頂部)相距 100 點(diǎn),也就是說(shuō) y 等于 100 點(diǎn)。最后是?[view2(>=50)],這和上一句相同,只是因?yàn)槭谴怪狈较?,所?50 是設(shè)置高度而不是寬度。整一句翻譯成白話就是說(shuō):在垂直方向上,view2在相對(duì)于父視圖(頂部) 100 點(diǎn)的位置處,并且它的高度不小于 50 點(diǎn)。

實(shí)際上我們的代碼還可以簡(jiǎn)化:

......

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view2(>=50)]" options:0 metrics:nil views:views]];

因?yàn)閮蓚€(gè)視圖水平方向上是并排(從左到右)的,所以我們可以將水平方向布局的代碼合并到一起。而垂直方向我們并非并排的,所以垂直方向的布局代碼我們不能合并。這里所講的并排的意思是后一個(gè)在前一個(gè)的后面,水平方向上明顯是這樣,但垂直方向上兩個(gè)視圖的?y?是相同的,所以無(wú)法合并在一起布局。

例二:我們繼續(xù)添加一個(gè)視圖?view3?填補(bǔ)?view?右下方的空缺,效果如圖:

查看圖片

代碼如下:

UIView *view = [UIView new];

[view setBackgroundColor:[UIColor redColor]];

[self.view addSubview:view];

UIView *view2 = [UIView new];

[view2 setBackgroundColor:[UIColor blueColor]];

[self.view addSubview:view2];

UIView *view3 = [UIView new];

[view3 setBackgroundColor:[UIColor orangeColor]];

[self.view addSubview:view3];

[view setTranslatesAutoresizingMaskIntoConstraints:NO];

[view2 setTranslatesAutoresizingMaskIntoConstraints:NO];

[view3 setTranslatesAutoresizingMaskIntoConstraints:NO];

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2, view3);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-(50)-[view(>=150)]-[view2(>=50)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(100)-[view(>=150)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:[view]-[view3(>=50)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-(100)-[view2(>=50)][view3(>=100)]" options:0 metrics:nil views:views]];

你可能注意到我把每個(gè)間距都使用小括號(hào)闊了起來(lái),這是可選的,你完全可以直接寫(xiě)間距,這么寫(xiě)只是告訴你還有這種語(yǔ)法。實(shí)際上沒(méi)什么必要這么寫(xiě),因?yàn)?VFL?語(yǔ)法并不支持運(yùn)算,例如把?(50)切分為?(10+40)?或?(5*10)?都是不合法的。

最后兩行是?view3?的布局代碼,簡(jiǎn)單解釋一下:

H:[view]-[view3(>=50)]

水平方向布局,view3?在?view?右側(cè)標(biāo)準(zhǔn)距離處,并且寬度不小于 50 點(diǎn)。

V:|-(100)-[view2(>=50)][view3(>=100)]

垂直方向布局,view2?距離父視圖(頂部)100 點(diǎn),并且高度不小于 50 點(diǎn);view3?緊挨著?view2?底部(沒(méi)有?-),并且高度不小于 100 點(diǎn)。

options

這個(gè)參數(shù)的值是位掩碼,使用頻率并不高,但非常有用。它可以操作在?VFL?語(yǔ)句中的所有對(duì)象的某一個(gè)屬性或方向。例如上面的例一,水平方向有兩個(gè)視圖,它們的垂直方向到頂部的距離相同,或者說(shuō)頂部對(duì)齊,我們就可以給這個(gè)參數(shù)傳入?NSLayoutFormatAlignAllTop?讓它們頂部對(duì)齊,這樣以來(lái)只需要指定兩個(gè)視圖的其中一個(gè)的垂直方向到頂部的距離就可以了。代碼:

......

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" options:NSLayoutFormatAlignAllTop metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:[view2(>=50)]" options:0 metrics:nil views:views]];

它的默認(rèn)值是?NSLayoutFormatDirectionLeadingToTrailing,根據(jù)當(dāng)前用戶的語(yǔ)言環(huán)境進(jìn)行設(shè)置,比如英文中就是從左到右,希伯來(lái)語(yǔ)中就是從右到左。

這個(gè)值符合我們常用的選項(xiàng)。NSLayoutFormatDirectionLeadingToTrailing?的值是?0 << 16,所以我們可以直接傳入?0?使用此值。

因?yàn)槭俏谎诖a,所以我們可以使用?|?進(jìn)行多選,例如例一,我們希望在現(xiàn)有約束的基礎(chǔ)上讓兩個(gè)視圖的高度相等,那代碼可以這樣寫(xiě):

......

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view, view2);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-50-[view(>=150)]-[view2(>=50)]" options:NSLayoutFormatAlignAllTop | NSLayoutFormatAlignAllBottom metrics:nil views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-100-[view(>=150)]" options:0 metrics:nil views:views]];

指定兩個(gè)視圖的頂部和底部約束相同,然后只設(shè)置其中一個(gè)視圖的相關(guān)約束即可。

靈活使用此參數(shù)可以節(jié)省不少時(shí)間,但這個(gè)參數(shù)內(nèi)容太多,如果你有興趣了解,可以看看我的另一篇博文:《Auto Layout 中的排列選項(xiàng)》

metrics

這是一個(gè)字典,字典的鍵必須是出現(xiàn)在?VFL?語(yǔ)句中的字符串,值必須是?NSNumber?類(lèi)型,作用是將在?VFL?語(yǔ)句中出現(xiàn)的鍵替換為相應(yīng)的值。例如本文中的第一個(gè)布局的例子,使用了這個(gè)參數(shù)后代碼就變成了這樣:

UIView *view = [UIView new];

[view setBackgroundColor:[UIColor redColor]];

[self.view addSubview:view];

[view setTranslatesAutoresizingMaskIntoConstraints:NO];

CGRect viewFrame = CGRectMake(50.f, 100.f, 150.f, 150.f);

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);

NSDictionary *metrics = @{@"left": @(CGRectGetMinX(viewFrame)),

? ? ? ? ? ? ? ? ? ? ? ? ? @"top": @(CGRectGetMinY(viewFrame)),

? ? ? ? ? ? ? ? ? ? ? ? ? @"width": @(CGRectGetWidth(viewFrame)),

? ? ? ? ? ? ? ? ? ? ? ? ? @"height": @(CGRectGetHeight(viewFrame))};

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view(>=height)]" options:0 metrics:metrics views:views]];

聰明的你看了這段代碼后肯定已經(jīng)明白這個(gè)參數(shù)的用途了,雖然使用頻率不高,但依然很有用,特別是要?jiǎng)討B(tài)計(jì)算約束值的時(shí)候非常有用。

實(shí)際上這個(gè)參數(shù)也可以使用?NSDictionaryOfVariableBindings(...)?宏來(lái)快速創(chuàng)建,代碼如下:

......

[view setTranslatesAutoresizingMaskIntoConstraints:NO];

NSNumber *left = @50.f;

NSNumber *top = @100.f;

NSNumber *width = @150.f;

NSNumber *height = @150.f;

NSDictionary *views = NSDictionaryOfVariableBindings(self.view, view);

NSDictionary *metrics = NSDictionaryOfVariableBindings(left, top, width, height);

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"H:|-left-[view(>=width)]" options:0 metrics:metrics views:views]];

[self.view addConstraints:[NSLayoutConstraint constraintsWithVisualFormat:@"V:|-top-[view(>=height)]" options:0 metrics:metrics views:views]];

views

又是一個(gè)字典,包含了?VFL?語(yǔ)句中用到的視圖。字典的鍵必須是出現(xiàn)在?VFL?語(yǔ)句中的視圖名稱,值必須視圖的實(shí)例。這個(gè)字典我們?cè)谥v?format?時(shí)已經(jīng)講過(guò),也用過(guò)很多次,相信你早已明白是怎么回事了。

講了這么多,可能你也發(fā)現(xiàn)了,只要學(xué)會(huì)了?VFL?語(yǔ)法,就可以方便地使用 Auto Layout 了,其他的知識(shí)都屬于輔助選項(xiàng),會(huì)的話,布局更輕松一些,不會(huì)也沒(méi)關(guān)系,實(shí)踐多了,自然就會(huì)了。

優(yōu)先級(jí) (Priority level)

約束條件有優(yōu)先級(jí),高優(yōu)先級(jí)約束會(huì)比低優(yōu)先級(jí)約束優(yōu)先得到滿足,系統(tǒng)內(nèi)置了 4 個(gè)優(yōu)先級(jí):

enum {

? ? UILayoutPriorityRequired = 1000,

? ? UILayoutPriorityDefaultHigh = 750,

? ? UILayoutPriorityDefaultLow = 250,

? ? UILayoutPriorityFittingSizeLevel = 50,

};

typedef float UILayoutPriority;

UILayoutPriorityRequired 這是默認(rèn)值,這意味著這個(gè)約束條件必須被精確地滿足。

UILayoutPriorityDefaultHigh

UILayoutPriorityDefaultLow

UILayoutPriorityFittingSizeLevel 這是內(nèi)置的最低優(yōu)先級(jí)。

相信你已經(jīng)看到每個(gè)等級(jí)的數(shù)值了,優(yōu)先級(jí)的取值在?0 ~ 1000?之間,取值越大,優(yōu)先級(jí)越高,越會(huì)被優(yōu)先滿足。

每個(gè)約束的默認(rèn)優(yōu)先級(jí)就是?UILayoutPriorityRequired,這意味著你給出的所有約束都必須得到滿足,一旦約束間發(fā)生沖突,你的應(yīng)用就會(huì) Crash。這也是在使用 Auto Layout 時(shí)經(jīng)常會(huì)犯的錯(cuò)誤:沒(méi)有給約束設(shè)置適當(dāng)?shù)膬?yōu)先級(jí)。

舉個(gè)例子說(shuō)明優(yōu)先級(jí)設(shè)置不當(dāng)?shù)那闆r,給我們首次使用 Auto Layout 時(shí)的例子再添加一個(gè)約束:

......

// `view` 的高度是 60 點(diǎn).

NSLayoutConstraint *viewHeight = [NSLayoutConstraint constraintWithItem:view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeHeight

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? relatedBy:NSLayoutRelationGreaterThanOrEqual

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:nil

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeNotAnAttribute

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constant:CGRectGetHeight(viewFrame)];

// `view` 緊貼著 `self.view` 的左邊.

NSLayoutConstraint *marginLeft = [NSLayoutConstraint constraintWithItem:view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeLeading

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? relatedBy:NSLayoutRelationEqual

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? toItem:self.view

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? attribute:NSLayoutAttributeLeading

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? multiplier:1

? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? constant:0];

// 把約束添加到父視圖上.

[self.view addConstraints:@[viewLeft, viewTop, viewWidth, viewHeight, marginLeft]];

運(yùn)行看看效果,程序 Crash 了!控制臺(tái) Log 中有這么一段信息:

"",""

可以看到第一條是?viewLeft?這個(gè)約束,它限制了?view?的左邊距離父視圖的左邊?50?點(diǎn)。

第二條是新添加的?marginLeft?這個(gè)約束,它限制了?view?的左邊距離父視圖的左邊?0?點(diǎn),也就是緊貼著父視圖的左邊。

很明顯這兩個(gè)約束是沖突的,當(dāng)系統(tǒng)嘗試根據(jù)優(yōu)先級(jí)進(jìn)行布局時(shí),發(fā)現(xiàn)它們的優(yōu)先級(jí)也相同,無(wú)法滿足兩個(gè)沖突的約束,所以拋出了異常。

我們只需要給兩個(gè)約束設(shè)置不同的優(yōu)先級(jí)即可解決。添加下面一行代碼:

[viewLeft setPriority:UILayoutPriorityDefaultHigh];

因?yàn)槟J(rèn)所有約束的優(yōu)先級(jí)都是?UILayoutPriorityRequired,所以我們只需要將?viewLeft?的優(yōu)先級(jí)設(shè)置得比默認(rèn)的低即可。

效果:

查看圖片

需要注意的一點(diǎn)是,約束的優(yōu)先級(jí)必須在它添加到視圖上之前設(shè)置,如果約束已經(jīng)添加到視圖上后去嘗試改變它的優(yōu)先級(jí),將會(huì)得到一個(gè)異常。

提高效率

Auto Layout 雖然很好,但無(wú)論是直接使用?NSLayoutConstraint?還是使用?VFL?來(lái)編寫(xiě)布局的代碼都比較麻煩。

好消息是有大量的開(kāi)源庫(kù)幫助我們提高編寫(xiě)布局代碼的效率。比較流行的有:

Masonry

PureLayout(前?UIView-AutoLayout

FLKAutoLayout

KeepLayout

我最初使用?UIView-AutoLayout,但因?yàn)樗恢С?OSX,所以后來(lái)使用過(guò)一段時(shí)間的?Masonry,當(dāng)?UIView-AutoLayout?的原作者發(fā)布?PureLayout?后,我就轉(zhuǎn)向了?PureLayout?并使用至今。

在我看來(lái),Masonry?和?PureLayout?差別并不大,PureLayout 的語(yǔ)法更偏向Objective-C。

下面是一個(gè) Instagram 頁(yè)面截圖,我們使用?PureLayout?來(lái)實(shí)現(xiàn)這個(gè)布局。

查看圖片

我把它分為頭像、昵稱、時(shí)間標(biāo)識(shí)、時(shí)間、贊標(biāo)識(shí)、贊的數(shù)量、贊按鈕、評(píng)論按鈕、更多按鈕以及中間的圖片視圖。

聲明以下屬性:

@property (nonatomic, strong) UIImageView *avatarImageView;

@property (nonatomic, strong) UILabel? ? *nicknameLabel;

@property (nonatomic, strong) UIView? ? ? *timestampIndicator;

@property (nonatomic, strong) UILabel? ? *timestampLabel;

@property (nonatomic, strong) UIImageView *contentImageView;

@property (nonatomic, strong) UIView? ? ? *likeIndicator;

@property (nonatomic, strong) UILabel? ? *likesLabel;

@property (nonatomic, strong) UIButton? ? *likeButton;

@property (nonatomic, strong) UIButton? ? *commentButton;

@property (nonatomic, strong) UIButton? ? *moreButton;

布局代碼如下:

// 頭像左邊距離父視圖左邊 10 點(diǎn).

[self.avatarImageView autoPinEdgeToSuperviewEdge:ALEdgeLeading withInset:10.f];

// 頭像頂邊距離父視圖頂部 10 點(diǎn).

[self.avatarImageView autoPinEdgeToSuperviewEdge:ALEdgeTop withInset:10.f];

// 設(shè)置頭像尺寸

[self.avatarImageView autoSetDimensionsToSize:kAvatarSize];

// 昵稱的左邊位于頭像的右邊 10 點(diǎn)的地方.

[self.nicknameLabel autoPinEdge:ALEdgeLeading toEdge:ALEdgeTrailing ofView:self.avatarImageView withOffset:10.f];

// 根據(jù)昵稱的固有內(nèi)容尺寸設(shè)置它的尺寸

[self.nicknameLabel autoSetDimensionsToSize:[self.nicknameLabel intrinsicContentSize]];

// 時(shí)間標(biāo)識(shí)的右邊位于時(shí)間視圖左邊 -10 點(diǎn)的地方, 從右往左、從下往上布局時(shí)數(shù)值都是負(fù)的。

[self.timestampIndicator autoPinEdge:ALEdgeTrailing toEdge:ALEdgeLeading ofView:self.timestampLabel withOffset:-10.f];

// 根據(jù)時(shí)間標(biāo)識(shí)的固有內(nèi)容尺寸設(shè)置它的尺寸

[self.timestampIndicator autoSetDimensionsToSize:CGSizeMake(10.f, 10.f)];

// 時(shí)間視圖的右邊距離父視圖的右邊 10 點(diǎn).

[self.timestampLabel autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:10.f];

// 根據(jù)時(shí)間視圖的固有內(nèi)容尺寸設(shè)置它的尺寸

[self.timestampLabel autoSetDimensionsToSize:[self.timestampLabel intrinsicContentSize]];

// 頭像、昵稱、時(shí)間標(biāo)識(shí)、時(shí)間視圖水平對(duì)齊。(意思就是說(shuō)只需要設(shè)置其中一個(gè)的垂直約束(y)即可)

[@[self.avatarImageView, self.nicknameLabel, self.timestampIndicator, self.timestampLabel] autoAlignViewsToAxis:ALAxisHorizontal];

// 內(nèi)容圖片視圖頂部距離頭像的底部 10 點(diǎn).

[self.contentImageView autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.avatarImageView withOffset:10.f];

// 內(nèi)容圖片視圖左邊緊貼父視圖左邊

[self.contentImageView autoPinEdgeToSuperviewEdge:ALEdgeLeading];

// 內(nèi)容圖片視圖的寬度等于父視圖的寬度

[self.contentImageView autoMatchDimension:ALDimensionWidth toDimension:ALDimensionWidth ofView:self];

// 內(nèi)容圖片視圖的高度等于父視圖的寬度

[self.contentImageView autoMatchDimension:ALDimensionHeight toDimension:ALDimensionWidth ofView:self];

// 贊標(biāo)識(shí)與頭像左對(duì)齊

[self.likeIndicator autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:self.avatarImageView];

// 贊標(biāo)識(shí)的頂部距離內(nèi)容圖片視圖底部 10 點(diǎn).

[self.likeIndicator autoPinEdge:ALEdgeTop toEdge:ALEdgeBottom ofView:self.contentImageView withOffset:10.f];

// 設(shè)置贊標(biāo)識(shí)的尺寸

[self.likeIndicator autoSetDimensionsToSize:CGSizeMake(10.f, 10.f)];

// 贊數(shù)量視圖與贊標(biāo)識(shí)水平對(duì)齊

[self.likesLabel autoAlignAxis:ALAxisHorizontal toSameAxisOfView:self.likeIndicator];

// 贊數(shù)量視圖的左邊距離贊標(biāo)識(shí)的右邊 10 點(diǎn).

[self.likesLabel autoPinEdge:ALEdgeLeading toEdge:ALEdgeTrailing ofView:self.likeIndicator withOffset:10.f];

// 以下請(qǐng)自行腦補(bǔ)...

[self.likesLabel autoSetDimensionsToSize:[self.likesLabel intrinsicContentSize]];

NSArray *buttons = @[self.likeButton, self.commentButton, self.moreButton];

[buttons autoMatchViewsDimension:ALDimensionHeight];

[buttons autoAlignViewsToEdge:ALEdgeBottom];

[self.likeButton autoPinEdge:ALEdgeLeading toEdge:ALEdgeLeading ofView:self.avatarImageView];

[self.likeButton autoPinEdgeToSuperviewEdge:ALEdgeBottom withInset:10.f];

[self.likeButton autoSetDimensionsToSize:CGSizeMake(50.f, 25.f)];

[self.commentButton autoPinEdge:ALEdgeLeading toEdge:ALEdgeTrailing ofView:self.likeButton withOffset:5.f];

[self.commentButton autoSetDimension:ALDimensionWidth toSize:65.f];

[self.moreButton autoPinEdgeToSuperviewEdge:ALEdgeTrailing withInset:10.f];

[self.moreButton autoSetDimension:ALDimensionWidth toSize:40.f];

注:文章來(lái)源于網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系小編刪除。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容