美文网首页
iOS UIView的学习笔记

iOS UIView的学习笔记

作者: 寻心_0a46 | 来源:发表于2019-07-06 23:57 被阅读0次

    UIView

    UIView为屏幕上的矩形区域管理内容的对象。视图是应用程序用户界面的基本构建块,UIView类定义了所有视图通用的行为。视图对象呈现其边界矩形内的内容,并处理与该内容的任何交互。

    UIView类是一个具体的类,您可以实例化它并使用它来显示固定的背景色。您还可以将其子类化以绘制更复杂的内容。要显示应用程序中常见的标签、图像、按钮和其他界面元素,请使用UIKit框架提供的视图子类,而不要试图定义自己的视图子类。

    因为视图对象是应用程序与用户交互的主要方式,所以它们有许多职责。以下是一些:

    1. 绘图和动画
    • 视图使用UIKit或核心图形在其矩形区域中绘制内容。
    • 某些视图属性可以设置为新值的动画。
    1. 布局和子视图管理
    • 视图可以包含零个或多个子视图。
    • 视图可以调整其子视图的大小和位置。
    • 使用自动布局来定义视图调整大小和重新定位的规则,以响应视图层次结构中的更改。

    3.事件处理

    • 视图是UIResponder的子类,可以响应触摸和其它类型的事件。
    • 视图可以安装手势识别器来处理常见的手势。
    • 视图可以嵌套在其他视图中以创建视图层次结构,这为组织相关内容提供了一种方便的方法。嵌套视图在嵌套的子视图(称为子视图)和父视图(称为父视图)之间创建父子关系。父视图可以包含任意数量的子视图,但每个子视图只有一个父视图。默认情况下,当子视图的可见区域超出其父视图的范围时,不会发生子视图内容的裁剪。使用clipsToBounds属性更改该行为。
    • 每个视图的几何图形由其框架和边界属性定义。frame属性在其父视图的坐标系统中定义视图的原点和维度。bounds属性定义了视图看到的内部尺寸,并且几乎只在定制绘图代码中使用。center属性提供了一种方便的方法来重新定位视图,而无需直接更改其框架或边界属性。

    UIView常用函数

    - (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;

    函数描述 : 使用指定的框架矩形初始化并返回新分配的视图对象。新视图对象必须插入到窗口的视图层次结构中才能使用。如果以编程方式创建视图对象,则此方法是UIView类的指定初始值设定项。子类可以重写此方法以执行任何自定义初始化,但必须在其实现开始时调用super。

    如果使用Interface Builder设计接口,则在随后从nib文件加载视图对象时不调用此方法。nib文件中的对象被重新构造,然后使用它们的initWithCoder:方法进行初始化,该方法修改视图的属性以匹配存储在nib文件中的属性。

    参数 :

    frame : 视图的边框矩形,以点为单位。框架的原点相对于要在其中添加它的超视图。此方法使用框架矩形相应地设置中心和边界属性。

    返回值 : 初始化的视图对象。

    - (instancetype)initWithFrame:(CGRect)frame NS_DESIGNATED_INITIALIZER;
    

    UIView(UIViewGeometry) - 视图几何图形

    常用属性

    @property(nonatomic) CGRect frame;

    属性描述 : 框架矩形,用于描述视图在其父视图坐标系中的位置和大小。在布局操作期间使用此矩形可设置视图的大小和位置。设置此属性会更改center属性指定的点,并相应地更改边界矩形中的大小。frame的坐标总是以点表示。对该属性的更改可以被动画化。

    如果transform属性不是identity转换,则该属性的值未定义,因此应该忽略。改变frame会自动重新显示视图,而无需调用它的drawRect:方法。如果你想让UIKit在frame改变时调用drawRect:方法,将contentMode属性设置为UIViewContentModeRedraw。但是,如果transform属性包含非标识转换,那么frame属性的值是未定义的,不应该被修改。在这种情况下,使用center属性重新定位视图,并使用bounds属性调整大小。

    @property(nonatomic) CGRect            frame;
    

    @property(nonatomic) CGRect bounds;

    函数描述 : 边界矩形,它在自己的坐标系统中描述视图的位置和大小。默认边界原点为(0,0),大小与frame属性中矩形的大小相同。更改此矩形的大小部分会使视图相对于其中心点增大或缩小。更改大小还会更改frame属性中矩形的大小以进行匹配。bounds的坐标始终以点指定。对该属性的更改可以被动画化。

    改变bounds会自动重新显示视图,而无需调用它的drawRect:方法。如果你想让UIKit调用drawRect:方法,将contentMode属性设置为UIViewContentModeRedraw。

    @property(nonatomic) CGRect            bounds;  
    

    @property(nonatomic) CGPoint center;

    函数描述 : 视图frame的中心点。中心点在其父视图的坐标系中以点指定。设置此属性将相应地更新frame属性中矩形的原点。如果要更改视图的位置,请使用此属性,而不是frame属性。中心点始终有效,即使在将缩放或旋转因子应用于视图的变换时也是如此。对此属性的更改可以设置动画。

    @property(nonatomic) CGPoint           center;
    

    @property(nonatomic) CGAffineTransform transform;

    属性描述 : 指定应用于视图的相对于其边界中心的转换。使用这个属性在父视图的坐标系统内缩放或旋转视图的frame。(要改变视图的位置,请修改center属性。)此属性的默认值是CGAffineTransformIdentity。对该属性的更改可以被动画化。

    相对于视图的锚点发生转换。默认情况下,定位点等于frame的中心点。要更改锚点,请修改视图的底层CALayer对象的锚点属性。

    在ios8.0及以后版本中,transform属性不会影响自动布局。自动布局计算一个视图的对齐矩形基于它的未转换的框架。

    当此属性的值不是identity转换时,frame属性中的值是未定义的,应该忽略。

    @property(nonatomic) CGAffineTransform transform;
    

    @property(nonatomic) CGFloat contentScaleFactor API_AVAILABLE(ios(4.0));

    属性描述 :应用于视图的比例因子。比例因子决定如何将视图中的内容从逻辑坐标空间(以点度量)映射到设备坐标空间(以像素度量)。这个值通常是1.0或2.0。较高的比例因子表示视图中的每个点都由底层的多个像素表示。例如,如果比例因子为2.0,视图帧大小为50 x 50点,则用于显示该内容的位图的大小为100 x 100像素。

    此属性的默认值是与当前显示视图的屏幕相关联的比例因子。如果自定义视图实现了一个自定义的drawRect:方法并且与一个窗口相关联,或者如果使用GLKView类来绘制OpenGL ES内容,你的视图以屏幕的全分辨率绘制。对于系统视图,即使在高分辨率屏幕上,这个属性的值也可能是1.0。

    通常,不需要修改此属性中的值。但是,如果你的应用程序使用OpenGL ES绘图,你可能想要改变比例因子以牺牲图像质量来换取渲染性能。

    @property(nonatomic) CGFloat           contentScaleFactor API_AVAILABLE(ios(4.0));
    

    @property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled API_UNAVAILABLE(tvOS);

    属性描述 : 一个布尔值,指示视图一次是否接收到多个触摸。当设置为YES时,视图会接收与多点触摸序列相关的所有触摸,并在视图的边界内开始。当设置为NO时,视图只接收在视图边界内开始的多点触摸序列中的第一个触摸事件。此属性的默认值为NO。

    此属性不影响附加到视图的手势识别器。手势识别器接收视图中发生的所有触摸。

    当此属性为NO时,同一窗口中的其他视图仍然可以接收触摸事件。如果希望该视图专门处理多点触摸事件,请将此属性和exclusiveTouch属性的值设置为YES。这个属性不会阻止视图被要求处理多个触摸。例如,两个子视图可能都将它们的触摸转发到一个共同的父视图,例如一个窗口或一个视图控制器的根视图。这个属性决定有多少最初针对视图的触摸被传递到那个视图。

    @property(nonatomic,getter=isMultipleTouchEnabled) BOOL multipleTouchEnabled API_UNAVAILABLE(tvOS);
    

    @property(nonatomic,getter=isExclusiveTouch) BOOL exclusiveTouch API_UNAVAILABLE(tvOS);

    属性描述 : 一个布尔值,指示接收器是否以独占方式处理触摸事件。将此属性设置为YES会导致接收器阻止将触摸事件传递到同一窗口中的其他视图。此属性的默认值为NO。

    @property(nonatomic,getter=isExclusiveTouch) BOOL       exclusiveTouch API_UNAVAILABLE(tvOS); 
    

    @property(nonatomic) BOOL autoresizesSubviews;

    属性描述 : 一个布尔值,用于确定接收器在其边界更改时是否自动调整其子视图的大小。设置为YES时,接收器在其边界更改时调整其子视图的大小。默认值为YES。

    @property(nonatomic) BOOL               autoresizesSubviews;
    

    @property(nonatomic) UIViewAutoresizing autoresizingMask;

    属性描述 :一个整数位掩码,用于确定接收器在其父视图的边界更改时如何调整自身大小。当视图的边界更改时,该视图会根据每个子视图的自动调整大小掩码自动调整其子视图的大小。通过使用C位或运算符组合UIViewAutoresizing中描述的常量,可以指定此掩码的值。通过组合这些常量,可以指定视图的哪些维度应相对于父视图增大或缩小。此属性的默认值为UIViewAutoresizingNone,这表示不应调整视图的大小。

    当沿同一轴设置多个选项时,默认行为是在柔性部分之间按比例分布大小差。与其他柔性部分相比,柔性部分越大,增长的可能性就越大。例如,假设此属性包括UIViewAutoresizingFlexibleWidth和UIViewAutoresizingFlexibleRightMargin常量,但不包括UIViewAutoresizingFlexibleLeftMargin常量,从而指示视图左边距的宽度是固定的,但视图的宽度和右边距可能会更改。因此,当视图宽度和视图右侧的间隙都增大时,视图将锚定在其父视图的左侧。

    如果自动调整大小的行为不提供视图所需的精确布局,则可以使用自定义容器视图并重写其layout subviews方法以更精确地定位子视图。

    @property(nonatomic) UIViewAutoresizing autoresizingMask;
    

    UIViewAutoresizing的枚举值 :

    typedef NS_OPTIONS(NSUInteger, UIViewAutoresizing) {
        //用于指示视图不调整大小的选项。
        UIViewAutoresizingNone                 = 0,
        //通过在左边距方向上展开或缩小视图来调整大小
        UIViewAutoresizingFlexibleLeftMargin   = 1 << 0,
        //通过扩展或缩小视图宽度来调整大小
        UIViewAutoresizingFlexibleWidth        = 1 << 1,
        //通过在右边距方向上展开或缩小视图来调整大小
        UIViewAutoresizingFlexibleRightMargin  = 1 << 2,
        //通过在上边距方向上展开或缩小视图来调整大小
        UIViewAutoresizingFlexibleTopMargin    = 1 << 3,
        //通过扩大或缩小视图的高度来调整大小
        UIViewAutoresizingFlexibleHeight       = 1 << 4,
        //通过在下边距方向上展开或缩小视图来调整大小
        UIViewAutoresizingFlexibleBottomMargin = 1 << 5
    };
    

    @property(nullable, nonatomic,copy) NSArray<__kindof UIGestureRecognizer *> *gestureRecognizers API_AVAILABLE(ios(3.2));

    属性描述:当前附加到视图的手势识别器对象。每个对象都是抽象基类UIGestureRecognitizer的子类的实例。此属性的默认值为零。如果添加手势识别器,然后将其删除,则该属性的值为空数组。

    @property(nullable, nonatomic,copy) NSArray<__kindof UIGestureRecognizer *> *gestureRecognizers API_AVAILABLE(ios(3.2));
    
    常用函数

    - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;

    函数描述 : 返回视图层次结构中包含指定点的接收者(包括它自己)的最远子代。这个方法通过调用每个子视图的pointInside:withEvent:方法来遍历视图层次结构,以确定哪个子视图应该接收一个触摸事件。如果pointInside:withEvent:返回YES,那么子视图的层次结构将被遍历,直到发现包含指定点的最前面的视图为止。如果视图不包含该点,则忽略其视图层次结构的分支。很少需要自己调用这个方法,但是可以重写它来对子视图隐藏触摸事件。

    此方法忽略隐藏、禁用用户交互或alpha级别小于0.01的视图对象的过滤。此方法在确定点击时不考虑视图的内容。因此,即使指定的点位于视图内容的透明部分中,仍可以返回该视图。

    位于接收器边界之外的点永远不会报告为点击,即使它们实际上位于接收器的子视图中。如果当前视图的clipsToBounds属性设置为NO,并且受影响的子视图超出了视图的边界,则可能会发生这种情况。

    参数 :

    point : 在接收器的局部坐标系(边界)中指定的点。

    event : 保证调用此方法的事件。如果从事件处理代码外部调用此方法,则可以指定nil。

    返回值 : 视图对象,它是当前视图的最远子代,包含点。如果点完全位于接收器的视图层次结构之外,则返回nil。

    - (nullable UIView *)hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event;
    

    - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event;

    函数描述 : 返回一个布尔值,该值指示接收器是否包含指定的点。

    参数 :

    point : 在接收器的局部坐标系(边界)中的点。

    event : 保证调用此方法的事件。如果从事件处理代码外部调用此方法,则可以指定nil。

    返回值 : 如果点在接收器的范围内,则为YES;否则为NO。

    - (BOOL)pointInside:(CGPoint)point withEvent:(nullable UIEvent *)event; 
    

    - (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;

    函数描述 : 将点从接收者的坐标系统转换为指定视图的坐标系统。

    参数 :

    point : 在接收器的局部坐标系(边界)中指定的点。

    view : 要转换到其坐标系点的视图。如果view为nil,则此方法将转换为窗口基坐标。否则,视图和接收器必须属于同一个UIWindow对象。

    返回值 : 转换为视图坐标系的点。

    - (CGPoint)convertPoint:(CGPoint)point toView:(nullable UIView *)view;
    

    简单的示例,例如使超出父视图的范围的按钮部分响应点击

    @interface testView : UIView
    
    @property (nonatomic, strong)UIButton *testCodeButton;
    
    @end
    
    @implementation testView
    
    - (instancetype)initWithFrame:(CGRect)frame{
        self = [super initWithFrame:frame];
        if(self){
            self.testCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
            self.testCodeButton.backgroundColor = [UIColor blueColor];
            self.testCodeButton.alpha = 0.5;
            self.testCodeButton.titleLabel.font = [UIFont systemFontOfSize:15];
            [self.testCodeButton setTitle:@"测试代码" forState:UIControlStateNormal];
            [self.testCodeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
            [self addSubview:self.testCodeButton];
            
            [self.testCodeButton mas_makeConstraints:^(MASConstraintMaker *make) {
                make.top.equalTo(self.mas_top).offset(-35);
                make.centerX.equalTo(self);
                make.size.mas_equalTo(CGSizeMake(150, 55));
            }];
        }
        return self;
    }
    
    - (UIView *)hitTest:(CGPoint)point withEvent:(UIEvent *)event {
        UIView *view = [super hitTest:point withEvent:event];
        if ([view isKindOfClass:[UIButton class]]) {
            return view;
        }else if (view == nil) {
            // 转换坐标系
            CGPoint newPoint = [self.testCodeButton convertPoint:point fromView:self];
            // 判断触摸点是否在button上
            if (CGRectContainsPoint(self.testCodeButton.bounds, newPoint)) {
                view = self.testCodeButton;
            }
        }
        return view;
    }
    
    @end
    
    
    @interface TestCodeController ()
    
    @property (nonatomic, strong) testView *tview;
    
    @end
    
    @implementation TestCodeController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.navigationItem.title = @"测试代码控制器";
        
        self.tview = [[testView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMaxY(self.view.frame) / 2 - 150 / 2, 150, 150)];
        self.tview.backgroundColor = [UIColor redColor];
        [self.tview.testCodeButton addTarget:self action:@selector(testCode) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:self.tview];
        
    }
    
    ///测试按钮点击
    - (void)testCode{
        YSUIntent *intent = [[YSUIntent alloc]initWithClassName:@"ShadowViewController"];
        intent.useNavigationToPush = YES;
        intent.method = OPEN_METHOD_POP;
        [self openIntent:intent];
    }
    
    @end
    

    没有实现hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event函数时:

    Jietu20200713-230732.gif

    实现hitTest:(CGPoint)point withEvent:(nullable UIEvent *)event函数时:

    Jietu20200713-231706.gif

    - (CGSize)sizeThatFits:(CGSize)size;

    函数描述 :要求视图计算并返回最适合指定大小的大小。此方法的默认实现将返回视图的现有大小。子类可以重写此方法,以根据任何子视图的期望布局返回自定义值。例如,一个UISwitch对象返回一个固定大小值,该值表示一个switch视图的标准大小,而一个UIImageView对象返回它当前显示的图像的大小。此方法不调整接收器的大小。

    参数 :

    size : 视图应计算其最佳拟合大小的大小

    返回值 : 适合接收者子视图的新大小。

    - (CGSize)sizeThatFits:(CGSize)size;   
    

    - (void)sizeToFit;

    函数描述 : 调整接收器视图的大小并移动它,使其将它的子视图括起来。如果要调整当前视图的大小以使其使用最合适的空间量,请调用此方法。特定的UIKit视图根据自己的内部需求调整自己的大小。在某些情况下,如果视图没有超视图,它可能会根据屏幕边界调整自身大小。因此,如果希望给定视图将自身调整为其父视图的大小,则应在调用此方法之前将其添加到父视图。

    不应重写此方法。如果要更改视图的默认大小信息,请改为重写sizeThatFits:。该方法执行任何所需的计算并将其返回给该方法,然后由该方法进行更改。

    - (void)sizeToFit; 
    

    注:一般在使用UILabel的时候会用到,使用这两个方法之前,必须要给label赋值,否则不会显示内容的。sizeThatFits会计算出最优的 size 但是不会改变 自己的 size。sizeToFit会计算出最优的 size 而且会改变自己的size。

    UIView(UIViewHierarchy) - 视图层次结构

    常用函数

    - (void)removeFromSuperview;

    函数描述 : 从父视图及其窗口中取消视图的链接,并将其从响应链中移除。如果视图的父视图不是nil,则父视图释放该视图。调用此方法将删除指向要删除的视图的任何约束,或指向要删除的视图的子树中的任何视图的约束。

    注 : 永远不要从视图的drawRect:方法中调用这个方法。

    - (void)removeFromSuperview;
    

    例如处理 bug,当页面刷新的时候,情况是这样的:

    屏幕快照 2019-06-05 上午11.54.06.png

    想到的解决办法就是在每次刷新前将视图中的子视图移除:

    1. makeObjectsPerformSelector :简化循环代码,数组的每个元素都会执行@selector(removeFromSuperview)指定的removeFromSuperview方法。前提是元素的类型要拥有这个方法,否则会出现unrecognized selector sent to instance错误。

    2. removeFromSuperview :将当前视图从其父视图移除。

    [self.imgBackView.subviews makeObjectsPerformSelector:@selector(removeFromSuperview)];
    
    - (void)removeFromSuperview{
        [self.imgBackView.subviews enumerateObjectsUsingBlock:^(__kindof UIView * _Nonnull subView, NSUInteger idx, BOOL * _Nonnull stop) {
            [subView removeFromSuperview];
        }];
    }
    

    处理完在此频繁刷新,是这样的:

    屏幕快照 2019-06-05 下午1.37.45.png

    - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;

    函数描述 : 在指定的索引处插入子视图。该方法建立了视图的强引用,并将它的下一个响应器设置为接收器,这是它的新父视图。视图只能有一个父视图。如果视图已经有一个父视图,而该视图不是接收器(接收器指调用函数的对象),则此方法在使接收器成为其新父视图之前删除先前的父视图。

    参数 :

    view : 要插入的视图。这个值不能为空。

    index : 要在其中插入视图的子视图属性数组中的索引。子视图索引从0开始,并且不能大于子视图的数量。

    - (void)insertSubview:(UIView *)view atIndex:(NSInteger)index;
    

    - (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2;

    函数描述 : 交换指定索引处的子视图。每个索引表示子视图属性中数组中相应视图的位置。子视图索引从0开始,并且不能大于子视图的数量。这个方法不改变任何一个视图的父视图,而只是交换它们在子视图数组中的位置。

    参数 :

    index1 : 接收器中第一个子视图的索引。

    index2 : 接收器中第二个子视图的索引。

    - (void)exchangeSubviewAtIndex:(NSInteger)index1 withSubviewAtIndex:(NSInteger)index2;
    

    例如交换两个视图的层级:

    @interface TestCodeController ()
    
    @property (nonatomic, strong) UIView *view1;
    @property (nonatomic, strong) UIView *view2;
    
    @end
    
    @implementation TestCodeController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.navigationItem.title = @"测试代码控制器";
        
        self.view1 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMaxY(self.view.frame) / 2 - 150 / 2, 150, 150)];
        self.view1.backgroundColor = [UIColor redColor];
        [self.view addSubview:self.view1];
        
        self.view2 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMaxY(self.view.frame) / 2 - 150 / 2, 150, 150)];
        self.view2.backgroundColor = [UIColor greenColor];
        [self.view addSubview:self.view2];
        
        UIButton *testCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
        testCodeButton.frame = CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 75 / 2, CGRectGetMaxY(self.view1.frame) + 20, 75, 30);
        testCodeButton.backgroundColor = [UIColor blueColor];
        testCodeButton.titleLabel.font = [UIFont systemFontOfSize:15];
        [testCodeButton setTitle:@"测试代码" forState:UIControlStateNormal];
        [testCodeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [testCodeButton addTarget:self action:@selector(testCode) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:testCodeButton];
    }
    
    ///测试按钮点击
    - (void)testCode{
        [self testexchangeSubviewAtIndex:self.view1 andView:self.view2];
    }
    
    ///交换视图层级
    - (void)testexchangeSubviewAtIndex:(UIView *)view1 andView:(UIView *)view2{
        NSInteger index1 = 0;
        NSInteger index2 = 0;
        for (int i = 0; i < self.view.subviews.count; i++) {
            if([self.view.subviews[i] isEqual:view1]){
                index1 = I;
            }else if([self.view.subviews[i] isEqual:view2]){
                index2 = I;
            }
        }
        if(index1 != index2){
            [self.view exchangeSubviewAtIndex:index1 withSubviewAtIndex:index2];
        }
    }
    
    

    效果如图:

    Jietu20200220-214404.gif

    - (void)addSubview:(UIView *)view;

    函数描述 : 将视图添加到接收器的子视图列表的末尾。该方法建立了视图的强引用,并将它的下一个响应器设置为接收器,这是它的新父视图。视图只能有一个父视图。如果视图已经有一个父视图,而该视图不是接收方,则此方法在使接收器成为其新父视图之前删除先前的父视图。

    参数 :

    view : 要添加的视图。添加后,此视图将出现在任何其他子视图之上。

    - (void)addSubview:(UIView *)view;
    

    - (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview;

    函数描述 : 在视图层次结构中的另一个视图下方插入视图。该方法建立了视图的强引用,并将它的下一个响应器设置为接收器,这是它的新父视图。视图只能有一个父视图。如果视图已经有一个父视图,而该视图不是接收器,则此方法在使接收器成为其新父视图之前删除先前的父视图。

    参数 :

    view : 要在另一个视图下插入的视图。如果它不是siblingSubview的同级,则从其superview中移除。

    siblingSubview : 插入视图上方的同级视图。

    - (void)insertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview;
    

    例如在视图层次结构中的另一个视图下方插入视图:

    #import "TestCodeController.h"
    
    @interface TestCodeController ()
    
    @property (nonatomic, strong) UIView *view1;
    @property (nonatomic, strong) UIView *view2;
    @property (nonatomic, strong) UIView *view3;
    
    @end
    
    @implementation TestCodeController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.navigationItem.title = @"测试代码控制器";
        
        self.view1 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMaxY(self.view.frame) / 2 - 150 / 2, 150, 150)];
        self.view1.backgroundColor = [UIColor redColor];
        [self.view addSubview:self.view1];
        
        self.view2 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMinY(self.view1.frame) + 30, 150, 150)];
        self.view2.backgroundColor = [UIColor greenColor];
        [self.view addSubview:self.view2];
        
        self.view3 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMinY(self.view1.frame) + 15, 150, 150)];
        self.view3.backgroundColor = [UIColor yellowColor];
        
        UIButton *testCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
        testCodeButton.frame = CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 75 / 2, CGRectGetMaxY(self.view1.frame) + 40, 75, 30);
        testCodeButton.backgroundColor = [UIColor blueColor];
        testCodeButton.titleLabel.font = [UIFont systemFontOfSize:15];
        [testCodeButton setTitle:@"测试代码" forState:UIControlStateNormal];
        [testCodeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [testCodeButton addTarget:self action:@selector(testCode) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:testCodeButton];
    }
    
    ///测试按钮点击
    - (void)testCode{
        [self testInsertSubview:self.view3 belowSubview:self.view2];
    }
    
    ///在视图层次结构中的另一个视图下方插入视图
    - (void)testInsertSubview:(UIView *)view belowSubview:(UIView *)siblingSubview{
        
        [self.view insertSubview:view belowSubview:siblingSubview];
    }
    

    效果如图 :

    Jietu20200225-101740.gif

    - (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview;

    函数描述 : 在视图层次结构中的另一个视图上方插入视图。此方法建立对视图的强引用,并将其下一个响应者设置为接收器,这是它的新父视图。视图只能有一个父视图。如果视图已经有一个父视图,而该视图不是接收器,则此方法在使接收器成为其新父视图之前删除先前的父视图。

    参数 :

    view : 要插入的视图。如果它不是siblingSubview的同级,则从其superview中移除。

    siblingSubview : 插入的视图下面的同级视图。

    - (void)insertSubview:(UIView *)view aboveSubview:(UIView *)siblingSubview;
    

    - (void)bringSubviewToFront:(UIView *)view;

    函数描述 : 移动指定的子视图,使其显示在其同级视图的顶部。此方法将指定的视图移动到“subviews ”属性中视图数组的末尾。

    参数 :

    view : 要移到前面的子视图。

    - (void)bringSubviewToFront:(UIView *)view;
    

    例如移动指定的子视图,使其显示在其同级视图的顶部:

    #import "TestCodeController.h"
    
    @interface TestCodeController ()
    
    @property (nonatomic, strong) UIView *view1;
    @property (nonatomic, strong) UIView *view2;
    @property (nonatomic, strong) UIView *view3;
    
    @end
    
    @implementation TestCodeController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.navigationItem.title = @"测试代码控制器";
        
        self.view1 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMaxY(self.view.frame) / 2 - 150 / 2, 150, 150)];
        self.view1.backgroundColor = [UIColor redColor];
        [self.view addSubview:self.view1];
        
        self.view2 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMinY(self.view1.frame) + 30, 150, 150)];
        self.view2.backgroundColor = [UIColor greenColor];
        [self.view addSubview:self.view2];
        
        self.view3 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMinY(self.view1.frame) + 15, 150, 150)];
        self.view3.backgroundColor = [UIColor yellowColor];
        [self.view addSubview:self.view3];
        
        UIButton *testCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
        testCodeButton.frame = CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 75 / 2, CGRectGetMaxY(self.view1.frame) + 40, 75, 30);
        testCodeButton.backgroundColor = [UIColor blueColor];
        testCodeButton.titleLabel.font = [UIFont systemFontOfSize:15];
        [testCodeButton setTitle:@"测试代码" forState:UIControlStateNormal];
        [testCodeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [testCodeButton addTarget:self action:@selector(testCode) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:testCodeButton];
    }
    
    ///测试按钮点击
    - (void)testCode{
        [self testBringSubviewToFront:self.view1];
    }
    
    ///移动指定的子视图,使其显示在其同级视图的顶部
    - (void)testBringSubviewToFront:(UIView *)view{
        [self.view bringSubviewToFront:view];
    }
    
    

    效果如图 :

    Jietu20200225-215556.gif

    - (void)sendSubviewToBack:(UIView *)view;

    函数描述 : 移动指定的子视图,使其显示在其同级视图的后面。此方法将指定的视图移动到“subviews”属性中视图数组的开头。

    参数 :

    view : 要移到后面的子视图。

    - (void)sendSubviewToBack:(UIView *)view;
    

    - (void)didAddSubview:(UIView *)subview;

    函数描述 : 告诉视图已添加子视图。此方法的默认实现不起任何作用。子类可以重写它,以便在添加子视图时执行其他操作。此方法是在使用任何相关视图方法添加子视图时调用的。

    参数 :

    subview : 作为子视图添加的视图。

    - (void)didAddSubview:(UIView *)subview;
    

    例如 :

    @interface TestView : UIView
    
    @property (nonatomic, assign) NSInteger number;
    
    @end
    
    @implementation TestView
    
    - (void)didAddSubview:(UIView *)subview{
        [super didAddSubview:subview];
        self.number += 1;
        NSLog(@"添加%ld次视图了",(long)self.number);
    }
    
    @end
    
    @interface TestCodeController ()
    
    @property (nonatomic, strong) TestView *contentView;
    @property (nonatomic, strong) UIView *view1;
    @property (nonatomic, strong) UIView *view2;
    @property (nonatomic, strong) UIView *view3;
    
    @end
    
    @implementation TestCodeController
    
    - (void)viewDidLoad {
        
        [super viewDidLoad];
        self.navigationItem.title = @"测试代码控制器";
        
        self.contentView = [[TestView alloc]initWithFrame:CGRectMake(0, 0, CGRectGetWidth(self.view.frame), CGRectGetHeight(self.view.frame))];
        [self.view addSubview:self.contentView];
        
        self.view1 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMaxY(self.view.frame) / 2 - 150 / 2, 150, 150)];
        self.view1.backgroundColor = [UIColor redColor];
        [self.contentView addSubview:self.view1];
        
        self.view2 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMinY(self.view1.frame) + 30, 150, 150)];
        self.view2.backgroundColor = [UIColor greenColor];
        [self.contentView addSubview:self.view2];
        
        self.view3 = [[UIView alloc]initWithFrame:CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 150 / 2, CGRectGetMinY(self.view1.frame) + 15, 150, 150)];
        self.view3.backgroundColor = [UIColor yellowColor];
        [self.contentView addSubview:self.view3];
        
        UIButton *testCodeButton = [UIButton buttonWithType:UIButtonTypeCustom];
        testCodeButton.frame = CGRectMake(CGRectGetMaxX(self.view.frame) / 2 - 75 / 2, CGRectGetMaxY(self.view1.frame) + 40, 75, 30);
        testCodeButton.backgroundColor = [UIColor blueColor];
        testCodeButton.titleLabel.font = [UIFont systemFontOfSize:15];
        [testCodeButton setTitle:@"测试代码" forState:UIControlStateNormal];
        [testCodeButton setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
        [testCodeButton addTarget:self action:@selector(testCode) forControlEvents:UIControlEventTouchUpInside];
        [self.view addSubview:testCodeButton];
    }
    
    

    输出如图 :

    截屏2020-02-25下午10.27.31.png

    - (void)willRemoveSubview:(UIView *)subview;

    函数描述 : 告诉视图子视图即将被删除。此方法的默认实现不起任何作用。子类可以重写它,以便在删除子视图时执行其他操作。当子视图的父视图更改或子视图从视图层次结构中完全删除时,将调用此方法。

    参数 :

    subview : 将被删除的子视图。

    - (void)willRemoveSubview:(UIView *)subview;
    

    - (void)layoutSubviews;

    函数描述 : 在某个类的内部调整子视图位置时,需要调用这个方法,默认没有做任何事情,需要子类进行重写 。 系统在很多时候会去调用这个方法,初始化不会触发layoutSubviews,但是如果设置了不为CGRectZero的frame并添加到父视图的时候就会触发,要在实现的最后调用[super layoutSubviews]。不应直接调用此方法。如果要强制进行布局更新,请在下次图形更新之前调用setNeedsLayout方法。如果要立即更新视图的布局,请调用layoutIfNeeded方法。

    layoutSubviews方法的触发:
    1.直接调用[self setNeedsLayout];
    2.addSubview的时候。
    3.当view的size发生改变的时候,前提是frame的值设置前后发生了变化。
    4.滑动UIScrollView的时候。
    5.旋转Screen会触发父UIView上的layoutSubviews事件

    - (void)layoutSubviews;
    

    通常也可以在layoutSubviews函数中对表视图的cell添加CALayer对象进行布局操作,比如添加分割线或者渐变图层,这里以简单的添加分割线为例,例如添加类似的分割线:

    截屏2021-07-06上午10.42.44.png

    如果在表视图的cell初始化时添加CALayer,通过self.contentView.frame获取的宽高通常不准确。

    cell初始化函数中添加CALayer的代码片段:

    ///底部分割线
    self.separatorLineLayer = [[CALayer alloc] init];
    NSLog(@"%f",CGRectGetHeight(self.contentView.frame));
    NSLog(@"%f",CGRectGetWidth(self.contentView.frame));
    self.separatorLineLayer.frame = CGRectMake(0, CGRectGetHeight(self.contentView.frame) - 0.5, CGRectGetWidth(self.contentView.frame), 0.6);
    [self.contentView.layer addSublayer:_separatorLineLayer];
    self.separatorLineLayer.backgroundColor = [UIColor grayColor].CGColor;
    

    打印的结果为:

    截屏2021-07-06上午10.28.16.png

    视图样式为:

    截屏2021-07-06上午10.31.56.png

    为了更加精确的布局cell中的CALayer对象,则可以在cell初始化函数中初始化作为分割线的CALayer对象,但不设置frame,将frame在layoutSubviews中进行布局,则能得到更为 精确的布局。

    cell初始化函数中添加CALayer的代码片段:

    ///底部分割线
    self.separatorLineLayer = [[CALayer alloc] init];
    [self.contentView.layer addSublayer:_separatorLineLayer];
    self.separatorLineLayer.backgroundColor = [UIColor grayColor].CGColor;
    

    在layoutSubviews函数中设置frame的代码片段:

    ///底部分割线
    - (void)layoutSubviews {
        [super layoutSubviews];
        NSLog(@"%f",CGRectGetHeight(self.contentView.frame));
        NSLog(@"%f",CGRectGetWidth(self.contentView.frame));
        self.separatorLineLayer.frame = CGRectMake(0, CGRectGetHeight(self.contentView.frame) - 0.5, CGRectGetWidth(self.contentView.frame), 0.6);
    }
    

    打印的结果为:

    截屏2021-07-06上午10.41.47.png

    视图样式为:

    截屏2021-07-06上午10.42.44.png

    - (void)setNeedsLayout;

    函数描述 : 使接收器的当前布局无效,并在下一个更新周期中触发布局更新。如果要调整视图子视图的布局,请在应用程序的主线程上调用此方法。此方法记录请求并立即返回。由于此方法不强制立即更新,而是等待下一个更新周期,因此可以使用它在更新任何视图之前使多个视图的布局无效。此行为允许您将所有布局更新合并到一个更新周期,这通常对性能更好。

    设置布局,告知系统这个视图需要更新布局,这个方法会立即返回,但是view会在下一次的更新周期中更新,调用视图的layoutSubviews。

    - (void)setNeedsLayout;
    

    例如 :

    - (void)setBgColor:(UIColor *)bgColor {
        _bgColor = bgColor;
        
        self.backgroundColor = bgColor;
        [self setNeedsLayout];
    }
    

    - (void)layoutIfNeeded;

    函数描述 : 如果布局更新挂起,则立即布局子视图。使用此方法强制视图立即更新其布局。使用自动布局时,布局引擎根据需要更新视图的位置,以满足约束的更改。使用接收消息的视图作为根视图,此方法从根开始布置视图子树。如果没有更新布局,则该方法退出而不修改布局或调用任何与布局相关的回调。

    layoutIfNeeded不一定会触发view的layoutSubviews。系统会检测layoutSubviews的触发条件,如果符合条件,那么会立即触发layoutSubviews,不会等待下一次的更新周期。但如果不符合layoutSubviews的触发条件则不会触发。

    - (void)layoutIfNeeded;
    

    例如 :在视图动画块代码中使用Masonry更改布局,在调用layoutIfNeeded后才会触发动画:

    [UIView animateWithDuration:1 animations:^{
        [self.customSelectRecurrenceView mas_remakeConstraints:^(MASConstraintMaker *make) {
            make.bottom.equalTo(self.view.mas_bottom).offset(-YSLCommonPadding * 2);
            make.centerX.equalTo(self.view);
            make.size.mas_equalTo(CGSizeMake(SCREEN_WIDTH - 20, 540));
        }];
        [self.view layoutIfNeeded];
     } completion:^(BOOL finished) {
        self.repetitionModeTableView.hidden = YES;
     }];
    

    UIView(UIViewRendering) - 视图渲染

    常用属性

    @property(nonatomic) BOOL clipsToBounds;

    属性描述 : 一个布尔值,用于确定子视图是否限制在视图的边界内。将此值设置为“YES”将导致子视图被剪裁到接收器的边界。如果设置为“NO”,则不会剪裁帧超出接收器可见边界的子视图。默认值为NO。

    @property(nonatomic)                 BOOL              clipsToBounds; 
    

    @property(nullable, nonatomic,copy) UIColor *backgroundColor UI_APPEARANCE_SELECTOR;

    属性描述 : 视图的背景色。对此属性的更改可以设置动画。默认值为nil,这将产生透明的背景色。

    @property(nullable, nonatomic,copy)            UIColor          *backgroundColor UI_APPEARANCE_SELECTOR;
    

    @property(nonatomic) CGFloat alpha;

    属性描述:视图的alpha值。此属性的值是介于0.0到1.0之间的浮点数,其中0.0表示完全透明,1.0表示完全不透明。更改此属性的值仅更新当前视图的alpha值。但是,该alpha值赋予的透明度会影响视图的所有内容,包括其子视图。例如,嵌入到alpha值为0.5的父视图中的alpha值为1.0的子视图将显示在屏幕上,就好像它的alpha值也是0.5一样。对此属性的更改可以设置动画。

    @property(nonatomic)                 CGFloat           alpha; 
    

    @property(nonatomic,getter=isOpaque) BOOL opaque;

    属性描述 : 决定视图是否不透明的布尔值。这个属性向绘图系统提供了应该如何处理视图的提示。如果设置为YES,绘图系统将视图视为完全不透明的,这允许绘图系统优化一些绘图操作并提高性能。如果设置为NO,绘图系统将正常地将视图与其他内容组合在一起。这个属性的默认值是YES。

    不透明视图应该用完全不透明的内容填充其边界——也就是说,内容的alpha值应该为1.0。如果视图是不透明的,或者没有填充其边界,或者包含完全或部分透明的内容,结果是不可预测的。如果视图是完全或部分透明的,则应该始终将此属性的值设置为NO。

    只需要在UIView的子类中为opaque属性设置一个值,这些子类使用drawRect:方法绘制自己的内容。不透明属性在系统提供的类(如UIButton、UILabel、UITableViewCell等)中不起作用。

    @property(nonatomic,getter=isOpaque) BOOL              opaque;
    

    @property(nonatomic) BOOL clearsContextBeforeDrawing;

    属性描述 : 一个布尔值,用来决定是否在绘制前自动清除视图的边界。当设置为YES时,绘制缓冲区在调用drawRect:方法之前自动清除为透明黑色。此行为可确保在重新绘制视图内容时不会留下任何视觉瑕疵。如果视图的opaque属性也设置为YES,则视图的backgroundColor属性不能为nil,否则可能会出现绘制错误。这个属性的默认值是YES。

    如果将此属性的值设置为NO,则负责确保在drawRect:方法中正确绘制视图的内容。如果图形代码已经进行了大量优化,则将此属性设置为“否”可以提高性能,尤其是在滚动视图时,可能只需要重新绘制视图的一部分。

    @property(nonatomic)                 BOOL              clearsContextBeforeDrawing;
    

    @property(nonatomic,getter=isHidden) BOOL hidden;

    属性描述 : 确定视图是否隐藏的布尔值。将此属性的值设置为“YES”将隐藏接收器,将其设置为“NO”将显示接收器。默认值为“NO”。

    隐藏视图将从其窗口中消失,并且不接收输入事件。不过,它仍保留在其superview的子视图列表中,并像往常一样参与自动调整大小。隐藏包含子视图的视图的效果是隐藏这些子视图及其可能具有的任何视图后代。这种效果是隐式的,不会改变接收者后代的隐藏状态。

    隐藏作为窗口当前第一响应程序的视图会导致视图的下一个有效键视图成为新的第一响应程序。

    此属性的值仅反映接收器的状态,而不说明接收器祖先在视图层次结构中的状态。因此,这个属性可以是NO,但是如果一个祖先是隐藏的,那么接收者可能仍然是隐藏的。

    @property(nonatomic,getter=isHidden) BOOL              hidden; 
    

    @property(nonatomic) UIViewContentMode contentMode;

    属性描述:当视图的边界改变时,用来确定视图如何布局其内容的标志。内容模式指定了当视图的边界改变时如何调整缓存的视图层位图。此属性通常用于实现可调整大小的控件。不用每次都重新绘制视图的内容,可以使用此属性来指定希望缩放内容(有或没有失真)或将它们固定到视图上的一个特定位置。

    @property(nonatomic)                 UIViewContentMode contentMode; 
    

    UIViewContentMode的枚举值

    typedef NS_ENUM(NSInteger, UIViewContentMode) {
        //根据需要通过更改内容的纵横比来缩放内容以适合其自身大小的选项。
        //UIImageView使用时,图片拉伸填充至整个UIImageView(图片可能会变形)
        UIViewContentModeScaleToFill,
        //通过保持纵横比缩放内容以适应视图大小的选项。视图边界的任何剩余区域都是透明的。
        //UIImageView使用时,图片拉伸至完全显示在UIImageView里面为止(图片不会变形),但可能长或宽某一方向留白
        UIViewContentModeScaleAspectFit, 
        //缩放内容以填充视图大小的选项。内容的某些部分可能会被剪裁以填充视图的边界。
        //UIImageView使用时,图片拉伸至完全显示在UIImageView里面为止(图片不会变形),但可能超过UIImageView的边界而被裁减
        UIViewContentModeScaleAspectFill, 
        //当边界改变时通过调用setNeedsDisplay方法重新显示视图的选项。
        UIViewContentModeRedraw, 
        //使内容在视图边界居中的选项,保持比例不变。
        UIViewContentModeCenter, 
        //将内容居中对齐视图边界顶部的选项。
        UIViewContentModeTop,
        //使内容在视图边界的底部居中的选项
        UIViewContentModeBottom,
        //在视图左侧对齐内容的选项。
        UIViewContentModeLeft,
        //在视图右侧对齐内容的选项。
        UIViewContentModeRight,
        //在视图左上角对齐内容的选项。
        UIViewContentModeTopLeft,
        //在视图右上角对齐内容的选项。
        UIViewContentModeTopRight,
        //对齐视图左下角内容的选项。
        UIViewContentModeBottomLeft,
        //对齐视图右下角内容的选项。
        UIViewContentModeBottomRight,
    };
    
    常用函数

    - (void)setNeedsDisplay;

    将接收者的整个边界矩形标记为需要重新绘制。可以使用此方法或setNeedsDisplayInRect:通知系统需要重新绘制视图的内容。此方法记录请求并立即返回。直到下一个绘图周期(此时将更新所有无效的视图)时,视图才实际重新绘制。

    如果你的视图是由一个CAEAGLLayer对象支持的,这个方法没有效果。它只用于使用本地绘图技术(如UIKit和Core Graphics)来呈现内容的视图。应该使用此方法来请求仅在视图的内容或外观更改时重新绘制视图。如果只是更改视图的几何形状,则通常不会重新绘制视图。相反,它的现有内容是根据视图的contentMode属性中的值进行调整的。重新显示现有内容可以避免重新绘制内容,从而提高性能

    与setNeedsLayout方法相似。该方法在调用时,会自动调用drawRect方法。drawRect方法主要用来画图。所以,当需要刷新布局时,用setNeedsLayout方法;当需要重新绘画时,调用setNeedsDisplay方法。

    - (void)setNeedsDisplay;
    

    例如 :

    - (void)setFillColor:(UIColor *)fillColor {
        if (fillColor) {
            _fillColor = fillColor;
        } else {
            _fillColor = [UIColor whiteColor];
        }
        [self setNeedsDisplay];
    }
    

    - (void)drawRect:(CGRect)rect;

    属性描述 : 该方法默认在视图加载过程中不做任何处理,当子类绘制视图内容时就可以在该方法中添加绘制的代码。如果在UIView初始化时没有设置Rect大小,将直接导致drawRect不被自动调用。如果设置了Rect,可以直接调用setNeedsDisplay触发。如果视图以其他方式设置其内容,则不需要重写此方法。例如,如果视图仅显示背景色,或者视图直接使用基础层对象设置其内容,则不需要重写此方法。

    在调用此方法时,UIKit已经为视图配置了适当的绘图环境,可以简单地调用渲染内容所需的任何绘图方法和函数。具体来说,UIKit创建和配置图形上下文,并调整该上下文的转换,使其原点与视图边框矩形的原点匹配。可以使用UIGraphicsGetCurrentContext函数获取对图形上下文的引用,但不要建立对图形上下文的强引用,因为它可以在对drawRect:方法的调用之间进行更改。

    此方法在首次显示视图或发生使视图的可见部分无效的事件时调用。你不应该自己直接调用这个方法。若要使视图的一部分无效,从而导致该部分被重绘,请改为调用setNeedsDisplay或setNeedsDisplayInRect:方法。

    参数 :

    rect : 视图边界中需要更新的部分。第一次绘制视图时,这个矩形通常是视图的整个可见边界。但是,在随后的绘图操作期间,矩形可能只指定视图的一部分。

    - (void)drawRect:(CGRect)rect;
    

    UIView(UIViewAnimationWithBlocks) - 视图动画代码块

    常用函数

    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations API_AVAILABLE(ios(4.0));

    函数描述 : 使用指定的持续时间对一个或多个视图的更改设置动画。此方法使用UIViewAnimationOptionCurveEaseInOut和UIViewAnimationOptionTransitionNone动画选项立即执行指定的动画。

    在动画期间,将暂时禁用正在设置动画的视图的用户交互。(在iOS 5之前,对整个应用程序禁用用户交互。)

    参数 :

    duration : 动画的总持续时间,以秒为单位。如果指定负值或0,则在不设置动画的情况下进行更改。

    animations : 包含要提交到视图的更改的块对象。这是以编程方式更改视图层次结构中视图的任何可设置动画的属性的地方。此块不接受任何参数,也没有返回值。此参数不能为NULL。

    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations API_AVAILABLE(ios(4.0));
    

    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));

    函数描述 : 使用指定的持续时间和完成处理程序为一个或多个视图的更改设置动画。此方法使用UIViewAnimationOptionCurveEaseInOut和UIViewAnimationOptionTransitionNone动画选项立即执行指定的动画。

    在动画期间,将暂时禁用正在设置动画的视图的用户交互。(在iOS 5之前,对整个应用程序禁用用户交互。)

    参数 :

    duration : 动画的总持续时间,以秒为单位。如果指定负值或0,则在不设置动画的情况下进行更改。

    animations : 包含要提交到视图的更改的块对象。这是以编程方式更改视图层次结构中视图的任何可设置动画的属性的地方。此块不接受任何参数,也没有返回值。此参数不能为NULL。

    completion : 动画序列结束时执行的块对象。这个块没有返回值,只有一个布尔参数,用来指示动画在完成处理程序被调用之前是否已经完成。如果动画的持续时间是0,这个块将在下一个运行循环的开始执行。该参数可以为NULL。

    + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));
    

    + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));

    函数描述:使用指定的持续时间、延迟、选项和完成处理程序为一个或多个视图的更改设置动画。

    此方法启动一组要在视图上执行的动画。animations参数中的block对象包含用于设置一个或多个视图特性动画的代码。

    在动画期间,将暂时禁用正在设置动画的视图的用户交互。(在iOS 5之前,对整个应用程序禁用用户交互。)如果希望用户能够与视图交互,请在options参数中包含UIViewAnimationOptionAllowUserInteraction常量。

    参数 :

    duration : 动画的总持续时间,以秒为单位。如果指定负值或0,则在不设置动画的情况下进行更改。

    delay :开始动画之前等待的时间量(以秒为单位)。指定值0以立即开始动画。

    options : 指示如何执行动画的选项掩码。

    animations : 包含要提交到视图的更改的块对象。这是以编程方式更改视图层次结构中视图的任何可设置动画的属性的地方。此块不接受任何参数,也没有返回值。此参数不能为NULL。

    completion : 动画序列结束时执行的块对象。这个块没有返回值,只有一个布尔参数,用来指示动画在完成处理程序被调用之前是否已经完成。如果动画的持续时间是0,这个块将在下一个运行循环的开始执行。该参数可以为空。

    + (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion API_AVAILABLE(ios(4.0));
    

    简单的练习代码片段:

    - (void)testAnimationBlock{
        //创建测试动画的标签
        UILabel *animationLabel = [[UILabel alloc]initWithFrame:CGRectMake(20, CGRectGetMaxY(self.view.frame) / 2, 200, 20)];
        animationLabel.textColor = [UIColor whiteColor];
        animationLabel.backgroundColor = [UIColor blueColor];
        animationLabel.text = @"我出现后就准备跑路了";
        animationLabel.textAlignment = NSTextAlignmentCenter;
        animationLabel.layer.cornerRadius = 8.0;
        animationLabel.layer.masksToBounds = YES;
        animationLabel.alpha = 0.0;
        [self.view addSubview:animationLabel];
        
        [UIView animateWithDuration:5.0 delay:10.0 options:UIViewAnimationOptionCurveEaseInOut animations:^{
            animationLabel.alpha = 1.0;
        } completion:^(BOOL finished) {
            [UIView animateWithDuration:2.0 animations:^{
                CGRect newAnimationLabelFrame = animationLabel.frame;
                newAnimationLabelFrame.origin.x = CGRectGetMaxX(self.view.frame) - 200 - 20;
                animationLabel.frame = newAnimationLabelFrame;
            } completion:^(BOOL finished) {
                animationLabel.text = @"我就跑路到这里吧";
            }];
        }];
    }
    

    动画效果如图 :

    Jietu20210220-140144.gif

    UIView (UIConstraintBasedLayoutLayering) - 基于约束的布局分层

    常用属性

    @property(nonatomic, readonly) CGSize intrinsicContentSize API_AVAILABLE(ios(6.0));

    属性描述 : 接收视图的自然大小,仅考虑视图本身的属性。自定义视图通常显示布局系统不知道的内容。通过设置此属性,自定义视图可以根据其内容与布局系统通信其希望的大小。这个内在大小必须独立于内容框架,因为例如,无法根据更改的高度将更改的宽度动态地传递给布局系统。如果自定义视图没有给定维度的内部大小,则可以对该维度使用UIViewNoIntrinsicMetric。

    @property(nonatomic, readonly) CGSize intrinsicContentSize API_AVAILABLE(ios(6.0));
    

    例如在自定义导航视图时,添加按钮不响应点击事件:

    截屏2020-05-07下午8.05.31.png

    可以在自定义的视图.h文件中覆盖intrinsicContentSize属性,在实例化自定义视图时设置intrinsicContentSize属性的值,例如 :

    @interface YSCNavigationItemTitleView : UIView
    
    @property(nonatomic, assign) CGSize intrinsicContentSize; //重写intrinsicContentSize属性
    @property(nonatomic, strong) UILabel *titleLabel; //标题标签
    @property(nonatomic, strong) UIButton *orderTypeSelectionButton; //订单类型选择按钮
    @property(nonatomic, strong) UIButton *labelMakeButton;//标题标签遮罩按钮
    
    @end
    
    
    - (void)createNavigationTitleView:(NSString *)text{
        //初始化订单汇总的自定义标题视图
        YSCNavigationItemTitleView *navigationItemTitleView = [[YSCNavigationItemTitleView alloc]initWithFrame:CGRectZero];
        //计算文本矩形
        CGRect rect = [navigationItemTitleView.titleLabel.text boundingRectWithSize:CGSizeMake(CGFLOAT_MAX, [YSCUiUtils singleCharactorSizeWithFont:[YSCUiUtils fontOne]].height) options:NSStringDrawingUsesLineFragmentOrigin | NSStringDrawingTruncatesLastVisibleLine attributes:@{NSFontAttributeName : [UIFont systemFontOfSize:20]} context:nil];
        //设置订单汇总的自定义标题视图大小
        navigationItemTitleView.intrinsicContentSize = CGSizeMake(rect.size.width, [YSCUiUtils singleCharactorSizeWithFont:[YSCUiUtils fontOne]].height);
    }
    

    UIView (UIConstraintBasedLayoutCoreMethods) - 基于约束的布局核心方法

    常用函数

    - (void)updateConstraints API_AVAILABLE(ios(6.0)) NS_REQUIRES_SUPER;

    函数描述 : 主要功能是更新view的约束,并会调用其所有子视图的该方法去更新约束。自定义视图应该通过重写此方法来设置更新自己的约束,然后调用setNeedsUpdateConstraints标记约束需要更新。系统在进行布局layout之前,会调用updateConstraints。要在实现的最后调用[super updateConstraints]。

    注 :在发生影响的更改后立即更新约束几乎总是更干净和更容易的。例如,如果要响应按钮点击更改约束,请直接在按钮的操作方法中进行更改。

    仅当在位更改约束太慢或视图正在生成大量冗余更改时,才应重写此方法。
    要计划更改,请对视图调用setNeedsUpdateConstraints。然后,系统在布局发生之前调用updateConstraint的实现。可以在自定义视图的属性未更改时验证内容的所有必要约束是否已到位。

    实现必须尽可能高效。不要停用所有约束,然后重新激活所需的约束。相反,应用必须有某种方法来跟踪约束,并在每次更新过程中验证它们。只更改需要更改的项。在每次更新过程中,必须确保对应用程序的当前状态具有适当的约束。

    不要在实现中调用setNeedsUpdateConstraints。调用setNeedsUpdateConstraints将调度另一个更新遍历,创建一个反馈循环。 调用[super updateConstraints]作为实现的最后一步。

    - (void)updateConstraints API_AVAILABLE(ios(6.0)) NS_REQUIRES_SUPER;
    

    updateViewConstraints

    - (void)updateViewConstraints API_AVAILABLE(ios(6.0));

    函数描述 :当视图控制器的视图需要更新其约束时调用, updateViewConstraints的出现方便了viewController,不用专门去重写controller的view,当view的updateConstraints被调用时,该view若有controller,该controller的updateViewConstraints便会被调用。
    要在实现的最后调用[super updateViewConstraints]。

    - (void)updateViewConstraints API_AVAILABLE(ios(6.0));
    

    IOS中View进行layout常用方法

    - (void)layoutSubviews;
    - (void)setNeedsLayout;
    - (void)setNeedsDisplay;
    - (void)layoutIfNeeded;
    - (void)drawRect:(CGRect)rect;
    - (CGSize)sizeThatFits:(CGSize)size;
    - (void)sizeToFit;
    - (void)updateConstraints;
    - (void)setNeedsUpdateConstraints; 
    

    相关文章

      网友评论

          本文标题:iOS UIView的学习笔记

          本文链接:https://www.haomeiwen.com/subject/acvyhctx.html