iOS頁面的布局方式

iOS有三種基本的界面布局的方法,分別是手寫UI,xib和storyboard。手寫UI是最早進行UI界面布局的方法,優點是靈活自由,缺點是需要寫大段的代碼進行布局。xib也是比較早出現的UI布局的方式,優點是不需要手寫代碼,但是每個界面對應一個xib,管理起來復雜。而storyboard則是在iOS5以后出現的,是蘋果官方主推的一個代替xib的策略,不僅能將xib匯總統一管理,還可以描述各種場景之間的過渡,缺點是多人協作開發時容易產生沖突。

下面主要介紹的是手寫頁面布局。

一、AutoresizingMasks

可以使用 AutoresizingMasks 進行頁面布局,在 UIView 中有一個autoresizingMask的屬性,它對應的是一個枚舉的值,屬性的意思就是自動調整子控件與父控件中間的位置,寬高。默認值是UIViewAutoresizingNone,控件不會隨父視圖的改變而改變。

typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
     UIViewAutoresizingNone = 0,
     UIViewAutoresizingFlexibleLeftMargin = 1 << 0, // 自動調整view與父視圖左邊距,以保證右邊距不變
     UIViewAutoresizingFlexibleWidth = 1 << 1, // 自動調整view的寬度,保證左邊距和右邊距不變
     UIViewAutoresizingFlexibleRightMargin = 1 << 2, // 自動調整view與父視圖右邊距,以保證左邊距不變
     UIViewAutoresizingFlexibleTopMargin = 1 << 3, // 自動調整view與父視圖上邊距,以保證下邊距不變
     UIViewAutoresizingFlexibleHeight = 1 << 4, // 自動調整view的高度,以保證上邊距和下邊距不變
     UIViewAutoresizingFlexibleBottomMargin = 1 << 5 // 自動調整view與父視圖的下邊距,以保證上邊距不變
}

AutoresizingMasks是對未來變化的一種預期,系統會生成frame的布局,當遇到需要使用到多個值的場景時,支持使用|操作符。
例如,需要設置播放器浮層隨播放器大小變化:

UIView *overlay = [[UIView alloc] init];
overlay.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[self addSubview:overlay];

Autoresizing需要注意的是,storyboard中設置的約束和手寫代碼中設置的約束是相反的。storyboard 圖形頁面里點的右邊的線和下邊的線的意思是“固定”。

二、Frame

frame指的是當前視圖在其父視圖中的位置和大小。
在初始化 view 的時候,可以設置 view 的frame

UIView *view = [[UIView alloc] initWithFrame:CGRectMake(10, 20, 30, 40)]; 

初始化一個距離父視圖左邊距10,上邊距20,寬30,高40的視圖。也可以修改聲明View的位置以及大小。

view.frame = CGRectMake(20, 10, 40, 30);

設置和修改視圖的 frame 可以完成對界面的布局。

2.1 bounds

提到 frame 不得不提 bounds,bounds指的是前視圖在其自身坐標系統中的位置和大小。可以看到兩者的區別在于坐標系不同。

2.2 layoutSubviews

需要重新布局視圖可以使用 layoutSubviews

1)可以在view里重寫layoutSubviews

2)可以在view controller里使用

viewWillLayoutSubviews 在autoresizingMasks前調用

viewDidLayoutSubviews 在autoresizingMasks后調用,肯定會覆蓋autoresizingMasks的結果

layoutSubviews可能會在不需要調用的時候調用,如果layoutSubviews的比較復雜,可能會卡頓

三、自動布局AutoLayout

前面講到的 frame 主要用于視圖的絕對位置,但是 iOS 設備有多個尺寸,如何對不同尺寸進行適應,蘋果的解決方案是使用 AutoLayout。

如果是從代碼層面開始使用 Autolayout,需要對使用的 View 的translatesAutoresizingMaskIntoConstraints 的屬性設置為NO。即可開始通過代碼添加Constraint,否則View還是會按照以往的autoresizingMask進行計算。而在 Interface Builder 中勾選了Use Auto layout,translatesAutoresizingMaskIntoConstraints 屬性都會被默認設置NO。

3.1 約束

自動布局里最重要的組成部分就是約束。分別可以設置視圖相對于另一個視圖的 leading、trailing、top、bottom、CenterX、CenterY 等關系。根據這些約束來確定視圖的相對位置。

視圖的約束之間的關系為線型關系。例如,視圖Y 相對于 視圖X 的位置可以表示為一個線性變換,即

Y = kX + b

即 Y 是 X 某個方向坐標或大小的 k 倍并偏移 b。k 和 b 的大小可以是0。如果 k = 1, b = 0, 則表示 Y 和 X 分別表示視圖的寬,則等式表示 Y 和 X 的寬度相等。

