視圖和視圖層級
視圖基礎
視圖是
- UIView 的一個實例, 或它的一個子類
- 視圖知道怎么繪制自己
- 能處理事件, 例如觸摸(touches)
- 視圖存在于視圖層級中, 它的根是程序的窗口
視圖層級
每個應用程序都有一個 UIWindow 的單個實例用作程序中所有視圖的容器。UIWindow 是 UIView 的子類, 所以窗口自己也是一個視圖献起。窗口在程序啟動時被創(chuàng)建性雄。一旦窗口創(chuàng)建完成, 其它視圖就會被添加到窗口上阅羹。
當其它視圖被添加到窗口中時, 它就是窗口的子視圖讹挎。窗口的子視圖還可以有子視圖, 結果就是視圖對象的層級, 而 window 窗口是它們的根(root)真友。
一旦視圖層級創(chuàng)建完成, 它會被畫到屏幕上薯鳍。這個過程可以被分為2步:
- 視圖層級中的每個視圖, 包括窗口, 繪制自己对扶。它們把自己渲染到它的圖層上(layers), 你可以把 layers 看作一張位圖袁辈。(layer 是 CALayer 的一個實例)
- 所有視圖的 layers 被組合到屏幕上
視圖和 Frames
當你用程序初始化一個視圖時, 使用 init(frame:) 指定初始化函數(shù)菜谣。(designated initializer) 這個函數(shù)接收一個參數(shù), 即 CGRect , 它會變成視圖的 frame, 即UIView 的一個屬性。
var frame: CGRect
視圖的frame 指定了視圖的大小和它相對于父視圖的位置晚缩。因為視圖的大小總是由它的 frame 指定, 視圖的形狀總是矩形尾膊。
CGRect 包含成員 origin
和 size
。origin
是類型為 CGPoint 的結構體, 它包含兩個 CGFloat 屬性: x 和 y荞彼。 size
是類型為 CGSize 的結構體, 它包含兩個 CGFloat 屬性: width 和 height冈敛。
在 Xcode 中新建一個叫做 WorldTrotter 的項目, 刪除 ViewController.swift 中的其它方法, 只保留如下結構:
import UIKit
class ViewController: UIViewController {
}
在視圖控制器的 view 被載入到內存中之后, 它的 viewDidLoad 方法會被調用。這個方法給了你自定義視圖層級的機會, 所以那是一個添加你實際視圖的好地方鸣皂。
在 ViewController.swift 中重寫 viewDidLoad 方法抓谴。創(chuàng)建一個 CGRect 作為 UIView 的 frame。然后創(chuàng)建一個 UIView 的實例, 并設置它的 backgroundColor 屬性為藍色寞缝。最后, 把 UIView 作為視圖控制器的 view 的子視圖添加上去以使它成為視圖層級的一部分癌压。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let firstFrame = CGRect(x: 160, y: 240, width: 100, height: 150)
let firstView = UIView(frame: firstFrame)
firstView.backgroundColor = UIColor.blueColor()
view.addSubview(firstView)
}
}
為了創(chuàng)建一個 CGRect, 你要使用它的構造函數(shù)并為 origin.x 、 origin.y荆陆、size.width滩届、size.height 傳入值。
為了設置 backgroundColor, 你要使用 UIColor 的類方法 blueColor()被啼。這是一個初始化 UIColor 實例為藍色的便利方法帜消。有很多 UIColor 便利方法用于普通顏色, 例如 greenColor()棠枉、blackColor() 和 clearColor()。
構建并運行該程序(Command-R)泡挺。 你會看到一個藍色的矩形, 它就是 UIView 的一個實例辈讶。 frame 中的這些值都是點(points), 而不是像素。如果那些值是像素, 則它們在不同分辨率的設備之間會不一致(例如 Retina vs. 非 Retina)娄猫。根據(jù)顯示器中像素的多少, 點也會表示多少數(shù)量的像素贱除。尺寸、位置稚新、線和曲線總是以點來描述的。
UIView 的每個實例都有一個 superview 屬性跪腹。當你添加一個視圖作為另一個視圖的子視圖時, 反轉的關系就會自動建立褂删。這時, UIView 的 superview 就是 UIWindow。
讓我們測試下視圖層級冲茸。首先, 在 ViewController.swift 中創(chuàng)建另外一個 UIView 實例, 使用不同的 frame 和背景色屯阀。
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let firstFrame = CGRect(x: 160, y: 240, width: 100, height: 150)
let firstView = UIView(frame: firstFrame)
firstView.backgroundColor = UIColor.blueColor()
view.addSubview(firstView)
let secondFrame = CGRect(x: 20, y: 30, width: 50, height: 50)
let secondView = UIView(frame: secondFrame)
secondView.backgroundColor = UIColor.greenColor()
view.addSubview(secondView)
}
}
現(xiàn)在我們調整一下視圖層級。
...
let secondView = UIView(frame: secondFrame)
secondView.backgroundColor = UIColor.greenColor()
firstView.addSubview(secondView)
現(xiàn)在綠色視圖在藍色視圖里面了轴术。
自動布局
默認地, 每個視圖有一個對齊矩形, 并且每個視圖層級都使用自動布局难衰。
對齊矩形和 frame 很相似。實際上這兩個矩形經(jīng)常是相似的逗栽。而 frame 包圍整個視圖, 對齊矩形只包圍你想用于對齊意圖的內容盖袭。圖 3.17 展示了它倆之間的不同。
你不能直接定義 view 的對齊矩形彼宠。你沒有足夠的信息(例如屏幕尺寸)來做到那鳄虱。相反, 你提供了一系列約束。 放在一塊兒, 這些約束能使系統(tǒng)確定布局屬性, 因此還有對齊矩形, 對于視圖層級中的每個視圖凭峡。
約束
不是每個布局屬性都需要一個約束拙已。如果你指定了最邊距和視圖的寬度, 那么視圖的右邊距就自動為了計算好了。
描述一個跟屏幕尺寸無關的視圖的約束, 例如你想要你最上面的 label 的約束為:
- 距離屏幕最上邊為 8 個點
- 在它的父視圖中水平居中
- 跟它的文本同高同寬
要在 Interface Builder 中把這個描述轉換為約束, 懂得怎么找到視圖的最近的兄弟視圖會有所幫助摧冀。最近的鄰居是在指定方向上最近的兄弟視圖倍踪。
如果一個視圖在指定方向上沒有任何兄弟視圖, 那么最近的鄰居就是它的父視圖, 也就是作為它的容器。
現(xiàn)在你能講清楚那個 Label 的約束了:
- 該 Label 的上邊距應該距離它的最近的鄰居(就是它的容器 — ViewController 中的 view) 8 個點索昂。
- 該 Label 的中心應該和它的父視圖的中心一樣建车。
- 該 Label 的寬度應該和以文本字體尺寸渲染的文本的寬度一樣
- 該 Label 的高度應該和以文本字體尺寸渲染的文本的高度相同。
固有內容尺寸
視圖的固有內容尺寸作為顯式的寬和高約束椒惨。如果你不指定明確測定寬度的約束, 那么視圖的寬就是它固有的寬度癞志。這同樣適用于高度。
現(xiàn)在我們對這 5 個 Labels 進行自動布局框产。
選擇最上面的那個 Label凄杯。 打開 Align 菜單并選擇 Horizontally in Container, 其中約束為 0错洁。確保 Update Frames 沒有被選中; 記住不要在視圖沒有足夠的約束之前更新 frame, 而這一個約束肯定不會提供足夠的信息來計算對齊矩形。繼續(xù)并添加一個約束戒突。
在畫布上選擇所有 5 個 Labels屯碴。同時給多個視圖添加約束也很方便。 打開 Pin 菜單并做如下選擇:
- 選擇最上面的 top 上邊距, 設置它的約束為 8
- 從 Align 菜單中, 選擇 Horizontal Centers
- 從 Updates Frames 菜單中, 選擇 Items of New Constraints
約束設置完成后的界面如下: