您的当前位置:首页正文

ios学记0008-画板drawingBoard

来源:花图问答

自己封装画板类

前言

这次简单学习了 CG (Core Graphics 框架)这个二次元框架,我自己简单使用之后觉得这个基于C语言弄出来的框架挺好用的,就是可能OC语法用久了,有些人会有些不习惯C语言的语法了.没关系,我相信你学习OC之前肯定已经熟练C语言了,用一用就习惯了.

学习建议

开始今天的正题,注释我都写在代码中了,很详细,基本实现了画板的基础功能,你完全可以直接把我的 G_DrawingBoard.h & G_DrawingBoard.m 文件代码copy出去直接使用.使用方法我会 paste 到文章的最后.

不过我还是希望通过我的口水话,能够提高你我的业务能力

//
//  G_DrawingBoard.h
//  UI_高级_画板
//
//  Created by Gavin Guan on 16/5/26.
//  Copyright © 2016年 Gavin Guan. All rights reserved.
//

#import <UIKit/UIKit.h>

#define TEST 1      
//这里值为1时,开启调试代码段,为0时,反之.

@interface G_DrawingBoard : UIView

@property (retain, nonatomic) NSMutableArray *G_paths;  //存每一条路径
@property (retain, nonatomic) NSMutableArray *G_lineDic;//存每一条路径的对应信息
@property (copy, nonatomic) UIColor *G_lineColor;       //存当前画笔颜色
@property (assign, nonatomic) CGFloat G_lineWidth;      //存当前画笔宽度(粗细)

- (void)refreshDisplay;              //刷新画板视图的方法

@end
//
//  G_DrawingBoard.m
//  UI_高级_画板
//
//  Created by Gavin Guan on 16/5/26.
//  Copyright © 2016年 Gavin Guan. All rights reserved.
//

#import "G_DrawingBoard.h"

@implementation G_DrawingBoard

//-------------------------------------------
//-------------------------------------------
#pragma mark - init方法 - 代码创建方法
- (instancetype)init
{
    self = [super init];
    if (self) {
        
        //仅仅单指,背景白色,属性值初始化
        self.multipleTouchEnabled = NO;  //关闭多点触控
        self.backgroundColor = [UIColor whiteColor];
        self.G_paths = [[NSMutableArray alloc] init];
        self.G_lineDic = [[NSMutableArray alloc] init];
        self.G_lineColor = [UIColor blackColor];
        self.G_lineWidth = 3;
        
    }
    return self;
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        //仅仅单指,背景白色,属性值初始化
        self.multipleTouchEnabled = NO;  //关闭多点触控
        self.backgroundColor = [UIColor whiteColor];
        self.G_paths = [[NSMutableArray alloc] init];
        self.G_lineDic = [[NSMutableArray alloc] init];
        self.G_lineColor = [UIColor blackColor];
        self.G_lineWidth = 3;
        
    }
    return self;
}

//-------------------------------------------
#pragma mark - awakeFromNib方法 - 使用nib或者storyBoard创建方法

-(void)awakeFromNib {
    //仅仅单指,背景白色,属性值初始化
    self.multipleTouchEnabled = NO;  //关闭多点触控
    self.backgroundColor = [UIColor whiteColor];
    self.G_paths = [[NSMutableArray alloc] init];
    self.G_lineDic = [[NSMutableArray alloc] init];
    self.G_lineColor = [UIColor blackColor];
    self.G_lineWidth = 3;
}

//-------------------------------------------
#pragma mark - drawRect方法
//该方法会在视图初次加载时调用,也会在向系统申请刷新视图时调用
- (void)drawRect:(CGRect)rect {

    //以下代码会逐个添加路径(信息)到绘图环境

    NSMutableArray *arr = self.G_paths;
    
#if TEST
    NSLog(@"arr count = %ld", self.G_paths.count);
#endif
    
    //获取当前绘制环境 如果你要在视图上话多个图形,并且封装在方法中,那么每个方法中都需要
    //获取一次context绘图环境(绘图上下文),这里只需获取一次
    CGContextRef context = UIGraphicsGetCurrentContext();
    
    //游历 path 数组
    for (int i = 0; i < self.G_paths.count; i++) {
        
            //获取绘制属性
        NSDictionary *lineDic = self.G_lineDic[i];
        UIColor *lineColor = lineDic[@"lineColor"];
        CGFloat lineWidth = [lineDic[@"lineWidth"] doubleValue] ;
                //设置线段颜色
        CGContextSetStrokeColorWithColor(context, lineColor.CGColor);
                //设置线条宽度
        CGContextSetLineWidth(context, lineWidth);
                //设置拐点的样式
        CGContextSetLineJoin(context, kCGLineJoinRound);
        
        //获取路径
        CGMutablePathRef path = (__bridge CGMutablePathRef)(arr[i]);
        
        //添加路径到当前绘制环境
        CGContextAddPath(context, path);
        
        //绘制路径
            //绘制模式
        CGContextDrawPath(context, kCGPathStroke);
    //绘制模式这段代码的运行机制是这样的,就像是一个绘制断点,之前的线条宽度之类的属性
    //如果后面你不修改,那么就是使用原有的,如果你修改了,就使用修改后的.
    //这里for循环结束一次,再次开始的时候,又会从我设置的lineDic中获取对应的信息来处
    //理下一次绘制时的"画笔"的属性,以实现画板.
    }
    
    
}

