iOS學(xué)習(xí)筆記(1)— UIView 渲染和內(nèi)容管理

轉(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效果

image

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;
} 

?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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