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

View

iOSAPP中和用戶打交道最多就是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)畫
    • 能夠在一個(gè)矩形區(qū)域繪制內(nèi)容
    • view的屬性可以做動(dòng)畫
  • 響應(yīng)事件
    • view可以接受事件
    • view參與響應(yīng)鏈
      本文講解如何創(chuàng)建view,管理views,繪制內(nèi)容,view的層級(jí)樹,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í)樹中.

使用XIB創(chuàng)建View

  • xib創(chuàng)建view是一種便捷方式,在xib中你可以拖拽UI元素進(jìn)入你的界面,配置各種屬性. xib的另一個(gè)好處是,所見即所得(xib中見到和運(yùn)行時(shí)的一樣)方便調(diào)試. 你可以將view的行為和代碼綁定起來,這樣view可以進(jìn)行用戶交互. 創(chuàng)建好后,xib會(huì)將view和view狀態(tài)等配置信息保存在nib文件中(一種資源文件)
  • 通常一個(gè)nib文件代表一個(gè)整個(gè)view層級(jí)樹,頂層是controller的view,然后再往controller的view中添加其他view. 要注意頂層view的大小要和設(shè)備以及內(nèi)容匹配.
  • 通常一個(gè)nib文件是和viewController綁定在一起的,在使用是controller會(huì)自動(dòng)從nib中加載UI界面; 如果你的nib文件沒有和controller綁定在一起的話,可以使用NSBundle或者UINib來手動(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模式來創(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:來創(chuàng)建的,UIImageView的initWithImage:等等.

view創(chuàng)建后需要將其添加到window中或其他view中,否則不能顯示.

給view的屬性賦值

通過UIView的屬性來控制view的顯示和行為.
下表展示view的屬性和作用

Properties Usage
alpha, hidden, opaque 這些控制view透明度. 注意opaque屬性,opaque屬性設(shè)置為YES可以提高性能
bounds,frame,center,transform 這些屬性控制view的size和position. transform用來做動(dòng)畫或者做view的復(fù)雜整體移動(dòng)
autoresizingMask, autoresizesSubviews 這些屬性用來控制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),用來標(biāo)記view的,方面后續(xù)使用tag值從view的層級(jí)樹中找到該view.使用tag來獲取view比遍歷尋找要快.

通過UIView的實(shí)例方法viewWithTag:,該方法使用深度優(yōu)先算法(參考數(shù)據(jù)結(jié)構(gòu)-樹)從層級(jí)樹中搜索目標(biāo),而且只會(huì)從view的本身和subview開始搜索,view的superview和其他層級(jí)樹不會(huì)搜索,也就是說如果你對(duì)root view調(diào)該方法,那么它會(huì)搜索整個(gè)頁面的層級(jí)樹,如果是樹中的某個(gè)view調(diào)用該方法,那么只會(huì)搜索某個(gè)子樹.

創(chuàng)建和管理view的層級(jí)樹

創(chuàng)建和管理view的層級(jí)樹,就是創(chuàng)建和管理APP的UI界面,層級(jí)樹決定了那個(gè)view響應(yīng)事件. 下圖展示Clock應(yīng)用的圖層,由許多view構(gòu)成UI界面:

Clock應(yīng)用的圖層

這一節(jié)講解如何創(chuàng)建view的層級(jí)樹,以及如何從層級(jí)樹找到特定的view,轉(zhuǎn)換不同的view的坐標(biāo)系.

添加移除subview

如果使用xib創(chuàng)建view層級(jí)樹,那么可以直觀地發(fā)現(xiàn)view之間的層級(jí)(父-子關(guān)系),而且界面不需要運(yùn)行就可以看到.
使用代碼創(chuàng)建的話,需要使用下么方法來創(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來確定位置和大小. subview超出superview的區(qū)域默認(rèn)是可見的,如果你想superview裁剪subview,可以將superview的clipsToBounds設(shè)置為YES.

往view的層級(jí)樹中插入subview的代碼可以寫controller的loadView(適合手動(dòng)用代碼)或者viewDidLoad中(適合xib)

下列代碼展示了Apple官方demoUIKit Catalog (iOS): Creating and Customizing UIKit Controls中類TransitionsViewController方法viewDidload中的代碼. TransitionsViewController用來管理兩個(gè)view間切換的動(dòng)畫. viewdidload中的代碼順序地創(chuàng)建一個(gè)容器view,image views用來做切換動(dòng)畫. 容器view的作用是方面做兩個(gè)image間的切換動(dòng)畫.

- (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,你可以在重寫下面方法來監(jiān)聽該通知:

  • willMoveToSuperview:,willMoveToWindow:,willRemoveSubview:
  • didAddSubview:,didMoveToSuperview,didMoveToWindow
    你可以使用上述通知來做一些和view層級(jí)樹變動(dòng)有關(guān)的操作

隱藏view

  • 有兩種方式:①設(shè)置屬性hidden為YES②設(shè)置屬性alpha為0.0
  • 隱藏的view不會(huì)響應(yīng)事件,但會(huì)參與view的布局
  • 如果想移除一個(gè)view通常隱藏該view,特別是當(dāng)該view在未來某刻需要顯示
  • 如果想給view做個(gè)隱藏/顯示動(dòng)畫,那么你應(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í)樹中找到特定的view

  • 有兩種方法:①通過保存一個(gè)該view的一個(gè)引用 ②設(shè)定一個(gè)唯一性的tag值,在使用viewWithTag:尋找
  • 通過引用方法的經(jīng)常使用,但使用tag的方法更加靈活硬編碼少點(diǎn).而且tag的方式也可以用來做數(shù)據(jù)的持久化操作,界面的恢復(fù).比如,在做界面恢復(fù)操作時(shí),可以先用個(gè)文件保存view的tag,然后將該文件寫到磁盤中,比把正界面保存好多了.在界面恢復(fù)時(shí),根據(jù)tag值可以快速確定view間的關(guān)系和是否需要顯示.

view的位移/縮放/旋轉(zhuǎn)

  • 每個(gè)view都有個(gè)transform屬性用來給view做仿射變換的, 改變view的transform會(huì)影響view的最終渲染的結(jié)果,一般用于實(shí)現(xiàn)滾動(dòng),動(dòng)畫,等視覺效果.
  • view的屬性transform的類型是一個(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提供了下面的方法用來計(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類似的工具方法:

  • convertPoint:fromWindow:
  • convertRect:fromWindow:
  • convertPoint:toWindow:
  • convertRect:toWindow:

這里有個(gè)涉及將一個(gè)旋轉(zhuǎn)過的view中的坐標(biāo)轉(zhuǎn)換到其他view的問題,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í),開發(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來控制subviews是否要重新resize. 如果給整個(gè)屬性設(shè)置為NO,那么當(dāng)view改變時(shí)它的subview也不會(huì)重新布局.
  • subview使用autoresizingMask來決定subview如何進(jìn)行大小和位置的設(shè)置.
  • 同樣的規(guī)則對(duì)subview的subview同樣有效.

在自動(dòng)布局的時(shí)候,給view設(shè)置autoresizingMask很重要,下表列舉了autoresizingMask(寬高上下左右)可能的取值,和每一個(gè)值對(duì)應(yīng)的布局操作,并且這些值可以疊加(做或運(yùn)算),然后賦值給view的autoresizingMask. 如果你是XIB來矩形局部可以使用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來設(shè)置autoresizingMask最簡單,而且還有一個(gè)動(dòng)畫展示方便理解.

注意:如果view的transform的值不為identity transform,那么view的frame會(huì)失效,同樣地對(duì)autoresizingMask也是一樣.

當(dāng)對(duì)view進(jìn)行了autoresizing設(shè)置好,UIKit還有提供一個(gè)接口開發(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方法,以供開發(fā)者手動(dòng)調(diào)整.你可以在自定義view中重寫該方法:

  • 調(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í)的,通常的做法是將大量內(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ì)變,比如電話進(jìn)來后status bar的高會(huì)變化.
    想學(xué)更多關(guān)于autoresizing的知識(shí)請(qǐng)參考蘋果文檔Handling Layout Changes Automatically Using Autoresizing Rules

在運(yùn)行時(shí)修改view

view會(huì)因?yàn)橛脩羰录淖?size,position,hidden,或者創(chuàng)建一個(gè)view的層級(jí)樹等),在iOS中view的改變可以發(fā)生下面的位置或者一下面的方法就行改變:

  • 在view controller中
    • view controller負(fù)責(zé)創(chuàng)建界面需要的view,可以從nib文件中加載,也可以從代碼中創(chuàng)建,而且controller也負(fù)責(zé)干掉無用了的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í)樹; 比如,添加一個(gè)額外的button和其他控件來處理編輯內(nèi)容,這需要調(diào)整view的層級(jí)樹.
  • 在Animation block中
    • 你可能會(huì)在Animation block處理兩組view的切換,隱藏界面中的一組view然后顯示另一組view
    • 當(dāng)你需要實(shí)現(xiàn)一個(gè)特殊的動(dòng)畫時(shí),你在Animation block中會(huì)對(duì)view的屬性進(jìn)行各種調(diào)整; 比如改變一個(gè)view的size
  • 其他方式
    • 你可能創(chuàng)建一組新的view以響應(yīng)手勢或者其它用戶事件
    • 當(dāng)你滾動(dòng)scroll view時(shí),你可能會(huì)同時(shí)隱藏和顯示tile subview
    • 當(dāng)鍵盤顯示時(shí),你可能會(huì)reposition和resize被鍵盤遮住的部分view,關(guān)于更多和鍵盤交互的知識(shí)請(qǐng)看Text Programming Guide for iOS

view controller是view的層級(jí)樹的管理者,大部分的view的修改都發(fā)生在這里,controller是view改變的終極負(fù)責(zé)人. 特別地,你可以在view controller中的setEditing:animated:方法中,將用戶界面切換到可編輯模式.

Animation block中是另一個(gè)頻繁需要修改view的地方. UIView內(nèi)置的動(dòng)畫接口可以做一些簡單的動(dòng)畫,比如你可以用如下幾個(gè)方法進(jìn)行view的切換動(dòng)畫:

  • transitionWithView:duration:options:animations:completion:
  • transitionFromView:toView:duration:options:completion:

CoreAnimation Layers的交互

每個(gè)view都一個(gè)layer用來展示內(nèi)容和動(dòng)畫. 盡管你通過view可以做很多,但你也可以直接操作view的layer

修改view的layer class

view中的layer類型在view創(chuàng)建后是不能修改的,因此可以通過view的layerClass類方法修改layer的類型.這個(gè)方法的默認(rèn)實(shí)現(xiàn)是返回[CALayer class],你可以在自定義view中重寫該方法然后返回想要的layer類型,如下代碼返回CATiledLayer類型.

+ (Class)layerClass {
    return [CATiledLayer class];
}

每個(gè)view在初始化實(shí)例之前會(huì)調(diào)用上面的方法返回layer的類型,然后根據(jù)類型創(chuàng)建layer對(duì)象. 另外將view自己設(shè)置為layer的delegate,此時(shí)layer和view的聯(lián)系就建立起來了,之后不能改變,你不能再將view自己設(shè)置為別的layer的delegate,如果你修改layer和view之間的關(guān)系,會(huì)導(dǎo)致view的內(nèi)容繪制出問題,和其他一些不可預(yù)的問題(比如crash掉)

知道其他Layer類型和作用嗎?請(qǐng)看Core Animation Reference Collection

往view中插入其他layer對(duì)象

如果你偏向使用layer而不是view,那么你可以將一個(gè)自定義的layer插入到view中. 一個(gè)自定義的layer對(duì)象是一個(gè)沒有任何view綁定的CALayer實(shí)例. 自定義layer中要使用Core Animation代碼,layer無法響應(yīng)事件只能繪制內(nèi)容,可以響應(yīng)view的size變化
下面的代碼展示了,如何使用layer,該layer用來顯示一個(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來保存加入view中的layer, 具體請(qǐng)看Core Animation Programming Guide

如何自定義view

當(dāng)UIKit提供的view無法滿足需求時(shí),就必須走上自定義view的道路. 自定義view可以完全由你控制,非常靈活.

注意:如果你使用OpenGL ES繪制內(nèi)容的話,你必須使用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)建,需要重寫initWithFrame:方法,或者自定義一個(gè)初始化方法
    • 如果重nib文件中加載,重寫initWithCoder:方法,在該方法中對(duì)view進(jìn)行一些狀態(tài)設(shè)置
  • 顯示dealloc方法,用來銷毀一些對(duì)象的
  • 要想定制任何內(nèi)容就需要重寫drawRect:方法:
  • 設(shè)置屬性autoresizingMask給view加上autoresizing功能
  • 如果你的view需要集成和管理許多的subview,那么:
    • 在初始化view的時(shí)候,創(chuàng)建subviews
    • 在創(chuàng)建subview的時(shí)候順便設(shè)置各個(gè)subview的autoresizingMask屬性
    • 如果view的subview需要手動(dòng)布局,重寫view的layoutSubviews
  • 實(shí)現(xiàn)touch-event,那么:
    • 通過addGestureRecognizer:方法給view添加合適的手勢
    • 如果你想手動(dòng)處理touches,那么可以重寫view的touchesBegan:withEvent:, touchesMoved:withEvent:, touchesEnded:withEvent:,touchesCancelled:withEvent: 四個(gè)方法(不管其他touch方法有沒有重寫,牢記你需要始終重寫touchesCancelled:withEvent:方法)
  • 如果你想定制打印的view,那么你需要重寫drawRect:forViewPrintFormatter:方法,具體請(qǐng)看Drawing and Printing Guide for iOS

另外,在重寫上面提到的方法中,你可以對(duì)view的許多屬性進(jìn)行操作,比如contentMode,也可以直接地或間接地的操作layer

初始化自定義view

每個(gè)自定義的view都需要提供initWithFrame:初始化方法.該方法在你手動(dòng)創(chuàng)建的view初始化時(shí)調(diào)用.下面的代碼展示了一個(gè)initWithFrame:方法的模板,在重寫該方法時(shí),你需要調(diào)用父類的的方法,設(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è)置,也可以重寫awakeFromNib方法對(duì)view進(jìn)一步設(shè)置.

實(shí)現(xiàn)重繪

如果自定義view需要繪制內(nèi)容,那么需要重寫drawRect:方法,在剛方法中實(shí)現(xiàn)重繪. Apple建議如果不是迫不得已的話,最好還是不要走重繪的路,可以用其他view代替.

drawRect:方法中只能干和內(nèi)容繪制相關(guān)的內(nèi)容,像更APP的數(shù)據(jù)結(jié)構(gòu)等其他和繪制無關(guān)的操作千萬不要放到這個(gè)方法中.該方法中的任務(wù)要盡量快速完成,如果你頻繁調(diào)該方法的話,那么需要優(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)行繪制.可以通過UIGraphicsGetCurrentContext方法來獲取當(dāng)前繪畫上下文.

注意:當(dāng)前繪畫上下文(current graphics context)只要在調(diào)用drawRect:時(shí)有效. UIKit可能會(huì)在不同繪制操作步驟中創(chuàng)建不同的繪畫上下文,所以你不要將該對(duì)像緩存起來供未來使用.

下面代碼展示了使用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的話,UIKit還要繪制被view遮住的內(nèi)容.
另外一個(gè)提高view性能的操作是設(shè)置clearsContextBeforeDrawing為NO,特別地,當(dāng)滾動(dòng)view的時(shí)候.如果你設(shè)置為YES的話,在drawRect方法更新內(nèi)容之前,UIKit要自動(dòng)地將view設(shè)置透明黑色. 設(shè)置NO可以避免這一操作.

響應(yīng)事件

view是一個(gè)響應(yīng)者(因?yàn)閁IView集成UIResponder). 為了能夠直接響應(yīng)事件,view可以通過手勢監(jiān)聽像taps,swipes,pinches等等這些手勢,但這是Apple封裝好的,你要可以重寫view的touches方法來自定義響應(yīng)事件:

  • touchesBegan:withEvent:
  • touchesMoved:withEvent:
  • touchesEnded:withEvent:
  • touchesCancelled:withEvent:

如果你想開啟多手指事件設(shè)置multipleTouchEnable為YES.
有的view,比如label是默認(rèn)關(guān)閉監(jiān)聽用戶事件的,既可以設(shè)置userInteractionEnabled為YES
你可以通過UIApplication對(duì)象的beginIgnoringInteractionEventsendIgnoringInteractionEvents方法來控制整個(gè)APP的事件響應(yīng)能力

注意:用UIView提供的動(dòng)畫方法進(jìn)行動(dòng)畫時(shí)是無法響應(yīng)用戶事件的. 你可以通過重寫相應(yīng)方法來配置相應(yīng)的特性,具體細(xì)節(jié)請(qǐng)看本系列文章(四)
在事件傳遞過程中,可以通過hitTest:withEvent:pointInside:withEvent:方法判斷一個(gè)view是否具有響應(yīng)特定event的能力.

垃圾清理-dealloc

自定義view有時(shí)需要用到該方法來清理垃圾. 不過很少用.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容