//-------------------------------------------
#pragma mark - touches方法

//手指接触到屏幕时,调用一次,仅一次
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //获取唯一的触摸对象 -- 开始位置
    UITouch *touch = [touches anyObject];
    CGPoint locationPoint = [touch locationInView:self];
    
#if TEST
    NSLog(@"当前触摸开始位置(%.2f , %.2f)", locationPoint.x, locationPoint.y);
#endif
    
    //创建可变路径
    CGMutablePathRef path = CGPathCreateMutable();
    
    //路径移动到初始点 - 如同"画笔"移动到某一点准备绘制
    CGPathMoveToPoint(path, NULL, locationPoint.x, locationPoint.y);

    //添加 path 到路径数组
    [self.G_paths addObject:(__bridge id _Nonnull)(path)];
    //存储 path 信息 - 当前"画笔"属性
    NSDictionary *lineDic = @{
                              @"lineColor" : self.G_lineColor,
                              @"lineWidth" : [NSNumber numberWithDouble:self.G_lineWidth]
                              };
    [self.G_lineDic addObject:lineDic];
    
    //手动释放路径 - 通过create方式创建的CGPathRef一定要free,不然有内存泄漏
    //CGPathRelease(path);
    //我多次通过代码实验,猜测通过桥接方法(如下代码)
    //[self.G_paths addObject:(__bridge id _Nonnull)(path)]; 
    //添加path时,运作机制类似于retain一次,所以这里不要free这个path
    //会影响到path数组中的元素path,成为野指针,释放的方法我写在了dealloc方法中
}

//仅仅在手指移动时调用(超级频繁)
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {

    //获取唯一的触摸对象 -- 移动路径点
    UITouch *touch = [touches anyObject];
    CGPoint locationPoint = [touch locationInView:self];
    
#if TEST
    NSLog(@"当前触摸移动路径(%.2f , %.2f)", locationPoint.x, locationPoint.y);
#endif
    
    //移动时,添加 实时点 到路径
    NSArray *arr = self.G_paths;
    CGMutablePathRef path = (__bridge CGMutablePathRef)([arr lastObject]);
    CGPathAddLineToPoint(path, NULL, locationPoint.x, locationPoint.y);
    
    //刷新视图
    [self refreshDisplay];
}

//-------------------------------------------
#pragma mark - 刷新绘制

- (void)refreshDisplay {
    
    //刷新视图
    [self setNeedsDisplay];
    //手动调用 drawRect: 方法是无效的,必须通过这个方法通知系统,然后系统自行调用 drawRect:
}

//-------------------------------------------
#pragma mark - dealloc方法

-(void)dealloc {
    
    NSMutableArray *arr = self.G_paths;
    
    for (int i = 0; i < self.G_paths.count; i++) {
        CGMutablePathRef path = (__bridge CGMutablePathRef)(arr[i]);
        CGPathRelease(path); //虽然是ARC模式,但CGPathRef还是要手动释放的.
    }
    
}

//-------------------------------------------
//-------------------------------------------
@end

下面是使用方法的代码简介 - 写在任意 viewController.m 中



#pragma mark - 按钮测试

//撤销
- (IBAction)repeal:(UIButton *)sender {

    //通过移除最后一个Object,刷新视图来实现撤销方法
    [self.drawingBoard.G_paths removeLastObject];
    [self.drawingBoard.G_lineDic removeLastObject];
    [self.drawingBoard refreshDisplay];
}

//变色 & 粗细
- (IBAction)changeColor:(UIButton *)sender {
    
    //这里仅仅是我自己测试时,使用的代码,你可以自己定义多个按钮
    //任意修改 lineColor & lineWidth 属性 - 修改当前"画笔"的属性
    if (self.drawingBoard.G_lineColor == [UIColor redColor]) {
        
        self.drawingBoard.G_lineColor = [UIColor blackColor];
        self.drawingBoard.G_lineWidth = 3;
    } else {
        self.drawingBoard.G_lineColor = [UIColor redColor];
        self.drawingBoard.G_lineWidth = 10;
    }
}

- (IBAction)clearAll:(UIButton *)sender {
    
    //通过移除全部Object,刷新视图来实现清屏
    [self.drawingBoard.G_paths removeAllObjects];
    [self.drawingBoard.G_lineDic removeAllObjects];
    [self.drawingBoard refreshDisplay];
}

以上就是这次全部内容,如果你喜欢,就给我点赞吧.╮(╯_╰)╭

我在之后对自己的代码又进行了一次封装优化,同时得到了高人指点再次,有了再次优化的idea,有需要的可以私我,发给你们.