系统化学习,知其然,知其所以然
一、创建和配置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)
当您更改视图的大小时,通常需要更改嵌入子视图的位置和大小,以适应其父视图的新大小。
superview 的 autoresizesSubviews 属性确定子视图是否调整大小。
如果此属性设置为YES,则视图使用每个子视图的 autoresizingMask 属性来确定如何调整和定位该子视图。
对任何子视图的大小更改会触发嵌入式子视图的类似布局调整。
对于视图层次结构中的每个视图,将该视图的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的大小
-
其他方法:
五、与图层进行交互(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 自己处理触摸事件,重新以下方法
- 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模式下几乎不用关注这部分工作。