一:首先檢視一下關于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, //視圖右下角對齊
};
最近有個妹子弄的一個關于擴大眼界跟内含的訂閱号,每天都會更新一些深度内容,在這裡如果你感興趣也可以關注一下(嘿對美女跟知識感興趣),當然可以關注後輸入:github 會有我的微信号,如果有問題你也可以在那找到我;當然不感興趣無視此資訊;