iOS开发之画图板(贝塞尔曲线)

283 阅读5分钟

贝塞尔曲线,听着挺牛气一词,不过下面我们在做画图板的时候就用到贝塞尔绘直线,没用到绘制曲线的功能。如果会点PS的小伙伴会对贝塞尔曲线有更直观的理解。这篇文章的重点不在于如何用使用贝塞尔曲线,而是利用贝塞尔划线的功能来封装一个画图板。

画图板的截图如下,上面的白板就是我们的画图板,是自己封装好的一个UIView,下面会详细的介绍如何封装这个画图板,下面的控件用来控制我们画图板的属性以及Undo,Redo和保存功能。点击保存时会把绘制的图片保存到手机的相册中。下面是具体的实现方案。 image.png 一、 封装画图板 其实上面的白板就是一继承于UiView的一个子类,我们可以在这个子类中添加我们画图板相应的属性和方法,然后实例化成对象添加到 ViewController中,当然为了省事添加白板的时候是通过storyboard来完成的,读者也可以自己实例化然后手动的添加到相应的 ViewController中。

  1. 封装白板的第一步是新建一个UIView的子类MyView,然后添加相应的属性和方法。MyView.h中的代码如下,代码具体意思请参照注释:
#import  
 
@interface MyView : UIView 
//用来设置线条的颜色 
@property (nonatomic, strong) UIColor *color; 
//用来设置线条的宽度 
@property (nonatomic, assign) CGFloat lineWidth; 
//用来记录已有线条 
@property (nonatomic, strong) NSMutableArray *allLine; 
 
//初始化相关参数 
-(void)initMyView; 
//unDo操作 
-(void)backImage; 
//reDo操作 
-(void)forwardImage; 
 
@end 
  1. 上面的代码是对外的接口,有些属性我们是写在MyView.m的延展中以实现私有的目的,MyView延展部分如下:
@interface MyView() 
//声明贝塞尔曲线 
@property(nonatomic, strong) UIBezierPath *bezier; 
//存储Undo出来的线条 
@property(nonatomic, strong) NSMutableArray *cancleArray; 
@end 
  1. 下面的代码就是实现部分的代码了,会根据不同功能给出相应的说明。

(1) 初始化我们的白板,给线条指定默认颜色和宽度并且给相应的变量分配内存空间,初始化代码如下:

 
-(void)initMyView 
{ 
    self.color = [UIColor redColor]; 
    self.lineWidth = 1; 
    self.allLine = [NSMutableArray arrayWithCapacity:50]; 
    self.cancleArray = [NSMutableArray arrayWithCapacity:50]; 
} 

(2) Undo功能的封装,相当于两个栈,把显示的线条出栈,进入为不显示的线条栈中,每执行一次此操作显示线条栈中的元素会少一条而不显示线条栈中会多一条,大致就这个意思吧,代码如下:

 
-(void)backImage           //UnDo操作
{ 
    if (self.allLine.count > 0) 
    { 
        int index = self.allLine.count - 1; 
         
        [self.cancleArray addObject:self.allLine[index]]; 
         
        [self.allLine removeObjectAtIndex:index]; 
         
        [self setNeedsDisplay 
         ]; 
    } 
} 
  1. Redo操作和Undo操作相反,从未显示栈中取出元素放入显示的栈中,代码中的栈我们是用数组来表示的,代码如下:
 
-(void)forwardImage 
{ 
    if (self.cancleArray.count > 0) 
    { 
        int index = self.cancleArray.count - 1; 
         
        [self.allLine addObject:self.cancleArray[index]]; 
     
        [self.cancleArray removeObjectAtIndex:index]; 
         
        [self setNeedsDisplay]; 
    } 
} 

