1. 程式人生 > >iOS開發-------塗鴉板(UIBezierPath 貝賽爾曲線)與 MVC初嘗試

iOS開發-------塗鴉板(UIBezierPath 貝賽爾曲線)與 MVC初嘗試

         塗鴉板,顧名思義就是能夠在上面畫點東西,貝賽爾曲線(UIBezierPath),也可以叫做貝賽爾路徑。因為path的直譯就是路徑,看起來很高大上,之前樓主也確實這麼認為的,很高大上,細細瞭解,其實也不難,畢竟難的東西蘋果都給我們封裝好了。初次用MVC模式來些iOS的東西,錯誤難免,請包涵,首先來看一下效果吧

首先寫一點測試的字,樓主寫字不好看,如圖一, 然後點選兩下撤銷,那麼如圖二, 然後點選恢復一下,如圖三,當點選clear的時候,全屏就清除了。


如果想用MVC,邏輯很重要,首先來屢屢邏輯

ViewController的直系下屬:

LineManager:完成對基礎模型Line的管理,負責Line的增刪改

TouchEvents:一個響應觸控的View,捕捉觸控點,彙報給VC,讓LineManager為線新增軌跡點

RenderView:只接觸是沒用的,還需要顯示的,根據LineManager彙報的Line的陣列來繪製軌跡

ButtonView:下面的按鈕是掌管按鈕功能的,通過彙報VC點選按鈕tag值,讓LineManager進行相應的操作

SettingManager:負責記錄修改後的相關屬性,並只在VC中進行讀取操作

MySettingViewController的直系下屬

SettingView:負責響應修改的相應,反饋給SettingVC,並將資料存到SettingManager中

SettingManager:完成記錄修改後的資料

Line

首先完成模型,Line,能夠想到的屬性如下

//
//  Line.h
//  塗鴉板
//
//  Created by YueWen on 15/9/24.
//  Copyright (c) 2015年 YueWen. All rights reserved.
//

#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface Line : NSObject

/**
 *  線的顏色
 *  預設是紅色
 */
@property(nonatomic,strong)UIColor * lineColor;

/**
 *  線的寬度
 *  預設寬度是2.0
 */
@property(nonatomic,assign)NSInteger width;

/**
 *  儲存路徑點的陣列,不能隨意修改,所以設定為readOnly
 */
@property(nonatomic,strong,readonly)NSArray * points;

/**
 *  貝賽爾曲線
 */
@property(nonatomic,strong,readonly)UIBezierPath * path;

/**
 *  新增點
 *
 *  @param point 新增的點
 */
-(void)addPoint:(CGPoint)point;

@end
既然如此,在Line.m中,需要一個可變陣列來增加線的點
/**
 *  延展中點的陣列
 */
@property(nonatomic,strong)NSMutableArray * mPoints;

實現相關的方法
- (instancetype)init
{
    self = [super init];
    if (self) {
        
        //初始化陣列
        self.mPoints = [NSMutableArray array];
        
        //預設為紅色
        self.lineColor = [UIColor redColor];
        
        //預設大小為2.0
        self.width = 2.0;
    }
    return self;
}


/**
 *  為線中新增點
 *
 *  @param point 新增的點
 */
-(void)addPoint:(CGPoint)point
{
    //轉成NSValue型別的物件
    NSValue * value = [NSValue valueWithCGPoint:point];
    
    //新增到陣列
    [self.mPoints addObject:value];
}


/**
 *  重寫點陣列的get方法
 *
 *  @return 返回點的陣列
 */
-(NSArray *)points
{
    return [NSArray arrayWithArray:self.mPoints];
}


/**
 *  重寫 貝賽爾曲線的get方法
 *
 *  @return 返回線的貝賽爾曲線
 */
-(UIBezierPath *)path
{
    
    //建立一個貝賽爾曲線
    UIBezierPath * pathTemp = [UIBezierPath bezierPath];
    
    //首先移動到第一個點
    [pathTemp moveToPoint:[self.mPoints[0] CGPointValue]];
    
    for (int i = 1 ;i < self.mPoints.count; i++)
    {
        //轉成普通的點
        CGPoint point = [self.mPoints[i] CGPointValue];
        
        //貝賽爾曲線新增點
        [pathTemp addLineToPoint:point];
    }
    return pathTemp;
}

TouchEventsView

