ios學記0008-畫板drawingBoard

weixin_34402408發表於2016-05-26

自己封裝畫板類

前言

這次簡單學習了 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,有需要的可以私我,發給你們.

相關文章