概述
UIView或者它的子類知道怎樣將自己繪制在一個(gè)矩形區(qū)域中形娇。我們app所有可視的的界面來自于視圖。創(chuàng)建和配置一個(gè)view非常的簡單示姿。比如你可以在Xib編輯器中拖一個(gè)UIButton或者其他view到一個(gè)View上甜橱,你也可以使用代碼來操作所有的繪制。你可以控制view顯示和消失栈戳、移動(dòng)岂傲、改變大小或者在它上面顯示其他view、給它做動(dòng)畫等子檀。
UIView是UIResponder的子類镊掖,所以也能相應(yīng)事件(和用戶交互,這也是UIView和CALayer本質(zhì)區(qū)別)褂痰。
視圖層次是視圖組織的主要模式亩进。一個(gè)view可以擁有很多子視圖,一個(gè)子視圖只能擁有一個(gè)直接父視圖缩歪,這樣就形成一個(gè)視圖的層級(jí)樹镐侯。如果一個(gè)視圖從界面上remove,它所有子視圖也會(huì)被remove掉。如果一個(gè)view隱藏(hidden)它所有子視圖hidden苟翻。其他變化同樣也會(huì)共享給他的子視圖韵卤。
我們應(yīng)該選擇xib還是code創(chuàng)建視圖,兩者沒有好壞這分崇猫,這取決于你的需求沈条、習(xí)慣和你的app的整體架構(gòu)。
Window
app的window是視圖層級(jí)最頂部的view诅炉。它是一個(gè)UIWindow對(duì)象(或者UIWindow的子類)蜡歹,UIWindow是UIView的子類。我們app擁有一個(gè)主window涕烧,在app運(yùn)行期間創(chuàng)建月而,而且不會(huì)被銷毀或者替換。其他所有可見的視圖都是它的子視圖议纯。
如果你的app可以在外部屏幕展示視圖父款,你將需要?jiǎng)?chuàng)建一個(gè)額外的UIWindow,但是在本章節(jié)瞻凤,我們假設(shè)只有一個(gè)屏幕憨攒,只有一個(gè)window
初始化一個(gè)window必須充滿設(shè)備的屏幕,確保設(shè)置window的frame等于屏幕的bounds阀参,如果你使用的是main.storyboard,在app加載的時(shí)候會(huì)自動(dòng)幫你創(chuàng)建肝集,AppDelegate頂部的那個(gè)注解@UIApplicationMain
。 如果你需要自己創(chuàng)建可以通過以下方式:
// 創(chuàng)建window iOS 9 之前
let w = UIWindow(frame: UIScreen.mainScreen().bounds)
// iOS 9 之后
let w = UIWindow()
window必須在app的整個(gè)生命周期都被持有蛛壳。所以AppDelegate擁有window的強(qiáng)引用杏瞻,我們一般不會(huì)將一個(gè)view直接放在主window上,而是通過將一個(gè)ViewController付給window的rootViewController
屬性衙荐,
如果你使用的是main.stroyboard 這個(gè)事情也是系統(tǒng)做好的捞挥,rootViewController
會(huì)直接指向main.stroyboard的initial view controller 。 一個(gè)VC成為window的rootViewController
后赫模,它的view也會(huì)變成UIWindow的直接子視圖树肃。你的app將會(huì)在調(diào)用window?.makeKeyAndVisible()
時(shí)顯示蒸矛。
總結(jié)下初始化瀑罗、創(chuàng)建、配置顯示主窗口的過程:(應(yīng)該考慮兩種情況)
-
通過main storyboard 創(chuàng)建
- storyboard 文件在 Info.plist 的鍵為 Main storyboard file base name 中指定(UIMainStoryboardFile)
- UIapplicationMain 實(shí)例化 UIWindow 并設(shè)置好 frame
- 把設(shè)置好的 UIWindow 的實(shí)例指定給 app delegate 的 window 屬性
- 實(shí)例化 view controller 并指定給 window 的 rootViewController 屬性
- 這些都發(fā)生在 app delegate 的 application:didFinishLaunchingWithOptions: 被調(diào)用之前
-
不使用 main storyboard
- 因?yàn)轫?xiàng)目模板都會(huì)自帶 storyboard雏掠,所以需要做以下步驟來獲得一個(gè)純凈的空白項(xiàng)目
- 在 General pane斩祭,選擇 Main 并且刪除
- 刪除 Main.storyboard 以及 ViewController.swift
- 刪掉 AppDelegate.swift 中的所有內(nèi)容
- 然后替換為以下代碼
- 因?yàn)轫?xiàng)目模板都會(huì)自帶 storyboard雏掠,所以需要做以下步驟來獲得一個(gè)純凈的空白項(xiàng)目
import UIKit
@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.window = UIWindow()
self.window!.rootViewController = UIViewController() // 也可以是自定義的子類
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
}
}
上面是app運(yùn)行最少需要的代碼,運(yùn)行指揮得到一個(gè)白板乡话。
如何使用window的子類:
- main storyboard
在app運(yùn)行時(shí)摧玫,在
UIApplicationMain
初始化完appDelegate后,就會(huì)詢問appDelegate的window屬性是否有值,如果為nil诬像,UIApplicationMain
就會(huì)創(chuàng)建一個(gè)UIWindow
的實(shí)例屋群,如果不為nil,直接使用其值作為main window坏挠。
??:
lazy var window : UIWindow? = {
return MyWindow()
}()
- 不使用main storyboard
這個(gè)就簡單了芍躏,window本來就是自己初始化的,直接
window = MyWondow()
app一運(yùn)行降狠,會(huì)有很多方法來引用主window
-
如果一個(gè) UIView 在界面中对竣,它自動(dòng)會(huì)有一個(gè) window 屬性,里面有對(duì) window 的引用
- 也可以使用 UIView 的 window 屬性來檢查這個(gè) view 是不是被嵌入到了 window 中榜配。如果不是否纬,那么 window 屬性為 nil。一個(gè) window 屬性為 nil 的 UIView 對(duì)用戶來說是不可見的.(
self.view.window
)
- 也可以使用 UIView 的 window 屬性來檢查這個(gè) view 是不是被嵌入到了 window 中榜配。如果不是否纬,那么 window 屬性為 nil。一個(gè) window 屬性為 nil 的 UIView 對(duì)用戶來說是不可見的.(
-
app delegate 實(shí)例會(huì)維護(hù)一個(gè)指向 window 的引用(window 屬性)蛋褥,可以通過 shared application 來獲取
let w = UIApplication.sharedApplication().delegate!.window
- 如果想要不那么通用的方法临燃,可以顯式轉(zhuǎn)換成 app delegate 類
let w = (UIApplication.sharedApplication().delegate as! AppDelegate).window
-
shared application 會(huì)在 keyWindow 屬性中維護(hù)一個(gè)指向 window 的引用
let w = UIApplication.sharedApplication().keyWindow
- 這個(gè)引用不是很穩(wěn)定,因?yàn)橄到y(tǒng)可能會(huì)創(chuàng)建臨時(shí)的 window 并且把它們當(dāng)做 key window,所以最好還是用第二種
Single View Application 模板
本章節(jié)重點(diǎn)是view壁拉,所以暫且不介紹ViewController相關(guān)內(nèi)容谬俄,后面章節(jié)會(huì)介紹
新建的Single View Application會(huì)自動(dòng)創(chuàng)建一個(gè)Main.storyboard 和 ViewController.swift作為window的rootViewController。
現(xiàn)在我們可以在ViewController.swift
中通過代碼添加一個(gè)view上去
就在viewDidLoad
方法中
override func viewDidLoad() {
super.viewDidLoad()
let mainview = self.view
let v = UIView(frame:CGRectMake(100,100,50,50))
v.backgroundColor = UIColor.redColor() // 紅色小塊
mainview.addSubview(v) // 添加到mainview
}
不包含main storyboard的實(shí)現(xiàn)方式
func application(application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?)
-> Bool {
self.window = UIWindow()
self.window!.rootViewController = UIViewController()
// here we can add subviews
let mainview = self.window!.rootViewController!.view
let v = UIView(frame:CGRectMake(100,100,50,50))
v.backgroundColor = UIColor.redColor() // small red square
mainview.addSubview(v) // add it to main view
// and the rest is as before...
self.window!.backgroundColor = UIColor.whiteColor()
self.window!.makeKeyAndVisible()
return true
}
Subview and Superview (子視圖和父視圖)
在iOS中弃理,一個(gè)subview的一部分或者全部都可以出現(xiàn)在其superview的外部溃论。一個(gè) view 可以和另一個(gè) view 重疊,即使不是其 subview 也可以繪制部分或全部繪制在另一個(gè) view 之前痘昌。
測(cè)試了下钥勋,在interface Builder中拖拽的時(shí)候是被擋住的
但是運(yùn)行結(jié)果是沒有擋住的
View 層級(jí) 的特點(diǎn)
- 如果一個(gè) view 被移出或者引入它的 superview,它的 subview 會(huì)跟著
- 一個(gè) view 的透明度會(huì)被其 subview 繼承
- 一個(gè) view 可以限制 subview 的顯示范圍辆苔,比如不讓 subview 超出 view 本身的范圍算灸,這叫做 clipping,被設(shè)置在 clipsToBounds
屬性中 - 一個(gè) superview 擁有它的 subview
- 如果一個(gè) view 的尺寸變化了驻啤,它的 subview 也會(huì)自動(dòng)被重新設(shè)置尺寸
一個(gè)UIView有一個(gè)superview
屬性和一個(gè)subviews
(數(shù)組)屬性(都是可空類型)菲驴。可以據(jù)此來判斷視圖層級(jí)骑冗。另外也有一個(gè) isDescendantOfView:
方法來檢查一個(gè) view 是不是另一個(gè) view 的 subview (可以不是直接子視圖)赊瞬。View 還有一個(gè) tag
屬性,可以通過 viewWithTag:
來進(jìn)行引用贼涩。我們最好給所有subviews設(shè)置不同的tag
使用代碼操作視圖層級(jí):(可以直接操作巧涧,也可以配合動(dòng)畫)
-
addSubview:
方法添加一個(gè) subview -
removeFromSuperview
移除一個(gè) subview -
insertSubview:atIndex:
指定index層級(jí) -
insertSubview:belowSubview:
在某個(gè)view下面添加一個(gè)subview -
insertSubview:aboveSubview:
在某個(gè)view上面添加一個(gè)subview -
exchangeSubviewAtIndex:withSubviewAtIndex:
交換兩個(gè)subview的位置 -
bringSubviewToFront:
將某個(gè)subview移動(dòng)到最前面 -
sendSubviewToBack:
將某個(gè)subview放到最后面
沒有一個(gè)方法可以直接移除一個(gè) view 的所有 subview。然而遥倦,因?yàn)橐粋€(gè) view 的 subview 數(shù)組是一個(gè)不可變的數(shù)組谤绳,所以可以用如下方法一次移除全部:
myView.subviews.forEach {$0.removeFromSuperview}
重寫下列方法就可以根據(jù)需要在不同的情況下進(jìn)行不同的操作:
didAddSubview:, willRemoveSubview:
didMoveToSuperview, willMoveToSuperview:
didMoveToWindow, willMoveToWindow:
Visibility and Opacity(可見性和透明度)
視圖的可見性可以通過設(shè)置 hidden 屬性來更改。一個(gè)隱藏的 view 無法接收觸摸事件,所以對(duì)于用戶來說相當(dāng)于不存在缩筛,但實(shí)際上是存在的消略,所以仍然可以在代碼中對(duì)其操作
View 的背景顏色可以通過其 backgroundColor 屬性來設(shè)置,顏色屬于 UIColor 類瞎抛。如果 backgroundColor 為 nil(默認(rèn)值) 那么背景就是透明的疑俭。
可以通過設(shè)置 view 的 alpha 屬性來修改透明程度,1.0 是完全不透明婿失,0.0 是透明钞艇。假設(shè)一個(gè) view 的 alpha 是 0.5,那么它的 subview 的 alpha 都是以 0.5 為基準(zhǔn)的豪硅,不可能高于 0.5哩照。而 UIColor 也有 alpha 這個(gè)屬性,所以即使一個(gè) view 的 alpha 是 1.0懒浮,它仍舊可能是透明的飘弧,因?yàn)槠?backgroundColor 可以是透明的。一個(gè) alpha 為 0.0 的 view 是完全透明的所以是不可見的砚著,通常來說也不可能被點(diǎn)擊次伶。
View 的 alpha 屬性不僅影響背景顏色,也會(huì)影響其內(nèi)容的透明度稽穆。(比如一個(gè)背景色將會(huì)滲透圖片)
我大概實(shí)驗(yàn)了下冠王,應(yīng)該是下面這個(gè)?? 的意思:
view的opaque
(不透明度),并不會(huì)影響view的樣子,更多的是對(duì)于系統(tǒng)繪制時(shí)的提示舌镶。如果一個(gè) view 的 opaque
設(shè)為 true
柱彻,因?yàn)椴挥每紤]透明的繪制,所以效率會(huì)高一點(diǎn)餐胀,并且再設(shè)置透明的背景顏色或者 alpha
屬性都無效哟楷。可能會(huì)讓人吃驚否灾,它的默認(rèn)值是 true
但是我設(shè)置了view的alpha=0.3
還是有不透明的效果(或者是疊加),在設(shè)置前后都打印了opaque
的值都是true
然后還手動(dòng)把opaque設(shè)置為false,但是也沒有什么用卖擅,懂的解釋下??
Frame
View 的 frame屬性(CGRect類) 是它本身的長方形在 superview 中的位置,注意是在 superview 的坐標(biāo)系中的位置墨技。默認(rèn)來說惩阶,superview 的坐標(biāo)系原點(diǎn)在左上,向右 x 增加健提,向下 y 增加琳猫。
看一個(gè)frame使用的簡單的例子:
let mainview = self.view
let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:CGRectMake(41, 56, 132, 194))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v3 = UIView(frame:CGRectMake(43, 197, 160, 230))
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
mainview.addSubview(v3)
以上很基礎(chǔ)的代碼伟叛,不贅述了私痹。
Bounds and Center
bounds
屬性對(duì)應(yīng)的是一個(gè) view 在自己的坐標(biāo)系統(tǒng)中的矩形尺寸(注意,frame
是在 superview
的坐標(biāo)系下的)
let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
這是一種很常見的 bounds
的用法,當(dāng)你需要往一個(gè) view 里放東西的時(shí)候紊遵,無論是手動(dòng)繪制還是放置一個(gè) subview
账千,通常都要使用 view 的 bounds
當(dāng)你改變一個(gè) view 的 bounds
時(shí),它的 frame
也會(huì)對(duì)應(yīng)改變暗膜,frame
的改變是基于其中心點(diǎn)的(中心點(diǎn)不會(huì)變)匀奏,下面的代碼描述了這個(gè)情況:
let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v2.bounds.size.height += 20
v2.bounds.size.width += 20\
效果就是從上圖變成了下圖,增加的 20 會(huì)被均勻分布在上下左右学搜,正好抵消了之前的設(shè)置娃善。
當(dāng)創(chuàng)建一個(gè) UIView 時(shí),其 bounds 的坐標(biāo)原點(diǎn)是 (0.0, 0.0)瑞佩,也就是左上角聚磺,如果改變了 bounds 的原點(diǎn),也就改變了其坐標(biāo)系炬丸,其 subview 一般也會(huì)有變化瘫寝,下面代碼描述了這種情況
let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.bounds.origin.x += 10
v1.bounds.origin.y += 10
如果你把那兩個(gè)10改成20 效果如圖
我們并沒有設(shè)置subview的任何屬性,然而subview移動(dòng)了稠炬,可以看到 subviw 向著原點(diǎn)移動(dòng)方向的反方向進(jìn)行了移動(dòng)焕阿,這是因?yàn)橐粋€(gè) view 的原點(diǎn)與其 frame
的左上角一致
我們看到改變 bounds
的 size
會(huì)影響其 frame
的 size
,改變 frame
的 size
會(huì)影響其 bounds
的 size
首启。只有 view 的 center
不會(huì)影響 bounds
的 size
暮屡。 這個(gè)屬性代表了 subview
在 superview
中的 position
。
書中給出獲取一個(gè)view的center的方法
let c = CGPointMake(theView.bounds.midX,theView.bounds.midY)
但是經(jīng)過我驗(yàn)證毅桃,這個(gè)方法獲得的是view相對(duì)于自己坐標(biāo)的栽惶,我自己測(cè)試代碼如下 , 或許應(yīng)該加上x 疾嗅, y 坐標(biāo)
let v3 = UIView(frame:CGRectMake(113, 111, 132, 194))
print(v3.center) // (179.0, 208.0)
let c = CGPointMake(v3.bounds.midX, v3.bounds.midY)
print(c) // (66.0, 97.0)
let c1 = CGPoint(x: v3.bounds.midX+113, y: v3.bounds.midY+111)
print(c1) // (179.0, 208.0)
view的bounds和center互不影響外厂,相互獨(dú)立的。frame是center和bounds便捷的表達(dá)代承。大多數(shù)情況我們只需要使用frame就可以了汁蝶。一般會(huì)通過init(frame:)
來創(chuàng)建一個(gè)view。注意有些情況下 frame 會(huì)沒有什么意義论悴,但是 bounds 和 center 總是有效的掖棉,所以建議多用 bounds 和 center 的組合,也比較容易理解膀估。
- bounds: 一個(gè) view 自己的坐標(biāo)系統(tǒng)
- center: 一個(gè) view 的坐標(biāo)系統(tǒng)和其 superview 的坐標(biāo)系統(tǒng)的關(guān)系
可以用如下方法來進(jìn)行不同 view 之間的坐標(biāo)轉(zhuǎn)換
convertPoint:fromView:, convertPoint:toView:
convertRect:fromView:, convertRect:toView:
如果第二個(gè)參數(shù)是nil幔亥,系統(tǒng)自動(dòng)填補(bǔ)為window
比如我們上面算center的例子也可以這樣轉(zhuǎn)換下
print(v3.center) // (179.0, 208.0)
let c = CGPointMake(v3.bounds.midX, v3.bounds.midY)
let c2 = v3.convertPoint(c, toView: self.view)
print(c) // (66.0, 97.0)
print(c2) // (179.0, 208.0)
注意,通過改變 center 來設(shè)置 view 的位置時(shí)察纯,如果高或?qū)挷皇桥紨?shù)帕棉,那么可能會(huì)導(dǎo)致 misaligned(錯(cuò)位)针肥。可以通過打開模擬器的 Debug -> Color Misaligned Images 來進(jìn)行檢測(cè)香伴。一個(gè)簡單的方法是調(diào)整好位置之后調(diào)用 makeIntegralInPlace 來設(shè)置 view 的 frame
Window Coordinates and Screen Coordinates(窗口坐標(biāo)和屏幕坐標(biāo))
設(shè)備屏幕是沒有 frame 的慰枕,但是有 bounds。Main window 也沒有 superview即纲,不過其 frame 被設(shè)置為屏幕的 bounds具帮,如:
let w = UIWindow(frame: UIScreen.mainScreen().bounds)
//iOS 9
let w = UIWindow() //系統(tǒng)自動(dòng)設(shè)置為上面代碼
大多數(shù)情況下,window是充滿整個(gè)屏幕的低斋,所以大多數(shù)情況下window的坐標(biāo)和screen的坐標(biāo)是一樣的蜂厅。
現(xiàn)在的 iOS 中坐標(biāo)系和手機(jī)是否選擇是有關(guān)的,有如下兩個(gè)屬性:(在實(shí)際開發(fā)中基本不會(huì)碰到)
- UIScreen 的
coordinateSpace
屬性- 這個(gè)坐標(biāo)空間會(huì)旋轉(zhuǎn)膊畴,就是高和寬在設(shè)備旋轉(zhuǎn)時(shí)會(huì)呼喚葛峻,(0.0, 0.0) 是這個(gè) app 本身的左上方
- UIScreen 的
fixedCoordinateSpace
屬性- 這個(gè)坐標(biāo)空間不會(huì)變化,就是物理上的左上角巴比,從用戶來看术奖,這里的 (0.0, 0.0) 可能是 app 本身的任何一個(gè)角
可以用下面的方法來對(duì)不同坐標(biāo)空間進(jìn)行轉(zhuǎn)換:
convertPoint:fromCoordinateSpace:,convertPoint:toCoordinateSpace:
convertRect:fromCoordinateSpace:, convertRect:toCoordinateSpace:
假設(shè)界面中有一個(gè) UIView v,我們想知道它的實(shí)際設(shè)備坐標(biāo)轻绞,可以用下面的代碼:
let r = v.superview!convertRect(v.frame, toCoordinateSpace: UIScreen.mainScreen().fixedCoordinateSpace)
但實(shí)際上你需要這種信息的機(jī)會(huì)非常少(反正我是沒遇到過需要使用的)采记,或者其實(shí)幾乎都不用擔(dān)心 window 坐標(biāo),因?yàn)樗械目梢姴僮鞫紩?huì)在 root view contoller 的 main view 中進(jìn)行政勃,它的 bounds 是會(huì)自動(dòng)調(diào)整的唧龄。
Transform ( 變換 )
一個(gè) view 的 transform 屬性改變這個(gè) view 是如何被繪制的,實(shí)際上就是一個(gè) CGAffineTransform類的 3x3 矩陣(線性代數(shù)中的概念)奸远。所有的變換都是以這個(gè) view 的 center 做基準(zhǔn)的既棺。
??
let v1 = UIView(frame:CGRectMake(113, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds.insetBy(dx: 10, dy: 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.transform = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
上面代碼的例子只是對(duì)前面例子的v1做了45度的旋轉(zhuǎn)。
我們?nèi)绻蛴∠聉1旋轉(zhuǎn)前后的三個(gè)屬性:
print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
// frame : (113.0, 111.0, 132.0, 194.0) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)
v1.transform = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
// frame : (63.7415946665928, 92.7415946665928, 230.516810666815, 230.516810666815) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)
發(fā)現(xiàn)只有frame變了懒叛,center和bounds都沒有變 丸冕,但是 frame 的數(shù)值已經(jīng)沒有意義,因?yàn)楝F(xiàn)在它的尺寸是能夠覆蓋當(dāng)前 view 的最小的矩形薛窥,并不會(huì)隨著 view 的旋轉(zhuǎn)而選擇胖烛。
如果我們把旋轉(zhuǎn)換成縮放
print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
v1.transform = CGAffineTransformMakeScale(1.8, 1)
print("frame : \(v1.frame) , bounds:\(v1.bounds) , center:\(v1.center)")
// frame : (113.0, 111.0, 132.0, 194.0) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)
// frame : (60.2, 111.0, 237.6, 194.0) , bounds:(0.0, 0.0, 132.0, 194.0) , center:(179.0, 208.0)
還是只有frame發(fā)生了改變 。因?yàn)樗奈恢貌]有變只是被拉長了诅迷。bouds并不會(huì)因此而改變
變換矩陣的計(jì)算可以連接的佩番,所以不同的變換是可以疊加的,并且順序是重要的(矩陣乘法不滿足交換律)
??
let v1 = UIView(frame:CGRectMake(20, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:v1.bounds)
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v2.transform = CGAffineTransformMakeTranslation(100, 0)
v2.transform = CGAffineTransformRotate(v2.transform, 45 * CGFloat(M_PI)/180.0)
這個(gè)例子我們先在主view上放了兩個(gè)完全重疊的view罢杉,然后對(duì)v2做了平移和旋轉(zhuǎn)的變換 趟畏,兩個(gè)疊加起來的
效果:
v2.transform = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
v2.transform = CGAffineTransformTranslate(v2.transform, 100, 0)
也可以使用這個(gè)方法CGAffineTransformConcat:
let r = CGAffineTransformMakeRotation(45 * CGFloat(M_PI)/180.0)
let t = CGAffineTransformMakeTranslation(100, 0)
v2.transform = CGAffineTransformConcat(t,r)
這個(gè)也需要注意順序
Trait Collections and Size Classes
界面上的每個(gè) view
(或者ViewController
) 都有一個(gè) traitCollection
屬性 , 值是一個(gè) UITraitCollection
,包含下面四個(gè)屬性:
-
displayScale
由當(dāng)前屏幕決定的縮放尺寸滩租,1 4以前的機(jī)型 基本沒有了應(yīng)該 2 赋秀、(4利朵,5,6 3) 3沃琅、 (iPhone 6 plus/6s Plus) 。(和UIScreen的scale值是一樣的) -
userInterfaceIdiom
一個(gè)UserIterfaceIdiom
值蜘欲,可能是 .Phone 或 .Pad益眉,來標(biāo)志不同的設(shè)備,默認(rèn)來說和 UIDevice 的 userInterfaceIdiom 屬性一致 -
horizontalSizeClass
,verticalSizeClass
姥份,是UIUserInterfaceSizeClass
值郭脂,可能是.Regular
或.Compact
- 水平和豎直都是 .Regular -> iPad
- 水平是 .Compact 豎直是 .Regular -> iPhone 在垂直方向,或者 iPad 的分屏應(yīng)用
- 水平和豎直都是 .Compact -> iPhone 在水平方向(iPhone 6/6s plus除外)
- 水平是 .Regular 豎直是 .Compact -> iPhone 6/6s Plus 在水平方向
當(dāng)應(yīng)用運(yùn)行時(shí)如果 trait collection 發(fā)生改變澈歉,會(huì)調(diào)用traitCollectionDidChange 方法
traitCollectionDidChange傳入的參數(shù)是舊的traitCollection值:
override func traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
print("old----\(previousTraitCollection?.verticalSizeClass.rawValue)")
print("old----\(previousTraitCollection?.horizontalSizeClass.rawValue)")
print("new----\(self.view.traitCollection.verticalSizeClass.rawValue)")
print("new----\(self.view.traitCollection.horizontalSizeClass.rawValue)")
}
切換幾次橫豎屏的結(jié)果:
traitCollection還可以自己設(shè)定展鸡,這個(gè)特性將在后面章節(jié)講到
Layout
superview 移動(dòng)的時(shí)候 subview 就會(huì)移動(dòng)。subview 大小和位置 會(huì)隨著 superview改變埃难,這就是layout莹弊。
一些superview動(dòng)態(tài)改變的例子:
- 屏幕旋轉(zhuǎn)的時(shí)候,左上角會(huì)發(fā)生變化涡尘,長寬也要對(duì)調(diào)
- 我們的app需要等比例匹配不同的設(shè)備尺寸
- universal app需要運(yùn)行在iPad和iPhone上忍弛,所以自己需要知道自己運(yùn)行的環(huán)境來適應(yīng)不同的屏幕
- 從xib初始化的view,需要resize去適應(yīng)所在的view
- view需要適應(yīng)別的view的變化對(duì)自己的影響考抄,比如navagationBar隱藏和顯示
- 细疚。。川梅。
在以上的任何情況下其他view可能需要Layout
Layout 有三種主要的執(zhí)行方式
- 手動(dòng)
layout:superview
在被更改尺寸會(huì)會(huì)發(fā)送 layoutSubviews 消息疯兼,如果你新建自己的子類并且重寫 layoutSubviews 就可以手動(dòng)進(jìn)行更改,這很麻煩贫途,但是可以做任何你想做的事情 -
Autoresizing:
iOS 6 之前的方式吧彪,主要是通過自己的autoresizingMask
屬性來變化 -
Autolayout:
根據(jù) view 的 constraints(NSLayoutConstraint) 來進(jìn)行變化,是很強(qiáng)大的功能丢早,不用寫代碼就可以進(jìn)行復(fù)雜的定制
Autoresizing(自動(dòng)調(diào)整大欣囱酢)
Autoresizing 是一種自動(dòng)拉伸和固定大小的一種概念,view有一個(gè)autoresizingMask
屬性 香拉,這個(gè)屬性是一個(gè)UIViewAutoresizing
的值 啦扬。默認(rèn)是.None
。下面舉例來看看它的用法:
let mainview = self.view
let v1 = UIView(frame:CGRectMake(100, 111, 132, 194))
v1.backgroundColor = UIColor(red: 1, green: 0.4, blue: 1, alpha: 1)
let v2 = UIView(frame:CGRectMake(0, 0, 132, 10))
v2.backgroundColor = UIColor(red: 0.5, green: 1, blue: 0, alpha: 1)
let v3 = UIView(frame:CGRectMake(v1.bounds.width-20, v1.bounds.height-20, 20, 20))
v3.backgroundColor = UIColor(red: 1, green: 0, blue: 0, alpha: 1)
mainview.addSubview(v1)
v1.addSubview(v2)
v1.addSubview(v3)
v2 和 v1 寬度相等
執(zhí)行結(jié)果:
如果我加上一句代碼凫碌,改變了v1的寬度呢扑毡?
v1.bounds.size.width += 40
結(jié)果如圖:
這時(shí)候就可以利用autoresizingMask
屬性來指定v2的寬度可伸縮。
v2.autoresizingMask = .FlexibleWidth
結(jié)果:
這里注定一點(diǎn)盛险,要先指定可伸縮瞄摊,再改變大小
同理v3也可以:
v2.autoresizingMask = .FlexibleWidth
v3.autoresizingMask = [.FlexibleTopMargin, .FlexibleLeftMargin]
v1.bounds.size.width += 40
v1.bounds.size.height -= 50
v3就相當(dāng)于在左邊和上邊放了彈簧
其實(shí)這種類似于約束布局勋又,現(xiàn)在大部分會(huì)選擇使用Autolayout,下面小結(jié)看下Autolayout的用法换帜。
Autolayout部分請(qǐng)戳:Views - AutoLayout