touchEventsView主要負責彙報觸控的點,所以比較簡單,首先定義三個Block程式碼塊,分別負責傳出開始觸控的點,移動中的點,手指離開螢幕的點
typedef void(^TouchBeginEventsBlock)(CGPoint point);
typedef void(^TouchMoveEventsBlock)(CGPoint point);
typedef void(^TouchEndEventsBlock)(CGPoint point);

在touchEventsView.m的檔案中,依舊宣告三個屬性,並在標頭檔案中宣告三個賦值方法(當然還可以在標頭檔案中宣告屬性,直接賦值)
/*宣告block的賦值方法*/
-(void)touchBeginEventsBlockHandle:(TouchBeginEventsBlock)b;

-(void)touchMoveEventsBlockHandle:(TouchMoveEventsBlock)b;

-(void)touchEndEventsBlockHandle:(TouchEndEventsBlock)b;

延展的屬性宣告
@property(nonatomic,strong)TouchBeginEventsBlock  touchBeginEvents;
@property(nonatomic,strong)TouchMoveEventsBlock touchMoveEvents;
@property(nonatomic,strong)TouchEndEventsBlock  touchEndEvents;

實現賦值方法
-(void)touchBeginEventsBlockHandle:(TouchBeginEventsBlock)b
{
    self.touchBeginEvents = b;
}

-(void)touchMoveEventsBlockHandle:(TouchMoveEventsBlock)b
{
    self.touchMoveEvents = b;
}

-(void)touchEndEventsBlockHandle:(TouchEndEventsBlock)b
{
    self.touchEndEvents = b;
}

最後實現的方法就是觸控事件,只要是繼承與UIView的類都可以實現方法
//開始觸控
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    //捕獲點
    CGPoint point = [[touches anyObject] locationInView:self];
    
    //執行程式碼塊,通知VC
    if (self.touchBeginEvents)
    {
        self.touchBeginEvents(point);
    }
}

//滑動的時候
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    //捕獲點
    CGPoint point = [[touches anyObject] locationInView:self];
    
    //執行程式碼塊,通知VC
    if (self.touchMoveEvents)
    {
        self.touchMoveEvents(point);
    }
    
}

//滑動結束,即手離開螢幕時
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
    //捕獲點
    CGPoint point = [[touches anyObject] locationInView:self];
    
    //執行程式碼塊,通知VC
    if (self.touchEndEvents)
    {
        self.touchEndEvents(point);
    }
 
}

//功能被搶的時候
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
    //捕獲點
    CGPoint point = [[touches anyObject] locationInView:self];
 
    //執行程式碼塊,通知VC
    if (self.touchEndEvents)
    {
        self.touchEndEvents(point);
    }
    
}

RenderView

      renderView只負責繪圖功能,自然屬性中只需要一個儲存線的陣列,每次劃線的時候,根據每個物件裡面的貝賽爾曲線(UIBezierPath)來繪製,因為儲存線的陣列是在延展中宣告的,所以標頭檔案中宣告一個賦值的方法
/**
 *  設定線的陣列
 *
 *  @param array 引數線的陣列
 */
-(void)setRenderLines:(NSArray *)array;

RenderView的init方法不再給出,別忘記給陣列初始化即可,下面是陣列的set方法
/**
 *  設定線的陣列
 *
 *  @param array 引數line陣列
 */
-(void)setRenderLines:(NSArray *)array
{
    self.lines = array;
    
    //相當於系統呼叫drawRect的繪製方法,不能手動呼叫,手動呼叫會出現卡屏
    [self setNeedsDisplay];
}

由於陣列中儲存的都是Line物件,需要的UIBezierPath(貝賽爾曲線)都存在Line物件中,所以需要遍歷繪製
//繪製方法只能在該方法下進行,在此方法中提供了一個繪畫的環境
- (void)drawRect:(CGRect)rect
{
    
    for (Line * line in self.lines)
    {
        UIBezierPath * path = line.path;
    
        //設定寬度
        path.lineWidth = line.width;
    
        //設定顏色
        [line.lineColor setStroke];
        
        //根據貝賽爾曲線進行繪圖
        [path stroke];
    }
    
}


LineManager

LineManager負責彙報給VC 他所存的線,所以需要一個回撥,樓主比較熟悉Block回撥,所以選擇Block,方法不為一
typedef void(^lineManagerDidChangeBlock)(NSArray * lines);
既然可以對線進行儲存刪除,必然需要一個可變陣列來存數,用延展定義相關的屬性
@property(nonatomic,strong)NSMutableArray * linesArray;//儲存需要繪製的線
@property(nonatomic,strong)NSMutableArray * deleteLinesArray;//儲存不需要繪製的線
@property(nonatomic,strong)lineManagerDidChangeBlock lineManagerDidChange;//回撥的程式碼塊


