本系列文章將從一個慢慢摸索中的新手的角度介紹 Auto Layout,講述我在這兩個月的學習中對它一點一滴的感覺,最終目的是讓你們在閱讀完以後可以本身上手使用,並完成絕大多數簡單的佈局約束。html
此係列文章代碼倉庫在 https://github.com/johnlui/AutoLayout ,有不明白的地方能夠參考個人 Auto Layout 設置哦,下載到本地打開就能夠了。ios
Auto Layout 是蘋果在 Xcode 5 (iOS 6) 中新引入的佈局方式,旨在解決 3.5 寸和 4 寸屏幕的適配問題。屏幕適配工做在 iPhone 6 及 plus 發佈之後變得更加劇要,並且以往的「笨辦法」的工做量大幅增長,因此不少人開始學習使用 Auto Layout 技術。c++
本系列文章的開發環境爲:git
OS X 10.10.3
github
Xcode Version 6.3.1 (6D1002)編程
新建一個 Single View Application,命名爲 AutoLayout,以下:swift
點擊選中 Main.storyboard,右側內容以下:app
一、2 兩個按鈕將會在將來的開發中產生巨大的做用,他們將擁有本系列文章的全局名稱:按鈕1,按鈕2。請先記下他們的位置。ide
這也是我對學習新的軟件編程技術的基本學習方法:有一個具體客觀驅動的目標,例如作一個真正要給客戶用的軟件,而不是「爲了學習新技術提升本身」這類僞目標。函數
讓咱們直接上手:繪製一個距離左右邊都有必定距離、固定高度、垂直居中的按鈕,叫「Swift on iOS」。
1. 第一步,從右側拖過來一個按鈕,置於頁面最中間。會有參考線出現,這一步很容易:
2. 選中這個 button,將按鈕背景色和前景色進行以下設置:
3. 將按鈕左側邊界往左拖動直到自動吸附,留下必定的距離。右側進行一樣操做:
4. 選中這個 button,修改文字爲 Swift on iOS:
5. 選中這個 button,點擊 按鈕2 ,選擇這一項:
這時候 button 周圍會出現一些藍色的線條,這些就是 Auto Layout 的約束項。
3.5:
4:
4.7:
5.5:
選中這個 button,在右側查看自動生成的約束項:
只有三項,這三項的意思分別是:和父視圖縱向居中對齊、右側和父視圖對齊、左側和父視圖對齊。
咱們很容易就能理解這樣能夠定位一個按鈕,可是總感受少了點什麼。實際上這三個自動生成的約束項並不能描述一個 button 的位置,由於少了一個關鍵的屬性:button 的高度。之後咱們會詳細地討論。
Auto Layout 的本質是依靠 某幾項約束條件 來達到對某一個元素的定位。咱們能夠在某個地方只使用一個約束,以達到一個小目的,例如防止內容遮蓋、防止邊界溢出等。但個人最佳實踐證實,若是把頁面上每個元素的位置都用 Auto Layout 進行 「嚴格約束」 的話,那麼 Auto Layout 能夠幫咱們省去很是多的計算 frame 的代碼。
簡單來講,嚴格約束就是對某一個元素的絕對定位,讓它在任一屏幕尺寸下都有着惟一的位置。這裏的絕對定位不是定死的位置,而是對一個元素 完善的約束條件。
讓咱們看圖說話:
咱們要在一個直角座標系裏描述一個矩形。
那麼只須要指定這個矩形的位置和大小。
那麼只要給出上圖中的四個值便可:到左邊界的距離,到上邊界的距離,寬度,高度。
這四個約束是最簡單的狀況。在對一個元素進行嚴格約束時,請直接在腦中構建這個元素,而且加上幾條約束條件,若是他沒法縮放和動彈,那麼嚴格約束就是成功的!
必須牢記,使用 Auto Layout 時最重要的是:對頁面上每個元素都進行嚴格約束,不嚴格的約束是萬惡之源。
此係列文章代碼倉庫在 https://github.com/johnlui/AutoLayout ,有不明白的地方能夠參考個人 Auto Layout 設置哦,下載到本地打開就能夠了。
上一篇文章中,咱們共同進行了 Auto Layout 的初體驗,在本篇咱們將一塊兒嘗試用 Auto Layout 實現三等分。
Auto Layout 的本質是用一些約束條件對元素進行約束,從而讓他們顯示在咱們想讓他們顯示的地方。
約束主要分爲如下幾種(歡迎補充):
相對於父 view 的約束。如:距離上邊距 10,左邊距 10。
相對於前一個元素的約束。如:距離上一個元素 20,距離左邊的元素 5 等。
對齊類約束。如:跟父 view 左對齊,跟上一個元素居中對齊等。
相等約束。如:跟父 view 等寬。
許多人剛開始接觸 Auto Layout,可能會覺得它只能實現上面的一、2功能,其實後面三、4兩個功能纔是強大、特別的地方。接下來咱們將嘗試設計橫向三等分:
第一個元素距離左邊必定距離。
最後一個元素距離右邊必定距離。
三者高度恆定,寬度相等。(此處咱們設置爲高度恆定(height 屬性),若是你須要的是固定長寬比,則須要設定 Aspect Ratio 屬性)
1和二、2和3的橫向間距固定。
4 寸:
4.7 寸:
此係列文章代碼倉庫在 https://github.com/johnlui/AutoLayout ,有不明白的地方能夠參考個人 Auto Layout 設置哦,下載到本地打開就能夠了。
本篇中咱們將嘗試自定義一個 UITableViewCell,並使用 Auto Layout 對其進行約束。
在前面的項目中,咱們採用 StoryBoard 來組織頁面,StoryBoard 能夠視爲許多個 xib 的集合,因此咱們能夠獲得兩個信息:
這個項目經過初始化主 StoryBoard 文件來展示 APP,而 UIViewController 類文件是經過 StoryBoard 文件的綁定來初始化並完成功能的。
咱們能夠建立新的 StoryBoard 文件或者新的 xib 文件來構造 UI,而後動態地加載進頁面。
咱們能夠一次性建立 xib 文件和類的代碼文件。
新建 Cocoa Touch Class:
設置和下圖相同便可:
分別選中上圖中的 一、2 兩處,檢查 3 處是否已經自動綁定爲 firstTableViewCell,若是沒有綁定,請先檢查選中的元素確實是 2,而後手動綁定便可。
切換一頁,以下圖進行 Identifier 設置:
新建一個 Table View Controller 頁面,並把咱們以前建立的 Swift on iOS 那個按鈕的點擊事件綁定過去,咱們獲得:
而後建立一個名爲 firstTableViewController 的 UITableViewController 類,建立流程跟前面基本一致。不要建立 xib。而後選中 StoryBoard 中的 Table View Controller(選中以後有藍色邊框包裹),在右側對它和 firstTableViewController 類進行綁定:
修改 firstTableViewController 類中的有效代碼以下:
import UIKit class firstTableViewController: UITableViewController { override func viewDidLoad() { super.viewDidLoad() var nib = UINib(nibName: "firstTableViewCell", bundle: nil) self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell") } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - Table view data source override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return 10 } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as firstTableViewCell cell.textLabel?.text = indexPath.row.description return cell } }
viewDidLoad() 中添加的兩行代碼是載入 xib 的操做。最下面的三個 func 分別是定義:
self.tableView 中有多少個 section
每一個 section 中分別有多少個條目
實例化每一個條目,提供內容
首先向 Images.xcassets 中隨意加入一張圖片。
而後在左側文件樹中選中 firstTableViewCell.xib,從右側組件庫中拖進去一個 Image View,而且在右側將其尺寸設置以下圖右側:
給 ImageView 添加約束:
選中該 ImageView(左箭頭所示),點擊自動 Auto Layout(右箭頭所示),便可。
給 ImageView 設置圖片:
再從右側組件庫中拖入一個 UILabel,吸附到最右側,垂直居中,爲其添加自動約束,這一步再也不贅述。
選中 firstTableViewCell.xib,切換到雙視圖,直接進行拖動綁定:
綁定完成!
在 firstTableViewController 中添加如下方法:
override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 50 }
修改 firstTableViewController 中如下函數爲:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as firstTableViewCell cell.firstLabel.text = indexPath.row.description return cell }
4.0 寸:
4.7 寸:
此係列文章代碼倉庫在 https://github.com/johnlui/AutoLayout ,有不明白的地方能夠參考個人 Auto Layout 設置哦,下載到本地打開就能夠了。
本文中,咱們將一塊兒使用 UIPanGestureRecognizer 和 Auto Layout,經過 22 行代碼實現拖動回彈效果。
刪除首頁中間的按鈕,添加一個 View ,設置一種背景色便於辨認,而後對其進行絕對約束:
拖動一個 UIPanGestureRecognizer 到該 View 上:
界面搭建完成。
切換到雙向視圖,分別右鍵拖動 UIPanGestureRecognizer 和該 View 的 Top Space 的 Auto Layout 屬性到 ViewController 中綁定:
而後將 UIPanGestureRecognizer 右鍵拖動綁定:
class ViewController: UIViewController { var middleViewTopSpaceLayoutConstant: CGFloat! var middleViewOriginY: CGFloat! @IBOutlet weak var middleView: UIView! @IBOutlet weak var middleViewTopSpaceLayout: NSLayoutConstraint! @IBOutlet var panGesture: UIPanGestureRecognizer! override func viewDidLoad() { super.viewDidLoad() panGesture.addTarget(self, action: Selector("pan")) middleViewTopSpaceLayoutConstant = middleViewTopSpaceLayout.constant middleViewOriginY = middleView.frame.origin.y } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } func pan() { if panGesture.state == UIGestureRecognizerState.Ended { UIView.animateWithDuration(0.4, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in self.middleView.frame.origin.y = self.middleViewOriginY }, completion: { (success) -> Void in if success { self.middleViewTopSpaceLayout.constant = self.middleViewTopSpaceLayoutConstant } }) return } let y = panGesture.translationInView(self.view).y middleViewTopSpaceLayout.constant = middleViewTopSpaceLayoutConstant + y } }
此係列文章代碼倉庫在 https://github.com/johnlui/AutoLayout ,有不明白的地方能夠參考個人 Auto Layout 設置哦,下載到本地打開就能夠了。
本文中,咱們將一塊兒使用 Auto Layout 技術,讓 UITableViewCell 的高度隨其內部的 UILabel 和 UIImageView 的內容自動變化。
放置一個按鈕,恢復到 firstTableViewController 的鏈接:
別忘了添加約束讓他居中哦。
將 firstTableViewCell 的尺寸設置爲 600 * 81,將 logo 的尺寸設置爲 80 * 80。將 logo 的約束脩改成以下圖所示:
修改 label 的尺寸和位置,添加約束以下圖:
爲了便於返回。操做以下圖:
選中 label,設置 lines 行數爲 0,表示不限長度自動折行:
修改 label 的文字內容讓其超出一行:
import UIKit class firstTableViewController: UITableViewController { var labelArray = Array<String>() // 用於存儲 label 文字內容 override func viewDidLoad() { super.viewDidLoad() var nib = UINib(nibName: "firstTableViewCell", bundle: nil) self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell") // 循環生成 label 文字內容 for i in 1...10 { var text = "" for j in 1...i { text += "Auto Layout" } labelArray.append(text) } } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() } // MARK: - Table view data source override func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { return 50 } override func numberOfSectionsInTableView(tableView: UITableView) -> Int { return 1 } override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { return labelArray.count } override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell cell.firstLabel.text = labelArray[indexPath.row] return cell } }
estimatedHeightForRowAtIndexPath 是 iOS 7 推出的新 API。若是列表行數有一萬行,那麼 heightForRowAtIndexPath 就會在列表顯示以前計算一萬次,而 estimatedHeightForRowAtIndexPath 只會計算當前屏幕中顯示着的幾行,會大大提升數據量很大時候的性能。
class firstTableViewController: UITableViewController { var labelArray = Array<String>() // 用於存儲 label 文字內容 var prototypeCell: firstTableViewCell! override func viewDidLoad() { super.viewDidLoad() var nib = UINib(nibName: "firstTableViewCell", bundle: nil) self.tableView.registerNib(nib, forCellReuseIdentifier: "firstTableViewCell") // 初始化 prototypeCell 以便複用 prototypeCell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell") as! firstTableViewCell ......
override func tableView(tableView: UITableView, estimatedHeightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat { let cell = prototypeCell cell.firstLabel.text = labelArray[indexPath.row] return cell.contentView.systemLayoutSizeFittingSize(UILayoutFittingCompressedSize).height + 1 }
上面讓 firstTableViewCell 根據 label 自動計算高度的過程當中,有一個超級大坑:若是給左側 UIImageView 賦的圖片較大(大於 80px),將看到以下奇怪的結果:
首先,把圖片的渲染模式改爲 Aspect Fit:
給 Images.xcassets 增長三張圖片,命名爲 0、一、2,尺寸從小到大:
給 cellForRowAtIndexPath 增長代碼:
if indexPath.row < 3 { cell.logoImageView.image = UIImage(named: indexPath.row.description) }
查看效果:
新建一個 Group(虛擬文件夾),叫 Extensions,並在其內部新建 UIImage.swift 文件,內容以下:
import UIKit extension UIImage { func resizeToSize(size: CGSize) -> UIImage { UIGraphicsBeginImageContext(size) self.drawInRect(CGRectMake(0, 0, size.width, size.height)) let newImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return newImage } }
給 UIImage 類擴展了一個名爲 resizeToSize 的方法,返回一個按照要求的大小重繪過的 UIImage 對象。修改 cellForRowAtIndexPath 的代碼爲:
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { let cell = tableView.dequeueReusableCellWithIdentifier("firstTableViewCell", forIndexPath: indexPath) as! firstTableViewCell cell.firstLabel.text = labelArray[indexPath.row] var image = UIImage(named: (indexPath.row % 3).description)! if image.size.width > 80 { image = image.resizeToSize(CGSizeMake(80, image.size.height * (80 / image.size.width))) } cell.logoImageView.image = image return cell }
搞定!
從上圖能夠看出,cell 已經能夠根據圖片和文字中比較高的一個徹底自適應。
感謝 《動態計算UITableViewCell高度詳解》,給我提供了許多基礎知識和靈感。
此係列文章代碼倉庫在 https://github.com/johnlui/AutoLayout ,有不明白的地方能夠參考個人 Auto Layout 設置哦,下載到本地打開就能夠了。
本文中,咱們將一塊兒使用 Auto Layout 技術製造一個炫酷的下拉刷新動畫。Auto Layout 除了在佈局的時候比較繁瑣之外,還有一個常常被人吐槽的點:讓許多 UIView.animateWithDuration 失效,甚至在界面上出現 「反方向動畫」 的視覺效果。本文中咱們將主要講述製造下拉刷新動畫的過程,關於 Auto Layout 與動畫的詳細配合咱們之後再一塊兒仔細探究。
使用一個跟窗體同樣很大的 mainView 把目前首頁的五個元素包括,並補全 Auto Layout 佈局。層次結構改變會讓除尺寸約束以外的全部約束消失。
UI 和代碼的綁定修改爲以下:
從新給 mainView 綁定上 panGesture,從 mainView 向 panGesture 拖動:
修改存儲值的變量名稱和初始化代碼:
... var mainViewTopSpaceLayoutConstraintValue: CGFloat! var mainViewOriginY: CGFloat! ... mainViewTopSpaceLayoutConstraintValue = topLayoutConstraintOfMainView.constant mainViewOriginY = mainView.frame.origin.y ...
修改手勢觸發的目標函數(增長冒號):
panGesture.addTarget(self, action: Selector("pan:"))
改寫動畫目標函數:
func pan(recongnizer: UIPanGestureRecognizer) { if panGesture.state == UIGestureRecognizerState.Ended { UIView.animateWithDuration(0.4, delay: 0, options: UIViewAnimationOptions.CurveEaseInOut, animations: { () -> Void in recongnizer.view?.frame.origin.y = self.mainViewOriginY }, completion: { (success) -> Void in if success { self.topLayoutConstraintOfMainView.constant = self.mainViewTopSpaceLayoutConstraintValue } }) return } let y = panGesture.translationInView(self.view).y topLayoutConstraintOfMainView.constant = mainViewTopSpaceLayoutConstraintValue + y }
拖動一個 View 到 mainView 上面,目的是置於 mainView 圖層之下。在 Xcode 裏,最上面的元素位於 UI 的最底層,這個順序跟 PS 正好相反。修改其 frame,添加 Auto Layout 約束,以下圖:
將 UI 和代碼綁定:
動畫的時候咱們須要該主視圖進行必定程度的下移,故將其 「到父視圖頂部的距離」 的約束也進行綁定。(此圖爲後期補充,請自動忽略其子元素)
將動畫主視圖更名爲 HiddenTopView。爲了方便的填充動畫元素,須要先將 HiddenTopView 移動到 mainView 之下,以便將圖層顯示在 mainView 之上,不被其遮擋,沒法編輯。我填充了三個圖片資源,所有加上約束。具體約束你們能夠自由發揮。效果以下:
因爲我使用了白雲元素,故將最大的 view 的背景色填充爲灰色,將 HiddenTopView 背景色設置爲透明。
咱們計劃整個下拉刷新動畫分爲四個部分。下拉時:
整個 HiddenTopView 下移
小云彩從屏幕右側飛入
大雲彩持續作往復運動
下拉距離超過必定值時,中間子元素進行必定程度的放大
「動畫規劃」中,1 上面已經準備過,二、3 須要將小云、大雲的橫向定位參數向代碼綁定,4 只需綁定中間元素便可。
因爲實現動畫效果的代碼描述起來過於麻煩,請直接看代碼:https://github.com/johnlui/AutoLayout/blob/1420fddee57d22ebd443656fb3158c7dede84b56/AutoLayout/ViewController.swift