服务器之家:专注于服务器技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - IOS - iOS简单画板开发案例分享

iOS简单画板开发案例分享

2021-01-15 15:34神户牛肉 IOS

这篇文章主要为大家分享了iOS实现简单画板开发案例,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

最近在学习quartz2d,学习了一个简单画板的实现,现在把实现过程记录一下。

主要用到的点就是画线,截屏,绘制图片,选择图片,以及保存所有绘制的线。

首先在storyboard上布局好控件,设置约束等等,最后的效果是这样:

iOS简单画板开发案例分享

自定义画板drawview,使用时可能是从xib中加载,也可能是手动创建,所以创建对象的方法需要实现两个:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#import <uikit/uikit.h>
 
@interface drawview : uiview
/** 线宽 */
@property (nonatomic, assign) nsinteger linewidth;
 
/** 颜色 */
@property(nonatomic, strong) uicolor *pathcolor;
 
/** 图片 */
@property(nonatomic, strong) uiimage *image;
 
- (void)clear;
 
- (void)undo;
?
1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)awakefromnib {
   
  [self setup];
   
}
 
- (instancetype)initwithframe:(cgrect)frame {
   
  if (self == [super initwithframe:frame]) {
    [self setup];
  }
  return self;
}

setup初始化方法,初始化时要做的事情就是给画板添加拖动手势,也可以将画笔路径的线宽在这里设置

?
1
2
3
4
5
6
7
8
9
10
11
//自定义初始化方法
- (void)setup {
   
  //添加手势
  uipangesturerecognizer *pan = [[uipangesturerecognizer alloc]initwithtarget:self action:@selector(pan:)];
  [self addgesturerecognizer:pan];
   
  //初始化时设置路径线宽
  _linewidth = 2;
   
}

手指在画板上移动时开始绘制线条,这里因为原生的uibezierpath类没有办法设置路径颜色,所以这里只能自定义path类了   

?
1
2
3
4
5
6
7
#import <uikit/uikit.h>
 
@interface drawpath : uibezierpath
 
@property (nonatomic, strong) uicolor *pathcolor;
 
@end

手指移动时,绘制线条,路径是自定义的path类

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@interface drawview ()
 
@property(nonatomic, strong)drawpath *path;
/** 保存所有路径的数组 */
@property(nonatomic, strong) nsmutablearray *patharr;
 
@end
 
//懒加载
- (nsmutablearray *)patharr {
  if (_patharr == nil) {
     
    _patharr = [nsmutablearray array];
     
  }
  return _patharr;
}
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
- (void)pan:(uipangesturerecognizer *)pan {
   
  //获取开始的触摸点
  cgpoint startp = [pan locationinview:self];
   
  if (pan.state == uigesturerecognizerstatebegan) {
     
    //创建贝塞尔路径
    _path = [[drawpath alloc]init];
    _path.linewidth = _linewidth;
    _path.pathcolor = _pathcolor;
     
    //不能在手指抬起时将路径添加到数组,因为在遍历数组画线时路径还没有被添加到数组里面
    [_patharr addobject:_path];
     
    //设置起点
    [_path movetopoint:startp];
     
  }
   
  //连线
  [_path addlinetopoint:startp];
   
  //重绘,调用drawrect方法
  [self setneedsdisplay];
   
}

画线实现drawrect方法,绘制线条或者图片时,是把数组中的路径全部画出来

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
- (void)drawrect:(cgrect)rect {
   
  //把所有路径画出来
  for (drawpath *path in self.patharr) {
     
    if ([path iskindofclass:[uiimage class]]) {
      //画图
      uiimage *image = (uiimage *)path;
      [image drawinrect:rect];
    }else {
      //画线
      [path.pathcolor set];
      [path stroke];
    }
  }
   
}

当把图片添加到画板时

?
1
2
3
4
5
6
7
8
9
- (void)setimage:(uiimage *)image {
   
  _image = image;
   
  [self.patharr addobject:image];
   
  //重绘调用drawrect才能在画板上显示图片
  [self setneedsdisplay];
}

还可以把直接更新路径数组的操作封装在画板中

 

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
- (void)clear {
  //清除
  [self.patharr removeallobjects];
   
  [self setneedsdisplay];
   
}
 