既然是單例必然宣告建立單例的方法,習慣以share開頭
/**
 *  單例方法
 *
 *  @return 返回單例
 */
+(instancetype)shareLineMangaer;
實現方法如下,但不要忘記在init方法裡初始化可變陣列
+(instancetype)shareLineMangaer
{
    static LineManager * lineManager = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        lineManager = [[LineManager alloc]init];
    });
    
    return lineManager;
}


當前面的開始觸碰時,需要lineManager重新建立一條線,並且將點賦值給貝塞爾曲線的首點,需要在.h中宣告,這裡只給出實現方法
/**
 *  新新增一條線,並初始化
 *
 *  @param point 起始點
 *  @param color 線的顏色
 *  @param width 線的寬度
 */
-(void)addPointWithStartEvents:(CGPoint)point WithColor:(UIColor *)color Width:(NSInteger)width
{
    //建立一條線
    Line * line = [[Line alloc]init];
    
    //設定屬性
    [line addPoint:point];
    line.lineColor = color;
    line.width = width;
    
    //新增到陣列
    [self.linesArray addObject:line];
    
    //執行程式碼塊通知VC
    if (self.lineManagerDidChange)
    {
        self.lineManagerDidChange(self.linesArray);
    }
    
}

在移動過程中以及結束後,需要不斷地往最後一個Line裡新增獲取到的點
/**
 *  為最後一次的線更新點
 *
 *  @param point 引數點
 */
-(void)addPointForLastLine:(CGPoint)point
{
    //獲取最後一條線
    Line * line = [self.linesArray lastObject];
    
    //增加點
    [line addPoint:point];
    
    //執行程式碼塊,通知VC
    if (self.lineManagerDidChange)
    {
        self.lineManagerDidChange(self.linesArray);
    }
}


      撤銷鍵的點選,從表面看是刪除了一條線,但實際不是,而是從需要渲染的Line陣列中取出最後一條線,放到暫時儲存的陣列中,相當於一個棧的操作,就是一個出棧與入棧的過程,因為兩者相似,只給出一段即可
/**
 *  刪除前一根線
 */
-(void)beforeButtonToDeleteLine
{
    //如果線陣列中的線存線上
    if (self.linesArray.count > 0)
    {
        //取出最後一根線
        Line * line = [self.linesArray lastObject];
        
        //快取陣列中新增
        [self.deleteLinesArray addObject:line];
        
        //從線陣列中刪除線
        [self.linesArray removeLastObject];
        
        //返回修改後的陣列
        if (self.lineManagerDidChange)
        {
            self.lineManagerDidChange(self.linesArray);
        }
       
    }

}

清除按鈕最簡單,只需要清空兩個陣列的元素即可
/**
 *  刪除所有儲存的線
 */
-(void)clearButtonToRemoveLine
{
    //清空陣列
    [self.linesArray removeAllObjects];
    [self.deleteLinesArray removeAllObjects];
    
    //返回修改後的陣列
    if (self.lineManagerDidChange)
    {
        self.lineManagerDidChange(self.linesArray);
    }
    
}

最後是根據button傳入的tag值進行相應的操作,rag值傳入的回撥在下面的ButtonView中提到
-(void)lineManagerDidChangeWithTag:(NSInteger)tag
{
    //根據tag值進行相應的操作
    switch (tag) {
        case 0://撤銷鍵
            [self beforeButtonToDeleteLine];
            break;
        case 1://恢復鍵
            [self nextButtonToRecoveryLine];
            break;
        case 2://清除鍵
            [self clearButtonToRemoveLine];
            break;
        default:
            break;
    }
}



ButtonView

buttonView就是一個防止按鈕的小View,因為它簡單,沒有用xib或者storyboard來寫,所以是一個純程式碼的小檢視 首先要有一個返回button的tag值的回撥
typedef void(^buttonPressedInButtonViewBlock)(NSInteger buttonTag);

自然需要在.m中的延展中定義一個程式碼塊的屬性以及set方法,這裡不再贅餘 首先定義一下屬性
@property(nonatomic,strong)UIButton * beforeButton;//返回先一步,消除一條線
@property(nonatomic,strong)UIButton * nextButton;//撤銷前一步
@property(nonatomic,strong)UIButton * clearButton;//清除所有的線

@property(nonatomic,strong)buttonPressedInButtonViewBlock buttonPressedBlock;

