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

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

  画图板的截图如下,上面的白板就是我们的画图板,是自己封装好的一个UIView,下面会详细的介绍如何封装这个画图板,下面的控件用来控制我们画图板的属性以及Undo,Redo和保存功能。点击保存时会把绘制的图片保存到手机的相册中。下面是具体的实现方案。

  一.封装画图板

    其实上面的白板就是一继承于UiView的一个子类,我们可以在这个子类中添加我们画图板相应的属性和方法,然后实例化成对象添加到ViewController中,当然为了省事添加白板的时候是通过storyboard来完成的,读者也可以自己实例化然后手动的添加到相应的ViewController中。

    1.封装白板的第一步是新建一个UIView的子类MyView,然后添加相应的属性和方法。MyView.h中的代码如下,代码具体意思请参照注释

 1 #import <UIKit/UIKit.h>
 2 
 3 @interface MyView : UIView
 4 //用来设置线条的颜色
 5 @property (nonatomic, strong) UIColor *color;
 6 //用来设置线条的宽度
 7 @property (nonatomic, assign) CGFloat lineWidth;
 8 //用来记录已有线条
 9 @property (nonatomic, strong) NSMutableArray *allLine;
10 
11 //初始化相关参数
12 -(void)initMyView;
13 //unDo操作
14 -(void)backImage;
15 //reDo操作
16 -(void)forwardImage;
17 
18 @end

    2、上面的代码是对外的接口,有些属性我们是写在MyView.m的延展中以实现私有的目的,MyView延展部分如下:

1 @interface MyView()
2 //声明贝塞尔曲线
3 @property(nonatomic, strong) UIBezierPath *bezier;
4 //存储Undo出来的线条
5 @property(nonatomic, strong) NSMutableArray *cancleArray;
6 @end

    3.下面的代码就是实现部分的代码了,会根据不同功能给出相应的说明

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

1 //进行一些初始化工作
2 -(void)initMyView
3 {
4     self.color = [UIColor redColor];
5     self.lineWidth = 1;
6     self.allLine = [NSMutableArray arrayWithCapacity:50];
7     self.cancleArray = [NSMutableArray arrayWithCapacity:50];
8 }

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

 1 //UnDo操作
 2 -(void)backImage
 3 {
 4     if (self.allLine.count > 0)
 5     {
 6         int index = self.allLine.count - 1;
 7         
 8         [self.cancleArray addObject:self.allLine[index]];
 9         
10         [self.allLine removeObjectAtIndex:index];
11         
12         [self setNeedsDisplay
13          ];
14     }
15 }

      (3)Redo操作和Undo操作相反,从未显示栈中取出元素放入显示的栈中,代码中的栈我们是用数组来表示的,代码如下:

