OC常用视图、动画方法

绘图

系统将内存中的对象渲染成屏幕上的图片
IOS中实现绘图功能的底层框架结构

  1. 最基础的绘图引擎:Core Graphics(QuartZ2D)
  2. 基于Core Graphics封装出来的第二个层次:Core Animation
  3. 基于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

故事板中添加手势

  1. 从资源库中选择合适的手势图标,拖拽到界面上
  2. 为了减少绑定过程,将手势直接拖拽到绑定的目标视图上
  3. 可以在场景顶端的横条中找到拖拽过的手势,然后选中,在第四个检查器中做常规设置
  4. 拆分视图下,选中手势对象,连线到代码中,添加对该手势动作的响应

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

推荐阅读更多精彩内容

  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥ios动画全貌。在这里你可以看...
    每天刷两次牙阅读 8,460评论 6 30
  • 在iOS中随处都可以看到绚丽的动画效果,实现这些动画的过程并不复杂,今天将带大家一窥iOS动画全貌。在这里你可以看...
    F麦子阅读 5,091评论 5 13
  • Core Animation基础 Core Animation 利用了硬件加速和架构上的优化来实现快速渲染和实时动...
    独木舟的木阅读 1,521评论 0 3
  • Core Animation其实是一个令人误解的命名。你可能认为它只是用来做动画的,但实际上它是从一个叫做Laye...
    小猫仔阅读 3,681评论 1 4
  • 在iOS实际开发中常用的动画无非是以下四种:UIView动画,核心动画,帧动画,自定义转场动画。 1.UIView...
    请叫我周小帅阅读 3,076评论 1 23