绘图
系统将内存中的对象渲染成屏幕上的图片
IOS中实现绘图功能的底层框架结构
- 最基础的绘图引擎:Core Graphics(QuartZ2D)
- 基于Core Graphics封装出来的第二个层次:Core Animation
- 基于Core Animation再封装出来就是UIKit
预备:
新建一个类,继承自UIView,实现drawRect方法,并且实现绘制的代码目前只能写在这个方法中,因为绘制过程是一个非常复杂的过程,系统只允许在特定的区域内拿到绘图上下文对象CGContextRef,该方法会由系统在创建视图实例时自动调用一次
绘制图形——UIBezier Path
step1:创建路径对象,勾勒路径
step2:设置填充或描边的颜色
step3:绘制[path stroke];
直线
UIBezierPath * path = [UIBezierPath bezierPath];
//勾勒路径
[path moveToPoint:CGPointMake(40, 40)];
[path addLineToPoint:CGPointMake(40, 140)];
//封闭图形
[path closePath];
弧线
UIBezierPath * path = [UIBezierPath bezierPath];
//圆弧的中心
CGPoint center = CGPointMake(self.bounds.size.width*0.5, self.bounds.size.height*0.5);
//圆弧的半径
CGFloat radius = MIN(self.bounds.size.width, self.bounds.size.height)*0.5-10;
//勾勒路径
//起点是二分之三π,终点是起点数值基础上累加一个弧度,累加的弧度要由下载数据决定,数据的百分之百时,弧度时2π,所以下载了0.3时,就用0.3*2π就可以了
[path addArcWithCenter:center radius:radius startAngle:M_PI_2*3 endAngle:M_PI_2*3+self.downloadValue*2*M_PI clockwise:YES];
曲线
UIBezierPath * path = [UIBezierPath bezierPath];
[path moveToPoint:CGPointMake(180, 40)];
//添加曲线
[path addCurveToPoint:CGPointMake(40, 180) controlPoint1:CGPointMake(40, 40) controlPoint2:CGPointMake(180, 180)];
矩形
UIBezierPath * path2 = [UIBezierPath bezierPathWithRect:CGRectMake(50, 50, 200, 100)];
圆角矩形
UIBezierPath * path2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(50, 50, 200, 100) cornerRadius:50];
[[UIColor blueColor]setStroke];
椭圆(正圆)
UIBezierPath * path3 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 200, 200, 100)];
总结:
前三种图形(直线、曲线、圆弧),先创建UIBezierPath然后使用addXXX方法实现路径设计
后两种图形(矩形、椭圆),在创建UIBezierPath时直接就设置了路径,使用bezierPathWithXXX方法
绘制字符串——NSString
只能画一行
NSString *str = @"这是一段用于测试多文本能够自动换行的测试文字,大概能够写个两三行看到结束就可以,恩,差不多了,就这样了吧!";
NSDictionary *attributes = @{
NSFontAttributeName:[UIFont systemFontOfSize:20],
NSForegroundColorAttributeName:[UIColor redColor]};
[str drawAtPoint:CGPointMake(30, 30) withAttributes:attributes];
在一个空间内绘制,自动换行
[str drawInRect:CGRectMake(30, 30, 150, 200) withAttributes:attributes];
根据文本内容自动设置空间
//自动计算出不超出指定的宽度和高度的前提下
//能够完整的显示字符串的最合适的高度和宽度
CGRect textFrame = [str boundingRectWithSize:CGSizeMake(200, 999) options:NSStringDrawingUsesLineFragmentOrigin attributes:attributes context:nil];
[str drawInRect:CGRectMake(50, 50, textFrame.size.width, textFrame.size.height) withAttributes:attributes];
绘制图片——UIImage
UIImage * image = [UIImage imageNamed:@"icon120"];
[image drawAtPoint:CGPointMake(50, 40)];
[image drawInRect:CGRectMake(50, 160, 120, 120)];
使用UIBezierPath的addClip方法,将路径以外的部分设置为绘图无效区,再来绘制图片时就能生成异形图片
[path addClip];
重绘
当数据发生改变时,希望能够根据新的数据重新绘内容时,调用视图的setNeedsDisplay方法,通知系统重绘。
[self setNeedsDisplay];
使用UIGraphiceBeginImageContextWithOptions和UIGraphiceEndImageContext开辟临时画布,在这个区间内可以编写绘图代码,最后将这块画布保存成一张新的图片
//申请一块临时画布
//opaque YES表示不透明 NO表示透明
//scale 画得比例
UIGraphicsBeginImageContextWithOptions(CGSizeMake(self.imageView.bounds.size.width, self.imageView.bounds.size.height), NO, 1);
UIBezierPath * path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.imageView.bounds.size.width, self.imageView.bounds.size.height)];
[path addClip];
UIImage * image = [UIImage imageNamed:@"icon120"];
[image drawInRect:CGRectMake(0, 0, self.imageView.bounds.size.width, self.imageView.bounds.size.height)];
//将画布中的图像另存为一张image对象
UIImage * newHeaderImage = UIGraphicsGetImageFromCurrentImageContext();
self.imageView.image = newHeaderImage;
UIGraphicsEndImageContext();
变形Transform
视图发生了位移(Translation)或者缩放(Scale)或者旋转(Rotation)这样的外观或位置的改变
实现变形:修改视图的transform属性即可
transform属性属于CGAffineTransform结构体类型(仿射变换)
包含有6个数值的3x3矩阵
可以借助函数帮我们计算出为了改变视图的样式而对应的矩阵值
实现帮我们计算矩阵的函数:
计算新的矩阵数值时,永远是基于视图没有任何变形的那个状态来计算
CGAffineTransformMakeTranslation()
CGAffineTransformMakeScale()
CGAffineTransformMakeRotation()
计算新的矩阵数值时,基于传入的矩阵状态来计算新的矩阵,会叠加变形效果
CGAffineTransformTranslate()
CGAffineTransformScale()
CGAffineTransformRotate()
使用
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, 20, 20);
手势
将用户物理性的触屏操作转变成了对象存储起来,所有手势的父类UIGestureRecognizer
系统将一下有特点的触屏操作封装成了不同的手势类型包括:
UITapGestureRecognizer
->点击(一次性手势)
UISwipeGestureRecognizer
->轻扫(解锁)(一次性手势)
UILongPressGestureRecognizer
->长按
UIPanGestureRecognizer
->拖拽
UIPinchGestureRecognizer
->捏合手势
UIRotationGestureRecognizer
->转转
使用手势
step1:创建指定手势的实例,在创建时设定好当该类型手势发生时,系统自动发什么消息(自动调用那个方法来响应)
step2:设置或读取手势对象的核心属性
step3:将手势添加到某个视图中,即代表,当用户在该视图上做了这样的手势时,系统才会捕获并调用方法来执行响应
Tap点击手势
核心属性:
numberOfTapsRequired
方法:
locationInView获取触点在某个视图坐标系下的坐标值
//1 创建Tap手势
UITapGestureRecognizer * tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
//2 设置手势的属性
//设置需要触发手势的点击数,默认1
tapGR.numberOfTapsRequired = 1;
//设置需要触发手势的触点的个数,默认1
tapGR.numberOfTouchesRequired = 1;
//3 将手势与具体的视图绑定在一起
[self.view addGestureRecognizer:tapGR];
//手势的响应方法
-(void)tap:(UITapGestureRecognizer *)gr{
//返回触摸点在哪个坐标系中的位置
CGPoint point = [gr locationInView:self.view];
NSLog(@"%@",NSStringFromCGPoint(point));
}
Swipe轻扫手势
属性:
direction方向
可以将左和右,或者上和下进行组合,使用按位或运算符
注意:Tap和Swipe手势归为一次性手势,即手势发生过程中,响应方法只执行一次
UISwipeGestureRecognizer * swipeGR = [[UISwipeGestureRecognizer alloc]initWithTarget:self action:@selector(swipe:)];
//扫动方向
swipeGR.direction = UISwipeGestureRecognizerDirectionRight|UISwipeGestureRecognizerDirectionLeft;
//绑定手势到视图上
[self.view addGestureRecognizer:swipeGR];
LongPress长按手势
设置:
minimumPressDuration长按需要的最少时间
长按手势有不同的状态,按下时,起始状态,移动中,改变状态,抬起时,结算状态。从一个状态到另一个状态发生变化时都会连续地调用响应方法
UILongPressGestureRecognizer * longPressGR = [[UILongPressGestureRecognizer alloc]initWithTarget:self action:@selector(longPress:)];
//设置长按动作的间隔秒数
longPressGR.minimumPressDuration = 2;
[self.view addGestureRecognizer:longPressGR];
-(void)longPress:(UILongPressGestureRecognizer *)gr{
NSLog(@"%@",NSStringFromCGPoint([gr locationInView:self.view]));
}
Pinch捏合手势
UIPinchGestureRecognizer * pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[self.view addGestureRecognizer:pinchGR];
-(void)pinch:(UIPinchGestureRecognizer *)gr{
//代表动作快慢的速率
//正数代表外扩,负数代表往内捏合,绝对值越大,代表动作越快,越小代表动作越慢
CGFloat veloctity = gr.velocity;
//代表动作外扩或向内捏合的比率倍数
//外扩:大于1的数,捏合:小于1的数
CGFloat scale = gr.scale;
NSLog(@"velocity = %.2f,scale=%.2f",veloctity,scale);
//每次计算完之后置1,让每次比较以上一次得到的数为基准
gr.scale = 1;
}
Rotation旋转手势
UIRotationGestureRecognizer * rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
[self.view addGestureRecognizer:rotationGR];
-(void)rotation:(UIRotationGestureRecognizer *)gr{
//旋转的弧度
//顺时针正数,逆时针负数
CGFloat rotation = gr.rotation;
NSLog(@"%.2f",rotation);
}
Pan拖拽手势
UIPanGestureRecognizer * panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:panGR];
-(void)pan:(UIPanGestureRecognizer *)gr{
//手势触点,在self.view坐标系下的点的坐标值
CGPoint location = [gr locationInView:self.view];
//手势移动到的新点相对于手势起始点的横向纵向距离
//手势移动了多远
CGPoint translation = [gr translationInView:self.view];
NSLog(@"%@ %@",NSStringFromCGPoint(location),NSStringFromCGPoint(translation));
}
多手势并存:
step1:设置需要并处的手势的代理为当前控制器
step2:控制器遵守协议UIGestureRecognizerDelegate
step3:实现
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer;方法,返回YES
//拖拽功能
UIPanGestureRecognizer * panGR = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(pan:)];
[self.view addGestureRecognizer:panGR];
//缩放功能
UIPinchGestureRecognizer *pinchGR = [[UIPinchGestureRecognizer alloc]initWithTarget:self action:@selector(pinch:)];
[self.view addGestureRecognizer:pinchGR];
//设置自己为代理
pinchGR.delegate = self;
//旋转功能
UIRotationGestureRecognizer * rotationGR = [[UIRotationGestureRecognizer alloc]initWithTarget:self action:@selector(rotation:)];
[self.view addGestureRecognizer:rotationGR];
//设置自己为代理
rotationGR.delegate = self;
//双击还原
UITapGestureRecognizer * tapGR = [[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(tap:)];
tapGR.numberOfTapsRequired = 2;
[self.view addGestureRecognizer:tapGR];
//实现图片跟随手势移动
-(void)pan:(UIPanGestureRecognizer *)gr{
//相对于起始点走了多远
//当视图发生放大缩小时,相应的视图的坐标系的比例也会放大或缩小
//要修改的transform属性,数据是使用imageView坐标系中的刻度值
//所以在读取手势移动了的距离是多少时,也要读取在imageView这个坐标系下的距离
CGPoint translation = [gr translationInView:self.imageView];
self.imageView.transform = CGAffineTransformTranslate(self.imageView.transform, translation.x, translation.y);
//将本次走了的距离归零
[gr setTranslation:CGPointZero inView:self.imageView];
//-------修改中心点实现位移------
// CGPoint translation2 = [gr translationInView:self.view];
// CGPoint center = self.imageView.center;
// center.x += translation2.x;
// center.y += translation2.y;
// self.imageView.center = center;
// [gr setTranslation:CGPointZero inView:self.view];
}
跟随手势捏合实现放大缩小
-(void)pinch:(UIPinchGestureRecognizer *)gr{
self.imageView.transform = CGAffineTransformScale(self.imageView.transform, gr.scale, gr.scale);
//去掉本次比率,归1
gr.scale = 1;
}
//缩放
-(void)rotation:(UIRotationGestureRecognizer *)gr{
self.imageView.transform = CGAffineTransformRotate(self.imageView.transform, gr.rotation);
gr.rotation = 0;
}
//实现多点触控并发方法
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}
//双击还原
-(void)tap:(UITapGestureRecognizer *)gr{
//Identity系统提供的一个常量,该常量中记录的就是没有发生任何变形时的那个矩阵
self.imageView.transform = CGAffineTransformIdentity;
}
深入坐标系
UIView——》frame bounds center transform
frame
a>什么是frame?
类型:
CGRect结构体类型
作用:
记录了试图左顶点在俯视图坐标系中的位置,以及视图在父视图内占的宽高
b>什么时候使用frame?
当需要将一个视图添加到另一个视图中做子视图时,一定要设置frame,保证在父视图中的位置即占据的区域大小
c>当修改frame时,其他三个属性是否会改变?
bounds:YES
center:YES
transform:NO
2>bounds
a>什么是bounds?
类型:
CGRect结构体类型
作用:
bounds中的x和y用于记录该视图自己坐标系的基准值,默认都是0,0后两个值记录的就是该视图自己的大小尺寸
b>什么时候使用bounds?
如果想要修改子视图的位置,可以调整bounds中的x或y(无法直接修改,要先建立CGRect的数值接收,修改后重新赋值),需要读取视图的大小时,那么就使用bounds中的width和height。在没有变形时,视图有多大,在父视图中就会占据多大空间,所以bounds中的w和h与frame中的w和h相等。
c>当修改bounds时,其他三个属性是否会改变?
frame:YES
center:NO
transform:NO
3>center
a>什么是center?
类型:
CGPoint类型
作用:
记录视图中心点在父视图坐标系下的位置
b>什么时候使用center?
如果想实现子视图位置的变化,则可以修改center
添加子视图时,如果不使用frame,那么可以使用bounds+center的组合设置来完成
c>当修改center时,其他三个属性是否会改变?
frame:YES
bounds:NO
transform:NO
4>transform
a>什么是transform?
类型:
CGAffineTransform类型
作用:
实现视图在展现出外观时,发生的位置的偏移、放大或缩小的效果、以及旋转效果
b>什么时候使用transform?
在展现外观时,有以上变化效果时,修改transform。
c>当修改transform时,其他三个属性是否会改变?
frame:YES
bounds:NO
center:NO
结论:
屏幕可以分为两个空间,一个是看的见的用户空间,一个是看不见的设备空间,transform属性记录的就是如何将设备空间中的视图映射到用户空间上,也就是说transform记录的是映射规则
frame是用户空间中的数据,也就是记录看见的结果的数据,bounds+center时设备空间中的数据,也就是合起来记录看不见的那个空间中视图的大小和位置,在不改变映射规则(transform)时,表里如一,但是,一旦改变了映射规则,那么就会表里不一,里面存储的视图展现出来时会根据transform发生改变
添加子视图,找frame
读取视图大小,找bounds
改位置,找center
实在是想旋转,找transform
故事板中添加手势
- 从资源库中选择合适的手势图标,拖拽到界面上
- 为了减少绑定过程,将手势直接拖拽到绑定的目标视图上
- 可以在场景顶端的横条中找到拖拽过的手势,然后选中,在第四个检查器中做常规设置
- 拆分视图下,选中手势对象,连线到代码中,添加对该手势动作的响应
图片视图默认不能与用户交互,所以添加手势到图片视图上以后,要开启用户交互功能,设置userInterActionEnabled为YES
UITouch触控
将用户的物理触屏动作转变成了数据存到了UITouch对象中
1>普通的视图
touchesBegan
touchesMoved
touchesEnded
touchesCancel
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
//按下触点,就将此点记录到属性中
self.beginPoint = [touch locationInView:self];
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
//创建路径
self.path = [UIBezierPath bezierPathWithRect:CGRectMake(self.beginPoint.x, self.beginPoint.y, point.x-self.beginPoint.x, point.y-self.beginPoint.y)];
self.path.lineWidth = 3;
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
//触点抬起时,将临时的路径确定保存到数组中
[self.allPath addObject:self.path];
}
- (void)drawRect:(CGRect)rect
{
[[UIColor redColor] setStroke];
//当前正在描绘,但还不确定结果的那个矩形
[self.path stroke];
//将所有存到数组中的已经确定了的矩形都重新一遍
for (UIBezierPath *path in self.allPath)
{
[path stroke];
}
}
2>控制器自带的视图
实现控制器的
touchesBegan
touchesMoved
touchesEnded
3>UITouch的用处
a>跟踪触点的轨迹,做类似于画板这样的应用
b>视图跟踪触点的移动,比pan手势的跟踪位置更准确
布局(Layout)
子视图在父视图中的摆放位置,通过设置frame实现布局,但是由于父视图可能因为屏幕的种类、横竖屏翻转、键盘弹起、各种bar的显示隐藏等因素发生改变,而此时如果不修改子视图的frame,依然保持在原有尺寸下摆放,那么界面就会出现混乱。所以当父视图大小发生变化时,希望调整子视图的frame,适应新的尺寸,保证布局出来的界面依然美观,于是使用布局技术实现该需求。
核心理念:
改frame
哪些因素会影响父视图大小发生变化?
1>屏幕尺寸(3.5、4、4.7、5.5)
2>横竖屏旋转
3>各种Bar
4>键盘弹起
5>特殊的bar 来电话的绿色条、开启热点的蓝色条、录音时的红色条
纯代码布局
理念:
只要检测到父视图的frame发生了变化,则将父视图中的所有子视图重新计算frame
关键点:
如何检测到父视图frame发生了变化
a>对控制器自带的那个view的直接子视图的布局
代码写在控制的viewDidLayoutSubViews方法中
-(void)viewDidLayoutSubviews{
[super viewDidLayoutSubviews];
//布局按钮
CGRect frame = self.button.frame;
frame.origin.x = self.view.bounds.size.width - 20 - frame.size.width;
//获取顶部被bar占据的高度
frame.origin.y = self.topLayoutGuide.length + 20;
self.button.frame = frame;
frame = self.label.frame;
frame.origin.x = self.view.bounds.size.width - frame.size.width - 20;
//获取底部被bar占据的高度
frame.origin.y = self.view.bounds.size.height - 20 - frame.size.height - self.bottomLayoutGuide.length;
self.label.frame = frame;
}
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self.navigationController setNavigationBarHidden:!self.navigationController.navigationBarHidden animated:YES];
[self.navigationController setToolbarHidden:!self.navigationController.toolbarHidden animated:YES];
}
b>对某个view的内部的子视图的布局
代码写在视图的layoutSubViews方法中(第一句一定要调用super中对应方法)
如:UITableViewCell对它内部子视图的布局、自定义tabBar对内部的子视图的布局
如何获取视图的顶部和底部被系统的bar占据的高度
AutoResizing布局
理念:
视图的位置由视图与父视图之间的间距决定,此间距可以设置为木棍或弹簧;视图的大小遵循等比变换原则——如父视图原有宽度是100,变化后是200,则父视图的宽是原来的二倍,于是父视图中的所有子视图的宽度也会变为原来的宽度的二倍
如何使用:
预备:一定要关闭Auto Layout,然后在视图的第五个检查器中会有6条红线,
step1:如果确定子视图与父视图的边缘距离固定,则点亮该方向的红线,
step2:如果确定子视图的宽高也需要变化,则点亮中间带有箭头的红线。
Auto Layout
核心理念:
通过为视图添加多个约束来说明视图的位置,当父视图发生变化时,系统就会在部位被任何一个约束的情况下,为我们自动计算出新的frame,从而达到自动布局的效果
约束:
添加的对视图位置或大小的限定条件
如:
文字描述:按钮距离顶部20,距离左边缘20,宽100,高40
约束1:竖直方向上的约束
约束2:水平方向上的约束
约束3:大小中宽的约束
约束4:大小中高的约束
使用约束的原则:
1>约束要准确
2>约束不能冲突
如何为视图添加约束:
方式一:不用写代码,在故事板中通过配置菜单
方式二:使用代码创建约束
约束类型:
NSLayoutConstraint
1>万能公式法:
任何一个约束都可以使用下面的公式进行描述:
view1.attr1 = view2.attr2 * multiplier + constant
按钮的左边缘距离父视图的左边缘为20个点
button.left = view.left * 1 + 20;
按钮的宽度是另一个按钮的宽度的两倍
button.width = button2.width * 2 + 0;
按钮的右边缘距离父视图的右边缘为20
button.right = view.right * 1 - 20;
按钮的宽度为80
button.width = nil.not * 0 + 80;
注意:
a>使用代码创建的视图实例,添加到父视图中时,系统会偷偷地为该视图添加两个约束,将视图的左和上的红线变成约束,一般都需要关闭该功能
b>约束创建好之后,要将该约束添加到视图所在的父视图身上
//1 关闭button1和button2的自动翻译红线为约束功能
button1.translatesAutoresizingMaskIntoConstraints = NO;
button2.translatesAutoresizingMaskIntoConstraints = NO;
//2 创建约束
//button1距离左边缘20个点 button1.left = view.left * 1 + 20
NSLayoutConstraint * c1 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeLeft relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeLeft multiplier:1 constant:20];
[self.view addConstraint:c1];
//button1.top = view.top * 1 + 20
NSLayoutConstraint * c2 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeTop multiplier:1 constant:60];
[self.view addConstraint:c2];
//button1.right = button2.left * 1 - 10
NSLayoutConstraint * c3 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:button2 attribute:NSLayoutAttributeLeft multiplier:1 constant:-10];
[self.view addConstraint:c3];
//button1.width = button2.width * 1 + 0
NSLayoutConstraint * c4 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeWidth relatedBy:NSLayoutRelationEqual toItem:button2 attribute:NSLayoutAttributeWidth multiplier:1 constant:0];
[self.view addConstraint:c4];
//button2.right = view.right * 1 - 20
NSLayoutConstraint * c5 = [NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeRight relatedBy:NSLayoutRelationEqual toItem:self.view attribute:NSLayoutAttributeRight multiplier:1 constant:-20];
[self.view addConstraint:c5];
//button2.top = button1.top * 1 + 0
NSLayoutConstraint * c6 = [NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeTop relatedBy:NSLayoutRelationEqual toItem:button1 attribute:NSLayoutAttributeTop multiplier:1 constant:0];
[self.view addConstraint:c6];
//button1.height = nil.notanattri * 0 + 40
NSLayoutConstraint * c7 = [NSLayoutConstraint constraintWithItem:button1 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:nil attribute:NSLayoutAttributeNotAnAttribute multiplier:0 constant:40];
[self.view addConstraint:c7];
//button2.height = button1.height * 1 + 0
NSLayoutConstraint * c8 = [NSLayoutConstraint constraintWithItem:button2 attribute:NSLayoutAttributeHeight relatedBy:NSLayoutRelationEqual toItem:button1 attribute:NSLayoutAttributeHeight multiplier:1 constant:0];
[self.view addConstraint:c8];
2>VFL法:Visual Format Language
使用一个特殊的字符串来表达一组视图之间的约束关系,系统会自动将字符串中表达的约束一次性创建成功,存到一个数组中
特殊的字符串中可用的特殊符号?
[ ]视图
-默认8个点间距
-x-间距为x
( )视图的宽度
|父视图边缘
V:竖直方向
button1.translatesAutoresizingMaskIntoConstraints = NO;
button2.translatesAutoresizingMaskIntoConstraints = NO;
button3.translatesAutoresizingMaskIntoConstraints = NO;
//创建一个尺寸对照表
NSDictionary * metrics = @{@"left":@20,
@"right":@20,
@"spacing":@20};
//创建视图的对照表
//此函数会自动将传入的对象名字符串做key,将此对象做key的value,生成如下形式的字典{@"b1":b1,@"b2":b2,@"b3":b3}
NSDictionary * dictionary = NSDictionaryOfVariableBindings(button1,button2,button3);
//1 创建水平方向的约束描述
NSString * hVFL = @"|-left-[button1]-spacing-[button2(==button1)]-spacing-[button3(==button1)]-right-|";
//2 将VFL变成一组约束
NSArray * cs1 = [NSLayoutConstraint constraintsWithVisualFormat:hVFL options:NSLayoutFormatAlignAllTop|NSLayoutFormatAlignAllBottom metrics:metrics views:dictionary];
//3 添加约束到父视图
[self.view addConstraints:cs1];
//创建竖直方向的VFL
NSString * vVFL = @"V:|-spacing-[button1(40)]";
NSArray * cs2 = [NSLayoutConstraint constraintsWithVisualFormat:vVFL options:0 metrics:metrics views:dictionary];
[self.view addConstraints:cs2];
动画(Animation)
动画
一般指"帧动画",一帧就是一张静态图片,一般1秒24帧以上,人眼就无法分辨图片的切换间隙了
帧率:FPS(Frame Per Second)每秒多少帧
ios中的动画
a>UIImage
[UIImage animatedImageNamed:@"ship-anim" duration:播放完一遍所有图片的时间]
b>UIImageView
1>拖拽到Assests.xcassets中的图片资源只能使用UIImage imageNamed方式来加载,不能使用绝对路径的方式来加载,因为资源库中的图片部署到沙盒中的xxx.app包中时都被转化到Assets.car文件中,此文件中没有路径一说。
2>没有拖拽到Assests.xcassets资源库中的图片,部署时会被添加到xxx.app包种的根路径下,此时加载这些有路径的图片时,可以使用imageNamed方法,也可以使用[[NSBundle mainBundle]pathForResource:fileName ofType:nil]方法。NSBundle mainBundle代表的就是xxx.app的根路径
c>UIView
类方法:
animatedxxx方法
可以做的动画效果:连续的改变center,实现位移动画,改变alpha,实现淡入淡出,改变transform,实现连续的旋转
基本步骤:
step1:在动画方法外,设置视图的开始状态
step2:调用UIView的类方法animatedxxx,填写完动画种类、时常相关参数后,在block块内,设置好视图的动画结束后的状态即可
d>通过修改约束实现动画
step1:先设置约束新的状态值
step2:调用UIView的animateWithDurationXX时,在block块内调用修改约束的视图的父视图的layoutIfNeeded方法完成动画过程的设置
Core Animation
UIView核心显示功能就是依靠CALayer实现的
UIView和CALayer的关系:1)UIView的显示能力是依靠底层的CALayer实现的,每一个UIView都包含了一个CALayer对象,修改了CALayer,会影响表现出来的UIView外观。2)CALayer是不能够响应事件的,但UIView由于继承自UIResponder,所以UIView还可以响应用户的触摸事件。
Core Animation中的动画只能添加到CALayer类型的对象身上,与UIView动画最大的区别就是CA动画是假的,视图看着好像位置发生了变化,但其实实际位置没变,在UIView动画中,由于明确的设定了动画结束时的状态,所以视图的数据会随着动画的结束而真的被改变
动画类的父类是CAAnimation,一般使用它的子类CABasicAnimation和CAKeyFrameAnimation
获取UIView底层的那个CALayer对象
通过视图的.layer属性就能拿到该视图底层的层对象
使用CALayer可以做什么
1>可以修改系统已有的UIView的layer改变视图的外观
常用属性:
masksToBounds
->是否可裁剪(修改圆形图片一定要选)
backgroundColor
->背景色
shadowXXXX
->阴影
borderColor
->边框的颜色
borderWidth
->边框的宽度
corneRadius
->圆角边框
transform(CATransform3D)
->变形
与尺寸位置有关的三个重要属性:
bounds
->大小
position
->位置
anchorPoint
->锚点
2>创建新的CALayer来完成视图的设计
CALayer
->绘制图片
CATextLayer
->绘制字符串
CAShapeLayer
->绘制图形
3>CALayer的动画
CADisplayLink
理念:
类似于定时器NSTimer,自身已经设定好,一秒钟会调用指定的方法60次,只需要算出每一帧改变的数值是多少
CABasicAnimation
基础动画:
只需要设置动画的起始值和终点值即可,一定要设置keyPath属性,以此说明动画需要修改的属性名是哪个
CAKeyFrameAnimation
关键帧动画:
可以定制动画过程中的细节,所以可以通过values属性记录中间每一个重要的变化点的数据,可以认为基础动画就是一个只有两个关键帧的动画,另外,关键帧最重要的效果就是可以自定义动画路径,通过path属性记录
NSNotification通知
什么是通知
一个对象发生了改变,希望让其他更多的对象知道他的改变,从而实现其他对象随之发生改变的一种信息传递手段
如何实现通知
使用系统发出的通知
//获取通知中心
NSNotificationCenter * center = [NSNotificationCenter defaultCenter];
//给通知中心发通知
//name:通知的名称
//object:谁发出的通知
//userInfo:通知的内容
[center postNotificationName:@"Update" object:self userInfo:@{@"title":@"TBBT",
@"episode":@"第12集"}];
//注册对"Update"通知的监听
//observer:谁想接收通知
//selector:用哪个方法响应
//name:监听的通知叫什么名
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(resiveNewNotifiaciton:) name:@"Update" object:nil];
//该方法会在接收到通知时自动调用
//参数就是系统封装好的通知对象,通过此参数可以拿到
//发出通知中的userInfo信息
-(void)resiveNewNotifiaciton:(NSNotification *)notification{
NSDictionary * dict = notification.userInfo;
NSLog(@"%@更新到了%@",dict[@"title"],dict[@"episode"]);
}
聊天气泡界面及功能
键盘弹起时,
使用约束设计Cell
创建高度不定的动态单元格
点击右下角不关键盘
——设置不要给文本框连线而是给文本框设置delegate,在当前控制器中遵守UITextFIeldDelegate,实现shouldClickReturn……方法中不要调用resignFirstResponder
使用下滑手势关闭键盘
——给tableView添加一个Swipe手势,设置该手势的delegate为当前控制器,遵守UIGestureRecoganizerDelegate协议,实现一个返回值是BOOL,xxxSimulatn的方法,返回YES
- (void)viewDidLoad {
[super viewDidLoad];
//为了让tableView自适应高度,需要设置如下两个属性
self.tableView.estimatedRowHeight = 70;
self.tableView.rowHeight = UITableViewAutomaticDimension;
//设置tableView的内边距,向下错64个点
self.tableView.contentInset = UIEdgeInsetsMake(64, 0, 0, 0);
}
//在view即将显示时添加对系统发出的键盘通知的监听
-(void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
//为当前控制器注册键盘弹起和关闭通知
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(openKeyboard:) name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(closeKeyboard:) name:UIKeyboardWillHideNotification object:nil];
}
//在view即将消失时取消键盘通知的监听
-(void)viewWillDisappear:(BOOL)animated{
[super viewWillDisappear:animated];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillShowNotification object:nil];
[[NSNotificationCenter defaultCenter]removeObserver:self name:UIKeyboardWillHideNotification object:nil];
}
//收到弹起键盘的通知后执行
-(void)openKeyboard:(NSNotification *)notification{
//读取弹起的键盘的高度
CGFloat keyboardHeight = [notification.userInfo[UIKeyboardFrameEndUserInfoKey] CGRectValue].size.height;
//读取动画的时长
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//读取动画的种类
NSInteger option = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
//修改底部输入视图的bottom约束
self.bottomLayoutConstraint.constant = keyboardHeight;
[UIView animateWithDuration:duration delay:0 options:option animations:^{
[self.view layoutIfNeeded];
//键盘弹起后,表视图移动到最底部
[self scrollToTableViewLastRow];
} completion:nil];
}
//收到收起键盘的通知后执行
-(void)closeKeyboard:(NSNotification *)notification{
//读取动画的时长
CGFloat duration = [notification.userInfo[UIKeyboardAnimationDurationUserInfoKey] floatValue];
//读取动画的种类
NSInteger option = [notification.userInfo[UIKeyboardAnimationCurveUserInfoKey] intValue];
//修改底部输入视图的bottom约束
self.bottomLayoutConstraint.constant = 0;
[UIView animateWithDuration:duration delay:0 options:option animations:^{
[self.view layoutIfNeeded];
} completion:nil];
}
//控制表视图滚动到最底部
-(void)scrollToTableViewLastRow{
NSIndexPath * lastIndexPath = [NSIndexPath indexPathForRow:self.allMessage.count-1 inSection:0];
[self.tableView scrollToRowAtIndexPath:lastIndexPath atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}
//界面显示后,表格已经生成了,在滚动到底部
-(void)viewDidAppear:(BOOL)animated{
[super viewDidAppear:animated];
[self scrollToTableViewLastRow];
}