- (void)undo {
  //撤销
  [self.patharr removelastobject];
   
  [self setneedsdisplay];
}

控制器中:

?
1
2
3
@interface viewcontroller () <uiimagepickercontrollerdelegate, uinavigationcontrollerdelegate>
@property (weak, nonatomic) iboutlet drawview *drawview;
@end

实现几个按钮对画板的操作:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
- (ibaction)clear:(id)sender {
   
  //清屏
  [_drawview clear];
   
}
 
- (ibaction)undo:(id)sender {
   
  //撤销
  [_drawview undo];
   
}
 
- (ibaction)eraser:(id)sender {
   
  //擦除 就是把路径的颜色设置为画板的背景色,假象
  _drawview.pathcolor = _drawview.backgroundcolor;
  _drawview.linewidth = 20;
   
}
 
- (ibaction)changelinewidth:(uislider *)sender {
   
  //改变路径线宽
  _drawview.linewidth = sender.value;
   
}
 
- (ibaction)changecolor:(uibutton *)sender {
   
   
  //改变路径颜色
  _drawview.pathcolor = sender.backgroundcolor;
   
}
 
- (ibaction)pickphoto:(id)sender {
   
  //选择照片
  //弹出系统相册
  uiimagepickercontroller *picker = [[uiimagepickercontroller alloc]init];
   
  //设置选择控制器的来源 uiimagepickercontrollersourcetypesavedphotosalbum:照片库
  picker.sourcetype = uiimagepickercontrollersourcetypesavedphotosalbum;
   
  //设置代理
  picker.delegate = self;
   
  //modal出控制器
  [self presentviewcontroller:picker animated:yes completion:nil];
   
}
 
- (void)imagepickercontroller:(uiimagepickercontroller *)picker didfinishpickingmediawithinfo:(nsdictionary<nsstring *,id> *)info {
   
  //获取选择的图片
  uiimage *image = info[uiimagepickercontrolleroriginalimage];
   
  //创建一个处理图片的view
  imagehandleview *handleview = [[imagehandleview alloc]initwithframe:self.drawview.bounds];
   
  handleview.handlecompletionblock = ^(uiimage *image){
     
    _drawview.image = image;
  };
   
  [self.drawview addsubview:handleview];
   
  //将图片画在画板上
  handleview.image = image;
   
  //_drawview.image = image;
   
  //dismiss
  [self dismissviewcontrolleranimated:yes completion:nil];
  //nslog(@"%@", info);
   
}
 
- (ibaction)save:(id)sender {
   
  [uiview animatewithduration:0.15 animations:^{
    //保存当前画板上的内容
     
    //开启上下文
    uigraphicsbeginimagecontextwithoptions(_drawview.bounds.size, no, 0);
     
    //获取位图上下文
    cgcontextref ctx = uigraphicsgetcurrentcontext();
     
    //把控件上的图层渲染到上下文
    [_drawview.layer renderincontext:ctx];
     
    //获取上下文中的图片
    uiimage *image = uigraphicsgetimagefromcurrentimagecontext();
     
    //关闭上下文
    uigraphicsendimagecontext();
     
    //保存图片到相册
    uiimagewritetosavedphotosalbum(image, self, @selector(image:didfinishsavingwitherror:contextinfo:), nil);
     
    self.drawview.alpha = 0;
  } completion:^(bool finished) {
    [uiview animatewithduration:0.15 animations:^{
      self.drawview.alpha = 1;
    }];
  }];
   
}
 
//保存成功后的方法必须是这个,不能随便写
- (void)image:(uiimage *)image didfinishsavingwitherror:(nserror *)error contextinfo:(void *)contextinfo {
   
  nslog(@"保存成功");
   
}

从相册选择完图片后把图片显示在画板上了但是还没有渲染到layer,这时候需要对图片进行移动缩放旋转这些操作的话,但是uiimage是不能拉伸旋转这些操作的,uiimageview才可以,所以解决思路就是自定义一个view来专门处理对图片的操作,在自定义view上放一个uiimageview,从相册选择图片后获取的image设置给uiimageview,这样的自定义view上操作uiiamgeview。