AutoLayout 的核心是:Every view requires at least two constraints along each axis to set position and size. 即在每個坐標軸上至少需要2個約束來確定視圖位置。

3.2 Ambiguous Layout

在開發過程中,你可以通過調用hasAmbiguousLayout 來測試你的view約束是否足夠的。函數會返回布爾值。如果有一個不同的frame就會返回yes,如果view的約束完全指定了就會返回no。
一個設定了完全約束的view的子view也可能存在ambiguous layout,需要為每一個view單獨測試layout是否存在ambiguous layout。

3.3 Intrinsic Content Size

使用autolayout時,view的content扮演著非常重要的角色。每個view的intrinsicContentSize描述了不會剪切的顯示完整view content的最小空間。例如一個image view,content size根據image顯示的size設置。一個大的image需要一個大的固有的content size。image的大小提供給了view。
對于button,固有的content size根據他的title而有不同。隨著title增長或者縮短,button的固有的content size也會調節來做適應,可以根據你自定義的font size和title text而有變化。

3.4 Compression Resistance and Content Hugging

3.4.1 compression resistance

壓縮阻力表示一個視圖的抗壓縮性。一個有高compression resistance的視圖會防止被壓縮。也不會允許content被裁剪,而會嘗試保存他的最小固有content size。
autolayout經常遇到兩個沖突的請求。當只有一個請求會成功時,他就會滿足高優先級的那個。可以分別設置水平和垂直方向的Compression Resistance。value從1(最低)到1,000(請求的優先級)不等。默認的是750。

[button setContentCompressionResistancePriority:500 forAxis:UILayoutConstraintAxisHorizontal]; 

3.4.2 content hugging

抗拉屬性表示view防止被拉伸的屬性,和壓縮阻力類似。默認值為250。

[button setContentHuggingPriority:501 forAxis:UILayoutConstraintAxisHorizontal];

3.5 VFL

Visual Format Language,即“可視化格式語言”。直接手寫約束很復雜,使用VFL相對簡單很多,但比較難進行調試。

  [self.view addConstraints: [NSLayoutConstraint            
constraintsWithVisualFormat:@"V:[view1]-8-[view2]"             
                    options:NSLayoutFormatAlignAllLeading
                    metrics:nil
                      views:NSDictionaryOfVariableBindings(view1, view2)]];

3.6 Masonry

VFL的寫法也相當復雜,可以使用第三方框架 Masonry,Masonry 是一個輕量級的布局框架,Masonry 源碼:https://github.com/Masonry/Masonry

例如,設置view1相對父View的每個邊距離為padding:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.edges.equalTo(superview).with.insets(padding);
}];

需要注意的是,在結構一樣的情況下用mas_updateConstraints,會更新當前的約束,但是如果要覆蓋緣由約束重新添加,則需要使用方法用mas_remakeConstraints

constraint加到兩個view的公共父view上,因此有一個奇怪的現象是一個view不持有自己的約束,而被其他view持有。在 Masonry 中,實際添加constraint的不一定是約束的持有者

四、更新布局方法

設置好約束以后,布局是如何更新的呢?

Constraints

- (void)updateConstraintsIfNeeded    // 立即重新計算約束,如果在這之前addConstraints,就可以更新約束
- (void)setNeedsUpdateConstraints   // 立即返回,標記說需要改變約束值,在當前update cycle結束后更新之前所有標記過要改變的約束,調用updateConstraints方法

Layout

- (void)layoutIfNeeded     // 立即更新布局,重新計算約束,如果在這之前addConstraints就會立即反應在頁面上
- (void)setNeedsLayout    // 同Constraints,不過是更新布局
- (void)layoutSubviews    // 布局當前頁面的子頁面

Draw

- (void)setNeedsDisplay   // 同Constraints,不過是重新渲染

4.1 Constraints,Layout,Draw調用順序

一個頁面更新的順序一般為
調用約束計算出frame(Constraints)→ 根據計算出的frame重新布局(Layout) → 根據重新布局的結果進行圖像渲染(Draw)

layout的改變會導致重新計算Constraints
layoutIfNeeded會調用updateConstraintsIfNeeded

Constraints的計算順序是低到上 (從subview到superview)
layout的更新順序是從頂到下(從superview到subview)

4.2 frame和約束區別

1、frame是不可以累加的,只能被替換掉,但是constraint可以累加

2、frame不可以跨級添加,但是contraint可以跨層級

3、contraint可以設定priority

另外,需要注意的是,在autolayout下使用frame,會把frame轉化成autolayout的約束,如果再進行約束的設置,由于多次累加可能會造成沖突

github博客:https://wf96390.github.io/blog/2016/03/16/autolayout/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,622評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,716評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,746評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,991評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,706評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,036評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,029評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,203評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,725評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,451評論 3 361
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,677評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,161評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,857評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,266評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,606評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,407評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,643評論 2 380

推薦閱讀更多精彩內容