初始化兩個方法
-(void)awakeFromNib//從xib中例項化的時候才走
{
    [self myInitView];
}



-(instancetype)initWithFrame:(CGRect)frame
{
    if (self = [super initWithFrame:frame])
    {
        [self myInitView];
    }
    return self;
}

實現自己寫的初始化方法myInitView
-(void)myInitView
{
    //初始化按鈕屬性
    self.beforeButton = [self myButtonInitWithTitle:@"撤銷" Tag:0];
    self.nextButton = [self myButtonInitWithTitle:@"恢復" Tag:1];
    self.clearButton = [self myButtonInitWithTitle:@"清除" Tag:2];
    
    //新增元件
    [self addSubview:self.beforeButton];
    [self addSubview:self.nextButton];
    [self addSubview:self.clearButton];
    
    //設定背景顏色
    self.backgroundColor = [UIColor whiteColor];
    
    //為按鈕進行佈局
    [self myLayoutConstrains];

}
樓主依舊比較懶,所以自定義一個button的初始化方法
/**
 *  自定義按鈕初始化
 *
 *  @param title 按鈕上的文字
 *  @param tag   按鈕的tag值
 *
 *  @return 初始化按鈕的地址
 */
-(UIButton *)myButtonInitWithTitle:(NSString *)title Tag:(NSInteger)tag
{
    //初始化按鈕,系統型別的
    UIButton * button = [UIButton buttonWithType:UIButtonTypeSystem];
    
    //設定tag值
    button.tag = tag;
    
    //設定按鈕上的文字
    [button setTitle:title forState:UIControlStateNormal];
    
    //取消按鈕的自動佈局
    button.translatesAutoresizingMaskIntoConstraints = NO;
    
    //確定button的目標動作回撥
    [button addTarget:self action:@selector(buttonPressed:) forControlEvents:UIControlEventTouchUpInside];
    
    //返回建立好按鈕的地址
    return button;
}
手動佈局的程式碼如下,用的字串進行的佈局
/**
 *  開始佈局
 */
-(void)myLayoutConstrains
{
    //設定水平佈局
    NSArray * buttonHorizatal = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"H:|-[_beforeButton]-[_nextButton(==_beforeButton)]-[_clearButton(==_beforeButton)]-|"] options:0 metrics:nil views:NSDictionaryOfVariableBindings(_beforeButton,_nextButton,_clearButton)];
    [self addConstraints:buttonHorizatal];
    
    //設定垂直佈局
    NSArray * buttonVerital1 = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-7-[_beforeButton(30)]-7-|"] options:0 metrics:nil views:NSDictionaryOfVariableBindings(_beforeButton)];
    [self addConstraints:buttonVerital1];
    
    NSArray * buttonVerital2 = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-7-[_nextButton(30)]-7-|"] options:0 metrics:nil views:NSDictionaryOfVariableBindings(_nextButton)];
    [self addConstraints:buttonVerital2];
    
    NSArray * buttonVerital3 = [NSLayoutConstraint constraintsWithVisualFormat:[NSString stringWithFormat:@"V:|-7-[_clearButton(30)]-7-|"] options:0 metrics:nil views:NSDictionaryOfVariableBindings(_clearButton)];
    [self addConstraints:buttonVerital3];
}
最後就是按鈕點選進行的回撥方法
/**
 *  按鈕被點選
 *
 *  @param button 進行目標動作回撥的按鈕
 */
-(void)buttonPressed:(UIButton *)button
{
    //自身的回撥存在
    if (self.buttonPressedBlock)
    {
        //返回按鈕的tag值
        self.buttonPressedBlock(button.tag);
    }
}


SettingView

settingView是掌管設定的頁面,因為說道MySettingManager必須需要settingView,所以先介紹settingView,這個比較複雜佈局,所以用的xib進行的佈局,如下      首先彙報肯定少不了回撥方法,樓主依舊選擇用Block回撥,當然方法肯定不唯一,定義兩個block,一個返回顏色,一個返回線寬,延展中的屬性定義以及set方法依舊不贅餘。
typedef void(^settingViewChangeColorBlock)(UIColor * lineColor);

typedef void(^settingViewChangeLineWidthChangeBlock)(CGFloat lineWidth);

接下來是當顏色的Slider發生變化時產生的回撥方法
/**
 *  顏色調節器發生變化的回撥
 *
 *  @param sender 調節器的傳送者
 */