?
1
2
3
4
5
6
7
8
9
#import <uikit/uikit.h>
 
@interface imagehandleview : uiview
/** 图片 */
@property(nonatomic, strong) uiimage *image;
 
/** block */
@property(nonatomic, strong) void(^handlecompletionblock)(uiimage *image);
@end
?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
#import "imagehandleview.h"
 
@interface imagehandleview () <uigesturerecognizerdelegate>
 
/** image */
@property(nonatomic, weak) uiimageview *imageview;
 
@end
 
@implementation imagehandleview
 
//防止图片上的触摸事件传递到画板
- (uiview *)hittest:(cgpoint)point withevent:(uievent *)event {
   
  return _imageview;
}
 
- (uiimageview *)imageview {
   
  if (_imageview == nil) {
    uiimageview *imagev = [[uiimageview alloc]initwithframe:self.bounds];
     
    _imageview = imagev;
     
    //设置imgaeview允许与用户交互
    _imageview.userinteractionenabled = yes;
     
    //添加手势
    [self setupgesturerecognizer];
     
    //把这个imageview添加到图片处理的view上
    [self addsubview:imagev];
     
  }
  return _imageview;
}
 
#pragma mark - 添加手势
- (void)setupgesturerecognizer {
   
  //平移手势
  uipangesturerecognizer *pan = [[uipangesturerecognizer alloc]initwithtarget:self action:@selector(pan:)];
  [_imageview addgesturerecognizer:pan];
   
  //旋转手势
  uirotationgesturerecognizer *rotation = [[uirotationgesturerecognizer alloc]initwithtarget:self action:@selector(rotation:)];
  rotation.delegate = self;
  [_imageview addgesturerecognizer:rotation];
   
  //缩放手势
  uipinchgesturerecognizer *pinch = [[uipinchgesturerecognizer alloc]initwithtarget:self action:@selector(pinch:)];
  pinch.delegate = self;
  [_imageview addgesturerecognizer:pinch];
   
  //长按手势
  uilongpressgesturerecognizer *longpress = [[uilongpressgesturerecognizer alloc]initwithtarget:self action:@selector(longpress:)];
  [_imageview addgesturerecognizer:longpress];
   
}
 
#pragma mark - 处理平移手势
- (void)pan:(uipangesturerecognizer *)pan {
   
  //获取手指的偏移量
  cgpoint tranp = [pan translationinview:self.imageview];
   
  //设置imageview的形变
  self.imageview.transform = cgaffinetransformtranslate(self.imageview.transform, tranp.x, tranp.y);
   
  //复位
  [pan settranslation:cgpointzero inview:self.imageview];
   
}
 
#pragma mark - 处理旋转手势
- (void)rotation:(uirotationgesturerecognizer *)rotation {
   
  //设置imageview的形变
  self.imageview.transform = cgaffinetransformrotate(self.imageview.transform, rotation.rotation);
   
  //复位
  rotation.rotation = 0;
   
}
 
#pragma mark - 处理缩放手势
- (void)pinch:(uipinchgesturerecognizer *)pinch {
   
  //设置imageview的形变
  self.imageview.transform = cgaffinetransformscale(self.imageview.transform, pinch.scale, pinch.scale);
   
  //复位
  pinch.scale = 1;
   
}
 
#pragma mark - 处理长按手势
- (void)longpress:(uilongpressgesturerecognizer *)longpress {
   
  //图片处理完成
  if (longpress.state == uigesturerecognizerstatebegan) {
     
    //高亮效果
    [uiview animatewithduration:0.25 animations:^{
      self.imageview.alpha = 0;
    } completion:^(bool finished) {
      
      [uiview animatewithduration:0.25 animations:^{
        self.imageview.alpha = 1;
      } completion:^(bool finished) {
         
        //高亮时生成一张新的图片
        //开启位图上下文
        uigraphicsbeginimagecontextwithoptions(self.bounds.size, no, 0);
         
        //获取位图上下文
        cgcontextref ctx = uigraphicsgetcurrentcontext();
         
        //把控件的图层渲染到上下文
        [self.layer renderincontext:ctx];
         
        //从上下文中获取新的图片
        uiimage *image = uigraphicsgetimagefromcurrentimagecontext();
         
        //关闭上下文
        uigraphicsendimagecontext();
         
        //调用block
        if(_handlecompletionblock) {
          _handlecompletionblock(image);
        }
         
        //移除父控件
        [self removefromsuperview];
         
      }];
       
    }];
     
     
  }
   
}
 