(4) 当开始触摸时我们新建一个BezierPath,把触摸起点设置成BezierPath的起点,并把将要画出的线条以及线条对应的属性封装成字典添加到显示栈中,代码如下

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    //新建贝塞斯曲线 
    self.bezier = [UIBezierPath bezierPath]; 
     
    //获取触摸的点 
    UITouch *myTouche = [touches anyObject]; 
    CGPoint point = [myTouche locationInView:self]; 
     
    //把刚触摸的点设置为bezier的起点 
    [self.bezier moveToPoint:point]; 
     
    //把每条线存入字典中 
    NSMutableDictionary *tempDic = [[NSMutableDictionary alloc] initWithCapacity:3]; 
    [tempDic setObject:self.color forKey:@"color"]; 
    [tempDic setObject:[NSNumber numberWithFloat:self.lineWidth] forKey:@"lineWidth"]; 
    [tempDic setObject:self.bezier forKey:@"line"]; 
     
    //把线加入数组中 
    [self.allLine addObject:tempDic]; 
 
} 

(5) 当移动也就是划线的时候把点存储到BezierPath中,代码如下

-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event 
{ 
    UITouch *myTouche = [touches anyObject]; 
    CGPoint point = [myTouche locationInView:self]; 
     
    [self.bezier addLineToPoint:point]; 
     
    //重绘界面 
    [self setNeedsDisplay]; 
     
} 

(6) 画出线条

 
// An empty implementation adversely affects performance during animation. 
- (void)drawRect:(CGRect)rect 
{ 
    //对之前的线的一个重绘过程 
    for (int i = 0; i < self.allLine.count; i ++) 
    { 
        NSDictionary *tempDic = self.allLine[i]; 
        UIColor *color = tempDic[@"color"]; 
        CGFloat width = [tempDic[@"lineWidth"] floatValue]; 
        UIBezierPath *path = tempDic[@"line"]; 
         
        [color setStroke]; 
        [path setLineWidth:width]; 
        [path stroke]; 
    } 
 
} 

二、 画图板的使用 上面是封装画图板要用到的全部代码,下面的代码就是如何在ViewController中使用我们的画图板了,如何实例化控件,以及控件的初始化,注册回调等在这就不做赘述了,下面给出了主要控件的回调方法。

  1. 通过Slider来调节线条的宽度
 
- (IBAction)sliderChange:(id)sender     //通过slider来设置线条的宽度
{ 
    self.myView.lineWidth = self.mySlider.value; 
} 
  1. 通过SegmentControl来设置线条的颜色
/通过segmentControl来设置线条的颜色 
- (IBAction)tapSegment:(id)sender { 
     
    switch (self.mySegment.selectedSegmentIndex) { 
        case 0: 
            self.myView.color = [UIColor redColor]; 
            break; 
        case 1: 
            self.myView.color = [UIColor blackColor]; 
            break; 
        case 2: 
            self.myView.color = [UIColor greenColor]; 
            break; 
             
        default: 
            break; 
    } 
     
} 
  1. undo和redo操作
 
- (IBAction)tapBack:(id)sender { 
    [self.myView backImage]; 
} 
 
 
//Redo操作 
- (IBAction)tapGo:(id)sender { 
    [self.myView forwardImage]; 
} 
  1. 保存操作,也许下面的保存操作在处理方式上略显笨拙,如有更好的解决方案请留言。 保存的时候我是先截了个屏,然后把白板进行切割,把切割后图片存入到相册中,代码如下:
 
- (IBAction)tapSave:(id)sender { 
    //截屏 
    UIGraphicsBeginImageContext(self.view.bounds.size); 
    [self.view.layer renderInContext:UIGraphicsGetCurrentContext()]; 
    UIImage *uiImage = UIGraphicsGetImageFromCurrentImageContext(); 
    UIGraphicsEndImageContext(); 
     
     
    //截取画图版部分 
    CGImageRef sourceImageRef = [uiImage CGImage]; 
    CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, CGRectMake(36, 6, 249, 352)); 
    UIImage *newImage = [UIImage imageWithCGImage:newImageRef]; 
     
    //把截的屏保存到相册 
    UIImageWriteToSavedPhotosAlbum(newImage , nil, nil, nil); 
     
    //给个保存成功的反馈 
    UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"存储照片成功" 
                                                    message:@"您已将照片存储于图片库中,打开照片程序即可查看。" 
                                                   delegate:self 
                                          cancelButtonTitle:@"OK" 
                                          otherButtonTitles:nil]; 
    [alert show]; 
 
} 

以上就是本画图板的主要代码了,有不足之处还望批评指正。转载请注明出处。在本文结束时在来几张截图吧(demo下载地址:www.pgyer.com/LTQ8):