- (IBAction)colorSliderChanged:(id)sender
{
    //根據 三原色 以及 透明度 建立Color物件
    UIColor * lineColor = [UIColor colorWithRed:(self.redColorSlider.value / 255) green:(self.greenColorSlider.value / 255) blue:(self.blueColorSlider.value / 255) alpha:1];
    
    //改變預覽label的顏色
    self.colorText.textColor = lineColor;
    
    //如果程式碼塊存在
    if (self.colorChangeBlock)
    {
        self.colorChangeBlock(lineColor);
    }
}

然後是線寬發生變化時產生的回撥
/**
 *  線的寬度調節器發生變化的回撥
 *
 *  @param sender 調節器的傳送者
 */
- (IBAction)lineWidthsliderChanged:(id)sender
{
    
    //獲取 線的寬度
    CGFloat lineWidth = self.lineWidthSlider.value;
    
    //如果程式碼塊存在
    if (self.lineWidthChangeBlock)
    {
        self.lineWidthChangeBlock(lineWidth);
    }
}

settingView就是這麼多,依舊很簡單

MySettingManager

      mySettingManager是一個儲存修改後的線的顏色以及線寬的一個單例,既然是單例自然少不了單例方法,依舊習慣用share開頭,只給出實現方法,不要忘記標頭檔案中宣告相關屬性以及宣告方法
//不允許外界修改,所以用readOnly,只能通過相應的方法設定
@property(nonatomic,strong,readonly)UIColor * lineColor;

@property(nonatomic,assign,readonly)CGFloat lineWidth;

接下來是單例的實現,以上兩個屬性不要忘記在init裡面初始化
+(instancetype)shareSettingManager
{
    static SettingManager * settingManager = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        settingManager = [[SettingManager alloc]init];
        
    });
    
    return settingManager;
}

接下來就是當顏色以及線寬發生變化時進行相應的賦值了,如下
-(void)settingManagerSetColor:(UIColor *)lineColor withLineWidth:(CGFloat)lineWidth
{
    //如果存在顏色
    if (lineColor)
    {
       _lineColor = lineColor;
        
    }
    
    //如果存線上寬
    if (lineWidth > 0)
    {
        _lineWidth = lineWidth;
    }
   
}

ViewController

viewController既為主頁面的控制器,屬性如下,前面都已經細解釋了各個屬性,因此不註釋了
@property(nonatomic,strong)TouchEventsView * touchEventsView;

@property(nonatomic,strong)RenderView * renderView;

@property(nonatomic,strong)LineManager * lineManager;

@property(nonatomic,strong)SettingManager * settingManager;

@property(nonatomic,strong)ButtonView * buttonView;


接下來就是看起來比較繁瑣的viewDidLoad方法了,其實就是一堆設定回撥的過程,會比較長,不過有註釋,可以慢慢研究
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //初始化線的管理者
    self.lineManager = [LineManager shareLineMangaer];
    
    //初始化設定管理者
    self.settingManager = [SettingManager shareSettingManager];
    
    //初始化觸碰view
    self.touchEventsView = [[TouchEventsView alloc]initWithFrame:touchFrame];

    //初始化繪圖view
    self.renderView = [[RenderView alloc]initWithFrame:touchFrame];
    
    //初始化按鈕view
    self.buttonView = [[ButtonView alloc]initWithFrame:CGRectMake(0, self.touchEventsView.frame.size.height, self.view.frame.size.width, 44)];
    
    //配置導航欄的相關元件
    [self otherSetting];
    
    //避免強引用迴圈
    __weak __block ViewController * copy_self = self;
    
    //當開始觸控式螢幕幕的時候
    [self.touchEventsView touchBeginEventsBlockHandle:^(CGPoint point) {
    
        //管理者建立線並且新增點為起始點
        [copy_self.lineManager addPointWithStartEvents:point WithColor:copy_self.settingManager.lineColor  Width:copy_self.settingManager.lineWidth];
        
    }];
    
    //手在觸控式螢幕上移動的時候
    [self.touchEventsView touchMoveEventsBlockHandle:^(CGPoint point) {
       
        //管理者為最後一個線更新點
        [copy_self.lineManager addPointForLastLine:point];
        
    }];
    
    //手離開觸控式螢幕的時候
    [self.touchEventsView touchEndEventsBlockHandle:^(CGPoint point) {
       
        //管理者為最後一個線更新點
        [copy_self.lineManager addPointForLastLine:point];
        
    }];
    
    //線的陣列發生變化時
    [self.lineManager lineManagerDidChangeBlockHandle:^(NSArray *lines) {
       
        //渲染檢視繪製
        [copy_self.renderView setRenderLines:lines];
        
    }];
    
    //buttonView被點選
    [self.buttonView buttonPressedBlockHandle:^(NSInteger buttonTag) {
       
        //管理者根據不同的tag值執行相應的操作
        [copy_self.lineManager lineManagerDidChangeWithTag:buttonTag];
    }];
    
    
    //設定背景顏色
    self.touchEventsView.backgroundColor = [UIColor whiteColor];
    self.renderView.backgroundColor = [UIColor clearColor];
    
    //新增
    [self.view addSubview:self.buttonView];
    [self.view addSubview:self.touchEventsView];
    [self.touchEventsView addSubview:self.renderView];
  
}