#pragma mark - 手势代理方法 <uigesturerecognizerdelegate>
- (bool)gesturerecognizer:(uigesturerecognizer *)gesturerecognizer shouldrecognizesimultaneouslywithgesturerecognizer:(uigesturerecognizer *)othergesturerecognizer {
   
  //yes表示同时支持多个手势
  return yes;
   
}
 
- (void)setimage:(uiimage *)image {
   
  _image = image;
   
  //把图片展示到uiimageview上
  self.imageview.image = image;
 
}
 
@end

需要注意的是,当长按将操作过的图片绘制都画板上生成一张新的图片后,这时候需要把这个image设置给画板drawview,但是这时候就必须要在专门处理图片的view中去import画板view,这样耦合性太强。所以为了解耦,可以使用代理或者block。我用了block将刚刚生成的image先保存起来,在控制器中初始化imagehandleview之后再赋值给drawview。

最后保存画板上的内容就是将画板上的内容生成图片保存到相册即可。注意,保存完之后执行的方法必须是这个:

 

复制代码 代码如下:
- (void)image:(uiimage *)image didfinishsavingwitherror:(nserror *)error contextinfo:(void *)contextinfo;

 

最后效果图是这样的:

iOS简单画板开发案例分享

以上就是本文的全部内容,希望对大家学习ios程序设计有所帮助。

延伸 · 阅读

精彩推荐
  • IOSIOS 屏幕适配方案实现缩放window的示例代码

    IOS 屏幕适配方案实现缩放window的示例代码

    这篇文章主要介绍了IOS 屏幕适配方案实现缩放window的示例代码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要...

    xiari5772021-06-01
  • IOS关于iOS自适应cell行高的那些事儿

    关于iOS自适应cell行高的那些事儿

    这篇文章主要给大家介绍了关于iOS自适应cell行高的那些事儿,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的...

    daisy6092021-05-17
  • IOSiOS 雷达效果实例详解

    iOS 雷达效果实例详解

    这篇文章主要介绍了iOS 雷达效果实例详解的相关资料,需要的朋友可以参考下...

    SimpleWorld11022021-01-28
  • IOSIOS开发之字典转字符串的实例详解

    IOS开发之字典转字符串的实例详解

    这篇文章主要介绍了IOS开发之字典转字符串的实例详解的相关资料,希望通过本文能帮助到大家,让大家掌握这样的方法,需要的朋友可以参考下...

    苦练内功5832021-04-01
  • IOSiOS布局渲染之UIView方法的调用时机详解

    iOS布局渲染之UIView方法的调用时机详解

    在你刚开始开发 iOS 应用时,最难避免或者是调试的就是和布局相关的问题,下面这篇文章主要给大家介绍了关于iOS布局渲染之UIView方法调用时机的相关资料...

    windtersharp7642021-05-04
  • IOSiOS通过逆向理解Block的内存模型

    iOS通过逆向理解Block的内存模型

    自从对 iOS 的逆向初窥门径后,我也经常通过它来分析一些比较大的应用,参考一下这些应用中某些功能的实现。这个探索的过程乐趣多多,不仅能满足自...

    Swiftyper12832021-03-03
  • IOSiOS中tableview 两级cell的展开与收回的示例代码

    iOS中tableview 两级cell的展开与收回的示例代码

    本篇文章主要介绍了iOS中tableview 两级cell的展开与收回的示例代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧...

    J_Kang3862021-04-22
  • IOS解析iOS开发中的FirstResponder第一响应对象

    解析iOS开发中的FirstResponder第一响应对象

    这篇文章主要介绍了解析iOS开发中的FirstResponder第一响应对象,包括View的FirstResponder的释放问题,需要的朋友可以参考下...

    一片枫叶4662020-12-25