美文网首页动画
第一篇:iOS中的贝塞尔曲线--UIBezierPath

第一篇:iOS中的贝塞尔曲线--UIBezierPath

作者: 意一ineyee | 来源:发表于2018-01-31 13:52 被阅读29次

    在开始学习Quartz2D之前,我们先总结下UIKit框架里的UIBezierPath,可以方便我们理解后面Quartz2D的部分内容,也可以方便我们和后面Quartz2D的部分内容做对比。


    目录

    一、什么是贝塞尔曲线?

    1、贝塞尔曲线的定义及绘制原理
    2、UIBezierPath常用的Api

    二、如何使用UIBezierPath?


    一、什么是贝塞尔曲线?

    1、贝塞尔曲线的定义及绘制原理
    • 定义:贝塞尔曲线的数学原理这里就不说了,早忘球了。贝塞尔曲线是指我们通过很少的控制点绘制出来的一条平滑的曲线。

    但是我不知道为什么UIKit里的UIBezierPath要叫UIBezierPath,看了下面,我们很明显能知道UIBezierPath不仅能用指定的方法绘制贝塞尔曲线,还能绘制别的任意你想要绘制的曲线,感觉贝塞尔曲线仅仅是UIBezierPath所能绘制的曲线的真子集。不知道是不是我没理解贝塞尔曲线的缘故,可能是我把贝塞尔曲线的范围给缩小了,因为我们知道可以绘制出一条直线,这条直线也可以看做是一条一次贝塞尔曲线,如果这样考虑的话,任意一条曲线都可以看作是一条N次贝塞尔曲线了,就能和UIBezierPath对应起来了,暂时这样考虑吧。

    • 绘制原理:那贝塞尔曲线的绘制原理是什么?下面举个例子说明:

    (1)绘制线段AC、BC,相交于点C;
    (2)在线段AC上取点D,在线段BC上取点E,使得AD:DC=CE:EB;
    (3)连接DE;
    (4)在线段DE上取点F,使得DF:FE=AD:DC=CE:EB;

    如下图:

    11.png

    (5)这样我们就算是找到了贝塞尔曲线上的一个点--F,按同样的道理让点D从A移动到C、点E从C移动到B,就会找到无数个点F,这样把点F连接起来就可以绘制出贝塞尔曲线。如下图:

    11.gif
    2、UIBezierPath常用的Api

    (1)常用路径参数

    [[UIColor <#someColor#>] set];// 同时设置路径颜色和路径的填充色
    [[UIColor <#someColor#>] setStroke];// 设置路径颜色
    [[UIColor <#someColor#>] setFill];// 设置路径的填充色
    
    @property(nonatomic) CGFloat lineWidth;// 路径宽度
    @property(nonatomic) CGLineCap lineCapStyle;// 路径开始和结尾的样式
    @property(nonatomic) CGLineJoin lineJoinStyle;// 路径转角处的样式
    
    lineCapStyle: 11.png lineJoinStyle: 11.png

    (2)笨拙绘制路径的方法:虽说使用这些方法画起某些特殊的路径来比下面的特殊方法慢一点,但是毫无疑问这些方法是绘制路径的根本,所以可以绘制各种各样任何你想要的路径。

    ①把路径当前点移动到指定的点point:

    - (void)moveToPoint:(CGPoint)point;
    
    • 对于很多绘制路径的操作,我们必须在发起路径绘制之前调用一下这个方法来设置一个起点;

    ②画一条直线:

    - (void)addLineToPoint:(CGPoint)point;
    
    • 以路径当前点为起始点,point为结束点,给当前路径添加一条直线;

    • 添加完之后会自动将路径的当前点设置为结束点point。

    • 这个方法不仅仅用于绘制一条很明显的直线,也可以用来绘制数学公式或者手在屏幕上随便滑动那种更加复杂的路径。

    ③画一段圆弧:

    - (void)addArcWithCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
    
    • 以路径当前点为起始点,center为中心,radius为半径,startAngle为画圆弧的起始角度(单位为弧度,水平方向的右侧为0和2π,水平方向的左侧为π),endAngle为圆弧的结束角度(单位为弧度),clockwise是否顺时针;
    • 添加完之后会自动将路径的当前点设置为结束点。
    11.png

    ④画一条二次贝塞尔曲线:

    - (void)addQuadCurveToPoint:(CGPoint)endPoint controlPoint:(CGPoint)controlPoint;
    
    • 以路径当前点为起始点,endPoint为结束点,controlPoint为控制点,给当前路径添加一条二次贝塞尔曲线;
    • 添加完之后会自动将路径的当前点设置为结束点endPoint。

    ⑤画一条三次贝塞尔曲线:

    - (void)addCurveToPoint:(CGPoint)endPoint controlPoint1:(CGPoint)controlPoint1 controlPoint2:(CGPoint)controlPoint2;
    
    • 以路径当前点为起始点,endPoint为结束点,controlPoint1和controlPoint2为两个控制点,给当前路径添加一条三次贝塞尔曲线;
    • 添加完之后会自动将路径的当前点设置为结束点endPoint。

    ⑥画一条虚线:

    - (void)setLineDash:(nullable const CGFloat *)pattern count:(NSInteger)count phase:(CGFloat)phase;
    
    • pattern:是一个C语言里的数组,如CGFloat pattern[] = {10, 10};,它代表实现和空白交替的模式:如{10, 10}代表从起点开始先画一个10pt的小实线,然后空白10pt,然后再画一个10pt的小实线,如此循环画下去,直到路径结束;再如{10, 20, 30}代表从起点开始先画一个10pt的小实线,然后空白20pt,然后再画一个30pt的小实线,然后再空白10pt,然后画一个20pt的小实线,然后再空白30pt,如此循环画下去,直到路径结束。
    • count:是pattern数组的长度;
    • phase:相位,默认为0,设置为几就代表虚线整体上相对phase=0的情况需要向左移动几。
      (下面会有例子,可对比理解下这三个参数)

    ⑥闭合路径:

    - (void)closePath;
    
    • 这个方法会在最后一个点和第一个点之间连接一条直线来将路径闭合;
    • 路径闭合完之后会自动将路径的当前点设置为路径的第一个点。

    (3)快速绘制特殊路径的方法:我们可以使用这些方法快速地绘制一个矩形、圆角矩形、圆弧、圆或者椭圆,而且再使用这些方法绘制路径时,不需要主动调用- (void)moveToPoint:(CGPoint)point;来设置路径的起始点,也不需要主动调用- (void)closePath;来闭合路径,这些方法都会自动的完成这两个操作。

    ①画一个矩形

    + (instancetype)bezierPathWithRect:(CGRect)rect;
    
    • 这个方法以rect的origin为起点,顺时针绘制出一个矩形;
    • 绘制完成后自动闭合路径,将结束点作为路径的当前点;

    ②画一个圆角矩形(四个角都会圆)

    + (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;
    

    ③画一个指定圆角的矩形(指定角会圆)

    + (instancetype)bezierPathWithRoundedRect:(CGRect)rect byRoundingCorners:(UIRectCorner)corners cornerRadii:(CGSize)cornerRadii;
    
    • cornerRadii是个CGSize的类型,暂时把半径设置为size的width值;
    • 但是如果size中比较大的值超过了矩形短边的一半,则圆角会采取矩形短边的一半为半径。

    ④画一段圆弧

    + (instancetype)bezierPathWithArcCenter:(CGPoint)center radius:(CGFloat)radius startAngle:(CGFloat)startAngle endAngle:(CGFloat)endAngle clockwise:(BOOL)clockwise;
    

    ⑤给定一个矩形,画出其内切圆或椭圆

    + (instancetype)bezierPathWithOvalInRect:(CGRect)rect;
    

    (4)渲染路径:两种渲染可单独使用,也可以同时使用
    ①空心渲染路径:

    - (void)stroke;
    
    • 这个方法用来空心渲染路径;
    • 并且在渲染路径之前,会自动复制一份最纯洁的图形上下文,因此我们不需要考虑图层上下文的事,只需要专注于使用UIBezierPath来绘制路径就可以了。

    ②实心渲染路径:

    - (void)fill;
    
    • 这个方法用来实心渲染路径,即渲染路径包围的部分,并不包含路径本身,因此如果路径很宽的话,是不会渲染路径本身的;
    • 并且在渲染路径之前,会自动复制一份最纯洁的图形上下文,因此我们不需要考虑图层上下文的事,只需要继续使用UIBezierPath来绘制路径就可以了。

    (5)路径的仿射变化:

    - (void)applyTransform:(CGAffineTransform)transform;
    
    • 我们可以用这个对路径进行仿射变换,但是要注意对路径的仿射变化其实是对整个图层进行了仿射变换。

    (6)移除路径上所有的点(或者说移除路径):

    - (void)removeAllPoints;
    
    • 这个方法用来移除路径上所有的点或者说移除路径。

    (7)剪裁效果:

    - (void)addClip;
    
    • 调用一下这个方法,我们可以把一个对象绘制到一个路径所覆盖的区域内,就可以达到对对象的剪裁效果;
    • 只要调用了这个方法,那么显示的范围就被限定为该bezierPath所包围的范围了,此时无论往上下文中绘制什么东西,只要超出了这个范围的就都不会显示。

    二、如何使用UIBezierPath?

    1、画一条直线
    (清单1.1)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath.lineWidth = 11;
        bezierPath.lineCapStyle = kCGLineCapButt;
        bezierPath.lineJoinStyle = kCGLineJoinRound;
        [bezierPath moveToPoint:CGPointMake(100, 100)];
        [bezierPath addLineToPoint:CGPointMake(200, 100)];
        [bezierPath stroke];
        
        UIBezierPath *bezierPath1 = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath1.lineWidth = 11;
        bezierPath1.lineCapStyle = kCGLineCapRound;
        bezierPath1.lineJoinStyle = kCGLineJoinRound;
        [bezierPath1 moveToPoint:CGPointMake(100, 200)];
        [bezierPath1 addLineToPoint:CGPointMake(200, 200)];
        [bezierPath1 stroke];
        
        UIBezierPath *bezierPath2 = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath2.lineWidth = 11;
        bezierPath2.lineCapStyle = kCGLineCapSquare;
        bezierPath2.lineJoinStyle = kCGLineJoinRound;
        [bezierPath2 moveToPoint:CGPointMake(100, 300)];
        [bezierPath2 addLineToPoint:CGPointMake(200, 300)];
        [bezierPath2 stroke];
    }
    
    @end
    
    11.png

    2、画圆弧、 圆、椭圆
    (清单1.2)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        // 画一段圆弧
        UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath.lineWidth = 11;
        bezierPath.lineCapStyle = kCGLineCapButt;
        bezierPath.lineJoinStyle = kCGLineJoinRound;
        [bezierPath moveToPoint:CGPointMake(150, 100)];
        [bezierPath addArcWithCenter:CGPointMake(100, 100) radius:50 startAngle:0 endAngle:M_PI_2 clockwise:YES];
        [bezierPath stroke];
        
        UIBezierPath *bezierPath1 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(300, 100) radius:50 startAngle:0 endAngle:M_PI_2 clockwise:NO];
        [[UIColor orangeColor] setStroke];
        [bezierPath1 stroke];
        
        // 画圆
        UIBezierPath *bezierPath2 = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        [bezierPath2 moveToPoint:CGPointMake(150, 250)];
        [bezierPath2 addArcWithCenter:CGPointMake(100, 250) radius:50 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
        [bezierPath2 stroke];
        
        UIBezierPath *bezierPath3 = [UIBezierPath bezierPathWithArcCenter:CGPointMake(300, 250) radius:50 startAngle:0 endAngle:M_PI * 2 clockwise:NO];
        [[UIColor orangeColor] setFill];
        [bezierPath3 fill];
        
        // 通过矩形来画出其内切圆或内切椭圆
        UIBezierPath *bezierPath4 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(50, 350, 100, 100)];
        [[UIColor orangeColor] setStroke];
        [bezierPath4 stroke];
    
        UIBezierPath *bezierPath5 = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(250, 350, 100, 50)];
        [[UIColor orangeColor] setStroke];
        [bezierPath5 stroke];
    }
    
    @end
    
    11.png

    3、画矩形、 圆角矩形、指定圆角的圆角矩形
    (清单1.3)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        // 画矩形
        UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath.lineWidth = 11;
        bezierPath.lineCapStyle = kCGLineCapButt;
        bezierPath.lineJoinStyle = kCGLineJoinRound;
        [bezierPath moveToPoint:CGPointMake(100, 100)];
        [bezierPath addLineToPoint:CGPointMake(200, 100)];
        [bezierPath addLineToPoint:CGPointMake(200, 200)];
        [bezierPath addLineToPoint:CGPointMake(100, 200)];
        [bezierPath closePath];// 或者[bezierPath addLineToPoint:CGPointMake(100, 100)];
        [bezierPath stroke];
        
        UIBezierPath *bezierPath1 = [UIBezierPath bezierPathWithRect:CGRectMake(250, 100, 100, 100)];
        [[UIColor orangeColor] setStroke];
        [bezierPath1 stroke];
        
        // 画圆角矩形
        UIBezierPath *bezierPath2 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 250, 100, 100) cornerRadius:10];
        [[UIColor orangeColor] setStroke];
        [bezierPath2 stroke];
        
        // 画指定圆角的矩形
        UIBezierPath *bezierPath3 = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 400, 100, 100) byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(40, 0)];
        [[UIColor orangeColor] setStroke];
        [bezierPath3 stroke];
    }
    
    @end
    
    11.png

    4、画二次贝塞尔曲线和三次贝塞尔曲线
    (清单1.4)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        // 画一条二次贝塞尔曲线
        UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath.lineWidth = 11;
        [bezierPath moveToPoint:CGPointMake(100, 100)];
        [bezierPath addQuadCurveToPoint:CGPointMake(300, 100) controlPoint:CGPointMake(200, 200)];
        [bezierPath stroke];
        
        // 画一条三次贝塞尔曲线
        UIBezierPath *bezierPath1 = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath1.lineWidth = 11;
        [bezierPath1 moveToPoint:CGPointMake(100, 300)];
        [bezierPath1 addCurveToPoint:CGPointMake(300, 300) controlPoint1:CGPointMake(150, 350) controlPoint2:CGPointMake(250, 250)];
        [bezierPath1 stroke];
    }
    
    @end
    
    11.png

    5、画一条虚线
    (清单1.5)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        CGFloat pattern[] = {10, 10};
        [bezierPath setLineDash:pattern count:2 phase:0];
        [bezierPath moveToPoint:CGPointMake(100, 100)];
        [bezierPath addLineToPoint:CGPointMake(300, 100)];
        [bezierPath stroke];
        
        UIBezierPath *bezierPath1 = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        CGFloat pattern1[] = {10, 20, 30};
        [bezierPath1 setLineDash:pattern1 count:3 phase:0];
        [bezierPath1 moveToPoint:CGPointMake(100, 120)];
        [bezierPath1 addLineToPoint:CGPointMake(300, 120)];
        [bezierPath1 stroke];
        
        UIBezierPath *bezierPath2 = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        CGFloat pattern2[] = {10, 10};
        [bezierPath2 setLineDash:pattern2 count:2 phase:5];
        [bezierPath2 moveToPoint:CGPointMake(100, 140)];
        [bezierPath2 addLineToPoint:CGPointMake(300, 140)];
        [bezierPath2 stroke];
        
        UIBezierPath *bezierPath3 = [UIBezierPath bezierPathWithArcCenter:self.center radius:100 startAngle:0 endAngle:M_PI * 2 clockwise:YES];
        [[UIColor redColor] setStroke];
        bezierPath3.lineWidth = 11;
        CGFloat pattern3[] = {1, 10};
        [bezierPath3 setLineDash:pattern3 count:2 phase:0];
        [bezierPath3 stroke];
    }
    
    @end
    
    11.png

    6、贝塞尔曲线的仿射变换

    我们发现UIBezierPath提供的方法里画的矩形只能是正的,如果我们要画一个歪的,也可以通过贝塞尔曲线的仿射变换来实现。

    (清单1.6)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(250, 100, 100, 100)];
        [[UIColor orangeColor] setStroke];
        bezierPath.lineWidth = 11;
        bezierPath.lineCapStyle = kCGLineCapButt;
        bezierPath.lineJoinStyle = kCGLineJoinRound;
        [bezierPath applyTransform:CGAffineTransformMakeRotation(M_PI_4)];
        [bezierPath stroke];
    }
    
    @end
    
    11.png

    7、剪裁路径覆盖的区域
    (清单1.7)

    //
    //  CustomView.m
    //  Quartz2D
    //
    //  Created by 意一yiyi on 2018/2/5.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    @implementation CustomView
    
    - (void)drawRect:(CGRect)rect {
        
        // 把图片切成任意你想要的形状显示
        UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:CGRectMake(100, 100, 100, 100) byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerBottomRight) cornerRadii:CGSizeMake(40, 0)];
        [[UIColor orangeColor] setStroke];
        [bezierPath stroke];
        [bezierPath addClip];// 剪裁路径覆盖的区域
        
        UIImage *image = [UIImage imageNamed:@"1.png"];
        [image drawAtPoint:CGPointMake(100, 100)];
    }
    
    @end
    
    原图 效果图

    8、利用- (void)addLineToPoint:(CGPoint)point;画数学函数曲线
    (清单1.8)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface CustomView ()
    
    @property (assign, nonatomic) float waveAmplitude;// 振幅
    @property (assign, nonatomic) float waveSpeed;// 波纹流动的速度
    @property (assign, nonatomic) float waveOffset;// 初相
    
    @end
    
    @implementation CustomView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        
        if (self = [super initWithFrame:frame]) {
            
            // 初始值
            self.waveAmplitude = 20;
            self.waveSpeed = 2.0;
            self.waveOffset = 0.0;
            
            CADisplayLink *displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(wave)];
            [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:(NSRunLoopCommonModes)];
        }
        
        return self;
    }
    
    - (void)drawRect:(CGRect)rect {
        
        self.waveOffset += self.waveSpeed;
        
        UIBezierPath *bezierPath = [[UIBezierPath alloc] init];
        [[UIColor redColor] setStroke];
        bezierPath.lineWidth = 1;
        
        // 起始点
        [bezierPath moveToPoint:CGPointMake(0, self.waveAmplitude)];
        
        // 连接各点
        for (CGFloat x = 0.0; x < kScreenWidth; x ++) {
            
            CGFloat y = 100 + self.waveAmplitude * sinf(3 * M_PI * x / kScreenWidth + self.waveOffset * M_PI / kScreenWidth);
            [bezierPath addLineToPoint:CGPointMake(x, y)];
        }
        
        // 渲染
        [bezierPath stroke];
    }
    
    - (void)wave {
        
        [self setNeedsDisplay];
    }
    
    @end
    
    1.gif

    9、利用- (void)addLineToPoint:(CGPoint)point;画任意想要的曲线
    (清单1.9)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    @interface CustomView ()
    
    @property (strong, nonatomic) UIBezierPath *bezierPath;
    
    @end
    
    @implementation CustomView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        
        if (self = [super initWithFrame:frame]) {
            
            self.bezierPath = [[UIBezierPath alloc] init];
            self.bezierPath.lineWidth = 1;// 笔的粗细
        
            
            UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
            panGesture.maximumNumberOfTouches = 1;// 一个指头画
            [self addGestureRecognizer:panGesture];
        }
        
        return self;
    }
    
    - (void)pan:(UIPanGestureRecognizer *)panGesture {
        
        // 获取平移到的点
        CGPoint currentPoint = [panGesture locationInView:self];
        
        if (panGesture.state == UIGestureRecognizerStateBegan) {
            
            // 设置起始点
            [self.bezierPath moveToPoint:currentPoint];
        }else if (panGesture.state == UIGestureRecognizerStateChanged) {
            
            // 连接平移点
            [self.bezierPath addLineToPoint:currentPoint];
        }
        
        // 触发-drawRect:方法
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect {
        
        // 笔的颜色
        [[UIColor redColor] setStroke];
        
        // 渲染出曲线
        [self.bezierPath stroke];
    }
    
    @end
    
    1.gif 1.png

    10、利用三次贝塞尔曲线减少曲线的锯齿,使曲线更加平滑

    参考地址:消灭画线条时的锯齿

    • 我们可以发现(清单1.9)中的线条有很明显的锯齿,所以我们可以使用三次贝塞尔曲线来平滑连接点连点,而不是使用-addLineToPoint:直接把两个点连起来。

    • 具体的思路就是:手指在滑动的过程中,我们要实时地获取四个点,0点和3点作为起始点和结束点,1点和2点作为控制点,用三次贝塞尔曲线把这四个点连接起来,然后3点作为新的起始点,跳转到下一波的四个点绘制三次贝塞尔曲线。

    (清单1.10)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    CGPoint pointArray[4];// 获取我们滑动手指时的四  个点,用来做三次贝塞尔曲线
    NSInteger pointIndex;// 点的下标
    
    @interface CustomView ()
    
    @property (strong, nonatomic) UIBezierPath *bezierPath;
    
    @end
    
    @implementation CustomView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        
        if (self = [super initWithFrame:frame]) {
    
            self.bezierPath = [[UIBezierPath alloc] init];
            self.bezierPath.lineWidth = 1;// 笔的粗细
        
            UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
            panGesture.maximumNumberOfTouches = 1;// 一个指头画
            [self addGestureRecognizer:panGesture];
        }
        
        return self;
    }
    
    - (void)pan:(UIPanGestureRecognizer *)panGesture {
        
        if (panGesture.state == UIGestureRecognizerStateBegan) {
            
            pointIndex = 0;
            
            // 获取到第一个点
            CGPoint currentPoint = [panGesture locationInView:self];
            pointArray[0] = currentPoint;
    
            // 设置起始点
            [self.bezierPath moveToPoint:pointArray[0]];
        }else if (panGesture.state == UIGestureRecognizerStateChanged) {
            
            pointIndex ++;
            
            // 获取平移到的点
            CGPoint currentPoint = [panGesture locationInView:self];
            pointArray[pointIndex] = currentPoint;
            
            if (pointIndex == 3) {// 获取够四个点了
                
                // 通过三次贝塞尔曲线而不是-addLineToPoint:连接平移的点,可以使得线条有更少的锯齿,更加平滑
                [self.bezierPath addCurveToPoint:pointArray[3] controlPoint1:pointArray[1] controlPoint2:pointArray[2]];
                
                // 触发-drawRect:方法
                [self setNeedsDisplay];
    
                // 获取下一波的四个点
                pointIndex = 0;
                pointArray[0] = [self.bezierPath currentPoint];
            }
        }
    }
    
    - (void)drawRect:(CGRect)rect {
        
        // 笔的颜色
        [[UIColor blackColor] setStroke];
        
        // 渲染出曲线
        [self.bezierPath stroke];
    }
    
    @end
    
    1.gif 11.png
    • 但是我们又可以发现(清单1.10)中还有可以优化的地方,就是上一波四个点和下一波四个点的连接过程,是上一波3点直接赋值下一波0点的操作,这样会导致每两段三次贝塞尔曲线的连接出现不平滑的现象,所以我们还可以做优化。

    • 具体的思路就是:手指在滑动的过程中,我们要实时地获取五个点,不像(清单1.10)中直接把3点作为上一波贝塞尔曲线的结束点和下一波贝塞尔曲线的起始点,而是把2点和4点的中点作为上一波贝塞尔曲线的结束点和下一波贝塞尔曲线的起始点,因为2点是上一波贝塞尔曲线的第二个控制点,而4点是下一波贝塞尔曲线的第一个控制点,由它俩选出来的中点作为上一波贝塞尔曲线的结束点和下一波贝塞尔曲线的起始点就可以保证两波三次贝塞尔曲线在“跳转”的时候更加平滑,其它点的作用和(清单1.10)一样。

    (清单1.11)

    //
    //  CustomView.m
    //  UIBezierPath
    //
    //  Created by 意一yiyi on 2018/1/29.
    //  Copyright © 2018年 意一yiyi. All rights reserved.
    //
    
    #import "CustomView.h"
    
    #define kScreenWidth [UIScreen mainScreen].bounds.size.width
    #define kScreenHeight [UIScreen mainScreen].bounds.size.height
    
    CGPoint pointArray[5];// 获取我们滑动手指时的五个点,用来做三次贝塞尔曲线
    NSInteger pointIndex;// 点的下标
    
    @interface CustomView ()
    
    @property (strong, nonatomic) UIBezierPath *bezierPath;
    
    @end
    
    @implementation CustomView
    
    - (instancetype)initWithFrame:(CGRect)frame {
        
        if (self = [super initWithFrame:frame]) {
            
            self.bezierPath = [[UIBezierPath alloc] init];
            self.bezierPath.lineWidth = 1;// 笔的粗细
            
            UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
            panGesture.maximumNumberOfTouches = 1;// 一个指头画
            [self addGestureRecognizer:panGesture];
        }
        
        return self;
    }
    
    - (void)pan:(UIPanGestureRecognizer *)panGesture {
        
        if (panGesture.state == UIGestureRecognizerStateBegan) {
            
            pointIndex = 0;
            
            // 获取到第一个点
            CGPoint currentPoint = [panGesture locationInView:self];
            pointArray[0] = currentPoint;
            
            // 设置起始点
            [self.bezierPath moveToPoint:pointArray[0]];
        }else if (panGesture.state == UIGestureRecognizerStateChanged) {
            
            pointIndex ++;
            
            // 获取平移到的点
            CGPoint currentPoint = [panGesture locationInView:self];
            pointArray[pointIndex] = currentPoint;
            
            if (pointIndex == 4) {// 获取够五个点了
                
                // 重新设置3点
                pointArray[3] = CGPointMake((pointArray[2].x + pointArray[4].x) / 2.0, (pointArray[2].y + pointArray[4].y) / 2.0);
                
                // 通过三次贝塞尔曲线而不是-addLineToPoint:连接平移的点,可以使得线条有更少的锯齿,更加平滑
                [self.bezierPath addCurveToPoint:pointArray[3] controlPoint1:pointArray[1] controlPoint2:pointArray[2]];
            }
            
            // 获取下一波的四个点
            pointIndex = 1;
            pointArray[0] = pointArray[3];
            pointArray[1] = pointArray[4];
        }
        
        // 触发-drawRect:方法
        [self setNeedsDisplay];
    }
    
    - (void)drawRect:(CGRect)rect {
        
        // 笔的颜色
        [[UIColor blackColor] setStroke];
        
        // 渲染出曲线
        [self.bezierPath stroke];
    }
    
    @end
    
    1.gif 11.png

    相关文章

      网友评论

        本文标题:第一篇:iOS中的贝塞尔曲线--UIBezierPath

        本文链接:https://www.haomeiwen.com/subject/dbuxzxtx.html