轉(zhuǎn)載自:https://www.cnblogs.com/zy1987/p/3184129.html
iOS中應(yīng)用程序基本上都是基于MVC模式開(kāi)發(fā)的。UIView就是模型-視圖-控制器中的視圖,在iOS終端上看到的、摸到的都是UIView。
UIView在屏幕上定義了一個(gè)矩形區(qū)域和管理區(qū)域內(nèi)容的接口。在運(yùn)行時(shí),一個(gè)視圖對(duì)象控制該區(qū)域的渲染;UIView繼承自UIResponder,UIResponder是用來(lái)響應(yīng)事件的類,UIView也具有響應(yīng)事件的能力。所以說(shuō)UIView具有三個(gè)基本的功能,繪制內(nèi)容并管理內(nèi)容的布局,響應(yīng)用戶交互,動(dòng)畫(huà)。正是因?yàn)閁IView具有這些功能,它才能擔(dān)當(dāng)起MVC中視圖層的作用。
在開(kāi)發(fā)中可以使用UIKit框架中已經(jīng)提供的視圖組件他們大多繼承自UIView,當(dāng)然也可以通過(guò)繼承UIView定義自己的視圖。只有主線程才能更新UI。UIKit框架內(nèi)的組件包括UIView及其子類的所有操作都必須在主線程中進(jìn)行,否則會(huì)出現(xiàn)不可預(yù)知的問(wèn)題。
本章主要介紹渲染和內(nèi)容管理。
1、創(chuàng)建
- (id)initWithFrame:(CGRect)frame; 通過(guò)frame創(chuàng)建一個(gè)view。
2、幾何屬性
視圖對(duì)象使用frame, bounds和center屬性來(lái)跟蹤它的尺寸和位置:
frame屬性指定了在視圖的尺寸和在父視圖中的位置。
center屬性指定了視圖的中點(diǎn)在父視圖的位置。
bounds屬性指定了在視圖本地坐標(biāo)系統(tǒng)中視圖的尺寸。默認(rèn)原點(diǎn)是(0,0)。
可以通過(guò)center和bounds計(jì)算得到frame,反之同理。frame.origin.x == center.x - bounds.size.width / 2; frame.size == bounds.size;
transform屬性用于視圖的動(dòng)畫(huà),通過(guò)操作transform實(shí)現(xiàn)視圖的旋轉(zhuǎn)、縮放。
- (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event
- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event
這兩個(gè)方法主要用于響應(yīng)者鏈, 用于確定視圖是否為第一響應(yīng)者,當(dāng)用戶碰觸屏幕的時(shí)候系統(tǒng)會(huì)生成一個(gè)碰觸點(diǎn)。
為了確認(rèn)哪個(gè)控件是這個(gè)碰觸的第一響應(yīng)者系統(tǒng)會(huì)調(diào)用window的hitTest方法,hitTest會(huì)調(diào)用pointInside方法判斷觸碰點(diǎn)是否在當(dāng)前視圖的區(qū)域內(nèi)。如果在返回YES,則調(diào)用該試圖所有子試圖的hitTest方法;如果不在則返回NO,hitTest函數(shù)直接返回nil;
如果pointInside方法返回YES,且沒(méi)有子視圖或者子視圖的hiteTest方法返回都為nil則此視圖為第一響應(yīng)者,第一響應(yīng)者是響應(yīng)者鏈的開(kāi)端
點(diǎn)轉(zhuǎn)換方法。比如一個(gè)view上有一個(gè)button,在button上有一個(gè)點(diǎn)p,這個(gè)點(diǎn)相對(duì)button的坐標(biāo)知道了,想知道這個(gè)點(diǎn)在這個(gè)view上的坐標(biāo),用這兩個(gè)api去轉(zhuǎn)換。
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView * )view
**[button convertPoint:p toView:view]** 這樣得到view上的點(diǎn)。
**- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView * )view**
**[view convertPoint:p fromView:button];**
同點(diǎn)轉(zhuǎn)換方法,轉(zhuǎn)換一個(gè)矩形
- (CGRect)convertRect:(CGRect)rect toView:(UIView * )view
- (CGRect)convertRect:(CGRect)rect fromView:(UIView * )view
- (CGSize)sizeThatFits:(CGSize)size
返回最合適的尺寸,size是首選尺寸。只返回尺寸不會(huì)改變尺寸,如一個(gè)UILabel的text發(fā)生改變,需要UILabel的bounds調(diào)整,調(diào)用這個(gè)方法會(huì)返回一個(gè)最合適的值。
- (void)sizeToFit
按照sizeThatFits的返回值重新設(shè)置視圖的bounds。
autoresizingMask 當(dāng)父視圖的bounds發(fā)生改變時(shí)通過(guò)這個(gè)屬性決定如何調(diào)整自己的frame。缺省的值為UIViewAutoresizingNone,表示當(dāng)父視圖bound變化時(shí),自己相對(duì)于父視圖的frame不變??梢酝ㄟ^(guò)|設(shè)置多個(gè)規(guī)則,如:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
UIViewAutoresizingNone
UIViewAutoresizingFlexibleLeftMargin 到屏幕左邊的距離隨著父視圖的寬度按比例改變
UIViewAutoresizingFlexibleWidth 視圖的寬度隨著父視圖的寬度按比例改變
UIViewAutoresizingFlexibleRightMargin 到屏幕右邊的距離隨著父視圖的寬度按比例改變
UIViewAutoresizingFlexibleTopMargin 到屏幕頂部的距離隨著父視圖的高度按比例改變
UIViewAutoresizingFlexibleHeight 視圖的高度隨著父視圖的高度等比例改變
UIViewAutoresizingFlexibleBottomMargin
**autoresizesSubviews **默認(rèn)值YES,如果設(shè)置為NO那么該視圖的所有直接子視圖的frame自動(dòng)調(diào)整行為將被忽略,也就是說(shuō)無(wú)論子視圖的autoresizingMask屬性設(shè)置成什么都相當(dāng)于置為UIViewAutoresizingNone。
3、視圖層次
UIView除了提供自己的內(nèi)容外,還可以作為一個(gè)視圖容器。當(dāng)一個(gè)視圖包含其他視圖時(shí),就在兩者之間建立了一個(gè)父子關(guān)系。在視覺(jué)上子視圖隱藏了父視圖的內(nèi)容,如果一個(gè)子視圖是完全不透明的,那么子視圖所在區(qū)域就完全遮擋了父視圖的相應(yīng)區(qū)域。如果子視圖是部分透明的那么兩個(gè)視圖在顯示上就混合在了一起。父子視圖關(guān)系也會(huì)影響一些視圖行為,改變父視圖的尺寸也會(huì)相應(yīng)的改變子視圖的尺寸。隱藏父視圖,改變父視圖的alpha值,轉(zhuǎn)換父視圖都會(huì)影響到子視圖。
UIView通過(guò)NSArray管理子視圖的。通過(guò)屬性subviews可以訪問(wèn)視圖的所有子視圖。通過(guò)NSArray的特點(diǎn)可以得出兩點(diǎn):
(1)子視圖的引用計(jì)數(shù)會(huì)+1,當(dāng)父視圖釋放的時(shí)候子視圖的引用計(jì)數(shù)-1。
?。?)子視圖是有序的,后加入的子視圖會(huì)疊在上一個(gè)子視圖之上。
- (void)addSubview:(UIView * )view 方法增加一個(gè)子視圖。
操作子視圖的方法:
- (void)insertSubview:(UIView * )view atIndex:(NSInteger)index
在指定的層級(jí)插入一個(gè)子視圖。最底層是0;最頂層就是subviews count,相當(dāng)于addSubview
- (void)insertSubview:(UIView * )b belowSubview:(UIView * )c
b成為子視圖并且在已有的子視圖c的下面
- (void)insertSubview:(UIView * )b aboveSubview (UIView * )c
b成為子視圖并且在已有的子視圖c的上面
- (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2 交換兩個(gè)子視圖的層級(jí)。
- (void)bringSubviewToFront:(UIView * )view 視圖上升到最頂層。
- (void)sendSubviewToBack:(UIView * )view
視圖下降到最底層
- (void)removeFromSuperview
刪除所有子視圖。
superview 屬性用于獲取當(dāng)前視圖的父視圖。
- (void)setNeedsLayout
調(diào)用此方法通知系統(tǒng)view的內(nèi)容需要重新繪制,會(huì)異步調(diào)用layoutSubviews方法,view會(huì)在下一個(gè)drawing周期繪制。如果只是簡(jiǎn)單的改變view的幾何形狀或者當(dāng)前視圖并沒(méi)有在窗口中顯示,系統(tǒng)可能不會(huì)調(diào)用layoutSubviews方法。
- (void)layoutIfNeeded
調(diào)用此方法通知系統(tǒng)view的內(nèi)容需要重新繪制,調(diào)用layoutSubviews方法。與setNeedLayout不同的時(shí)不會(huì)等到下一個(gè)drawing周期,會(huì)立即調(diào)用layoutSubviews方法。
- (void)layoutSubviews
此方法的缺省實(shí)現(xiàn)是空。子類可以去重寫(xiě)此方法當(dāng)需要更精確的subviews布局。當(dāng)subviews的autoresizes行為不能滿足要求時(shí)才去重寫(xiě)此方法??梢栽趯?shí)現(xiàn)中直接設(shè)置subviews的frame。此方法不能被直接調(diào)用。如果想要在下一個(gè)drawing周期去更新view布局,應(yīng)該調(diào)用setNeedsLayout方法;如果想立即更新view的布局,應(yīng)該調(diào)用layoutIfNeeded方法。
layoutSubviews之所以不能被直接調(diào)用的原因可能是系統(tǒng)在繪制時(shí)需要進(jìn)行一些操作并判斷需不需要調(diào)用layoutSubviews的方法,并且在一次運(yùn)行循環(huán)(run loop)中無(wú)論調(diào)用多少次setNeedsLayout都只調(diào)用一次layoutSubviews。這樣就避免了資源的重復(fù)調(diào)用。
4、渲染屬性
clipsToBounds 當(dāng)值為YES時(shí),子視圖如果超過(guò)了當(dāng)前視圖的區(qū)域,超出的部分就會(huì)被裁掉。默認(rèn)值是NO,也就是說(shuō)當(dāng)子視圖顯示區(qū)域大于主視圖的時(shí)候還是正常顯示不會(huì)裁剪。
backgroundColor 設(shè)置背景色
alpha設(shè)置視圖的透明度,當(dāng)為1時(shí)不透明,為0時(shí)完全透明,既隱藏。默認(rèn)值是1。alpha值會(huì)影響到子視圖。如果想讓父視圖半透明而子視圖不受影響可以設(shè)置父視圖的backgroundColor = [UIColor colorWithWhite:0 alpha:0.8],這樣就ok了。
opaque給繪圖系統(tǒng)提供一個(gè)性能優(yōu)化開(kāi)關(guān)。如果該值為YES,那么繪圖在繪制該視圖的時(shí)候把整個(gè)視圖當(dāng)作不透明對(duì)待。這樣,繪圖系統(tǒng)在執(zhí)行繪圖過(guò)程中會(huì)優(yōu)化一些操作并提升系統(tǒng)性能;如果是設(shè)置為NO, 繪圖系統(tǒng)將其和其他內(nèi)容平等對(duì)待,不去做優(yōu)化操作。為了性能方面的考量,默認(rèn)被置為YES(意味著‘優(yōu)化’)。
一個(gè)不透明視圖需要整個(gè)邊界里面的內(nèi)容都是不透明的。基于這個(gè)原因,opaque設(shè)置為YES,要求對(duì)應(yīng)的alpha必須為1.0。如果一個(gè)UIView實(shí)例opaque被設(shè)置為YES, 而同時(shí)它又沒(méi)有完全填充它的邊界(bounds),或者它包含了整個(gè)或部分的透明的內(nèi)容視圖,那么將會(huì)導(dǎo)致未知的結(jié)果。
clearsContextBeforeDrawing 決定繪制前是否清屏,默認(rèn)為YES。用于提高描畫(huà)性能,特別是在可滾動(dòng)的視圖中。當(dāng)這個(gè)屬性被設(shè)置為YES時(shí),UIKIt會(huì)在調(diào)用drawRect:方法之前,把即將被該方法更新的區(qū)域填充為透明的黑色。將這個(gè)屬性設(shè)置為NO可以取消相應(yīng)的填充操作,view中原有內(nèi)容會(huì)保留。
hidden 視圖是否隱藏,默認(rèn)為NO(顯示),YES為隱藏。
contentMode 視圖內(nèi)容的填充方式。類型是UIViewContentMode。默認(rèn)值是UIViewContentModeScaleToFill,填充到整個(gè)視圖區(qū)域,不等比例拉伸。
typedef NS_ENUM(NSInteger, UIViewContentMode) {
UIViewContentModeScaleToFill, 填充到整個(gè)視圖區(qū)域,不等比例拉伸
UIViewContentModeScaleAspectFit, 長(zhǎng)寬等比填充視圖區(qū)域,當(dāng)某一個(gè)邊到達(dá)視圖邊界的時(shí)候就不再拉伸,保證內(nèi)容的長(zhǎng)寬比是不變的同時(shí)盡可能的填充視圖區(qū)域。
UIViewContentModeScaleAspectFill, 長(zhǎng)寬等比填充視圖區(qū)域,當(dāng)某一個(gè)邊到達(dá)視圖邊界的時(shí)候還繼續(xù)拉伸,直到另一個(gè)方向達(dá)到視圖邊界。內(nèi)容的長(zhǎng)寬比不變的同時(shí)填滿整個(gè)視圖區(qū)域,不顯示超過(guò)的部分。
UIViewContentModeRedraw, 重繪視圖邊界
UIViewContentModeCenter, 視圖居中
UIViewContentModeTop, 視圖頂部對(duì)齊
UIViewContentModeBottom, 視圖底部對(duì)齊
UIViewContentModeLeft, 視圖左側(cè)對(duì)齊
UIViewContentModeRight, 視圖右側(cè)對(duì)齊
UIViewContentModeTopLeft, 視圖左上角對(duì)齊
UIViewContentModeTopRight, 視圖右上角對(duì)齊
UIViewContentModeBottomLeft, 視圖左下角對(duì)齊
UIViewContentModeBottomRight, 視圖右下角對(duì)齊
};
以上模式中凡是沒(méi)有帶scale的,內(nèi)容bounds超出視圖bounds時(shí)只有未超出部分才在視圖bounds中顯示。
下圖很好的解釋了contentMode效果

