View编程指南2—Views

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

一、创建和配置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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容