1. 程式人生 > >iOS頁面的佈局方式

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的約束,如果再進行約束的設定,由於多次累加可能會造成衝突