美文网首页
View编程指南2—Views

View编程指南2—Views

作者: 好_快 | 来源:发表于2017-11-21 17:04 被阅读16次

    系统化学习,知其然,知其所以然

    一、创建和配置View对象(Creating and Configuring View Objects)

    有两种方式可以创建View对象:编程方式Interface Builder

    1.1 创建

    方式1:Interface Builder

    创建VIew最简单的方式是使用 Interface Builder,可以达到所见即所得效果。您在设计时看到的是运行时获得的内容。将活动对象保存在一个nib文件中,这是一个资源文件,用于保存对象的状态和配置。

    在视图控制器中使用nib文件时,只需使用nib文件信息初始化视图控制器即可。视图控制器在适当的时候处理视图的加载和卸载。但是,如果您的nib文件未与视图控制器相关联,则可以使用NSBundle或UINib对象手动加载nib文件内容,该对象使用nib文件中的数据重新构建视图对象。

    方式2:Programmatically

    如果以编程方式创建视图,则可以使用标准 allocation/initialization 模式来执行此操作。 视图的默认初始化方法是 initWithFrame:方法,该方法设置视图相对于(即将建立的)父视图的初始大小和位置。 例如,要创建一个新的泛型UIView对象,可以使用类似于以下的代码:

    CGRect viewRect = CGRectMake(0,0,100,100);
    UIView * myView = [[UIView alloc] initWithFrame:viewRect];
    
    注意:虽然所有的视图都支持initWithFrame:方法,但是每个View也可能有一个建议首选的初始化方法。 有关任何自定义初始化方法的信息,请参阅该类的参考文档。

    1.2 设置属性 Setting the Properties of a View

    UIView有几个声明的属性来控制视图的外观和行为。 这些属性用于操纵视图的大小和位置,视图的透明度,背景颜色和渲染行为。 所有这些属性都具有适当的默认值,您可以根据需要稍后进行更改。 可以通过 Programmatically 和 Interface Builder 两张途径修改。详情请查看API文档。

    1.3 标记视图 Tagging Views for Future Identification

    UIView包含一个tag属性,可以使用一个整数值来唯一标记一个View,并在运行时执行对这些视图的搜索。 (基于标记的搜索比自己迭代视图层次更快。)tag属性的默认值为0。

    使用UIView的 viewWithTag: 方法搜索视图。 此方法执行View及其子视图的深度优先搜索。不会向上或者横行搜索,只向下搜索,所以从root view 开始可以得到更多内容。

    二、创建和管理视图层次结构 Creating and Managing a View Hierarchy

    2.1 添加和删除子视图 Adding and Removing Subviews

    Interface Builder 是构建视图层次结构最方便的方式。可以用图形方式组装视图,查看视图之间的关系,并确切了解在运行时将如何显示这些视图。使用Interface Builder时,将结果视图层次结构保存在一个nib文件中,在运行时加载,因为需要相应的视图。

    以编程方式创建视图,请创建并初始化它们,然后使用以下方法将它们排列为层次结构:

    //添加
    - (void)addSubview:(UIView *)view; 
    
    //插入
    - (void)insertSubview:(UIView *)view 
                  atIndex:(NSInteger)index;
    - (void)insertSubview:(UIView *)view 
             aboveSubview:(UIView *)siblingSubview;
    - (void)insertSubview:(UIView *)view 
             belowSubview:(UIView *)siblingSubview;
             
    //跳转顺序
    - (void)bringSubviewToFront:(UIView *)view;
    - (void)sendSubviewToBack:(UIView *)view;
    - (void)exchangeSubviewAtIndex:(NSInteger)index1 
                withSubviewAtIndex:(NSInteger)index2;
    //移除
    - (void)removeFromSuperview;
    

    在视图控制器的 loadView 或 viewDidLoad 方法可以添加子视图到视图层次结构。

    • 如果以编程方式构建视图,则将视图创建代码放置在视图控制器的 loadView 方法中。
    • 无论是以编程方式创建视图还是从nib文件加载视图,都可以在 viewDidLoad 方法中包含其他视图配置代码。

    当将子视图添加到另一个视图时,UIKit通知更改的父视图和子视图。 如果实现自定义视图,则可以通过覆盖以下方法中的一个或者多个来截获这些通知。

    //父视图即将变化
    - (void)willMoveToSuperview:(UIView *)newSuperview;
    //父视图已经变化
    - (void)didMoveToSuperview;
    
    //调用view的窗口即将变化
    - (void)willMoveToWindow:(UIWindow *)newWindow;
    //调用view的窗口变化
    - (void)didMoveToWindow;
    
    //一个子视图即将移除
    - (void)willRemoveSubview:(UIView *)subview;
    //一个子视图已添加
    - (void)didAddSubview:(UIView *)subview;
    
    

    2.2 隐藏视图 (Hiding Views)

    隐藏视图有两种方式

    • 设置 hidden = YES ;
    • 设置 alpha = 0 ;

    但是有以下地方需要注意

    • 隐藏视图仍然参与自动布局,如果还需要显示话,隐藏比移除效果更好。
    • 隐藏视图不会自动退出 first responder ,需要手动结束;
    • hidden不是可以做动画属性,使用alpha 替代 ;

    2.3 访问视图层次结构中的视图(Locating Views in a View Hierarchy)

    有两种方式可以访问

    • 保持指针
    • 使用tag属性

    2.4 Translating, Scaling, and Rotating Views

    可以使用 UIView transform 属性进行translate、 scale、 rotate 操作。transform 属性包含了一个 CGAffineTransform 结构体,默认为 identity transform ,不会更改视图的外观。

    例如

    // M_PI/4.0 is one quarter of a half circle, or 45 degrees.
    CGAffineTransform xform = CGAffineTransformMakeRotation(M_PI/4.0);
    self.view.transform = xform;
    

    [图片上传失败...(image-db10b0-1511255073202)]

    注意点

    • 仿射变换添加顺序很重要,会导致不同结果
    • 仿射变换的中心点是视图的 Center 。
    • 更多详情

    2.5 坐标转换 (Converting Coordinates in the View Hierarchy)

    • UIView通过以下方法转换坐标
    - (CGPoint)convertPoint:(CGPoint)point 
                   fromView:(UIView *)view;
                   
    - (CGPoint)convertPoint:(CGPoint)point 
                     toView:(UIView *)view;
                     
    - (CGRect)convertRect:(CGRect)rect 
                 fromView:(UIView *)view;
                 
    
    - (CGRect)convertRect:(CGRect)rect 
                   toView:(UIView *)view;
    
    • UIWindow通过以下方法转换坐标
    - (CGPoint)convertPoint:(CGPoint)point 
                 fromWindow:(UIWindow *)window;
                 
    - (CGRect)convertRect:(CGRect)rect 
               fromWindow:(UIWindow *)window;
               
    - (CGPoint)convertPoint:(CGPoint)point 
                   toWindow:(UIWindow *)window;
                   
    - (CGRect)convertRect:(CGRect)rect 
                 toWindow:(UIWindow *)window;
    

    三、在运行时调整视图的大小和位置(Adjusting the Size and Position of Views at Runtime)

    每当视图的大小发生变化时,其子视图的大小和位置都必须相应地改变。 UIView支持自动布局和手动布局。

    • 通过自动布局,您可以设置每个视图在其父视图调整大小时应遵循的规则,然后完全忽略调整大小的操作。
    • 通过手动布局,您可以根据需要手动调整视图的大小和位置。

    3.1 触发布局变化条件(Being Prepared for Layout Changes)

    在视图中发生以下任何事件时,可能会发生布局更改:

    • 视图的bounds属性发生变化

    • 设备方向更改例如横屏,通常会触发rootview的bounds发生变化

    • view的layer发生更改,并且需要布局。

    • 调用视图的setNeedsLayout或layoutIfNeeded方法来强制执行布局

    • 通过调用视图底层对象的setNeedsLayout方法来强制执行布局

    3.2 使用自动调整规则自动处理布局更改(Handling Layout Changes Automatically Using Autoresizing Rules)

    当您更改视图的大小时,通常需要更改嵌入子视图的位置和大小,以适应其父视图的新大小。

    1. superview 的 autoresizesSubviews 属性确定子视图是否调整大小。

    2. 如果此属性设置为YES,则视图使用每个子视图的 autoresizingMask 属性来确定如何调整和定位该子视图。

    3. 对任何子视图的大小更改会触发嵌入式子视图的类似布局调整。

    对于视图层次结构中的每个视图,将该视图的autoresizingMask属性设置为适当的值是处理自动布局更改的重要部分。表3-2列出了可应用于给定视图的自动调整选项,并描述了在布局操作过程中的效果。可以使用 OR 运算符组合或者相加。如果使用Interface Builder来组装视图,则可以使用“自动调整大小”检查器来设置这些属性。

    //默认值,不会自动调整大小
    UIViewAutoresizingNone
    
    //父视图的高度改变时改变高度
    UIViewAutoresizingFlexibleHeight
    
    //父视图的宽度改变时改变宽度
    UIViewAutoresizingFlexibleWidth
    
    //视图左边缘和父视图左边缘之间的距离根据需要增长或缩小。如果不包含此常数,则视图的左边距离超视图的左边缘保持固定的距离。
    UIViewAutoresizingFlexibleLeftMargin
    
    //视图右边缘和父视图右边缘之间的距离根据需要增长或缩小。如果不包含此常数,则视图的右边距离超视图的右边缘保持固定的距离。
    UIViewAutoresizingFlexibleRightMargin
    
    //视图下边缘和父视图下边缘之间的距离根据需要增长或缩小。如果不包含此常数,则视图的下边距离超视图的下边缘保持固定的距离。
    UIViewAutoresizingFlexibleBottomMargin
    
    //视图上边缘和父视图上边缘之间的距离根据需要增长或缩小。如果不包含此常数,则视图的上边距离超视图的上边缘保持固定的距离。
    UIViewAutoresizingFlexibleTopMargin
    
    

    [图片上传失败...(image-9dbd73-1511255073202)]

    其中设置常量的地方会自动调整,否则为固定值。配置自动调整规则的最简单方法是使用Interface Builder的“大小”检查器中的“自动调整”控件。上图中灵活的宽度和高度常数与“自动调整”控件图中的宽度和大小指示器具有相同的行为。但是,指示效果是相反的。在界面构建器中,边缘指示符的存在意味着边距具有固定大小,并且缺少指示符意味着边距具有灵活的大小。幸运的是,Interface Builder提供了一个动画来展示自动修改行为对你的视图的影响。

    3.3 手动调整视图的布局(Tweaking the Layout of Your Views Manually)

    只要视图的大小发生变化,UIKit就会应用该视图的子视图的自动调整行为,然后调用视图的 layoutSubviews 方法以使其进行手动更改。 当自动调整没有产生所需的结果时可以在自定义视图中实现 layoutSubviews 方法。 此方法的实现可以执行以下任何操作:

    • 调整任何直接子视图的大小和位置。

    • 添加或删除子视图或核心动画层。

    • 通过调用setNeedsDisplay或setNeedsDisplayInRect:方法强制子视图重绘。

    经常手动布置子视图的一个地方是在实现大的可滚动区域时。由于对其可滚动内容拥有单个大视图是不切实际的,因此应用程序通常会实现一个根视图,其中包含许多较小的视图。每个图块代表可滚动内容的一部分。当滚动事件发生时,根视图调用其setNeedsLayout方法来启动布局更改。其layoutSubviews方法然后根据发生的滚动量重新定位平铺视图。当tile从视图的可见区域滚出时,layoutSubviews方法将tile移动到传入边缘,替换进程中的内容。

    编写布局代码时,请务必以下列方式测试代码:

    • 更改视图的方向以确保布局在所有支持的接口方向上正确。
    • 确保你的代码正确响应状态栏高度的变化。当打电话时,状态栏高度会增加,当用户结束通话时,状态栏的大小会减小。

    四、运行时修改视图(Modifying Views at Runtime

    由于应用程序从用户接收输入,他们调整其用户界面以响应该输入。 应用程序可能会通过重新排列视图,更改其大小或位置,隐藏或显示视图或加载全新的视图来修改视图。 在iOS应用程序中,有几种地方需要和方法可以执行这些操作:

    • 在 view controller 中:

      • 1 视图控制器必须在显示之前创建其视图。它可以从一个nib文件加载视图或以编程方式创建它们。当这些视图不再需要时,就把它们处理掉。
      • 2 当设备改变方向时,视图控制器可能会调整视图的大小和位置以匹配。作为调整新方向的一部分,可能会隐藏一些视图,并显示其他视图。
      • 3 当视图控制器管理可编辑的内容时,它可能会调整其视图层次结构。例如,它可能会添加额外的按钮和其他控件来方便编辑其内容的各个方面,这可能还需要调整任何现有的视图以适应现有的页面布局。
    • 在 animation blocks 中:

      • 1 当您想要在用户界面的不同视图之间切换时,可以隐藏一些视图并在动画块中显示其他视图
      • 2 实现特殊效果时,可以使用动画块来修改视图的各种属性。例如,要动画改变视图的大小,你可以改变它的frame的大小
    • 其他方法:

      • 1 触摸事件或手势发生时,您的界面可能会通过加载一组新的视图或更改当前的视图来响应。
      • 2 当用户与滚动视图交互时,大的可滚动区域可能会隐藏并显示切片子视图。更多详情
      • 3 当键盘出现时,您可以重新定位或调整视图的大小,使其不会位于键盘下方。 更多详情

    五、与图层进行交互(Interacting with Core Animation Layers)

    每个视图对象都有一个专用的图层,用于管理屏幕上视图内容的显示和动画。 虽然您可以使用视图对象做很多事情,但您也可以根据需要直接使用相应的图层对象。 视图的图层对象存储在视图的layer属性中。

    5.1 更改与视图关联的图层类(Changing the Layer Class Associated with a View)

    View的layer在其创建完成后无法更改。原因在于

    • view在初始化之前先调用layerClass方法来获取layer对象;
    • 然后layer.delegate = view;
    • view持有layer
    • view不能作为其他layer的delegate
    • 更改视图的所有权或委托代理关系会导致绘图问题和应用程序崩溃

    更改View默认layer的唯一方法是创建子类,重写该方法并返回不同的值。例如,

    + (Class)layerClass
    {
        return [CATiledLayer class];
    }
    

    更多详情

    5.2 Embedding Layer Objects in a View

    如果喜欢使用layer,可以使用自定义layer添加到View中构建视层级结构。自定义图层负责渲染内容和调整大小位置,不响应事件。
    例如

    - (void)viewDidLoad {
        [super viewDidLoad];
     
        // Create the layer.
        CALayer* myLayer = [[CALayer alloc] init];
     
        // Set the contents of the layer to a fixed image. And set
        // the size of the layer to match the image size.
        UIImage layerContents = [[UIImage imageNamed:@"myImage"] retain];
        CGSize imageSize = layerContents.size;
     
        myLayer.bounds = CGRectMake(0, 0, imageSize.width, imageSize.height);
        myLayer = layerContents.CGImage;
     
        // Add the layer to the view.
        CALayer*    viewLayer = self.view.layer;
        [viewLayer addSublayer:myLayer];
     
        // Center the layer in the view.
        CGRect        viewBounds = backingView.bounds;
        myLayer.position = CGPointMake(CGRectGetMidX(viewBounds), CGRectGetMidY(viewBounds));
     
    }
    

    六、自定义视图(Defining a Custom View)

    6.1 重点事项 Checklist for Implementing a Custom View

    • 1 定义合适的初始化方法

      • 1 代码 重写 - (instancetype)initWithFrame:(CGRect)frame; 方法或者定义新的方法

      • 1 nib文件加载的视图,重写 - (instancetype)initWithCoder:(NSCoder *)aDecoder;方法;

    • 2 实现 - (void)dealloc; 方法来清理自定义数据。

    • 3 要处理任何自定义绘图,请覆盖- (void)drawRect:(CGRect)rect;方法并在那里绘制绘图

    • 4 设置视图的autoresizingMask属性以定义其自动布局行为。

    • 5 如果View管理一个或多个子视图,请执行以下操作:

      • 1 在视图的初始化序列中创建这些子视图。
      • 1 在创建时设置每个子视图的autoresizingMask属性。
      • 1 如果子视图需要自定义布局,请覆盖layoutSubviews方法以实现您的布局代码。
    • 6 要处理基于触摸的事件,请执行以下操作:

      • 1 通过使用- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;方法添加手势到视图
      • 1 自己处理触摸事件,重新以下方法
    - (void)touchesBegan:(NSSet<UITouch *> *)touches 
               withEvent:(UIEvent *)event;
               
    - (void)touchesMoved:(NSSet<UITouch *> *)touches 
               withEvent:(UIEvent *)event;
               
    - (void)touchesEnded:(NSSet<UITouch *> *)touches 
               withEvent:(UIEvent *)event;
               
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches 
                   withEvent:(UIEvent *)event;           
    
    • 7 如果打印 view 版本信息,请实现- (void)drawRect:(CGRect)rect forViewPrintFormatter:(UIViewPrintFormatter *)formatter;方法。

    除了可以重新的方法外,还有很多通过API直接设置的属性来控制view显示效果。

    6.2 初始化自定义视图(Initializing Your Custom View)

    • 代码 重写 - (instancetype)initWithFrame:(CGRect)frame; 方法或者定义新的方法。
      例如
    - (id)initWithFrame:(CGRect)aRect {
        self = [super initWithFrame:aRect];
        if (self) {
              // setup the initial properties of the view
              ...
           }
        return self;
    }
    
    • nib文件加载的视图,重写 - (instancetype)initWithCoder:(NSCoder *)aDecoder;方法;可以通过实现- (void)awakeFromNib;添加额外的初始化工作。

    6.3 实现绘图代码(Implementing Your Drawing Code)

    一般来说,首选系统提供的标准视图或者组合它们来呈现您的内容,那么这是首选。然后选择自定义绘图,对于需要进行自定义绘图的视图,您需要重写 - (void)drawRect:(CGRect)rect;方法并在那里进行绘制。

    在调用视图的drawRect:方法之前,UIKit为视图配置基本的绘图环境。具体来说,它创建一个图形上下文,并调整坐标系、剪辑区域以匹配视图的坐标系和可见边界。在调用drawRect:方法时,可以使用原生绘图技术(如UIKit和Core Graphics)开始绘制内容。可以使用UIGraphicsGetCurrentContext函数获取指向当前图形上下文的指针。

    • drawRect:方法的实现应该完成一件事情:绘制你的内容。

    • 此方法不是要更新数据或执行任何与绘图无关的任务的地方。
      它应该配置绘图环境,绘制您的内容,并尽快退出。

    • 如果drawRect:方法可能会被频繁地调用,那么应该尽可能地优化绘图代码,并在每次调用方法时尽可能少地绘制。

    重要提示:当前的图形上下文仅在对视图的drawRect:方法进行一次调用期间才有效。 UIKit可能为这个方法的每个后续调用创建一个不同的图形上下文,所以你不应该尝试缓存对象并在以后使用它。

    示例代码

    - (void)drawRect:(CGRect)rect {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGRect    myFrame = self.bounds;
     
        // Set the line width to 10 and inset the rectangle by
        // 5 pixels on all sides to compensate for the wider line.
        CGContextSetLineWidth(context, 10);
        CGRectInset(myFrame, 5, 5);
     
        [[UIColor redColor] set];
        UIRectFrame(myFrame);
    }
    

    性能优化的2条建议

    • view.opaque = YES;
    • view.clearsContextBeforeDrawing = NO;

    6.4 事件响应(Responding to Events)

    • 1 通过添加手势来处理事件。使用- (void)addGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer;方法添加手势到视图
    • 2 自己处理触摸事件,重新以下方法
    - (void)touchesBegan:(NSSet<UITouch *> *)touches 
               withEvent:(UIEvent *)event;
               
    - (void)touchesMoved:(NSSet<UITouch *> *)touches 
               withEvent:(UIEvent *)event;
               
    - (void)touchesEnded:(NSSet<UITouch *> *)touches 
               withEvent:(UIEvent *)event;
               
    - (void)touchesCancelled:(NSSet<UITouch *> *)touches 
                   withEvent:(UIEvent *)event;           
    
    • 3 multipleTouchEnabled 属性控制多点触控
    • 4 响应事件开关有两种方式
      • userInteractionEnabled 属性控制是否响应事件
      • 通过一对方法也可以控制
    //此二者方法通常在动画开始结束时调用。动画期间是不响应触摸事件。
    
    - (void)beginIgnoringInteractionEvents;
    
    - (void)endIgnoringInteractionEvents;
    
    • 5 通过重写以下方法来判断触摸事件是否发生在视图内
    - (BOOL)pointInside:(CGPoint)point 
              withEvent:(UIEvent *)event;
              
    - (UIView *)hitTest:(CGPoint)point 
              withEvent:(UIEvent *)event;
    

    6.5 清理(Cleaning Up After Your View)

    重写- (void)dealloc;方法来清理内容,例如

    - (void)dealloc {
        // Release a retained UIColor object
        [color release];
     
        // Call the inherited implementation
        [super dealloc];
    }
    

    但是在ARC模式下几乎不用关注这部分工作。

    参考连接 Views

    相关文章

      网友评论

          本文标题:View编程指南2—Views

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