autoresizingMask當(dāng)父視圖的bounds發(fā)生改變時(shí)通過(guò)這個(gè)屬性決定如何調(diào)整自己的frame。缺省的值為UIViewAutoresizingNone,表示當(dāng)父視圖bound變化時(shí),自己相對(duì)于父視圖的frame不變??梢酝ㄟ^(guò)|設(shè)置多個(gè)規(guī)則,如:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight
<pre class="declaration" style="margin: 0px; padding: 0px; white-space: pre-wrap; word-wrap: break-word;"> enum {
UIViewAutoresizingNone = 0,
UIViewAutoresizingFlexibleLeftMargin = 1 << 0, 到屏幕左邊的距離隨著父視圖的寬度按比例改變
UIViewAutoresizingFlexibleWidth = 1 << 1, 視圖的寬度隨著父視圖的寬度按比例改變
UIViewAutoresizingFlexibleRightMargin = 1 << 2, 到屏幕右邊的距離隨著父視圖的寬度按比例改變
UIViewAutoresizingFlexibleTopMargin = 1 << 3, 到屏幕頂部的距離隨著父視圖的高度按比例改變
UIViewAutoresizingFlexibleHeight = 1 << 4, 視圖的高度隨著父視圖的高度等比例改變
UIViewAutoresizingFlexibleBottomMargin = 1 << 5
};
typedef NSUInteger UIViewAutoresizing;</pre>
autoresizesSubviews默認(rèn)值YES,如果設(shè)置為NO那么該視圖的所有直接子視圖的frame自動(dòng)調(diào)整行為將被忽略,也就是說(shuō)無(wú)論子視圖的autoresizingMask屬性設(shè)置成什么都相當(dāng)于置為UIViewAutoresizingNone。
contentStretch用于制定哪部分是可拉伸的,取值在 0.0到1.0之間。下面用一個(gè)例子解釋contentStretch是如何工作的。
[imageView setContentStretch:CGRectMake(150.0/300.0, 100.0/200.0, 10.0/300.0, 10.0/200.0)];
image.png的大小是 200 x 150 ;
mageView的frame是(0,0,300,200);
150.0/300.0表示x軸上,前150個(gè)像素不進(jìn)行拉伸。
100.0/200.0表示y軸上,前100個(gè)像素不進(jìn)行拉伸。
10.0/300.0表示x軸上150后的10個(gè)像素(151-160)進(jìn)行拉伸,直到image.png鋪滿imageView。
10.0/200.0表示y軸上100后的10個(gè)像素(101-110)進(jìn)行拉伸,直到image.png鋪滿imageView。
- (void)setNeedsDisPlay
調(diào)用此方法通知系統(tǒng)view需要重新繪制,會(huì)異步自動(dòng)調(diào)用drawRect方法。
- (void)setNeedsDisplayInRect:(CGRect)rect
同樣異步調(diào)用drawRect方法。在一次運(yùn)行循環(huán)(run loop)中無(wú)論調(diào)用setNeedsDisplay或setNeedsDisplayInRect多少次,只調(diào)用drawRect一次。也是從減少資源開(kāi)銷的角度考慮的。
- (void)drawRect:(CGRect)rect
此方法的缺省實(shí)現(xiàn)是空。子類使用原生的繪制技術(shù)(Core Graphics and UIKit)繪制內(nèi)容時(shí)應(yīng)該重寫(xiě)此方法,在方法里面寫(xiě)出自己的drawing code。如果view設(shè)置自己的內(nèi)容用其他的方法,則不需要去重寫(xiě)此方法。如果直接從UIView對(duì)象繼承,實(shí)現(xiàn)此方法不需要call super。然而如果從其他的UIView對(duì)象繼承,則應(yīng)該調(diào)用super。當(dāng)view 第一次顯示或者當(dāng)view的可視的一部分無(wú)效時(shí),此方法會(huì)調(diào)用。此方法不能直接被調(diào)用。調(diào)用setNeedsDisplay 或者 setNeedsDisplayInRect: 方法會(huì)觸發(fā)重新繪制
5、視圖繪制周期
UIView類使用一個(gè)點(diǎn)播繪制模型來(lái)展示內(nèi)容,當(dāng)一個(gè)視圖第一次出現(xiàn)在屏幕前,系統(tǒng)會(huì)要求它繪制自己的內(nèi)容,在該流程中系統(tǒng)會(huì)創(chuàng)建一個(gè)快照,這個(gè)快照是出現(xiàn)在屏幕中得視圖內(nèi)容的可見(jiàn)部分。如果從來(lái)沒(méi)有改變視圖的內(nèi)容,則這個(gè)視圖繪制的方法drawRect可能永遠(yuǎn)都不會(huì)再被調(diào)用。這個(gè)快照?qǐng)D像在大部分涉及到視圖的操作中被重用。
如果改變了視圖的內(nèi)容,系統(tǒng)也不會(huì)馬上重繪視圖。使用setNeedsDisPlay或setNeedsDisplayInRect方法廢除該視圖同時(shí)讓系統(tǒng)在稍后重繪視圖。系統(tǒng)等待當(dāng)前運(yùn)行循環(huán)(run loop)結(jié)束,然后開(kāi)始重繪。這個(gè)延遲可以用來(lái)廢除多個(gè)視圖、增加或刪除視圖、隱藏、重設(shè)大小。所有的改變會(huì)在稍后一起生效。
什么是run loop?個(gè)人認(rèn)為可以簡(jiǎn)單的理解為一個(gè)事件的處理過(guò)程。例如:用戶點(diǎn)擊屏幕會(huì)產(chǎn)生兩個(gè)run loop。當(dāng)用戶按下的時(shí)候會(huì)產(chǎn)生一個(gè)run loop;當(dāng)用戶抬起的時(shí)候會(huì)產(chǎn)生另一個(gè)run loop。
如果在一個(gè)run loop中調(diào)用setNeedsDisplayInRect方法,系統(tǒng)會(huì)保證在這個(gè)run loop結(jié)束前調(diào)用一次drawRect方法;無(wú)論在當(dāng)前run loop調(diào)用setNeedsDisplayInRect方法多少次都只調(diào)用一次drawRect。setNeedsDisplayInRect方法如果在一個(gè)run loop中刷新不同的區(qū)域,最后drawRect方法會(huì)將這些區(qū)域組合起來(lái)一起刷新,組合原則用最小的矩形圈起來(lái)所有區(qū)域,并刷新這個(gè)區(qū)域。如果刷新區(qū)域是屏幕左下角和右上角兩個(gè)點(diǎn),有可能刷新整個(gè)屏幕。
6、相關(guān)控件
UIWindow
UIWindow對(duì)象是所有UIView的父視圖,UIWindow類是UIView的子類,可以看作是特殊的UIView。一般應(yīng)用程序只有一個(gè)UIWindow對(duì)象,即使有多個(gè)UIWindow對(duì)象,也只有一個(gè)UIWindow可以接受到用戶的觸屏事件。UIWindow初始化在appDelegate里。 [self.window makeKeyAndVisible]方法,使window顯示。
UIScreen
UIScreen用于獲取設(shè)備屏幕的尺寸。
[UIScreen mainScreen].bounds 獲取整個(gè)屏幕的大??;當(dāng)應(yīng)用程序有狀態(tài)欄時(shí),返回值:{{0, 0}, {320, 480}}
[UIScreen mainScreen].applicationFrame獲取應(yīng)用程序窗口的大小;當(dāng)應(yīng)用程序有狀態(tài)欄時(shí),返回值:{{0, 20}, {320, 460}}
UIViewController
UIViewController是MVC中的C控制器,控制管理UIView。UIViewController同UIView的關(guān)系相當(dāng)于相框和相片的關(guān)系,相框可以操作相片、替換相片、決定相片的顯隱藏,反之則不行。UIView工作在第一線,向用戶展示表現(xiàn)的內(nèi)容,并接受用戶的交互;UIViewController負(fù)責(zé)兩個(gè)方面,數(shù)據(jù)和行為,通過(guò)數(shù)據(jù)更新view,通過(guò)行為處理用戶交互操作,注意這里是“處理”而不是“響應(yīng)”。
UIViewController同UIView一樣繼承與UIResponder,所以同樣處于響應(yīng)者鏈上,同樣可以接收觸碰等用戶事件。
可以通過(guò)下面的方法獲得UIView所屬的UIViewController
- (UIViewController*)viewController
{
for(UIView* next = [self superview]; next; next = next.superview)
{
UIResponder* nextResponder = [next nextResponder];
if ([nextResponder isKindOfClass:[UIViewController class]])
{
return (UIViewController*)nextResponder;
}
}
return nil;
}