iOS View 編程指導(dǎo)(三)-View

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界面:

Clock應(yīng)用的圖層

這一節(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)一張圖片:


旋轉(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),看下圖解釋:


轉(zhuǎn)換旋轉(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的setNeedsLayoutlayoutIfNeeded方法
  • 給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è)置UIViewAutoresizingFlexibleTopMarginUIViewAutoresizingFlexibleBottomMargin,那么UIKit會(huì)這一軸上平均的分配任意大小

autoresizingMask圖示

上面的配置同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ā)送setNeedsDisplaysetNeedsDisplayInRect:消息強(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è)置
  • 顯示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:方法)
  • 如果你想定制打印的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ì)象的beginIgnoringInteractionEventsendIgnoringInteractionEvents方法來(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ò)很少用.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末壹将,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 222,729評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件彼棍,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)捍壤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,226評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)鞍爱,“玉大人鹃觉,你說(shuō)我怎么就攤上這事《锰樱” “怎么了盗扇?”我有些...
    開(kāi)封第一講書(shū)人閱讀 169,461評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)沉填。 經(jīng)常有香客問(wèn)我粱玲,道長(zhǎng),這世上最難降的妖魔是什么拜轨? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 60,135評(píng)論 1 300
  • 正文 為了忘掉前任抽减,我火速辦了婚禮,結(jié)果婚禮上橄碾,老公的妹妹穿的比我還像新娘卵沉。我一直安慰自己颠锉,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,130評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布史汗。 她就那樣靜靜地躺著琼掠,像睡著了一般。 火紅的嫁衣襯著肌膚如雪停撞。 梳的紋絲不亂的頭發(fā)上瓷蛙,一...
    開(kāi)封第一講書(shū)人閱讀 52,736評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音戈毒,去河邊找鬼艰猬。 笑死,一個(gè)胖子當(dāng)著我的面吹牛埋市,可吹牛的內(nèi)容都是我干的冠桃。 我是一名探鬼主播,決...
    沈念sama閱讀 41,179評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼道宅,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼食听!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起污茵,我...
    開(kāi)封第一講書(shū)人閱讀 40,124評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤樱报,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后泞当,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體迹蛤,經(jīng)...
    沈念sama閱讀 46,657評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,723評(píng)論 3 342
  • 正文 我和宋清朗相戀三年零蓉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了笤受。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,872評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡敌蜂,死狀恐怖箩兽,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情章喉,我是刑警寧澤汗贫,帶...
    沈念sama閱讀 36,533評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站秸脱,受9級(jí)特大地震影響落包,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜摊唇,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,213評(píng)論 3 336
  • 文/蒙蒙 一咐蝇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧巷查,春花似錦有序、人聲如沸抹腿。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,700評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)警绩。三九已至,卻和暖如春盅称,著一層夾襖步出監(jiān)牢的瞬間肩祥,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,819評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工缩膝, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留混狠,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 49,304評(píng)論 3 379
  • 正文 我出身青樓逞盆,卻偏偏與公主長(zhǎng)得像檀蹋,于是被迫代替她去往敵國(guó)和親松申。 傳聞我的和親對(duì)象是個(gè)殘疾皇子云芦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,876評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容