最近項(xiàng)目中用到了一個(gè)客戶簽字的功能,具體步驟就是客戶簽字確認(rèn)雁社,App把客戶的簽字生成一張PNG圖片上傳到服務(wù)器浴井,服務(wù)器再生成PDF下發(fā)到App展示,所以需要實(shí)現(xiàn)一個(gè)畫板的功能霉撵。
剛開始的時(shí)候是想通過drawRect方法配合UIBezierPath來實(shí)現(xiàn)的磺浙。
具體步驟如下:
- 創(chuàng)建一個(gè)UIBeizerPath類型的屬性path,用來在touchMove里面記錄手指劃過的軌跡徒坡。創(chuàng)建一個(gè)可變數(shù)組pathArray撕氧,用來儲(chǔ)存多個(gè)UIBeizerPath軌跡。
- 在touchBegan方法里面把path的初始點(diǎn)移動(dòng)到手指所在位置
- 在touchMoved方法里面記錄手指移動(dòng)過的軌跡喇完,調(diào)用setNeedsDispaly方法伦泥,該方法會(huì)自動(dòng)調(diào)用drawRect方法,在drawRect方法里面,對(duì)數(shù)組中的UIBeizerPath進(jìn)行繪制不脯。
- 在touchesEnded方法里面把path屬性添加到數(shù)組里面府怯,并且把path屬性置nil,以便于下次繪制的時(shí)候?qū)ath進(jìn)行初始化防楷。
下面上代碼:
牺丙、、复局、
- (NSMutableArray *)pathArray {
if (_pathArray == nil) {
_ pathArray = [NSMutableArray array];
}
return _pathArray;
}
- (UIBezierPath *)path {
if (_path == nil) {
_path = [UIBezierPath bezierPath];
_path.lineWidth = 10;
_path.lineCapStyle = kCGLineCapRound;
_path.lineJoinStyle = kCGLineJoinRound;
}
return _path;
}
- (void)touchesBegan:(NSSet*)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
[self.path moveToPoint:point];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
[self.path addLineToPoint:point];
[self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet*)touches withEvent:(UIEvent *)event{
[self.pathArray addObject:self.path];
self.path = nil;
}
- (void)drawRect:(CGRect)rect {
[[UIColor blackColor] setStroke];
for (UIBezierPath *path in self.pathArray) {
[path stroke];
}
[self.path stroke];
}
赘被、、肖揣、
做出來的效果如下:
可以看到功能算是實(shí)現(xiàn)了,但是性能優(yōu)化方面實(shí)在太差浮入,如果手指一直移動(dòng)的話CPU和內(nèi)存都會(huì)暴漲龙优。后來在網(wǎng)上看到了一篇文章內(nèi)存惡鬼drawRect,我才知道了內(nèi)存暴漲的原因事秀,因?yàn)槲抑貙懥薲rawRect方法彤断。而且我在touchMoved方法里面頻繁對(duì)視圖進(jìn)行了重新繪制,這個(gè)渲染的過程會(huì)大量的消耗CPU資源易迹。
我們?cè)谑謾C(jī)屏幕上看到的視圖宰衙,實(shí)際上都是由UIView的屬性CALayer來進(jìn)行繪制和渲染的。而CALayer內(nèi)部有一個(gè)contents屬性睹欲,該屬性默認(rèn)可以傳一個(gè)id類型的對(duì)象供炼。contents也被稱為寄宿圖,當(dāng)我們調(diào)用drawRect方法的時(shí)候窘疮,系統(tǒng)就會(huì)為視圖分配一個(gè)寄宿圖袋哼,這個(gè)寄宿圖的像素尺寸等于視圖大小乘以contentsScale(這個(gè)屬性與屏幕分辨率有關(guān),我們的畫板程序在不同模擬器下呈現(xiàn)不同的內(nèi)存消耗量也是因?yàn)樵撝挡煌┑闹嫡⑸馈K蕴喂幔还苣愕膁rawRect方法里面有沒有代碼,實(shí)際上都會(huì)對(duì)內(nèi)存有所損耗蔚出。
作者在文章中也提出了解決方法弟翘,就是直接使用專有圖層CAShapeLayer。CAShaperLayer是一個(gè)通過矢量圖形而不是bitmap來繪制的圖層子類骄酗。用CGPath來定義想要繪制的圖形稀余,CAShapeLayer會(huì)自動(dòng)渲染。與直接使用CoreGraphics繪制layer對(duì)比酥筝,CAShapeLayer有以下優(yōu)點(diǎn):
- 渲染快速滚躯。CAShapeLayer使用了硬件加速,繪制同一圖形比用CoreGraphics快很多。
- 高效使用內(nèi)存掸掏。一個(gè)CAShapeLayer不需要像普通CALayer一樣創(chuàng)建一個(gè)寄宿圖形茁影,所以無論有多大,都不會(huì)占用太多內(nèi)存丧凤。
- 不會(huì)被圖層邊界裁剪掉(這個(gè)我在寫畫板的過程中也發(fā)現(xiàn)了募闲,就算手指移動(dòng)的軌跡超出了畫板的范圍,也能繪制出軌跡愿待,設(shè)置視圖的maskToBounds就可以解決了)浩螺。
- 不會(huì)出現(xiàn)像素化。
OK仍侥,既然知道了替代方法要出,接下來就是擼起袖子干了,步驟如下:
- 創(chuàng)建一個(gè)UIBezierPath類型的屬性path农渊,用來記錄手指劃過的軌跡患蹂。創(chuàng)建一個(gè)CAShapeLayer類型的屬性shapeLayer,用來渲染軌跡砸紊。
- 在touchBegan方法里面把path的初始點(diǎn)移動(dòng)到手指所在位置传于。
- 在touchMoved方法里面用path記錄手指的移動(dòng)軌跡,并且把path賦值給CAShapeLayer進(jìn)行渲染醉顽。
- 在touchEnded方法里面把shaperLayer和path置nil沼溜,以便于畫下一條軌跡的時(shí)候?qū)λ鼈冞M(jìn)行初始化。
代碼:
游添、系草、、
- (CAShapeLayer *)shapeLayer{
if (_shapeLayer == nil) {
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.strokeColor = [UIColor blackColor].CGColor;
_shapeLayer.fillColor = [UIColor clearColor].CGColor;
_shapeLayer.lineJoin = kCALineJoinRound;
_shapeLayer.lineCap = kCALineCapRound;
_shapeLayer.lineWidth = 10;
[self.layer addSublayer:_shapeLayer];
}
return _shapeLayer;
}
- (UIBezierPath *)path{
if (_path == nil) {
_path = [UIBezierPath bezierPath];
_path.lineWidth = 10;
}
return _path;
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
[self.path addLineToPoint:point];
self.shapeLayer.path = self.path.CGPath;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint point = [touch locationInView:self];
[self.path moveToPoint:point];
}
- (void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
self.shapeLayer = nil;
self.path = nil;
}
否淤、悄但、、
現(xiàn)在我們?cè)賮砜匆幌聝?nèi)存:
基本沒有什么大的損耗了石抡。