一:首先查看一下關於UIView的定義
NS_CLASS_AVAILABLE_IOS(2_0) @interface UIView : UIResponder <NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace> + (Class)layerClass; // 默認為 [CALayer class].用於創建視圖的底層時使用。 - (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER; //初始化視圖 - (nullable instancetype)initWithCoder:(NSCoder *)aDecoder NS_DESIGNATED_INITIALIZER; @property(nonatomic,getter=isUserInteractionEnabled) BOOL userInteractionEnabled; // 默認是是。如果設置為NO,用戶事件(觸摸,鍵)將被忽略,並從事件隊列中刪除。 @property(nonatomic) NSInteger tag; // 默認為 0 @property(nonatomic,readonly,strong) CALayer *layer; // 返回視圖層。將始終返回一個非零值。視圖是層的委托. + (UIUserInterfaceLayoutDirection)userInterfaceLayoutDirectionForSemanticContentAttribute:(UISemanticContentAttribute)attribute NS_AVAILABLE_IOS(9_0); //用戶界面的布局方向 IOS9以後的功能 @property (nonatomic) UISemanticContentAttribute semanticContentAttribute NS_AVAILABLE_IOS(9_0); //UIView 也增加了 UISemanticContentAttribute 這樣一個屬性來判斷視圖是否會遵循顯示的方向規則(默認是 UISemanticContentAttributeUnspecified ) @end
從上面我們可以知道,UIView是繼承於UIResponder,並且有相應的委托協議NSCoding, UIAppearance, UIAppearanceContainer, UIDynamicItem, UITraitEnvironment, UICoordinateSpace;關於UIResponder的知識點我們放在下一個文章進行學習,首先了解一下幾個協議的作用:
a:NSCoding 實現了NSCoding協議以後,就可以進行歸檔轉換了,這個協議在平常的歸檔類中是必須要遵循了;
b:UIAppearance+UIAppearanceContainer iOS5及其以後提供了一個比較強大的工具UIAppearance,我們通過UIAppearance設置一些UI的全局效果,這樣就可以很方便的實現UI的自定義效果又能最簡單的實現統一界面風格;改變UIAppearance協議對象的屬性,就能改變所有其遵從該協議類的所有實例,iOS應用的appearance只會對當視圖加入到窗口時有效,對於已經添加在窗口的視圖不起作用,需要通過移除其所在的視圖層次位置,再添加回來.
c:UIDynamicItem 遵守了UIDynamicItem協議,能做物理仿真效果
d:UITraitEnvironment 該協議用於監聽和獲取SizeClass的情況
e:UICoordinateSpace 獲取當前screen旋轉之後的坐標體系,有時候需要在 Core Graphics 和 UIKit 的坐標系之間進行轉換,就要遵循這個協議
二:關於UIView幾個重要的分類
a: UIView(UIViewGeometry)這個裡面的內容也是我們經常要用到,包含一些相應的坐標內容及相應的觸分事件;
@interface UIView(UIViewGeometry) @property(nonatomic) CGRect frame; //視圖在父視圖中的尺寸和位置(以父控件的左上角為坐標原點) @property(nonatomic) CGRect bounds; // 控件所在矩形框的位置和尺寸(以自己左上角為坐標原點,所以bounds的x\y一般為0)而寬高則為frame size @property(nonatomic) CGPoint center; // 中心點的坐標,控件中點的位置(以父控件的左上角為坐標原點) @property(nonatomic) CGAffineTransform transform; // 仿射變換(通過這個屬性可以進行視圖的平移、旋轉和縮放) @property(nonatomic) CGFloat contentScaleFactor NS_AVAILABLE_IOS(4_0); //內容視圖伸張的模式,修改contentScaleFactor可以讓UIView的渲染精度提高,這樣即使在CGAffineTransform放大之後仍然能保持銳利 @property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled; // 默認是NO 是否允許多點觸摸 @property(nonatomic,getter=isExclusiveTouch) BOOL exclusiveTouch; // 默認是NO 可以達到同一界面上多個控件接受事件時的排他性,從而避免一些問題。也就是說避免在一個界面上同時點擊多個button //這兩個方法主要是進行用戶事件的攔截 - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event; // 去調用pointInside:withEvent:. 點在接收機的坐標系統中 - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; // 如何在范圍內則返回YES // 視圖中的坐標轉換
// 將像素point由point所在視圖轉換到目標視圖view中,返回在目標視圖view中的像素值 - (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
// 將像素point從view中轉換到當前視圖中,返回在當前視圖中的像素值 - (CGPoint)convertPoint:(CGPoint)point fromView:(nullable UIView *)view;
// 將rect由rect所在視圖轉換到目標視圖view中,返回在目標視圖view中的rect - (CGRect)convertRect:(CGRect)rect toView:(nullable UIView *)view;
// 將rect從view中轉換到當前視圖中,返回在當前視圖中的rect - (CGRect)convertRect:(CGRect)rect fromView:(nullable UIView *)view; //自動尺寸調整屬性 @property(nonatomic) BOOL autoresizesSubviews; // 默認為YES. if set, subviews are adjusted according to their autoresizingMask if self.bounds changes @property(nonatomic) UIViewAutoresizing autoresizingMask; // 默認值為: UIViewAutoresizingNone - (CGSize)sizeThatFits:(CGSize)size; // 返回“最佳”的大小,以適應給定的大小。不實際調整視圖。默認是返回現有視圖大小 - (void)sizeToFit; // 隨著當前視圖邊界和更改邊界大小,調用這個方法實際上是調用 sizeThatFits 方法。 @end
知識點1:frame: 以父視圖為原點;bounds : 以自身為原點;center : 以父視圖為原點。
知識點2:autoresizesSubviews屬性的大意是:默認autoresizesSubviews = YES。如果UIView設置了autoresizesSubviews,那麼他的子控件的bounds如果發生了變化,他的子控件將會根據子控件自己的autoresizingMask屬性的值來進行調整。
知識點3:autoresizingMask是一個枚舉值,作用是自動調整子控件與父控件中間的margin(間距)或者子控件的寬高。默認其枚舉值是UIViewAutoresizingNone。如下是其全部的枚舉值;
UIViewAutoresizingNone就是不自動調整。 UIViewAutoresizingFlexibleLeftMargin 自動調整與superView左邊的距離,保證與superView右邊的距離不變。 UIViewAutoresizingFlexibleRightMargin 自動調整與superView的右邊距離,保證與superView左邊的距離不變。 UIViewAutoresizingFlexibleTopMargin 自動調整與superView頂部的距離,保證與superView底部的距離不變。 UIViewAutoresizingFlexibleBottomMargin 自動調整與superView底部的距離,也就是說,與superView頂部的距離不變。 UIViewAutoresizingFlexibleWidth 自動調整自己的寬度,保證與superView左邊和右邊的距離不變。 UIViewAutoresizingFlexibleHeight 自動調整自己的高度,保證與superView頂部和底部的距離不變。
例如:UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin 自動調整與superView左邊的距離,保證與左邊的距離和右邊的距離和原來距左邊和右邊的距離的比例不變。比如原來距離為20,40,調整後的距離應為50,100,即50/20=100/40;UIView的autoresizesSubviews屬性為YES時(默認為YES),autoresizing才會生效。如果視圖的autoresizesSubviews屬性被設置為 NO,則該視圖的直接子視圖的所有自動尺寸調整行為將被忽略。類似地,如果一個子視圖的自動尺寸調整掩碼被設置為 UIViewAutoresizingNone,則該子視圖的尺寸將不會被調整,因而其直接子視圖的尺寸也不會被調整。
知識點4:iOS中,hit-Testing的作用就是找出這個觸摸點下面的View是什麼,HitTest會檢測這個點擊的點是不是發生在這個View上,如果是的話,就會去遍歷這個View的subviews,直到找到最小的能夠處理事件的view,如果整了一圈沒找到能夠處理的view,則返回自身。UIView中提供兩個方法用來確定hit-testing View分別為hitTest,pointInside;
知識點5:當一個View收到hitTest消息時,會調用自己的pointInside:withEvent:方法,如果pointInside返回YES,則表明觸摸事件發生在我自己內部,則會遍歷自己的所有Subview去尋找最小單位(沒有任何子view)的UIView,如果當前View.userInteractionEnabled = NO,enabled=NO(UIControl),或者alpha<=0.01, hidden等情況的時候,hitTest就不會調用自己的pointInside了,直接返回nil,然後系統就回去遍歷兄弟節點。【關於事件分發可以看這文章:iOS事件分發機制】
實例:我們可以利用hit-Test做一些事情,比如我們點擊了ViewA,我們想讓ViewB響應 @implementation STPView - (instancetype)initWithFrame:(CGRect)frame { self = [super initWithFrame:frame]; if (self) { UIButton *button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(0, 0, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2); button.tag = 10001; button.backgroundColor = [UIColor grayColor]; [button setTitle:@"Button1" forState:UIControlStateNormal]; [self addSubview:button]; [button addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown]; UIButton *button2 = [UIButton buttonWithType:UIButtonTypeCustom]; button2.frame = CGRectMake(0, CGRectGetHeight(frame) / 2, CGRectGetWidth(frame), CGRectGetHeight(frame) / 2); button2.tag = 10002; button2.backgroundColor = [UIColor darkGrayColor]; [button2 setTitle:@"Button2" forState:UIControlStateNormal]; [self addSubview:button2]; [button2 addTarget:self action:@selector(_buttonActionFired:) forControlEvents:UIControlEventTouchDown]; } return self; } - (void)_buttonActionFired:(UIButton *)button { NSLog(@"=====Button Titled %@ ActionFired ", [button titleForState:UIControlStateNormal]); } - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event { UIView *hitView = [super hitTest:point withEvent:event]; if (hitView == [self viewWithTag:10001]) { return [self viewWithTag:10002]; } return hitView; } @end
知識點6:UIView 類定義了下面這些方法,用於在不同的視圖本地坐標系統之間進行坐標轉換:
convertPoint:fromView: convertRect:fromView: convertPoint:toView: convertRect:toView: UIWindow 的版本則使用窗口坐標系統。 convertPoint:fromWindow: convertRect:fromWindow: convertPoint:toWindow: convertRect:toWindow:
b:UIView(UIViewHierarchy)這個分類裡面主要包含一些視圖的層級關系及視圖生命周期操作;
@interface UIView(UIViewHierarchy) @property(nullable, nonatomic,readonly) UIView *superview; //父視圖 @property(nonatomic,readonly,copy) NSArray<__kindof UIView *> *subviews; //子視圖數組 @property(nullable, nonatomic,readonly) UIWindow *window; //當前window //刪除視圖 - (void)removeFromSuperview; //插入視圖 - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index; //調整視圖順序 - (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2; //增加視圖 - (void)addSubview:(UIView *)view; //插入視圖在子視圖siblingSubview下面 - (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview; //插入視圖在子視圖siblingSubview上面 - (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview; //能夠將參數中的view調整到父視圖的最上面 - (void)bringSubviewToFront:(UIView *)view; //能夠將參數中的view調整到父視圖的最下面 - (void)sendSubviewToBack:(UIView *)view; //當視圖添加子視圖時調用 - (void)didAddSubview:(UIView *)subview; //當子視圖從本視圖移除時調用 - (void)willRemoveSubview:(UIView *)subview; //當視圖即將加入父視圖時 / 當視圖即將從父視圖移除時調用 - (void)willMoveToSuperview:(nullable UIView *)newSuperview; //當試圖加入父視圖時 / 當視圖從父視圖移除時調用 - (void)didMoveToSuperview; //當視圖即將加入父視圖時 / 當視圖即將從父視圖移除時調用 - (void)willMoveToWindow:(nullable UIWindow *)newWindow; //// 當視圖加入父視圖時 / 當視圖從父視圖移除時調用 - (void)didMoveToWindow; - (BOOL)isDescendantOfView:(UIView *)view; // returns YES for self. - (nullable UIView *)viewWithTag:(NSInteger)tag; //可以通過tag查找對應的視圖包括它自個 // Allows you to perform layout before the drawing cycle happens. -layoutIfNeeded forces layout early - (void)setNeedsLayout; //標記為需要重新布局,異步調用layoutIfNeeded刷新布局,不立即刷新,但layoutSubviews一定會被調用 - (void)layoutIfNeeded; //如果,有需要刷新的標記,立即調用layoutSubviews進行布局 - (void)layoutSubviews; // 這個方法,默認沒有做任何事情,需要子類進行重寫 @property (nonatomic) UIEdgeInsets layoutMargins NS_AVAILABLE_IOS(8_0); @property (nonatomic) BOOL preservesSuperviewLayoutMargins NS_AVAILABLE_IOS(8_0); // default is NO - 設置為使通過或從該視圖的父對其子視圖的邊緣的級聯行為 - (void)layoutMarginsDidChange NS_AVAILABLE_IOS(8_0); @property(readonly,strong) UILayoutGuide *layoutMarginsGuide NS_AVAILABLE_IOS(9_0); @property (nonatomic, readonly, strong) UILayoutGuide *readableContentGuide NS_AVAILABLE_IOS(9_0); @end
知識點1: isDescendantOfView:方法來判定一個視圖是否在其父視圖的視圖層中。一個視圖層次的根視圖沒有父視圖,因此其superview 屬性被設置為nil。對於當前被顯示在屏幕上的視圖,窗口對象通常是整個視圖層次的根視圖。
知識點2:為某個視圖添加子視圖時,UIKit 會向相應的父子視圖發送幾個消息,通知它們當前發生的狀態變化。您可以在自己的定制視圖中對諸如willMoveToSuperview: 、willMoveToWindow: 、 willRemoveSubview: 、 didAddSubview: 、didMoveToSuperview 和 didMoveToWindow這樣的方法進行重載,以便在事件發生的前後進行必要的處理,並根據發生的變化更新視圖的狀態信息;關於增加視圖跟移除視圖的實例:
實例1: MyView *view = [[MyView alloc] initWithFrame:CGRectMake(10, 30, 300, 300)]; view.tag = 100; view.backgroundColor = [UIColor redColor]; [self.view addSubview:view]; UIView *v = [[UIView alloc] initWithFrame:CGRectMake(10, 10, 30, 30)]; v.backgroundColor = [UIColor yellowColor]; [view addSubview:v]; 2016-05-10 11:02:46.737 Test[2489:101281] willMoveToSuperview: 2016-05-10 11:02:46.737 Test[2489:101281] didMoveToSuperview 2016-05-10 11:02:46.738 Test[2489:101281] didAddSubview: 2016-05-10 11:02:46.738 Test[2489:101281] willMoveToWindow: 2016-05-10 11:02:46.738 Test[2489:101281] didMoveToWindow 實例2: MyView *myView = [(MyView *)self.view viewWithTag:100]; [myView removeFromSuperview]; 2016-05-10 11:02:48.394 Test[2489:101281] willMoveToSuperview: 2016-05-10 11:02:48.395 Test[2489:101281] willMoveToWindow: 2016-05-10 11:02:48.395 Test[2489:101281] didMoveToWindow 2016-05-10 11:02:48.395 Test[2489:101281] didMoveToSuperview 2016-05-10 11:02:48.396 Test[2489:101281] willRemoveSubview:
知識點3:layoutSubviews在以下情況下會被調用
1.1、init初始化不會觸發layoutSubviews,但是是用initWithFrame 進行初始化時,當rect的值不為CGRectZero時,也會觸發 1.2、addSubview會觸發layoutSubviews 1.3、設置view的Frame會觸發layoutSubviews,當然前提是frame的值設置前後發生了變化 1.4、滾動一個UIScrollView會觸發layoutSubviews 1.5、旋轉Screen會觸發父UIView上的layoutSubviews事件 1.6、改變一個UIView大小的時候也會觸發父UIView上的layoutSubviews事件
c:UIView(UIViewRendering) 此分類裡面主要包括關於視圖的一些透明度、裁剪、隱藏、背景色等的設置;
@interface UIView(UIViewRendering) //重寫 進行繪畫 - (void)drawRect:(CGRect)rect; //setNeedsDisplay是更新整個view。 - (void)setNeedsDisplay; //setNeedsDisplayInRect是更新view的部分區域。 - (void)setNeedsDisplayInRect:(CGRect)rect; @property(nonatomic) BOOL clipsToBounds; // 如果子視圖的范圍超出了父視圖的邊界,那麼超出的部分就會被裁剪掉 默認值為 NO. @property(nullable, nonatomic,copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR; // 默認值 nil. @property(nonatomic) CGFloat alpha; // 透明度 默認值為 1.0 @property(nonatomic,getter=isOpaque) BOOL opaque; // default is YES. 不透明的視圖必須填充其整個范圍,或結果是未定義的。在drawRect主動CGContext:不會被清除,可能有非零像素 @property(nonatomic) BOOL clearsContextBeforeDrawing; // 決定繪制前是否清屏,默認為YES。用於提高描畫性能,特別是在可滾動的視圖中。當這個屬性被設置為YES時,UIKIt會在調用drawRect:方法之前,把即將被該方法更新的區域填充為透明的黑色 @property(nonatomic,getter=isHidden) BOOL hidden; // 是否隱藏 默認為NO @property(nonatomic) UIViewContentMode contentMode; // 視圖內容的填充方式 默認 UIViewContentModeScaleToFill @property(nonatomic) CGRect contentStretch NS_DEPRECATED_IOS(3_0,6_0); // 已棄用 animatable. default is unit rectangle {{0,0} {1,1}}. Now deprecated: please use -[UIImage resizableImageWithCapInsets:] to achieve the same effect. @property(nullable, nonatomic,strong) UIView *maskView NS_AVAILABLE_IOS(8_0); //色調顏色,開始用於iOS7 @property(null_resettable, nonatomic, strong) UIColor *tintColor NS_AVAILABLE_IOS(7_0); //色調模型,可以和tintColor結合著使用 @property(nonatomic) UIViewTintAdjustmentMode tintAdjustmentMode NS_AVAILABLE_IOS(7_0); //更新視圖的渲染 - (void)tintColorDidChange NS_AVAILABLE_IOS(7_0); @end
知識點1:UIViewContentMode的說明
@property(nonatomic) UIViewContentMode contentMode; typedef NS_ENUM(NSInteger, UIViewContentMode) { UIViewContentModeScaleToFill, //填充到整個視圖區域,不等比例拉伸。 UIViewContentModeScaleAspectFit, //長寬等比填充視圖區域,當某一個邊到達視圖邊界的時候就不再拉伸,保證內容的長寬比是不變的同時盡可能的填充視圖區域。 UIViewContentModeScaleAspectFill, //長寬等比填充視圖區域,當某一個邊到達視圖邊界的時候還繼續拉伸,直到另一個方向達到視圖邊界。內容的長寬比不變的同時填滿整個視圖區域,不顯示超過的部分。 UIViewContentModeRedraw, //重繪視圖邊界 UIViewContentModeCenter, //視圖居中 UIViewContentModeTop, //視圖頂部對齊 UIViewContentModeBottom, //視圖底部對齊 UIViewContentModeLeft, //視圖左側對齊 UIViewContentModeRight, //視圖右側對齊 UIViewContentModeTopLeft, //視圖左上角對齊 UIViewContentModeTopRight, //視圖右上角對齊 UIViewContentModeBottomLeft, //視圖左下角對齊 UIViewContentModeBottomRight, //視圖右下角對齊 };