View
iOSAPP中和用戶(hù)打交道最多就是view,view有多作用,下面隨便列舉幾個(gè):
- 布局管理
- a view能夠定義和父視圖相關(guān)的,默認(rèn)的大小變動(dòng)行為
- a view使用一個(gè)數(shù)組管理它的subviews
- a view能夠改變subview的size和position
- a view能將一個(gè)坐標(biāo)系下的point轉(zhuǎn)換到另一個(gè)坐標(biāo)系的點(diǎn)
- 繪制內(nèi)容和動(dòng)畫(huà)
- 能夠在一個(gè)矩形區(qū)域繪制內(nèi)容
- view的屬性可以做動(dòng)畫(huà)
- 響應(yīng)事件
- view可以接受事件
- view參與響應(yīng)鏈
本文講解如何創(chuàng)建view,管理views,繪制內(nèi)容,view的層級(jí)樹(shù),view如何處理事件傳遞(更多內(nèi)容請(qǐng)看Event Handling Guide for iOS)
創(chuàng)建和設(shè)置View
可以使用代碼手動(dòng)創(chuàng)建也可用XIB創(chuàng)建,創(chuàng)建完view后,將其組合到view的層級(jí)樹(shù)中.
使用XIB創(chuàng)建View
- xib創(chuàng)建view是一種便捷方式,在xib中你可以拖拽UI元素進(jìn)入你的界面,配置各種屬性. xib的另一個(gè)好處是,所見(jiàn)即所得(xib中見(jiàn)到和運(yùn)行時(shí)的一樣)方便調(diào)試. 你可以將view的行為和代碼綁定起來(lái),這樣view可以進(jìn)行用戶(hù)交互. 創(chuàng)建好后,xib會(huì)將view和view狀態(tài)等配置信息保存在
nib
文件中(一種資源文件) - 通常一個(gè)nib文件代表一個(gè)整個(gè)view層級(jí)樹(shù),頂層是controller的view,然后再往controller的view中添加其他view. 要注意頂層view的大小要和設(shè)備以及內(nèi)容匹配.
- 通常一個(gè)nib文件是和viewController綁定在一起的,在使用是controller會(huì)自動(dòng)從nib中加載UI界面; 如果你的nib文件沒(méi)有和controller綁定在一起的話(huà),可以使用
NSBundle
或者UINib
來(lái)手動(dòng)從nib文件中加載界面.
想要學(xué)習(xí)更多的關(guān)于xib使用的知識(shí)請(qǐng)參考Apple文檔Interface Builder User Guide
以及controller如何加載nib文件,創(chuàng)建自定義viewController請(qǐng)看View Controller Programming Guide for iOS
以及學(xué)習(xí)如何手動(dòng)從nib文件中加載UI界面的知識(shí)請(qǐng)看Resource Programming Guide中的Nib Files
使用代碼創(chuàng)建View
通常使用allocation/initialization模式來(lái)創(chuàng)建view的,view的默認(rèn)初始化方法是initWithFrame:
CGRect viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
注意:雖然所有的view都支持
initWithFrame:
方法,但有的view有其自己的初始化方法,比如UIButton,通常都是使用buttonWithType:
來(lái)創(chuàng)建的,UIImageView的initWithImage:
等等.
view創(chuàng)建后需要將其添加到window中或其他view中,否則不能顯示.
給view的屬性賦值
通過(guò)UIView的屬性來(lái)控制view的顯示和行為.
下表展示view的屬性和作用
Properties | Usage |
---|---|
alpha, hidden, opaque | 這些控制view透明度. 注意opaque 屬性,opaque屬性設(shè)置為YES可以提高性能 |
bounds,frame,center,transform | 這些屬性控制view的size和position. transform 用來(lái)做動(dòng)畫(huà)或者做view的復(fù)雜整體移動(dòng) |
autoresizingMask, autoresizesSubviews | 這些屬性用來(lái)控制view和subviews的automatic resizing行為. 當(dāng)superview的bounds發(fā)生改變時(shí),autoresizingMask 控制view的變化;autoresizesSubviews 控制view的subviews是否需要resize. |
contentMode,contentStretch,contentScaleFactor | 這些影響view的內(nèi)容繪制. contentScaleFactor 屬性用于需要自定義重繪view的高分辨率的屏幕. |
gestureRecognizer, userInteractionEnabled, MultipleTouchEnabled, exclusiveTouch | 這些屬性控制view對(duì)于touch events的處理. |
backgroundColor, subviews, drawRect:,layer | 這些屬性控制view的內(nèi)容顯示和繪制 |
想要知道更多請(qǐng)看UIView的接口UIView Class Reference
給view添加一個(gè)記號(hào)
UIView有個(gè)tag
屬性(integer,整型,默認(rèn)為0),用來(lái)標(biāo)記view的,方面后續(xù)使用tag值從view的層級(jí)樹(shù)中找到該view.使用tag來(lái)獲取view比遍歷尋找要快.
通過(guò)UIView
的實(shí)例方法viewWithTag:
,該方法使用深度優(yōu)先算法(參考數(shù)據(jù)結(jié)構(gòu)-樹(shù))從層級(jí)樹(shù)中搜索目標(biāo),而且只會(huì)從view的本身和subview開(kāi)始搜索,view的superview和其他層級(jí)樹(shù)不會(huì)搜索,也就是說(shuō)如果你對(duì)root view調(diào)該方法,那么它會(huì)搜索整個(gè)頁(yè)面的層級(jí)樹(shù),如果是樹(shù)中的某個(gè)view調(diào)用該方法,那么只會(huì)搜索某個(gè)子樹(shù).
創(chuàng)建和管理view的層級(jí)樹(shù)
創(chuàng)建和管理view的層級(jí)樹(shù),就是創(chuàng)建和管理APP的UI界面,層級(jí)樹(shù)決定了那個(gè)view響應(yīng)事件. 下圖展示Clock應(yīng)用的圖層,由許多view構(gòu)成UI界面:
這一節(jié)講解如何創(chuàng)建view的層級(jí)樹(shù),以及如何從層級(jí)樹(shù)找到特定的view,轉(zhuǎn)換不同的view的坐標(biāo)系.
添加移除subview
如果使用xib創(chuàng)建view層級(jí)樹(shù),那么可以直觀地發(fā)現(xiàn)view之間的層級(jí)(父-子關(guān)系),而且界面不需要運(yùn)行就可以看到.
使用代碼創(chuàng)建的話(huà),需要使用下么方法來(lái)創(chuàng)建和管理:
- 將subview添加到superview使用
addSubview:
方法,該方法將subview添加superview的屬性subviews
數(shù)組中末尾 - 要將subview加入superview的subviews中的某一個(gè)為使用方法
insertSubView:...
- 要想將某個(gè)view位置改變一下,可以使用
bringSubviewToFront:
,sendSubviewToBack:
,exchangeSubviewAtIndex:withSubviewAtIndex:
,使用這些方法比使用add,remove,insert等方法要快. - 想將一個(gè)view從superview中移除,可以使用
removeFromSuperview
方法
當(dāng)一個(gè)subview添加到superview后,會(huì)根據(jù)frame來(lái)確定位置和大小. subview超出superview的區(qū)域默認(rèn)是可見(jiàn)的,如果你想superview裁剪subview,可以將superview的clipsToBounds
設(shè)置為YES.
往view的層級(jí)樹(shù)中插入subview的代碼可以寫(xiě)controller的loadView
(適合手動(dòng)用代碼)或者viewDidLoad
中(適合x(chóng)ib)
下列代碼展示了Apple官方demoUIKit Catalog (iOS): Creating and Customizing UIKit Controls中類(lèi)TransitionsViewController
方法viewDidload
中的代碼. TransitionsViewController
用來(lái)管理兩個(gè)view間切換的動(dòng)畫(huà). viewdidload中的代碼順序地創(chuàng)建一個(gè)容器view,image views用來(lái)做切換動(dòng)畫(huà). 容器view的作用是方面做兩個(gè)image間的切換動(dòng)畫(huà).
- (void)viewDidLoad {
[super viewDidLoad];
self.title = NSLocalizedString(@"TransitionsTitle", @"");
// create the container view which we will use for transition animation (centered horizontally)
CGRect frame = CGRectMake(round((self.view.bounds.size.width - kImageWidth) / 2.0),
kTopPlacement, kImageWidth, kImageHeight);
self.containerView = [[UIView alloc] initWithFrame:frame];
[self.view addSubview:self.containerView];
// create the initial image view
frame = CGRectMake(0.0, 0.0, kImageWidth, kImageHeight);
self.mainView = [[[UIImageView alloc] initWithFrame:frame] autorelease];
self.mainView.image = [UIImage imageNamed:@"scene1.jpg"];
[self.containerView addSubview:self.mainView];
// create the alternate image view (to transition between)
CGRect imageFrame = CGRectMake(0.0, 0.0, kImageWidth, kImageHeight);
self.flipToView = [[[UIImageView alloc] initWithFrame:imageFrame] autorelease];
self.flipToView.image = [UIImage imageNamed:@"scene2.jpg"];
}
如果你將一個(gè)subview又添加到另一個(gè)view,UIKit會(huì)通知它superview和它的subview. 如果是自定義的view,你可以在重寫(xiě)下面方法來(lái)監(jiān)聽(tīng)該通知:
-
willMoveToSuperview:
,willMoveToWindow:
,willRemoveSubview:
-
didAddSubview:
,didMoveToSuperview
,didMoveToWindow
你可以使用上述通知來(lái)做一些和view層級(jí)樹(shù)變動(dòng)有關(guān)的操作
隱藏view
- 有兩種方式:①設(shè)置屬性
hidden
為YES②設(shè)置屬性alpha
為0.0 - 隱藏的view不會(huì)響應(yīng)事件,但會(huì)參與view的布局
- 如果想移除一個(gè)view通常隱藏該view,特別是當(dāng)該view在未來(lái)某刻需要顯示
- 如果想給view做個(gè)隱藏/顯示動(dòng)畫(huà),那么你應(yīng)該使用
alpha
而不是hidden
注意:如果你隱藏的view當(dāng)前是first responder,那么事件會(huì)繼續(xù)傳遞給它,所以你因該在隱藏它同時(shí)將其resign first responder. 更多關(guān)于響應(yīng)鏈的知識(shí)請(qǐng)看Event Handling Guide for iOS
如何在層級(jí)樹(shù)中找到特定的view
- 有兩種方法:①通過(guò)保存一個(gè)該view的一個(gè)引用 ②設(shè)定一個(gè)唯一性的tag值,在使用
viewWithTag:
尋找 - 通過(guò)引用方法的經(jīng)常使用,但使用tag的方法更加靈活硬編碼少點(diǎn).而且tag的方式也可以用來(lái)做數(shù)據(jù)的持久化操作,界面的恢復(fù).比如,在做界面恢復(fù)操作時(shí),可以先用個(gè)文件保存view的tag,然后將該文件寫(xiě)到磁盤(pán)中,比把正界面保存好多了.在界面恢復(fù)時(shí),根據(jù)tag值可以快速確定view間的關(guān)系和是否需要顯示.
view的位移/縮放/旋轉(zhuǎn)
- 每個(gè)view都有個(gè)
transform
屬性用來(lái)給view做仿射變換的, 改變view的transform會(huì)影響view的最終渲染的結(jié)果,一般用于實(shí)現(xiàn)滾動(dòng),動(dòng)畫(huà),等視覺(jué)效果. - view的屬性
transform
的類(lèi)型是一個(gè)CGAffineTransform
結(jié)構(gòu)體,默認(rèn)值是identity transform(不會(huì)改變view外觀).你可以隨時(shí)給transform賦值,如下:
// M_PI/4.0 is one quarter of a half circle, or 45 degrees.
CGAffineTransform xform = CGAffineTransformMakeRotation(M_PI/4.0);
self.view.transform = xform;
下圖展示了transform如何旋轉(zhuǎn)一張圖片:
- 給view添加的多個(gè)仿射變換的順序會(huì)影響最終結(jié)果,比如選擇后位移和位移后選擇的結(jié)果是不一樣的,即使旋轉(zhuǎn)和位移的次數(shù)相同.做放射變換時(shí)view的center是不會(huì)變的,想知道更多的知識(shí)請(qǐng)看文檔Quartz 2D Programming Guide中的Transforms
切換不同的坐標(biāo)系
很多時(shí)候,特別是在處理touch events的時(shí)候,經(jīng)常要計(jì)算一個(gè)view的坐標(biāo)在其他view中的坐標(biāo); 比如要計(jì)算touches在某個(gè)view中的坐標(biāo). UIView
提供了下面的方法用來(lái)計(jì)算其他view在該view本地坐標(biāo):
convertPoint:fromView:
convertRect:fromView:
convertPoint:toView:
convertRect:toView:
上面方法中convert...:fromView:
將其他view中的坐標(biāo)轉(zhuǎn)換到當(dāng)前view的坐標(biāo),相反地,convert...:toView:
試講當(dāng)前view的坐標(biāo)轉(zhuǎn)換到其他view中的坐標(biāo).在上面4個(gè)方法中如果view的值設(shè)為nil,那么自動(dòng)地認(rèn)為和window進(jìn)行轉(zhuǎn)換.
UIWindow
也停供了和UIView
類(lèi)似的工具方法:
- convertPoint:fromWindow:
- convertRect:fromWindow:
- convertPoint:toWindow:
- convertRect:toWindow:
這里有個(gè)涉及將一個(gè)旋轉(zhuǎn)過(guò)的view中的坐標(biāo)轉(zhuǎn)換到其他view的問(wèn)題,UIKit會(huì)算出該旋轉(zhuǎn)view剛好包含旋轉(zhuǎn)view的矩形框,然后再講矩形框轉(zhuǎn)換到其他view的坐標(biāo),看下圖解釋:
如何在運(yùn)行時(shí)調(diào)整view的大小和位置
只要view的size改變了,那么view的subview的position和size也要相應(yīng)的改變. UIView提供兩種方式進(jìn)行View的布局:①自動(dòng)布局(當(dāng)superview變動(dòng)時(shí),設(shè)置view間的布局規(guī)則,實(shí)際的位置和大小有系統(tǒng)根據(jù)前面設(shè)置的規(guī)則自己計(jì)算) ②手動(dòng)布局(superview的size改變時(shí),開(kāi)發(fā)者自己計(jì)算subview的size和position)
為布局變動(dòng)做準(zhǔn)備
布局的變動(dòng)會(huì)因?yàn)橄旅娴倪@些原因:
- 改變view中bounds的size
- 旋轉(zhuǎn)了界面方向,通常會(huì)改變r(jià)oot view的bounds
- view的layer中加了CoreAnimation要求改變布局
- 調(diào)用了view的
setNeedsLayout
和layoutIfNeeded
方法 - 給view的layer發(fā)送
setNeedsLayout
消息
使用Autoresizing(和autolayout不一樣)進(jìn)行布局
- 當(dāng)view的size改變時(shí),view可以用屬性
autoresizesSubviews
來(lái)控制subviews是否要重新resize. 如果給整個(gè)屬性設(shè)置為NO,那么當(dāng)view改變時(shí)它的subview也不會(huì)重新布局. - subview使用
autoresizingMask
來(lái)決定subview如何進(jìn)行大小和位置的設(shè)置. - 同樣的規(guī)則對(duì)subview的subview同樣有效.
在自動(dòng)布局的時(shí)候,給view設(shè)置autoresizingMask
很重要,下表列舉了autoresizingMask(寬高上下左右)可能的取值,和每一個(gè)值對(duì)應(yīng)的布局操作,并且這些值可以疊加(做或運(yùn)算),然后賦值給view的autoresizingMask
. 如果你是XIB來(lái)矩形局部可以使用Autosizing inspector進(jìn)行相應(yīng)的設(shè)置.
Autoresizing Mask | 描述 |
---|---|
UIViewAutoresizingNone | 不進(jìn)行autoresize(默認(rèn)值) |
UIViewAutoresizingFlexibleHeight | 高度隨superview而變,如果不包含該值,高度不會(huì)改變 |
UIViewAutoresizingFlexibleWidth | 寬度隨superview而變,如果不包含該值,寬度不變 |
UIViewAutoresizingFlexibleLeftMargin | view的左邊和superview左邊的距離可以可變,如果不包含該值,那么間距不變 |
UIViewAutoresizingFlexibleRightMargin | view的右邊和superview右邊的距離可以可變,如果不包含該值,那么間距不變 |
UIViewAutoresizingFlexibleBottomMargin | view的底邊和superview底邊的距離可以可變,如果不包含該值,那么間距不變 |
UIViewAutoresizingFlexibleTopMargin | view的頂邊和superview頂邊的距離可以可變,如果不包含該值,那么間距不變 |
下圖展示上面取值代表物理意義上的圖示,某一個(gè)值的缺失代表這一物理意義是固定值,否則是隨superview的大小可變. 如果你對(duì)view進(jìn)行配置是,在同一軸上有多個(gè)可變配置,比如你對(duì)一個(gè)view同時(shí)設(shè)置UIViewAutoresizingFlexibleTopMargin和UIViewAutoresizingFlexibleBottomMargin,那么UIKit會(huì)這一軸上平均的分配任意大小
上面的配置同xib中的Autoresizing inspector來(lái)設(shè)置autoresizingMask最簡(jiǎn)單,而且還有一個(gè)動(dòng)畫(huà)展示方便理解.
注意:如果view的
transform
的值不為identity transform,那么view的frame會(huì)失效,同樣地對(duì)autoresizingMask也是一樣.
當(dāng)對(duì)view進(jìn)行了autoresizing設(shè)置好,UIKit還有提供一個(gè)接口開(kāi)發(fā)者手動(dòng)的調(diào)整view的布局.
手動(dòng)對(duì)view的布局進(jìn)行調(diào)整
當(dāng)一個(gè)view的size改變時(shí),UIKit利用autoresizingMask對(duì)view的subview進(jìn)行autoresizing,然后調(diào)用view的layoutSubViews
方法,以供開(kāi)發(fā)者手動(dòng)調(diào)整.你可以在自定義view中重寫(xiě)該方法:
- 調(diào)整subview的size和position
- 添加或者移除subview或者CoreAnimation layer
- 給subview發(fā)送
setNeedsDisplay
或setNeedsDisplayInRect:
消息強(qiáng)制subview重繪
特別提醒:如果你的應(yīng)用中有個(gè)需要滾動(dòng)顯示大量視圖的view,那么layoutSubviews
方法中的代碼很重要. 因?yàn)橛靡淮髩K顯示所有的內(nèi)容是不現(xiàn)實(shí)的,通常的做法是將大量?jī)?nèi)容分塊顯示在subview中,就像磚頭(tile View)一樣,可以復(fù)用. 所以view滾動(dòng)時(shí),在layoutSubViews
中需要將顯示完的tile View的位置放到即將要顯示的位置,然后重繪它的內(nèi)容. 關(guān)于如何顯示tileview的具體做法可以參考Apple的demoScrollViewSuite
當(dāng)你進(jìn)行布局時(shí),代碼中要確認(rèn)下面幾件事:
- 當(dāng)旋轉(zhuǎn)手機(jī)屏幕時(shí),你的布局代碼是否還能正確生效
- 你的布局代碼能否適應(yīng)status bar高度的改變,因?yàn)閟tatus bar有時(shí)會(huì)變,比如電話(huà)進(jìn)來(lái)后status bar的高會(huì)變化.
想學(xué)更多關(guān)于autoresizing的知識(shí)請(qǐng)參考蘋(píng)果文檔Handling Layout Changes Automatically Using Autoresizing Rules
在運(yùn)行時(shí)修改view
view會(huì)因?yàn)橛脩?hù)事件改變(size,position,hidden,或者創(chuàng)建一個(gè)view的層級(jí)樹(shù)等),在iOS中view的改變可以發(fā)生下面的位置或者一下面的方法就行改變:
- 在view controller中
- view controller負(fù)責(zé)創(chuàng)建界面需要的view,可以從nib文件中加載,也可以從代碼中創(chuàng)建,而且controller也負(fù)責(zé)干掉無(wú)用了的view
- 當(dāng)屏幕旋轉(zhuǎn)時(shí),controller負(fù)責(zé)調(diào)整view(大小位置隱藏創(chuàng)建等改變)
- 當(dāng)controller處理可編輯內(nèi)容時(shí),在進(jìn)入/退出可編輯狀態(tài)時(shí),controller可能會(huì)調(diào)整view的層級(jí)樹(shù); 比如,添加一個(gè)額外的button和其他控件來(lái)處理編輯內(nèi)容,這需要調(diào)整view的層級(jí)樹(shù).
- 在Animation block中
- 你可能會(huì)在Animation block處理兩組view的切換,隱藏界面中的一組view然后顯示另一組view
- 當(dāng)你需要實(shí)現(xiàn)一個(gè)特殊的動(dòng)畫(huà)時(shí),你在Animation block中會(huì)對(duì)view的屬性進(jìn)行各種調(diào)整; 比如改變一個(gè)view的size
- 其他方式
- 你可能創(chuàng)建一組新的view以響應(yīng)手勢(shì)或者其它用戶(hù)事件
- 當(dāng)你滾動(dòng)scroll view時(shí),你可能會(huì)同時(shí)隱藏和顯示tile subview
- 當(dāng)鍵盤(pán)顯示時(shí),你可能會(huì)reposition和resize被鍵盤(pán)遮住的部分view,關(guān)于更多和鍵盤(pán)交互的知識(shí)請(qǐng)看Text Programming Guide for iOS
view controller是view的層級(jí)樹(shù)的管理者,大部分的view的修改都發(fā)生在這里,controller是view改變的終極負(fù)責(zé)人. 特別地,你可以在view controller中的setEditing:animated:
方法中,將用戶(hù)界面切換到可編輯模式.
Animation block中是另一個(gè)頻繁需要修改view的地方. UIView內(nèi)置的動(dòng)畫(huà)接口可以做一些簡(jiǎn)單的動(dòng)畫(huà),比如你可以用如下幾個(gè)方法進(jìn)行view的切換動(dòng)畫(huà):
transitionWithView:duration:options:animations:completion:
transitionFromView:toView:duration:options:completion:
CoreAnimation Layers的交互
每個(gè)view都一個(gè)layer用來(lái)展示內(nèi)容和動(dòng)畫(huà). 盡管你通過(guò)view可以做很多,但你也可以直接操作view的layer
修改view的layer class
view中的layer類(lèi)型在view創(chuàng)建后是不能修改的,因此可以通過(guò)view的layerClass
類(lèi)方法修改layer的類(lèi)型.這個(gè)方法的默認(rèn)實(shí)現(xiàn)是返回[CALayer class]
,你可以在自定義view中重寫(xiě)該方法然后返回想要的layer類(lèi)型,如下代碼返回CATiledLayer
類(lèi)型.
+ (Class)layerClass {
return [CATiledLayer class];
}
每個(gè)view在初始化實(shí)例之前會(huì)調(diào)用上面的方法返回layer的類(lèi)型,然后根據(jù)類(lèi)型創(chuàng)建layer對(duì)象. 另外將view自己設(shè)置為layer的delegate,此時(shí)layer和view的聯(lián)系就建立起來(lái)了,之后不能改變,你不能再將view自己設(shè)置為別的layer的delegate,如果你修改layer和view之間的關(guān)系,會(huì)導(dǎo)致view的內(nèi)容繪制出問(wèn)題,和其他一些不可預(yù)的問(wèn)題(比如crash掉)
知道其他Layer類(lèi)型和作用嗎?請(qǐng)看Core Animation Reference Collection
往view中插入其他layer對(duì)象
如果你偏向使用layer而不是view,那么你可以將一個(gè)自定義的layer插入到view中. 一個(gè)自定義的layer對(duì)象是一個(gè)沒(méi)有任何view綁定的CALayer實(shí)例. 自定義layer中要使用Core Animation代碼,layer無(wú)法響應(yīng)事件只能繪制內(nèi)容,可以響應(yīng)view的size變化
下面的代碼展示了,如何使用layer,該layer用來(lái)顯示一個(gè)圖像:
- (void)viewDidLoad {
[super viewDidLoad];
// Create the layer.
CALayer* myLayer = [[CALayer alloc] init];
// Set the contents of the layer to a fixed image. And set
// the size of the layer to match the image size.
UIImage layerContents = [[UIImage imageNamed:@"myImage"] retain];
CGSize imageSize = layerContents.size;
myLayer.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
myLayer = layerContents.CGImage;
// Add the layer to the view.
CALayer* viewLayer = self.view.layer;
[viewLayer addSublayer:myLayer];
// Center the layer in the view.
CGRect viewBounds = backingView.bounds;
myLayer.position = CGPointMake(CGRectGetMidX(viewBounds), CGRectGetMidY(viewBounds));
}
你可以往view中加入多個(gè)layer,因?yàn)関iew的layer也有個(gè)數(shù)組屬性sublayers
來(lái)保存加入view中的layer, 具體請(qǐng)看Core Animation Programming Guide
如何自定義view
當(dāng)UIKit提供的view無(wú)法滿(mǎn)足需求時(shí),就必須走上自定義view的道路. 自定義view可以完全由你控制,非常靈活.
注意:如果你使用OpenGL ES繪制內(nèi)容的話(huà),你必須使用GLKView代替繼承
UIView
.具體請(qǐng)看OpenGL ES Programming Guide
關(guān)于實(shí)現(xiàn)自定義View的基本操作
實(shí)現(xiàn)自定義view要做的事主要有兩件:①展示內(nèi)容 ②管理view的交互,當(dāng)想更好的實(shí)現(xiàn)自定義view光這兩點(diǎn)還不夠,下面列舉了實(shí)現(xiàn)自定義view需要完成的步驟:
- 給view定義幾個(gè)何時(shí)的初始化方法:
- 如果手動(dòng)創(chuàng)建,需要重寫(xiě)
initWithFrame:
方法,或者自定義一個(gè)初始化方法 - 如果重nib文件中加載,重寫(xiě)
initWithCoder:
方法,在該方法中對(duì)view進(jìn)行一些狀態(tài)設(shè)置
- 如果手動(dòng)創(chuàng)建,需要重寫(xiě)
- 顯示dealloc方法,用來(lái)銷(xiāo)毀一些對(duì)象的
- 要想定制任何內(nèi)容就需要重寫(xiě)
drawRect:
方法: - 設(shè)置屬性
autoresizingMask
給view加上autoresizing功能 - 如果你的view需要集成和管理許多的subview,那么:
- 在初始化view的時(shí)候,創(chuàng)建subviews
- 在創(chuàng)建subview的時(shí)候順便設(shè)置各個(gè)subview的
autoresizingMask
屬性 - 如果view的subview需要手動(dòng)布局,重寫(xiě)view的
layoutSubviews
- 實(shí)現(xiàn)touch-event,那么:
- 通過(guò)
addGestureRecognizer:
方法給view添加合適的手勢(shì) - 如果你想手動(dòng)處理touches,那么可以重寫(xiě)view的
touchesBegan:withEvent:
,touchesMoved:withEvent:
,touchesEnded:withEvent:
,touchesCancelled:withEvent:
四個(gè)方法(不管其他touch方法有沒(méi)有重寫(xiě),牢記你需要始終重寫(xiě)touchesCancelled:withEvent:
方法)
- 通過(guò)
- 如果你想定制打印的view,那么你需要重寫(xiě)
drawRect:forViewPrintFormatter:
方法,具體請(qǐng)看Drawing and Printing Guide for iOS
另外,在重寫(xiě)上面提到的方法中,你可以對(duì)view的許多屬性進(jìn)行操作,比如contentMode
,也可以直接地或間接地的操作layer
初始化自定義view
每個(gè)自定義的view都需要提供initWithFrame:
初始化方法.該方法在你手動(dòng)創(chuàng)建的view初始化時(shí)調(diào)用.下面的代碼展示了一個(gè)initWithFrame:
方法的模板,在重寫(xiě)該方法時(shí),你需要調(diào)用父類(lèi)的的方法,設(shè)置view的狀態(tài),初始化實(shí)例變量,然后再將初始化完成的view返回.
- (id)initWithFrame:(CGRect)aRect {
self = [super initWithFrame:aRect];
if (self) {
// setup the initial properties of the view
...
}
return self;
}
如果從nib文件中加載view,那么你要記得回調(diào)用initWithCoder:
方法而不是initWithFrame:
,該方法是協(xié)議NSCoding的一部分. 在該方法中,你可以view的狀態(tài)進(jìn)行設(shè)置,也可以重寫(xiě)awakeFromNib
方法對(duì)view進(jìn)一步設(shè)置.
實(shí)現(xiàn)重繪
如果自定義view需要繪制內(nèi)容,那么需要重寫(xiě)drawRect:
方法,在剛方法中實(shí)現(xiàn)重繪. Apple建議如果不是迫不得已的話(huà),最好還是不要走重繪的路,可以用其他view代替.
在drawRect:
方法中只能干和內(nèi)容繪制相關(guān)的內(nèi)容,像更APP的數(shù)據(jù)結(jié)構(gòu)等其他和繪制無(wú)關(guān)的操作千萬(wàn)不要放到這個(gè)方法中.該方法中的任務(wù)要盡量快速完成,如果你頻繁調(diào)該方法的話(huà),那么需要優(yōu)化你的繪制算法,能夠快速完成.
在調(diào)用drawRect:
方法前,UIKit會(huì)先給view配置內(nèi)容繪制環(huán)境. 特別是創(chuàng)建graphic context對(duì)象和調(diào)整坐標(biāo)系. 當(dāng)環(huán)境創(chuàng)建后,你才能用UIKit和core graphic等技術(shù)進(jìn)行繪制.可以通過(guò)UIGraphicsGetCurrentContext
方法來(lái)獲取當(dāng)前繪畫(huà)上下文.
注意:當(dāng)前繪畫(huà)上下文(current graphics context)只要在調(diào)用
drawRect:
時(shí)有效. UIKit可能會(huì)在不同繪制操作步驟中創(chuàng)建不同的繪畫(huà)上下文,所以你不要將該對(duì)像緩存起來(lái)供未來(lái)使用.
下面代碼展示了使用drawRect:
方法繪制一個(gè)邊寬為10.0的view:
- (void)drawRect:(CGRect)rect {
CGContextRef context = UIGraphicsGetCurrentContext();
CGRect myFrame = self.bounds;
// Set the line width to 10 and inset the rectangle by
// 5 pixels on all sides to compensate for the wider line.
CGContextSetLineWidth(context, 10);
CGRectInset(myFrame, 5, 5);
[[UIColor redColor] set];
UIRectFrame(myFrame);
}
如果你知道你的view的內(nèi)容是不透明的,那么你可以將view的opaque
屬性設(shè)置為YES,這樣可以提高性能. 如果你設(shè)置NO的話(huà),UIKit還要繪制被view遮住的內(nèi)容.
另外一個(gè)提高view性能的操作是設(shè)置clearsContextBeforeDrawing
為NO,特別地,當(dāng)滾動(dòng)view的時(shí)候.如果你設(shè)置為YES的話(huà),在drawRect方法更新內(nèi)容之前,UIKit要自動(dòng)地將view設(shè)置透明黑色. 設(shè)置NO可以避免這一操作.
響應(yīng)事件
view是一個(gè)響應(yīng)者(因?yàn)閁IView集成UIResponder). 為了能夠直接響應(yīng)事件,view可以通過(guò)手勢(shì)監(jiān)聽(tīng)像taps,swipes,pinches等等這些手勢(shì),但這是Apple封裝好的,你要可以重寫(xiě)view的touches方法來(lái)自定義響應(yīng)事件:
- touchesBegan:withEvent:
- touchesMoved:withEvent:
- touchesEnded:withEvent:
- touchesCancelled:withEvent:
如果你想開(kāi)啟多手指事件設(shè)置multipleTouchEnable
為YES.
有的view,比如label是默認(rèn)關(guān)閉監(jiān)聽(tīng)用戶(hù)事件的,既可以設(shè)置userInteractionEnabled
為YES
你可以通過(guò)UIApplication
對(duì)象的beginIgnoringInteractionEvents
和endIgnoringInteractionEvents
方法來(lái)控制整個(gè)APP的事件響應(yīng)能力
注意:用UIView提供的動(dòng)畫(huà)方法進(jìn)行動(dòng)畫(huà)時(shí)是無(wú)法響應(yīng)用戶(hù)事件的. 你可以通過(guò)重寫(xiě)相應(yīng)方法來(lái)配置相應(yīng)的特性,具體細(xì)節(jié)請(qǐng)看本系列文章(四)
在事件傳遞過(guò)程中,可以通過(guò)hitTest:withEvent:
和pointInside:withEvent:
方法判斷一個(gè)view是否具有響應(yīng)特定event的能力.
垃圾清理-dealloc
自定義view有時(shí)需要用到該方法來(lái)清理垃圾. 不過(guò)很少用.