iOS適配之旅——Autolayout時代
開篇
其實對于 Autolayout 的資料非常非常的多,博主也是略自皮毛,也上不了大雅之堂。這里只是給大家稍微整理一下,可定也不是很全面,主要是針對 AutoLayout 給這個系列進行稍微的講解一下。其實呢,對于很多人來說 Autolayout 都懂的非常多,我呢,其實也不是很想寫,我也是被人架著刀放在脖子上去寫的。既然大家不想聽,那這篇就到此為止吧。(特么的,你是來騙我們進來的么,要么將要么退票錢:rage:)好吧,既然大家都想稍微聽聽,那就聽老娘,咳咳咳,不好意思,聽本少爺慢慢說來。
引言
其實相對于 Autoresizing 來說, Autolayout 是另一個質的飛躍。他能適配的情況更多。而對于 Autolayout 來說他是一個非常強大的工具,即使對于之后的 Size Callses 來說其中其中用的還是 Autolayout ,那么久有人會問了, AutoLayout 到底是什么呢,或者說是他是怎么實現的呢。其實 Autolayout 都是基于一種叫做約束( NSLayoutConstraint )的東西。其實很多iOS程序員應該都懂。至于是關于 NSLayoutConstraints 入門我就不說了。這里我就開始說一些我想說的東西吧。 好了接下來我主要分三個方面來給大家講述如何添加約束。當然,下面的知識最好是建立在對 NSLayoutConstraint 有一定了解的情況下來看,畢竟我是一個非常傲嬌的啦。
代碼添加
首先其實對于 Autolayout 來說添加的方式有兩種,分別是xib和代碼添加。 而對于xib上的添加小伙伴應該會比較熟悉一點,那么我們就從代碼添加開始吧。眾所周知,所謂的 Autolayout 時間里在一些列的 NSLayoutConstraint 的實例上進行確定每個控件的大小以及位置的。
創建
好了下面先說說 NSLayoutConstraint 在代碼上是如何創建的吧。
iOS 6
上代碼,關門放OC:
// 水平居中 [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterX relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterX multiplier:1.0 constant:0]]; // 垂直居中 [self.view addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeCenterY relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeCenterY multiplier:1.0 constant:0]]; // 寬度為20 [centerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeWidth multiplier:1.0 constant:20]]; // 高度為20 [centerView addConstraint:[NSLayoutConstraint constraintWithItem:centerView attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:centerView attribute:NSLayoutAttributeWidth multiplier:1.0 constant:0]];
Swift也就類似了,這里就寫一個吧。Swift上:
NSLayoutConstraint(item: firstItem, attribute: .Trailing, relatedBy: .Equal, toItem: secondItem, attribute: .Leading, multiplier: 1.0, constant: 0)
如果想要用代碼進行添加約束,記得將對應的 translatesAutoresizingMaskIntoConstraints 屬性設置成NO或者false.
可以看得出其實一個 NSLayoutConstraint 是由兩個控件以及對應的屬性和關系得到的。(當然兩個控件也有可能是指同一個控件,如要設置一個控件的高等于寬的情況等)那么,這里就采用網絡上經常看到的等式進行說明一下,其實這里就是 firstItem.attribute relatedBy secondItem.attribute * multiplier + constant 這樣的等式。當然其實上面方法還有一個參數是沒有初始化的,就是 priority ,這個用來規定是用來規定優先級的,數值是從1-1000,數值越大優先級越高。優先級高的先滿足,在滿足優先級高的情況下再去滿足優先級低的。通過上述方法創建的情況下 priority 是默認值是1000的。 當然對于firstItem和secondItem還是有一定條件約束的,也就是這兩個視圖有同一個“祖先”視圖,比如下面這種情況是允許的:
- UIView:A
- UIView:B
- UILabel
- UIImageView
- UIView:C
- UIButton </ul> </li> </ul>
那么在這種情況下 UILabel和UIImageView 以及 UIViewB和UIImageView 之間是可以建立約束的,但是 UIButton和UIView:A中的其他控件 之間是不可以建立約束的。這是為什么呢,這個就有點像在加家譜中。如A家族中的所有成員都有同一個祖先,那么A家族中的所有成員之間多少都扯的上一些親屬關系,可是你突然來一個B家族,他們之間沒有血緣關系,平時也沒有關聯,那么總不能說B家族中的成員跟A家族中的成員有親屬關系吧。(那B家族中的成員嫁給A家族呢?)咳咳咳,保安保安,這里有人搗亂,趕緊拉出去。
好了,回歸正傳,這里其實還有一個很多剛開始用代碼寫約束的人有的一個疑問,就是經常我們需要給某一個 UIView 添加約束,那么它的約束應該添加到firstItem還是secondItem呢?其實單純的說添加給他們兩個哪一個控件都是不夠準確的,當然在一些情況下也有可能是兩個Item中的一個。其實應該是加在離他們最近的"祖先"上。當然這個"祖先"也有可能就是他們自己。比如 UIILabel和UIView:B 的約束就加在 UIView:B 上。而 UIImageView和UILabel 加在 UIView:A 上。
除了上面這種創建方式以外,還有在采用 VFL 來進行創建一系列的約束,比如他可以為一個視圖創建好一個方向上的所有約束之類的。但是關于 VFL 資料還是相對比較少,而且官方文檔講解的也比較少。博主也不愛用這種方式,如果有興趣的小伙伴可以Google中搜索百度然后再搜索VFL來查找資料。:smiling_imp: 不過這里還是提供一下他的創建方法:
// OC代碼,在NSLayoutConstraint中
- (NSArray<__kindof NSLayoutConstraint > )constraintsWithVisualFormat:(NSString )format options:(NSLayoutFormatOptions)opts metrics:(nullable NSDictionary<NSString ,id> )metrics views:(NSDictionary<NSString , id> *)views;
</pre>
// Swift代碼 public class func constraintsWithVisualFormat(format: String, options opts: NSLayoutFormatOptions, metrics: [String : AnyObject]?, views: [String : AnyObject]) -> [NSLayoutConstraint]
iOS 7
關于Auto Layout在iOS7上的變動相對比較少,其中最大的變動應該是關于創建約束的方式進行巨大變化(預期說是iOS 7的變化,不如說是Xcode的變化),在Xcode 5中是支持了通過Nib來進行添加約束了,詳情看最后一小節。
iOS 8
在iOS 8中添加了許多 NSLayoutAttribute 的屬性,讓在添加約束上更加方便以及可添加的約束更加細致到具體細節。這里需要特別提的是關于 Margin 的屬性,具體是什么情況可以看看下文的一些提到的一些地方。
iOS 9
是不是覺得上面的創建方式麻煩的要死。博主也覺得,所以在Github上就出現了一個開源叫做 Masonry ,有興趣的小伙伴可以看看,特別是那些需要從低版本開始兼容的小伙伴多看看。 如果即熟悉 Masonry 并且也熟悉iOS 9關于AutoLayout的新特性的小伙伴一定覺得有一部分特性是類似的。關于建立約束上,iOS 9在 UIView 中添加了 NSLayoutAnchor 以及其中的API方便大家在創建約束上更加直觀以及方便。 下面稍微上一下代碼,具體的大家可以查看 NSLayoutAnchor 這個類:
// 可以這樣 [UIImageView.trailingAnchor constraintEqualToAnchor:UILabel.leadingAnchor] // 或者 [UIImageView.trailingAnchor constraintEqualToAnchor:UILabel.leadingAnchor constant:20]
// Swift 代碼 UIImageView.leadingAnchor.constraintEqualToAnchor(UILabel.trailingAnchor) // 或者 UIImageView.leadingAnchor.constraintEqualToAnchor(UILabel.trailingAnchor, constant: 20)
是不是覺得相對于之前創建方式更加直觀以及方便,并且這個創建好之后完全不用在意他是哪一個 UIView 來添加 Constraint 。它默認會將其加到正確的視圖上。當然在建立好正確的Constraints之后,記得用 NSLayoutConstraint 中的 activateConstraints 來激活對應的約束。
在iOS 9中還添加了一個名為 UILayoutGuide ,這個組件雖然不像是 UIView 一樣顯示在界面上,但是確實有想其他視圖控件占據著布局中的一個位置和空間,就好像有一個有一個透明的 UIView 一樣,但是它卻不在視圖的層次中。下面舉一個使用的例子:chestnut::
UILayoutGuide *space1 = [[UILayoutGuide alloc] init]; [self.view addLayoutGuide:space1]; UILayoutGuide *space2 = [[UILayoutGuide alloc] init]; [self.view addLayoutGuide:space2]; [space1.widthAnchor constraintEqualToAnchor:space2.widthAnchor].active = YES; [self.saveButton.trailingAnchor constraintEqualToAnchor:space1.leadingAnchor].active = YES; [self.cancelButton.leadingAnchor constraintEqualToAnchor:space1.trailingAnchor].active = YES; [self.cancelButton.trailingAnchor constraintEqualToAnchor:space2.leadingAnchor].active = YES; [self.clearButton.leadingAnchor constraintEqualToAnchor:space2.trailingAnchor].active = YES;
let space1 = UILayoutGuide() self.view .addLayoutGuide(space1) let space2 = UILayoutGuide() self.view.addLayoutGuide(space2) space1.widthAnchor.constraintEqualToAnchor(space2.widthAnchor).active = true self.saveButton.trailingAnchor.constraintEqualToAnchor(space1.leadingAnchor).active = true self.cancelButton?.leadingAnchor.constraintEqualToAnchor(space1.trailingAnchor).active = true self.cancelButton.trailingAnchor.constraintEqualToAnchor(space2.leadingAnchor).active = true self.clearButton?.leadingAnchor.constraintEqualToAnchor(space2.trailingAnchor).active = true
上方的代碼是建立的三個按鈕,并且在 saveButton 、 cancelButton 以及 clearButton 之間建立了相等的間距。
除了以上的新增內容外,其實在iOS9中也添加了一個很重要的視圖,如果那些只從iOS9開始兼容的小伙伴,可以多用這個視圖------ UIStackView 。有興趣的小伙伴可以多去嘗試以下,是一個非常不錯的空間,能適配一些之前適配過程中適配不好的一些情況。當然如果有小伙伴希望從iOS6.0開始兼容,但是也想用 UIStackView 的功能的話,小伙伴可以使用以下用 FDStackView 這個控件。由于 UIStackView 不是本篇的重點,所以這里就不給大家展開來講解了。
好了,既然上面說了添加約束,那么在代碼上怎么添加約束呢?
添加(刪除)約束
iOS 6
直接上代碼吧,先來個OC版的:
[UIVIew:A addConstraint:[NSLayoutConstraint constraintWithItem:UIImageView attribute:NSLayoutAttributeTrailing relatedBy:NSLayoutRelationEqual toItem:UILabel attribute:NSLayoutAttributeLeading multiplier:1.0 constant:0]]
然后來個Swift版本
UIView:A.addConstraint(NSLayoutConstraint(item: UIImageView, attribute: .Trailing, relatedBy: .Equal, toItem: UILabel, attribute: .Leading, multiplier: 1.0, constant: 0))
通過上述代碼就可以進行添加了。當然除了這個 addConstraint 還有一個 addConstraints 方法是用來添加多個約束的情況。當然,在添加約束之后,小伙伴們最好調用一些跟新最近的“祖先”的 layoutIfNeeded 方法。這樣才能生效。 除了添加約束的Add方法外,蘋果官方還提供了對應的remove方法,與之相對應的分別是 removeConstraint 和 removeConstraints 的方法,小伙伴可以自己去嘗試一下。
當然,如果在講約束的某一個視圖刪除之后,不要忘了將含有該視圖的所有約束情況,否則會出現crash的情況
iOS 8
上面提到添加和刪除約束的方式,可是根據前人的經驗,發現通過Add和Remove的方法來控制約束的話,相對來說還是比較笨重的,而且效率不是很高。為此,蘋果官方在iOS 8中推出了 activateConstraints 和 deactivateConstraints 的接口。特別是有些時候我們只是針對兩到三種不同情況進行不同的適配,那么在重復的使用Add和Remove的方式會使我們App的效率降低很多,因此在iOS 8中推出了 activateConstraints 和 deactivateConstraints 。 那么具體怎么用呢?其實也是非常非常簡單的,只要通過下面這種方式即可:
// 使約束生效 [NSLayoutConstraint activateConstraints:constraintsArray] // 使約束失效 [NSLayoutConstraint deactivateConstraints:constraintsArray]
// 使約束生效 NSLayoutConstraint.activateConstraints(constraintsArray) // 使約束失效 NSLayoutConstraint.deactivateConstraints(constraintsArray)
當然相對應的在 NSLayoutConstraint 中也添加了 active 的屬性,我們可以通過這個屬性來判斷一個約束是否生效。 并且在WWDC 2015上也建議盡量用 activateConstraints 和 deactivateConstraints 來提高程序的效率。
可是如果小伙伴們在自定義的控件的時候,如果將添加約束的代碼放在 updateConstraints 來進行添加的,然后大家加完之后發現是沒有效果的,那是因為這里其實是需要對UIView的 requiresConstraintBasedLayout 這個方法進行重寫,需要將其返回值設置成 YES 或 true 才可以。
Priority
關于Priority在前面我們也稍微提過,就是針對優先級來說的。可是對于 Priority 來說還有兩個特別的優先級,分別為 Content Hugging Priority 以及 Content Compression Resistance Priority ,這兩個如果有了解的童鞋幾乎可以跳過,沒了解的童鞋這里稍微講一下。通過這兩個屬性來看看優先級是怎么工作的。
在講解這個之前,先給大家講一個概念,叫做 Intrinsic size 。什么叫做 Intrinstic Size 呢?這里舉個例子,比如對于一個 UILabel 字體、內容等一系列參數,其實是可以立即確定 UILabel 的Size的。那么這種內在的Size,不用通過其他手段或者是跟其他視圖無關而能獲得的Size被稱為 Intrinstic Size 。類似的還有 UIButton 以及 UIImageView 都有對應的 Intrinstic Size 。換一句話來說,其實在我們對于這些控件的Size不進行設置,而只對Position進行設置的情況下,這些控件都會自動根據內容進行產生 Intrinstic Size 。
好了,繼續說上面兩個 Priority ,這兩個 Priority 可以理解為 可以壓縮度 以及 可拉伸度 (當然別的博客可能有別的說法),不過中文的翻譯都是為了讓童鞋們更容易理解。至于這里我為什么叫做 可壓縮度 和 可拉伸度 呢?因為對我來說 Hug 是擁抱的意思,那這個 Content Hugging Priority 應該是如果優先級越高,那就 可壓縮度 應該就越高,那剩下的另一個就相反咯。如果 Content Compression Resistance Priority 的值越大,那就是可拉伸度就越高咯。而這里的可拉伸度和可壓縮度針對的對象是什么呢?聰明的小伙伴們應該猜出來了,就是 Intrinstic Size 。
下面用一個:chestnut:來解釋給大家看看是什么意思。 比如我有一個昵稱的長度不確定,可是當我給 UILabel 添加了一個 Width 的約束為60,可是昵稱突然長度變成了70怎么辦,其實只需要將 Content Compression Resistance Priority 的值設置的大于60約束的Priority即可。也就是在兩個針對同一個屬性值約束的情況下,當然是優先滿足大的,也就是可拉伸度的屬性啦。雖然你說寬度為60,可是我的等級比你大,也就是說先滿足我,所以可以拉伸。 同樣的,對于如果出現 Width 的約束為60,可是昵稱的長度變成50的情況,如果 Content Hugging Priority 的優先級大于 Width 的情況下, UILabel 的寬度也會變成50。 下面通過兩張圖來看看上面所說的例子:
可以看出雖然寬度都設置為60了,可是在內容多或者少的時候,其寬度都會根據內容還自動壓縮或者拉伸。因為我講 Width 這個約束的 Priority 設置為10,而 Content Compression Resistance Priority 和 Content Hugging Priority 的值分別為251何750,所以總會根據內容進行變化,當然其實這樣就跟沒設置寬度約束一樣了。因為 UILabel 的寬度總是等于 Intrinstic Size 。 那么在代碼上怎么進行設置呢?其實也是非常簡單的:
// Objective-C代碼 [longLbl setContentCompressionResistancePriority:750 forAxis:UILayoutConstraintAxisHorizontal];
// Swift代碼 longLbl.setContentHuggingPriority(750, forAxis: .Horizontal)
不管從代碼上還是xib上,我們都可以看得出來,關于這兩個約束其實還有分的是水平方向還是垂直方向,其實 壓縮 和 拉伸 就是指針對一個方向的嘛。
xib和storyboard的逆襲
這邊我們將講講在Xcode中一些常用的關于適配方面的工具吧,用好這些工具能讓你的適配效率更好,而且也能體會到其中的便利的地方。
工具
首先先來說說在xib和storyboard(以下都用xib代替二者)右下角的這個幾個按鈕
的功能,以及提供講解以下大概的用途和使用方法。
Stack
左邊第一個工具的功能非常簡單,就是將選擇好的幾個控件放到一個 UIStackView 中,由于是 UIStackView 所以這里就不進行展示了。
Align
從圖表可以看得出來他是針對于兩個控件之間,由下圖可以看得出來這個主要是針對兩個視圖相對位置的功能,洋氣一點的說話就叫Align咯。
那么具體怎么能方便的用這個功能呢?其實也非常簡單的,就是只要將要進行對其的幾個控件都選擇然后點擊要根據要對其的方式,選擇對應的方式即可,具體方法如下:
可以看得出上圖是將三個控件先選中(按住cmd然后左鍵點擊需要選擇的控件)后,然后再點擊需要對其的方式即可。這里指的一提的在下方有一個 Update Frames 的下拉框里面有有三個選項,分別是: None , Items of New Constraints 以及 All Frames in Containers ,那么這三個都是什么用的呢?其實可以看得出來這個主要是根據控件的位置和大小的,從名字就可以看得出來 None 就是不更新,這里主要講下后面兩個,第一個是對于那些新加的約束有關的控件都會出行刷新布局,而對于那些跟新的約束無關的視圖不會變化。而最后一個,只要在同一個Container中的所有控件的視圖都會進行刷新布局。小伙伴們可以試試。
Pin
關于右邊第二個的功能總結起來就是添加增對其他控件(默認是父控件)以及控件本身的一些約束,怎么說呢?看看點擊Pin這個按鈕的彈出框吧。
上圖是只選擇一個控件的情況下,可以看得出當前情況下的 Equal Widths 、 Equal Heights 以及 Align 都是暗的,即不可選擇的。在這種情況下默認情況下是針對離自己最近的控件的約束以及自己的約束。 這里主要講解一下兩個部分上面那個部分,其他部分都是很好理解的,即上面的四個選擇上面四個選擇主要是針對里自己最近的一個控件的約束,怎么說呢?看下這張圖:
如果在紅色控件添加上下左右的約束的情況下,控件下邊(Bottom)的約束的相對約束是UILabel的上邊。而在除了上下左右以外,還有一個 Constrain to margins 的單選框,而這個主要是針對父控件來說的。那么具體是什么意思呢?其實簡單的理解就是針對父控件20單位的位置的約束。也就是說如果我們選擇這個選項,并且我們選擇了上下左右(假設是針對父控件的情況下)都為0的情況下,那么其實這個時候就等價于我們具體父控件的上下左右都為20的情況。那么這個可以用在什么時候呢?博主對于具體的使用場景也不是很清楚,不過看過一篇文章有講到一個使用場景,即在iOS 8的情況下,橫屏的時候status bar的默認下是隱藏的(只是指不占高度位置,而不是完全不見),即我們的視圖是從屏幕最頂端開始布局的,那么這個時候可能就會出現字跟status bar的重合,那么這個時候就有必要設置成針對 Margin 的約束了。
在上面我們提到了 Equal Widths 、 Equal Heights 以及 Align 三個不可使用的情況下,那么在上面情況下這些是可以用的呢?其實也很簡單,就是在你選擇多個控件的情況。在多選的情況下,那么這里的約束要么是相對于這兩個控件(如 Align 和 Equal Widths 等),要不然就是兩個都是同樣的針對選中控件的最近控件的約束,大家可以多去試試。這里就不一一講述了。
Resolve Auto Layout Issues
終于到最后一個了,從名字來看就知道這個其實是針對當出現問題的時候的解決方案。一般來說針對那些出現 waranings 的處理方案。那么具體有哪些可選的方案呢?就不告訴你,就不告訴你,就不告訴你。
可以看的出來,主要分為兩大塊,一塊是所謂的 Selected Views 和 All Views in View ,其實上下兩個中的每個選項的功能是一樣的,只是針對的視圖不同而已。 Selected Views 是針對所選擇的視圖,而 All Views in View 是指所有的視圖。所以這里就針對其中一個進行說明:
- Update Frames:根據當前約束改變視圖布局,快捷鍵是 alt + cmd + = 這個快捷鍵很好用
- Update Constraints:根據當前的Frame來修改約束
- Add Missing Contraints:對那些還缺失約束視圖的添加缺失的約束,當然這里添加的約束是Xcode推薦的
- Reset to Suggested Constraints:將當前視圖的約束變成Xcode推薦的約束(這個反而用的比較少)
- Clear Constraints:清除所有跟視圖相關的約束
關于上方的提到的所謂的Xcode推薦的約束本寶寶真的不知道他是根據什么形式進行推薦的,所以不要再逼本寶寶了。麻煩那個知道的小伙伴能給本寶寶科普一下。 其實除了在這里的四個按鈕以外,在菜單欄里面的 Editor 項中也有很多關于xib上的適配。這里就不一一講解了,小伙伴可以自己去試試,里面有一些功能也是挺好用的,如果 Editor -> Embed In 這個,還有 Editor -> Algin 也是很好用的功能。
Utitlities視圖
關于這個這個視圖估計小伙伴們熟的不能再熟了,那么他到底是什么鬼呢?
夠熟悉吧,這里我就主要就講講 Utilities -> Show the Size inspector (倒數第二個tab)。畢竟跟約束相關的主要內容都是在這個tab中的。其中可以看到中間有一部分叫做 Constraints 的地方,可以看得出來我們可以在圖中看到上下左右,水平垂直中線以及寬和高的約束。當其為虛線的時候代表其在這個地方沒有約束,實線表示其有在這個地方有約束。
那么我們可以怎么使用這個地方的 Constraints ,首先我們可以點擊某一個約束,如下圖中我們點擊了Bottom的約束,那么在下方我們可以看到所有跟選中視圖的Bottom相關的約束都會列出來,比如和父視圖的約束以及跟子視圖的約束。
這里可以看得出 Constraints 可以看得出某個位置的約束,有一種類似過濾的功能。
其次下方的約束也可以進行點擊右邊的 Edit 進行修改約束的屬性當然也可以通過雙擊來進行查看到具體約束的信息。當然也是在 Utilities -> Show the Size inspector 這個位置。這里順帶提一句,當點擊右邊 Edit 的這個效果也可以在xib上雙擊某個約束出現相同的效果,如下圖:(小伙伴可以自己試試)
然后我們再講講約束詳情的一些東西吧,看看下圖是一個約束的詳情:
可以看得出 First item 和 Second item 是講上面的 Item 和 Attribute 合在了一起。并且這里指的注意的是當如果是Button的時候,如果是 Margin 的 Attribute 的時候是相對文字的約束,我們都知道在 UIButton 在周圍都會留一下空間,所以我們經常設置 UIbutton 的約束的時候,都會針對整個 UIButton ,如果在這里選擇Attribute為 Margin 相關的時候,則會針對里面文字。小伙伴可以試試,剩余的東西大家應該都比較熟悉。
當然這里還有一個需要提的,就是 Identifier 在出現約束報錯的情況下回起到非常大的作用。小伙伴們如果在出現報錯的情況下,建議大家設置 Identifier 這樣能將報錯的信息從全地址的狀態變化成顯示具體 Identifier 的狀態。
xib的結尾
最后我們說說Xib最常用的一種添加約束的情況,小伙伴應該都會,就是通過點擊按住 ctrl 然后點擊鼠標左鍵進行拖拽進行添加新的約束,這里我就不給大家獻丑了。而關于出現Xib上的約束的 warning 和 error 的情況小伙伴們應該也知道怎么處理了,都是非常常用的方法,這里就不進行累述了。
Skill-干貨
好了,終于到了這里了。上面理論的東西太多了,小伙伴們可以稍微消化消化。雖然內容多,可是大部分來說還是偏操作性質的,所以接受起來可能相對容易一點。想到以前上課看那么多理論的東西就頭疼,我還是比較喜歡實踐。咱們的老馬克思主義說了,實踐是檢驗真理的唯一標準嘛。在實踐多了之后,對于一些理論的知識的接受度就更深入了。好了,廢話不多說了,這里來說一些干貨吧,基本上都是針對于Xib的。雖然博主是個IB粉呢。
干貨1 ---------- Preview工具
那么什么是Preview工具呢?其實非常簡單,同樣一幅圖可以搞定:
看出來了吧,他其實就是一個預覽的功能,在大部分普通的適配其實都可以通過 Preview 工具來預覽查看,這樣就不用每次運行才能看到效果了,通過這個工具能查看在各個尺寸下我們的布局的排列情況。那么這個怎么點出來呢?
其實也非常簡單,首先先打開 Assistant Editor
即中間那個,然后在點最左邊那個按鈕,就會出現如下的菜單:
選擇最后一個即可。如果實在不懂的妹子,可以來問我,漢子就算了。哈哈。(Kidding!!!!)
干貨2 ---------- 查看視圖層級
很經常會出現一種情況,就是我們經常會有視圖層級很多,即一個視圖嵌套一個視圖,嵌套的層很多。特別是如果出現視圖重疊的情況下,想要正確的選擇到正確視圖這里主要講解三個方法,用三個視圖來給大家展示一下:
- 通過文件導航器來選擇
- 通過視圖層級來選擇
- 通過鼠標和左鍵選擇(其實主要講的是這個)
前面兩種都很常見,最后一種怎么出現的呢?很簡單,通過安裝 cmd + shift 在點擊需要選擇控件的地方點擊左鍵即可。這樣就會列出點擊地方的整個視圖層次,最上面是在最后面的視圖,最下面的是顯示在最上方的視圖。
干貨3 ---------- wrap_content的實現
本人以前也做過Android的開發,在Android的布局中,在設置高度和寬度的時候可以設置成兩個值 fill_parent (同match_parent)和 wrap_content 兩個常量值。其實兩個的意思分別是:子視圖根據父視圖縮放以及父視圖根據子視圖內容縮放。簡單來說就是根據父視圖大小改變還是根據子視圖改變。 而在iOS開發中,我們經常用的應該就是根據父視圖改變的,從 Autoresizing 到 AutoLayout 相對父控件的約束來說都是根據父視圖改變子視圖的大小。那么我就在想了,能不能再iOS中實現 wrap_content 即根據子視圖的大小來調整父視圖的大小呢?其實這個功能的使用場景還是挺多的。 下面我這里就舉一個例子來說說,比如我們在一個希望自訂一個titleView,這個titleView的左邊有一個定位的圖標,中間有一個 UILabel 用來顯示定位的城市名,右邊有一個箭頭的圖標。那么這個titleView的寬度應該就會根據城市的不同進行縮放了。那么我們在家的時候最外層的 UIView 即titleView就需要根據里面的內容進行所謂的 wrap_content 的處理。
下面我們就用一個Demo在Xib上的約束以及Preview的功能來進行講解(假設上面所說的titleView中最高的是 UILabel )。首先我們先來看看效果:
當前選中的是紅色的背景的 UIView 可以從右邊的視圖看的出來,除了設置了相對父視圖的位置信息的以外,沒有對 UIView 設置關于大小的約束。那么 UIView 的帶下是如何來的呢?可以看得出來,除了針對父視圖的位置約束外,剩下的就是子視圖相對 UIView 的約束了。那么肯定就是子視圖生成的。(廢話,你前面不是也說針對子視圖內容來縮放父視圖大小)其實實現的機理也非常簡單,只要保證在距離 UIView 中的子視圖在某一個方向上是打通的,例如水平方向上:
- |UIView.Leading - 20 - UIImageView.Leading|
- UIImage.Width = 15
- |UIImageView.Trailing - 20 - UILabel.Leading|
- UILabel.Width = Intrinsic size .width
- |UILabel.Trailing - 20 - UIImageView.Leading|
- UIImageView.Widht = Intrinsic size .width
- |UIImageView.Trailing - 20 - UIView.Trailing|
可以看到水平上從UIView.Leading到UIView.Trailing是走的通的,而不能出現斷掉的情況。而垂直方向就簡單了:
- |UIView.Top - 20 - UILabel.Top|
- UILabel.Height = Intrinsic size .height
- |UILabel.Bottom - 20 - UIView.Bottom|
同樣在UIView.Top到UIView.Bottom的方向上也是完全走得通的。那么總結來說就是一句話,就是在父控件視圖的任何方向上都能保證約束能從一個方向的一段走到另一端,其中不會出現斷層的情況下,則能保證其能出現 wrap_content 的效果。就好像是在一個方向上搭建一條路,能從路的一段走到另一端即可。
博主估計這里的原因估計是因為當如果保證某一個方向上的約束鏈走的通的情況下能讓UI布局的內部機制在計算UIView的寬度和高度的情況下都有可參考的值,主要是保證父視圖能算出準確的寬和高。
Demo見WrapContentViewController.xib!!!
Tips: 其實下方另外兩個干貨都和這個干貨的原理很類似,小伙伴可以思考一下。由于原理都差不多,所以接下里就稍微一帶而過了,大概講一下實現的方式和原理。
干貨4 ---------- UIScrollView的適配
很多小伙伴在剛開始適配 UIScrollView 的情況下都是相當蛋疼啊,因為明明都配置了 UIScrollView 相對父控件的上下左右距離都為0的。也就是 UIScrollView 的大小與父控件一樣大,可是為什么偏偏在添加了子視圖后,瞬間整個視圖的適配就會出錯,總是適配不好。記得博主剛開始適配 UIScrollView 是這個表情:
那么到底是為什么呢?其實在添加子視圖之后出錯的原因是適配機制希望我們通過適配來確定 UIScrollView 的 contentSize 的大小。為什么這么說呢?其實如果適配成功過的小伙伴都知道,在我們適配好 UIScrollView 之后, contentSize 是不需要設置的,那么如何設置呢?其實跟上面的 wrap_content 一樣,保證在每個方向上的"路"都是通的就可以了。
Demo見ScrollViewController.xib!!!
干貨5 ---------- 自動計算Cell的高度
還記得之前博主要做一個類似朋友圈(QQ空間、微博等)的功能,即根據文字、圖片、評論以及點贊等信息來自動調整一條說說的高度。當初那叫一個糾結啊,也只能說當初博主比較水,只能印著頭皮在算所謂的 Frame 那一個一個的 if-else 語句寫的博主那叫一個心驚肉跳。深怕其中一個寫錯了,找問題就麻煩了。結果最后還是因為各種奇葩的原因算的不夠準。后來看了2015年的WWDC大會才知道了這個好東西。
由于 UITableViewCell 的寬度都是跟 UITableView 等寬,所以主要是針對 UITableViewCell 的高度。平常我們都是在Xib或者在 UITableViewDelegate 中實現方法來返回,可是其實在iOS 7之后就提供了實現動態高度的方法,具體原理還是跟上面一樣,保證在垂直方向上保證"路"是能通的。然后針對tableView設置如下兩個屬性即可:
tableview.estimatedRowHeight = 60; // 設置UITableViewCell每行大概多高 tableview.rowHeight = UITableViewAutomaticDimension;
tableview.estimatedRowHeight = 60 // 設置UITableViewCell每行大概多高 tableview.rowHeight = UITableViewAutomaticDimension
其中第一個屬性estimatedRowHeight是用來估算 UITableView 的 contentSize 用的。第二個是告訴UITableView去根據一些方法來計算Cell的高度。只要保證這兩句代碼和垂直"路"是通的情況下,則能實現Cell的動態高度。
苦逼的總結
這篇文章的跨度好久,從三月初就開始寫,寫到四月中旬,主要是其中的三月份公司的項目太忙了,不僅博客停了,包括開源項目的更新基本也停了,不過這里也慢慢開始恢復分享一些內容給大家。當然其實本來還是比較喜歡講解一些操作性質的內容,除了這些內容其實還有一些其他的內容,如以前的 Autoresizing 在與 Autolayout 混用的情況下會將之前的 Autoresizing 的配置都改成一個個 Constraints -------- NSAutoresizingMaskLayoutConstraint 的形式,從而讓 Autolayout 也能兼容到 Autoresizing 。
當然關于適配的內容也非常非常的多,我這里也一時半刻說不完,并且蘋果官方的適配機制也會在不斷的更新,我們需要不斷的學習來做到更好的適配。當然這篇文章的中的一些內容與之前整理的WWDC的內容有些重合的地方,在WWDC的內容上也整理了很多關于IB以及AutoLayout的技巧,有興趣的小伙伴可以返回重新看看。
博主整理文章真的不容易,如果覺得文章內有問題的小伙伴可以留言,我會在看到的時候給你回復。博主整理文章不容易,且讀且珍惜。
哦,忘記了,還有關于適配的最后一個部分 Size Class ,小伙伴們記得回來看啊,要有始有終啊。