//ReDo操作
-(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的起点,并把将要画出的线条以及线条对应的属性封装成字典添加到显示栈中,代码如下

 1 -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
 2 {
 3     //新建贝塞斯曲线
 4     self.bezier = [UIBezierPath bezierPath];
 5     
 6     //获取触摸的点
 7     UITouch *myTouche = [touches anyObject];
 8     CGPoint point = [myTouche locationInView:self];
 9     
10     //把刚触摸的点设置为bezier的起点
11     [self.bezier moveToPoint:point];
12     
13     //把每条线存入字典中
14     NSMutableDictionary *tempDic = [[NSMutableDictionary alloc] initWithCapacity:3];
15     [tempDic setObject:self.color forKey:@"color"];
16     [tempDic setObject:[NSNumber numberWithFloat:self.lineWidth] forKey:@"lineWidth"];
17     [tempDic setObject:self.bezier forKey:@"line"];
18     
19     //把线加入数组中
20     [self.allLine addObject:tempDic];
21 
22 }

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

 1 -(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
 2 {
 3     UITouch *myTouche = [touches anyObject];
 4     CGPoint point = [myTouche locationInView:self];
 5     
 6     [self.bezier addLineToPoint:point];
 7     
 8     //重绘界面
 9     [self setNeedsDisplay];
10     
11 }

      (6)画出线条

 1 // Only override drawRect: if you perform custom drawing.
 2 // An empty implementation adversely affects performance during animation.
 3 - (void)drawRect:(CGRect)rect
 4 {
 5     //对之前的线的一个重绘过程
 6     for (int i = 0; i < self.allLine.count; i ++)
 7     {
 8         NSDictionary *tempDic = self.allLine[i];
 9         UIColor *color = tempDic[@"color"];
10         CGFloat width = [tempDic[@"lineWidth"] floatValue];
11         UIBezierPath *path = tempDic[@"line"];
12         
13         [color setStroke];
14         [path setLineWidth:width];
15         [path stroke];
16     }
17 
18 }

二.画图板的使用

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

    1、通过Slider来调节线条的宽度

1 //通过slider来设置线条的宽度
2 - (IBAction)sliderChange:(id)sender
3 {
4     self.myView.lineWidth = self.mySlider.value;
5 }

    2、通过SegmentControl来设置线条的颜色

 1 /通过segmentControl来设置线条的颜色
 2 - (IBAction)tapSegment:(id)sender {
 3     
 4     switch (self.mySegment.selectedSegmentIndex) {
 5         case 0:
 6             self.myView.color = [UIColor redColor];
 7             break;
 8         case 1:
 9             self.myView.color = [UIColor blackColor];
10             break;
11         case 2:
12             self.myView.color = [UIColor greenColor];
13             break;
14             
15         default:
16             break;
17     }
18     
19 }

    3、undo和redo操作

 1 //Undo
 2 - (IBAction)tapBack:(id)sender {
 3     [self.myView backImage];
 4 }
 5 
 6 
 7 //Redo操作
 8 - (IBAction)tapGo:(id)sender {
 9     [self.myView forwardImage];
10 }

    4.保存操作,也许下面的保存操作在处理方式上略显笨拙,如有更好的解决方案请留言。 保存的时候我是先截了个屏,然后把白板进行切割,把切割后图片存入到相册中,代码如下:

 1 //把画的图保存到相册
 2 - (IBAction)tapSave:(id)sender {
 3     //截屏
 4     UIGraphicsBeginImageContext(self.view.bounds.size);
 5     [self.view.layer renderInContext:UIGraphicsGetCurrentContext()];
 6     UIImage *uiImage = UIGraphicsGetImageFromCurrentImageContext();
 7     UIGraphicsEndImageContext();
 8     
 9     
10     //截取画图版部分
11     CGImageRef sourceImageRef = [uiImage CGImage];
12     CGImageRef newImageRef = CGImageCreateWithImageInRect(sourceImageRef, CGRectMake(36, 6, 249, 352));
13     UIImage *newImage = [UIImage imageWithCGImage:newImageRef];
14     
15     //把截的屏保存到相册
16     UIImageWriteToSavedPhotosAlbum(newImage , nil, nil, nil);
17     
18     //给个保存成功的反馈
19     UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"存储照片成功"
20                                                     message:@"您已将照片存储于图片库中,打开照片程序即可查看。"
21                                                    delegate:self
22                                           cancelButtonTitle:@"OK"
23                                           otherButtonTitles:nil];
24     [alert show];
25 
26 }

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

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏iOS开发攻城狮的集散地

iOS 封装跑马灯和轮播效果

8604
来自专栏非典型技术宅

iOS动画系列之五:基础动画之缩放篇&旋转篇Swift+OC1. 思路和最终成果2. 抽取公共方法3. 懒加载Layer4. 添加动画

1641
来自专栏陈满iOS

iOS开发·适配iPhone X相关的宏和方法

适配iPhone X和Xcode 9的过程中,除了与导航栏相关的问题,还有一个问题经常出现,就是UITableView相关的问题。下面两个办法可以解决多数错位的...

1494
来自专栏iOS 开发

UI进阶13 Quartz2DQuartz2D

1293
来自专栏数据结构与算法

菜鸡博客开……开……开源了!

因为很多人找我要过博皮源码,所以本宝宝经过深思熟虑,最终决定把自己的源码分享给大家!

1654
来自专栏郭霖

Android高级图片滚动控件,编写3D版的图片轮播器

大家好,好久不见了,最近由于工作特别繁忙,已经有一个多月的时间没写博客了,我也是深感惭愧。那么今天的这篇既然是阔别了一个多月的文章,当然要带来更加给力点的内容了...

8458
来自专栏向治洪

React Native项目实战之搭建美团个人中心界面

在很多app应用型APP中,个人中心往往会单独出一个模块,而对于刚入门React Native的朋友来说,怎么去实现一些静态的页面,并且怎么着手实现,怎么分层,...

4276
来自专栏数据小魔方

leaflet在线地图进阶宝典之——高级辅助特性

本文跟大家分享leaflet在线地图的高级附加属性,这些属性通常来讲仅仅作为我们数据额可视化项目的修饰元素,而并不会影响数据元素。 但是有了这些辅助修饰元素,往...

3654
来自专栏Android机动车

Android 约束布局ConstraintLayout1.1.0 版详解

在http://dyg8.com/20180205/Android-ConstraintLayout-Detailed/这篇文章中,我们对 Constraint...

1304
来自专栏wOw的Android小站

[iOS] 列表滑动展开隐藏头部HeaderView

首先看一下BiliBili客户端的视频浏览界面。默认界面Header完全展开,并且Header显示AV号(别乱想,就是视频编号了)以及播放按钮。滑动之后Head...

6362

扫码关注云+社区

领取腾讯云代金券