你真的了解UIView吗?

时间:2023-12-10 09:55:26

一:首先查看一下关于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(, , CGRectGetWidth(frame), CGRectGetHeight(frame) / );
button.tag = ;
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(, CGRectGetHeight(frame) / , CGRectGetWidth(frame), CGRectGetHeight(frame) / );
button2.tag = ;
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:]) {
return [self viewWithTag:];
}
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(, , , )];
view.tag = ;
view.backgroundColor = [UIColor redColor];
[self.view addSubview:view]; UIView *v = [[UIView alloc] initWithFrame:CGRectMake(, , , )];
v.backgroundColor = [UIColor yellowColor];
[view addSubview:v]; -- ::46.737 Test[:] willMoveToSuperview:
-- ::46.737 Test[:] didMoveToSuperview
-- ::46.738 Test[:] didAddSubview:
-- ::46.738 Test[:] willMoveToWindow:
-- ::46.738 Test[:] didMoveToWindow

实例2:
MyView *myView = [(MyView *)self.view viewWithTag:];
[myView removeFromSuperview]; -- ::48.394 Test[:] willMoveToSuperview:
-- ::48.395 Test[:] willMoveToWindow:
-- ::48.395 Test[:] didMoveToWindow
-- ::48.395 Test[:] didMoveToSuperview
-- ::48.396 Test[:] willRemoveSubview:

知识点3layoutSubviews在以下情况下会被调用

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

知识点1UIViewContentMode的说明

@property(nonatomic) UIViewContentMode contentMode;
typedef NS_ENUM(NSInteger, UIViewContentMode) {
UIViewContentModeScaleToFill, //填充到整个视图区域,不等比例拉伸。
UIViewContentModeScaleAspectFit, //长宽等比填充视图区域,当某一个边到达视图边界的时候就不再拉伸,保证内容的长宽比是不变的同时尽可能的填充视图区域。
UIViewContentModeScaleAspectFill, //长宽等比填充视图区域,当某一个边到达视图边界的时候还继续拉伸,直到另一个方向达到视图边界。内容的长宽比不变的同时填满整个视图区域,不显示超过的部分。
UIViewContentModeRedraw, //重绘视图边界
UIViewContentModeCenter, //视图居中
UIViewContentModeTop, //视图顶部对齐
UIViewContentModeBottom, //视图底部对齐
UIViewContentModeLeft, //视图左侧对齐
UIViewContentModeRight, //视图右侧对齐
UIViewContentModeTopLeft, //视图左上角对齐
UIViewContentModeTopRight, //视图右上角对齐
UIViewContentModeBottomLeft, //视图左下角对齐
UIViewContentModeBottomRight, //视图右下角对齐
};

最近有个妹子弄的一个关于扩大眼界跟内含的订阅号,每天都会更新一些深度内容,在这里如果你感兴趣也可以关注一下(嘿对美女跟知识感兴趣),当然可以关注后输入:github 会有我的微信号,如果有问题你也可以在那找到我;当然不感兴趣无视此信息;

你真的了解UIView吗?