1、View架构
1.1 简介
UIView表示屏幕上的一块矩形区域,它在App中占有绝对重要的地位,因为IOS中几乎所有可视化控件都是UIView的子类。UIView的功能 :
1) 管理矩形区域里的内容;
2) 处理矩形区域中的事件;
3) 子视图的管理;
4) 实现动画。
图 11 UIView及子类继承关系
1.2 基本结构体
1) CGPoint
该结构表示在二维坐标系中的坐标点。
struct CGPoint {
CGFloat x;
CGFloat y;
};
2) CGSize
该结构表示一个矩形的长宽值。
struct CGSize {
CGFloat width;
CGFloat height;
};
3) CGRect
该结构表示一个矩形在坐标系中的位置。
struct CGRect {
CGPoint origin;
CGSize size;
};
这三个结构体均在一个头文件里:CGGeometry.h
1.3 UIView属性
UIView已经声明来一些属性来控制view的显示和行为,若需要可以在程序和 Interface Builder中进行修改。
1.3.1 坐标属性
UIView有三个属性用来控制view的尺寸和位置,其语义如下:
- frame:是CGRect 类型,其成员变量origin是指在父视图的坐标系统中的位置;
- center:是CGPoint类型,其是指子视图的中心点在父视图坐标系统中的位置;
- bounds:是CGRect类型,是指当前视图在自身的坐标系统中的位置。
如图 12所示是frame和center两个属性的差别。
图 12 Frame、center和bounds的差别
1.3.2 透明度属性
1.3.2.1 alpha属性[5]
液晶显示器是由一个个的像素点组成的,每个像素点都可以显示一个由RGBA颜色空间组成的一种色值。其中的A就表示透明度alpha,UIView中alpha是一个浮点值,取值范围0~1.0,表示从完全透明到完全不透明。
alpha的默认值是1.0,但当把alpha的值设置成0以后:
- 当前的UIView和subview都会被隐藏,而不管subview的alpha值为多少。
- 当前UIView会从响应者链中移除,而响应者链中的下一个会成为第一响应者。
另外,更改alpha值时,默认是有动画效果的,这是因为图层在Cocoa中是由Core Animation中CALayer表示的,该动画效果是CALayer的隐含动画。
1.3.2.2 hidden属性
该属性为BOOL类型值,用来表示UIView是否隐藏,默认值是NO。
当值设为YES时[5]:
- 当前的UIView和subview都会被隐藏,而不管subview的hidden值为多少。
- 当前UIView会从响应者链中移除,而响应者链中的下一个会成为第一响应者
hidden与alpha不同的是,hidden只有两种可能:隐藏或可见,而alpha可以设置多种值,从0~1.0。
1.3.2.3 opaque属性[5]
该属性为BOOL值,UIView的默认值是YES,但UIButton等子类的默认值都是NO。opaque属性是一个合成开关。当设置为YES值时显示本来的颜色,若设置为NO,则与其下层view进行RGB颜色合成。
图 13 opaque属性值差异
前面讲过,显示器中的每个像素点都可以显示一个由RGBA颜色空间组成的色值,比如图 13中有红色和绿色两个图层色块,对于没有交叉的部分,是红色和或绿色,而相交的颜色为黄色。这里的黄色是怎么来的呢?原来,GPU会通过图层一和图层二的颜色进行图层混合,计算出混合部分的颜色,最理想情况的计算公式如下: R = S + D * ( 1 – Sa )
其中,R表示混合结果的颜色,S是源颜色(位于上层的红色图层一),D是目标颜色(位于下层的绿色图层二),Sa是源颜色的alpha值,即透明度。公式中所有的S和D颜色都假定已经预先乘以了他们的透明度。
知道图层混合的基本原理以后,再回到正题说说opaque属性的作用。当UIView的opaque属性被设为YES以后,按照上面的公式,也就是Sa的值为1,这个时候公式就变成了: R = S
即不管D为什么,结果都一样。因此GPU将不会做任何的计算合成,不需要考虑它下方的任何东西(因为都被它遮挡住了),而是简单从这个层拷贝。这节省了GPU相当大的工作量。由此看来,opaque属性的真实用处是给绘图系统提供一个性能优化开关!
按照前面的逻辑,当opaque属性被设为YES时,GPU就不会再利用图层颜色合成公式去合成真正的色值。因此,如果opaque被设置成YES,而对应UIView的alpha属性不为1.0的时候,就会有不可预料的情况发生。
1.3.3 内容属性
1.3.3.1 contentMode属性
该属性是枚举值,其决定了图像在视图内是怎样的显示方式,其可以决定图像是拉伸或者填满方式来适应视图的变化。当视图的 frame 或 bounds属性发生了width或height值变化,那么将导致视图重新绘制图像的显示方式。
NOTE
你可以使用 setNeedsDisplay或者setNeedsDisplayInRect:方法来强制对一个view进行重绘。
1) 属性值
contentMode属性可以设置的值为如下,默认是UIViewContentModeScaleToFill。
typedef enum {
UIViewContentModeScaleToFill,
UIViewContentModeScaleAspectFit,
UIViewContentModeScaleAspectFill,
UIViewContentModeRedraw,
UIViewContentModeCenter,
UIViewContentModeTop,
UIViewContentModeBottom,
UIViewContentModeLeft,
UIViewContentModeRight,
UIViewContentModeTopLeft,
UIViewContentModeTopRight,
UIViewContentModeBottomLeft,
UIViewContentModeBottomRight,
} UIViewContentMode;
其中主要分为如下几种:
- ScaleToFill为将图片按照整个区域进行拉伸(会破坏图片的比例)
- ScaleAspectFit将图片等比例拉伸,可能不会填充满整个区域
- ScaleAspectFill将图片等比例拉伸,会填充整个区域,但是会有一部分过大而超出整个区域。
- 至于Top,Left,Right等等就是将图片在view中的位置进行调整。
2) 示例[6]
比如在视图中添加一个ImageView,然后修改ImageView内图像的不同显示方式,为了比较效果将图像的背景色设置为绿色。
- UIViewContentModeScaleAspectFill显示
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 350, 250)];
_imageView.image = [UIImage imageNamed:@"monkey"];
_imageView.contentMode = UIViewContentModeCenter;
_imageView.backgroundColor = [UIColor greenColor];
[self.view addSubview:_imageView];
_imageView.contentMode = UIViewContentModeScaleAspectFill;
}
图 14 UIViewContentModeScaleAspectFill显示方式
- UIViewContentModeScaleAspectFit显示
- (void)viewDidLoad {
[super viewDidLoad];
_imageView = [[UIImageView alloc] initWithFrame:CGRectMake(0, 0, 350, 250)];
_imageView.image = [UIImage imageNamed:@"monkey"];
_imageView.contentMode = UIViewContentModeCenter;
_imageView.backgroundColor = [UIColor greenColor];
[self.view addSubview:_imageView];
_imageView.contentMode = UIViewContentModeScaleAspectFit;
}
图 15 UIViewContentModeScaleAspectFit显示方式
1.3.4 subviews属性
该属性为 NSArray <UIView *>类型,即为一个UIView*类型的数组,并且为只读类型。该属性是指在当前视图中的子视图列表,而数组元素的顺序表示子视图显示的顺序,坐标为0的子视图是处于最底层。
1.3.5 transform属性
1.3.5.1 实现原理
transform是view的一个重要属性,它在矩阵层面上改变view的显示状态,能实现view的缩放、旋转、平移等功能。transform是CGAffineTransform类型的。使用transform后view的frame被真实改变的。CGAffineTransform结构定义为:
struct CGAffineTransform {
CGFloat a, b, c, d;
CGFloat tx, ty;
};
transform详解
在view可以看做有很多像素块构成,可以用(x,y)代表一个像素块,x为宽,y为高。transform就是改变每个像素块的形状。在运算过程中,[x,y,1]表示原来的像素块,而新的像素块[xn,yn,1]是由[x,y,1]乘以矩阵
{
a, b, 0
c, d, 0
tx,ty,1
}生成的。化简矩阵相乘,公式为
xn=ax+cy+tx;
yn=bx+dy+ty;
这个矩阵的第三列是固定的,所以每次变换时,只需传入前两列的六个参数[a,b,c,d,tx,ty]即可。
在CGAffineTransform的生成函数中,大多是两两对应的,一个带make字样,一个没有。带make字样的是直接生成一个新的CGAffineTransform,没有make字样的则是在一个CGAffineTransform的基础上生成新的。
其中的缩放、旋转和平移函数都只是修改了CGAffineTransform矩阵中的部分值,从而实现了不同功能,并且返回值均是CGAffineTransform类型。
1.3.5.2 实现的是放大和缩小
在原来transform的基础上生成一个新的transform。生成新的transform相当于将t‘ = [sx ,0 ,0,sy ,0, 0]这六个参数代入矩阵中。
则矩阵为 [ sx, 0 ,0
0 , sy,0
0 , 0 ,1];
代入公式中,xn=(sx)*x,yn=(sy)*y。所以sx,sy分别代表x轴与y轴上的放大倍数。
CGAffineTransformScale(CGAffineTransform t,CGFloat sx, CGFloat sy);
CGAffineTransformMakeScale(CGFloat sx, CGFloat sy);
若希望对某个视图进行缩放,但对视图内部的控件则保持原大小,那么可以通过坐标转换实现。如下所示, _parentView为进行缩放的视图,_subView为保持不变的视图,self.view为_parentView的父视图:
1 - (IBAction)downbutton:(id)sender {
2 CGRect subFrame = _subView.frame;
3 CGRect parFrame = [self.view convertRect:subFrame fromView:_subView];
4 _parentView.transform = CGAffineTransformScale(_parentView.transform,0.5, 0.5);;
5
6 CGRect conFrame = [self.view convertRect:parFrame toView:_parentView];
7 conFrame.origin = subFrame.origin;
8 [_subView setFrame:conFrame];
9 }
1.3.5.3 实现的是旋转
angle为弧度值,即当angle为3.14时,则旋转180度。矩阵的六个参数为t‘ = [ cos(angle),sin(angle),-sin(angle),cos(angle) , 0, 0];
CGAffineTransformRotate(CGAffineTransform t,CGFloat angle)
CGAffineTransformMakeRotation(CGFloat angle);
1.3.5.4 实现的是平移
矩阵的六个参数为t‘ = [1,0,0,1,tx,ty] ;代入公式,xn=x+tx,yn=y+ty。
CGAffineTransformTranslate(CGAffineTransform t,CGFloat tx, CGFloat ty);
CGAffineTransformMakeTranslation(CGFloat tx,CGFloat ty);
1.4 界面元素:window(窗口)、视图(view)
UIWindow是一种特殊的UIView,通常在一个app中只会有一个UIWindow。iOS程序启动完毕后,创建的第一个视图控件就是UIWindow,接着创建控制器的view,最后将控制器的view添加到UIWindow上,于是控制器的view就显示在屏幕上了。其中这个view称为根视图或是初始化视图,一般也只有一个。
1.5创建UIView
有两种方式创建view对象,Interface Builder和Programmatically。
1.5.1 Interface Builder
最简单的方式是使用图形界面的Interface Builder方法,通过这种方式能够从对象库中拖拽一个view对象到interface,安排它们的层次结构并对其配置,同时能够将这些view连接到代码中,从而实现它们的行为。可以参考《Interface Builder User Guide》文档。若创建完view后,可以在代码中获得view对象的controller,如:
1 swift实现:
2 var blue:blueViewController! blue = storyboard?.instantiateViewControllerWithIdentifier("blue") as! blueViewController;
3 Object-C实现:
4 blueViewController* blue = [self.storyboard instantiateViewControllerWithIdentifier:@"blue"];
1.5.2 Programmatically
还可以通过程序进行创建,其中可以使用标准的allocation/initialization模式。默认的initialization方法是initWithFrame,可以该方法初始化view尺寸和相对位置。比如object c实现为:
CGRect viewRect = CGRectMake(0, 0, 100, 100);
UIView* myView = [[UIView alloc] initWithFrame:viewRect];
2、View层次结构
管理视图的层次结构对应用开发来说是一个很重要的部分,对视图的组织即影响视图的显示,又影响事件是如何响应的。如图 21所示的Clock应用。
图 21 view层次结构
2.1 增加和移除子视图(subview)
一个UIView里面可以包含许多的Subview(其他的UIView),而这些 Subview 彼此之间是有所谓的阶层关系,这有点类似绘图软体中图层的概念,其中第0层是在最低下,即层数越高越在上层,从而上层的视图会遮住下层的视图。如所示表 21时UIView的常用方法。
表 21 UIView常用方法
Method |
usage |
addSubview |
这个是父视图的方法,通过这个方法能够将一个Subview添加到父视图的subview列表的尾部; |
insertSubview |
这个是父视图方法,可以将子视图插入父视图subview列表的指定位置; |
removeFromSuperview |
这个是子视图方法,通过它能够将子视图从父视图的subview列表中移除; |
bringSubviewToFront |
这个是父视图方法,将Subview往前移动一个图层(与它的前一个图层对调位置); |
sendSubviewToBack |
这个是父视图方法,将Subview往后移动一个图层(与它的后一个图层对调位置); |
exchangeSubviewAtIndex |
这个是父视图方法,交互子视图的位置。 |
如下的程序是在父视图控制器中实现两个子视图之间的切换:
1 func viewMove(from:UIViewController!,to:UIViewController!){
2 let subViewNun = self.view.subviews.count //为视图的层数,待会将to视图放在最上层。
3 if from != nil{
4 from!.willMoveToParentViewController(nil);
5 from!.view.removeFromSuperview();//调用子视图方法,从父视图中移除子视图。
6 from!.removeFromParentViewController();
7 }
8 if to != nil{
9 to. view.frame = view.frame//设置子视图的框架与父视图一样
10 self.addChildViewController(to!)//将子视图控制器添加到父视图的subview controller列表中
11 self.view.insertSubview(to!.view, atIndex: subViewNun)//将子视图添加到父视图的subview列表中
12 to!.didMoveToParentViewController(self) //调用子视图控制器方法,表明视图图发生变化了
13 }
14 }
当添加一个subview到另一个view中,UIKit即会通知父视图,又会通知子视图发生视图内容发生变化了。所以如果实现了定制的view,那么可以通过重载willMoveToSuperview, willMoveToWindow, willRemoveSubview, didAddSubview, didMoveToSuperview或didMoveToWindow方法来拦截这些消息,可以通过这些消息来更新相关的视图层次结构的状态或信息。
2.2 隐藏视图
为了隐藏视图的可见性,可以通过将"hidden"属性设置为yes,或者是将"alpha"属性值设置为0.0。被隐藏的视图不会再从系统中接收到触摸事件,当隐藏的视图还是会参加一些布局操作。其中若需要在隐藏时设置动画操作,那么只能通过修改alpha属性,而不能修改hidden属性,因为hidden不是animation属性。
Important:
如果要对第一响应者进行隐藏操作时,那么系统不会取消该视图的第一响应。所以当有消息到来时,仍然会把消息传递给这个被隐藏的视图,若要取消其事件的接收,需要手动进行取消。
2.3 加载视图
在视图的层次结构中获得其层次结构的子视图,有如下两种方式:
- 可以先存储某个子视图的指针,然后就可以获取子视图;
- 在层次结构中的任何一个视图调用viewWithTag:方法就能获取指定的视图;
也可以通过"Storyboard ID"值获得UIViewController,然后获得其view,如:
1 UIViewController *parentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"parentView"];
2 UIView *_parentView = parentViewController.view;
2.3.1 指针方式
可以在一个合适的地方创建一个指向视图的指针,那么就可以通过这个指针获取相应的视图,这种方式非常简单和普遍。如果使用Interface Builder创建视图,那么可以将该视图 outlets到视图控制器中,从而能方便获得。
2.3.2 tag方式
可以通过UIView的viewWithTag:方法获取指定值的子视图,该方法是从当前视图开始向子视图搜索,可以搜索多层子视图。
UIView *subView = [self.view viewWithTag:22];
设置视图的tag值有两种方式:
- interface builder
可以在interface builder设置tag值,但是需要先添加到视图的层次结构中,才能搜索到。
图 22 interface builder 设置tag值
- programmatically
[self.view setTag:1];
2.4 转换坐标
在一个视图的层次结构中,可以在不同的坐标系统中实现坐标值转换。这种转换是指在同一个平面中的点,在不同坐标值。其中UIView提供如下的方法:
- (CGPoint)convertPoint:(CGPoint)point fromView:(UIView *)view
- (CGRect)convertRect:(CGRect)rect fromView:(UIView *)view
- (CGPoint)convertPoint:(CGPoint)point toView:(UIView *)
- (CGRect)convertRect:(CGRect)rect toView:(UIView *)view
注意:
? convert...:fromView: 方法是从其它坐标系统的坐标值转换到当前坐标系统中;
? convert...:toView:方法是从当前坐标系统的坐标值转换到其它坐标系统中。
当对旋转的视图进行坐标转换时,其矩阵的尺寸会发生变化。如图 23所示,右边最内层的紫色矩形是在转换前的视图,而紫色矩形外的浅绿色是转换后的矩形。
图 23 旋转视图的转换
如下是从父视图的坐标值简单转换到子视图中:
1 -(void) convert
2 {
3 CGRect viewRect = CGRectMake(20, 20, 100, 100);
4 UIView* myView = [[UIView alloc] initWithFrame:viewRect];
5 [myView setBackgroundColor:[UIColor yellowColor]];
6 [myView setTag:23];
7 [self.view addSubview:myView];
8
9 CGRect rect = [self.view bounds];
10 CGPoint oright = rect.origin;
11
12 CGRect parentRect = [myView convertRect:rect toView:self.view];
13 CGPoint parentPoint = [myView convertPoint:oright toView:self.view];
14
15 NSLog(@"sub:%f",oright.x);
16 NSLog(@"parent:%f",parentPoint.x);
17 }
18 2016-01-23 21:42:08.559 viewProject[3402:399380] sub:0.000000
19 2016-01-23 21:42:08.560 viewProject[3402:399380] parent:20.000000
2.5 自适应布局
当父视图的尺寸和位置发生了改变,那么其相应子视图应该适应父视图的变化。
子视图为了适应父视图的变化,可以设置子视图的autoresizingMask属性,从而当父视图发生变化,那么子视图也能相应发生变化。其中autoresizingMask属性可以设置如表 22所示的各种值。若设置了autoresizingMask某一项值,那么该项就动态适应父视图的变化;若没有设置的项则保持原来固定的尺寸或位置。
表 22 autoresizingMask值
Autoresizing mask |
Description |
UIViewAutoresizingNone |
子视图根据固定值适应父视图的变化,不修改子视图的bounds,这是默认值。 |
UIViewAutoresizingFlexibleHeight |
当父视图的高发生变化,那么当前设置的子视图的高也跟着变化。 |
UIViewAutoresizingFlexibleWidth |
当父视图的宽发生变化,那么当前设置的子视图的宽也跟着变化。 |
UIViewAutoresizingFlexibleLeftMargin |
表示当前子视图左侧边缘与父视图左侧边缘的距离是否进行动态变化;如果子视图不设置该值,那么当父视图发生变化时,子视图左侧距离保持固定值。 |
UIViewAutoresizingFlexibleRightMargin |
表示当前子视图右侧边缘与父视图右侧边缘的距离是否进行动态变化;如果子视图不设置该值,那么当父视图发生变化时,子视图右侧距离保持固定值。 |
UIViewAutoresizingFlexibleTopMargin |
表示当前子视图顶部边缘与父视图顶部边缘的距离是否进行动态变化;如果子视图不设置该值,那么当父视图发生变化时,子视图顶部距离保持固定值。 |
UIViewAutoresizingFlexibleBottomMargin |
表示当前子视图低部边缘与父视图低部边缘的距离是否进行动态变化;如果子视图不设置该值,那么当父视图发生变化时,子视图低部距离保持固定值。 |
注意:
若失通过Interface Builder创建子视图,也可以在inspector设置子视图的自动布局。不过在inspector的设置与通过autoresizingMask设置的方式是相反的,设置autoresizingMask某项值,表示该项是自动适应,而设置了inspector某项值,表示该项固定值。
如下是通过对比设置了autoresizingMask属性后的对比图:
1) 不手动设置autoresizingMask值
1 -(void) autoresizing
2 {
3 UIViewController *parentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"parentView"];
4 _parentView = parentViewController.view;
5 [self.view addSubview:_parentView];
6
7 CGRect viewRect = CGRectMake(20, 20, 100, 100);
8 UIView* subView = [[UIView alloc] initWithFrame:viewRect];
9 [subView setBackgroundColor:[UIColor yellowColor]];
10 [_parentView addSubview:subView];
11 }
12
13 - (IBAction)downbutton:(id)sender { //按钮相应操作
14 CGRect rect = CGRectMake(0, 0, 200, 200);
15 [_parentView setBounds:rect];
16 }
图 24 不设置autoresizingMask值的变化图
2) 手动设置autoresizingMask值
1 -(void) autoresizing
2 {
3 UIViewController *parentViewController = [self.storyboard instantiateViewControllerWithIdentifier:@"parentView"];
4 _parentView = parentViewController.view;
5 [self.view addSubview:_parentView];
6
7 CGRect viewRect = CGRectMake(20, 20, 100, 100);
8 UIView* subView = [[UIView alloc] initWithFrame:viewRect];
9 [subView setBackgroundColor:[UIColor yellowColor]];
10 subView.autoresizingMask = UIViewAutoresizingFlexibleLeftMargin|UIViewAutoresizingFlexibleWidth |
11 UIViewAutoresizingFlexibleRightMargin|UIViewAutoresizingFlexibleTopMargin|UIViewAutoresizingFlexibleHeight| UIViewAutoresizingFlexibleBottomMargin;
12 [_parentView addSubview:subView];
13 }
14
15 - (IBAction)downbutton:(id)sender { //按钮相应操作
16 CGRect rect = CGRectMake(0, 0, 200, 200);
17 [_parentView setBounds:rect];
18 }
图 25 设置autoresizingMask值的变化图
3、ViewController
3.1 简介
在mvc设计模式中,controller对象提供特定的逻辑,负责连接应用程序的data和view,而且将data展示给用户。在ios应用程序中,view controller是一个特殊类型的controller对象,可以使用它来展示和管理一系列view,同时能够应用它从一个屏幕转到另一个屏幕。
每种UIView都可以指定一个UIViewController类或子类对其进行管理,当然也不是每种UIView都需要UIViewController类,其中将需要UIViewController的UIView称作content view(内容视图),因为这些content view是应用程序的主要容器。
在storyboard中将UIView和UIViewController合称为scene,并且在storyboard中不能独立存在UIView对象或组件,而是需要将UIView组件放置在UIViewController内(可放置多个),但是在xib文件中却是可以独立存在的。也就是说在content view中可以放置普通的UIView和容器类型的UIView,这样将放置在content view的UIView称为子view,而将content view称作父view。在content view中的子view是于图像的层次结构进行组织的,即上层的view可能会阻挡下层view的视线。如图 31所示的view层次结构,其中RootView需要放置在UIViewController中。
图 31s
View controller类型:
- custom:这是一种用于表示屏幕显示内容的控制器对象。
- container:这是一种特殊的view controller对象,它负责管理其它view controller对象,并定义view之间的导航关系。比navigation, tab bar和split view controllers
- modal:这既是custom view controller,又是container view controller,
Method |
usage |
|
addChildViewController |
向视图控制器容器中添加子视图控制器; |
|
removeFromParentViewController |
向视图控制器容器中添加子视图控制器; |
|
transitionFromViewController |
交换两个子视图控制器的位置; |
|
willMoveToParentViewController |
从视图控制器容器中被移动到另外一个视图控制器,如果没有父视图控制器,将为nil; |
|
didMoveToParentViewController |
当从一个视图控制容器中添加或者移除viewController后,该方法被调用。 |
图 32
4、参考文献
[1] View Programming Guide for IOS
[2] 《Beginning iPhone Development with Swift Exploring the iOS SDK-Apress(2014)》Chapter6
[3] http://blog.csdn.net/chengyingzhilian/article/details/7894276
[4] http://www.gowhich.com/blog/543
[5] http://blog.csdn.net/martin_liang/article/details/40739845
[6] 示例程序:https://github.com/Shawn-WangDapeng/ViewContentModeDemo