因為是在navigationController進行的跳轉,所以新增兩個itembarbutton,能夠進行相應的操作
/**
 *  配置導航欄的相關元件
 */
-(void)otherSetting
{
    //新增左右兩個tabBarbutton
    self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"儲存" style:UIBarButtonItemStyleDone target:self action:@selector(toSavePicture)];
    self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc]initWithTitle:@"設定" style:UIBarButtonItemStyleDone target:self action:@selector(toSettingViewContoller)];
}

設定按鈕的回撥方法如下
/**
 *  跳轉頁面的回撥方法
 */
-(void)toSettingViewContoller
{
    //建立settingViewController物件
    MySettingViewController * mySettingViewController = [[MySettingViewController alloc]init];
    
    //跳轉
    [self.navigationController pushViewController:mySettingViewController animated:YES];
    
}
儲存按鈕的回撥方法如下
/**
 *  儲存當前圖片(截圖)
 */
-(void)toSavePicture
{
    //建立一個基於點陣圖的圖形上下文並指定大小
    UIGraphicsBeginImageContext(self.renderView.bounds.size);
    
    //renderIncontext呈現接受者以及其子範圍到指定的上下文
    [self.renderView.layer renderInContext:UIGraphicsGetCurrentContext()];
    
    //返回一個基於當前圖形上的上下文圖片
    UIImage * image = UIGraphicsGetImageFromCurrentImageContext();
    
    //移除棧定基於當前點陣圖的圖形上下文
    UIGraphicsEndImageContext();
    
    //將圖片儲存到圖片庫
    UIImageWriteToSavedPhotosAlbum(image, nil, nil, nil);
}

看一下儲存的結果吧 畫一張測試圖,點選儲存後,去相簿中看

MySettingController

mySettingController是設定頁面的控制器,所以理論和上面的控制器邏輯是一樣的,而且更為簡單,因為只負責顯示設定View的功能,以下是viewDidLoad的方法
- (void)viewDidLoad {
    [super viewDidLoad];
    
    //獲取單例
    self.settingManager = [SettingManager shareSettingManager];
    
    //初始化setting物件
    self.settingView = [[[NSBundle mainBundle] loadNibNamed:@"SettingView" owner:nil options:nil] lastObject];
    
    //設定frame
    self.settingView.frame = self.view.frame;
    
    
    //避免強引用迴圈
    __block __weak MySettingViewController * copy_self = self;
    
    
    
    //如果顏色調色器發生變化
    [self.settingView settingViewChangeColorBlockHandle:^(UIColor *lineColor)
    {
        //為設定管理者設定顏色
        [copy_self.settingManager settingManagerSetColor:lineColor withLineWidth:-1];

        
    }];
    
    //如果線寬調節器發生變化
    [self.settingView settingViewChangeLineWidthBlock:^(CGFloat lineWidth) {
       
        //為設定管理者設定線寬
        [copy_self.settingManager settingManagerSetColor:nil withLineWidth:lineWidth];
    }];
    
    //新增元件
    [self.view addSubview:self.settingView];
    
}

AppDelegate

因為全程沒有使用storyboard,所以需要在appDelegate中設定相關的屬性,方法如下,很簡單
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    //設定根window
    UIWindow * window = [[UIWindow alloc]initWithFrame:[UIScreen mainScreen].bounds];
    self.window = window;
    
    //初始化一個畫板Controller
    ViewController * viewController = [[ViewController alloc]init];
    viewController.navigationItem.title = @"塗鴉板";
    
    //初始化一個navigationController
    UINavigationController * navigationController = [[UINavigationController alloc]initWithRootViewController:viewController];
    
    //設定根檢視
    self.window.rootViewController = navigationController;
    
    [self.window makeKeyAndVisible];
    
    
    return YES;
}
至此,塗鴉板基本完成,好好享受自己的塗鴉板吧