(iOS)使用auto layout進行複雜布局時,UILabel的相關trick
給心急的同學先說說結論:(因為我也是一個心急的同學)
1. 對於UILabel,設定number of lines相當於設定了一個縱向的constraint;也即意味著,UILabel設定三個constraint就夠了
2. 對於UILabel,橫向設定一個<=的constraint,可以讓UILabel自行適配寬度
3. 文章結尾有測試題哦,喜歡挑戰的同學請往後看
一。本文:
先來一段情懷。
第一個項目是純手碼的,MRC,且幾乎沒有使用第三方庫。整天樂呵呵的在紙上計算布局的座標,各種大小都是根據比例Realtime Compute的。缺點顯而易見,布局的代碼太多,把邏輯部分的代碼都擠沒了;好處是,我感覺超可控,幾乎沒有意料外的bug,也不會怕蘋果更新版本影響到app的使用。(註:最新的版本使用storyboard進行介面切換,大多數布局還是手碼的)
第二個項目過於龐大,我使用了Storyboard+auto layout,ARC,數個4位元Star且維護活躍的第三方庫。設計師對UI的控制超精細,auto layout真正讓我從介面布局中解放出來了。不過從最開始機械的使用“橫向兩個constraint+縱向兩個constraint”,到現在主要使用“UITableView-FDTemplateLayoutCell”(https://github.com/forkingdog/UITableView-FDTemplateLayoutCell)進行tableviewcell的布局,還是有些值得討論和學習的地方。
二。討論對象:
以下均假設情境為使用UITableViewCell進行布局,UITableViewCell內有數個Label/Image/Button等複雜排版。一般而言,Image和Button的大小不變或者和Cell保持比例關係,比較容易處理;而Label則可能由於文字的長短,字型大小的變化,導致寬度和高度上的變化,帶來計算上的麻煩。
三。設定方法(以下兩個方法各有運用情境)
1:基本款
採用“橫向兩個constraint+縱向兩個constraint”的方法,精確到point的控制每個介面元素的位置和大小。如:
為Label設定了Top, Left, Righ和Height4個屬性,即可完全控制Label的大小。
但,若Label中需要顯示的內容可能佔用多行,則可以將number of lines設定為0(有時標題最多顯示兩排時,則可設定為2),然後將Height這個constraint牽出來作為IB Outlet,則可以在程式中隨時修改,如:
由於文字長短和cell的寬度均會影響Label的布局,從而決定cell的高度,因此heightForCellAtIndexPath:的傳回值需要通過計算得到。因此此cell會實現兩個函數:
+ (CGFloat)cellHeightWithData:(MDDataType*)data withCellWidth:(CGFloat)cellWidth;- (void)updateWithData:(MDDataType*)data;
前者class method由heightForCellAtIndexPath:調用,避免執行個體化cell。其中調用
[label.text boundingRectWithSize:CGSizeMake(cellWidth, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{NSFontAttributeName:label.font} context:nil].size.height
來計算label的高度,然後合成整個cell的高度
後者instance method由cellForRowAtIndexPath:調用,執行個體化cell中的各個控制項參數,然後將上面代碼的計算結果賦值給constraint的IB Outlet.
優點:計算快捷效率高,控制精準。
缺點:IB中設定的gap,margin等數值需要和cellHeightWithData此函數中使用的hardcode數值保持一致,一個資料兩方維護,簡直不能忍。另:若Label的字型大小變化,則可能需要重新設定,不方便。
2. 自適應款
利用constraint中的ratio,multiply,>=,<=等設定,完成auto layout的自動布局。
這個設定是在前面的設定中,刪掉了Height這個constraint,這個時候,可以理解為number of lines充當了第四個constraint,從而可以讓auto layout進行布局。果不其然,auto layout提示說該布局和運行後不符(運行後被auto layout到正確的位置了)。這個warning一方面告訴我們說auto layout可以正確handle這個label,另一方面告訴我們應該用Update Frame這個方法將Label擺放正確。
另一種情況,某些label可能無法佔滿整個橫向地區,其後可能會有別的控制項,可以設定<=constraint,使其進行自動布局,
調適型配置,加上前面提到的“UITableView-FDTemplateLayoutCell”(https://github.com/forkingdog/UITableView-FDTemplateLayoutCell),所有關於布局之類的margin,gap等等,就只用在IB中搞定了。幾乎沒必要牽出constraint作為outlet來手動設定了。並且cell所需要實現的函數僅為:
- (void)updateWithData:(MDDataType*)data;
在heightForRowAtIndexPath:和
cellForRowAtIndexPath:中均調用此函數即可。【具體請參考UITableView-FDTemplateLayoutCell的文檔和原始碼】
優點:布局全部在IB中,代碼中幾乎可以不用管
缺點:由於heightForRowAtIndexPath中需要執行個體化cell,且進行布局計算,因此效率會稍低(當然,會有些最佳化方法)
四。使用情境(以下標題均描述uitableview中各個cell的布局難度)
A. 均一高度
hardcode吧,多好啊
(缺圖)
B. 易算高度
比如高度和寬度比例為16:9,使用class method進行計算吧,多好啊。
+ (CGFloat)cellHeightWithData:(MDDataType*)data withCellWidth:(CGFloat)cellWidth;
(缺圖)
C. 複雜布局+不需要考慮效率
使用UITableView-FDTemplateLayoutCell吧
(缺圖)
D. 複雜布局+效率優先
需要特殊情況特殊對待
(不缺圖)
五。習題
i) 某個列表由tableview實現,每個cell都有很多資訊,其中Title可長可短,短的顯示一行,長的最多顯示兩行。標題旁顯示Date,有如下四種情況:
情況1:標題佔一行,且可以在右側放置日期,則標題和日期放置在同一行
|------------------------------------------------------|
| Title Short Date |
| (Others) |
情況2:標題佔一行,但第一行容納不下日期,日期放在第二行
|------------------------------------------------------|
| Title Ratherrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr Long |
| Date |
| (Others) |
情況3:標題佔兩行,第二行可以在右側防止日期,則日期放置在第二行
|------------------------------------------------------|
| Title Verrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr |
| rrrrrrrrrrrrrrrry Long Date |
| (Others) |
情況4:標題佔兩行,但第二行容納不下日期,則日期放在第二行,標題Trunk Tail,顯示...
|------------------------------------------------------|
| Title Terrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrr |
| rrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrrriblely ... Date |
| (Others) |