[绘图实例]-绘图

本篇文章是学习传智播客iOS开发课程的整理笔记。

相关参考链接:iOS7之定制View Controller切换效果之presentViewController:animated:completion:方法:http://ningandjiao.iteye.com/blog/2049105

1>水印简介

    *  图片水印作用:防止他人盗取图片,加一些Logo,生成一张新的图片。

    *  怎么生成新的图片?和绘图一样的,需要拿到上下文做事情,这里也需要拿到上下文,生成一个新的图片。

    *  什么上下文?位图上下文,在这个上下文画东西,就能输出到新的图片上。

    *  怎么获取?之前用的都是图层上下文,系统会自动创建,但是我们位图上下文,需要我们手动创建

    *  总结:只要不和view有关系的上下文,都需要我们手动创建。

    *  在哪获取图像上下文,viewDidLoad,不需要拿到系统创建的图层上下文,没必要在drawRect方法里写,直接viewDidLoad就行了。

    *  怎么创建图像上下文了?之前说过跟上下文有关的以什么开头,UIGraphics

    *  UIGraphicsBeginImageContextWithOptions:看注释,create bitmap,创建一个位图上下文,而且这种方法得到的图片最清晰。

    *  解释参数(size:新图片尺寸 opaque:YES:不透明NO:透明 scale:0.0不伸缩)

    *  绘制内容(图片,文字)

    *  获取图片:把位图上下文的内容生成一个图片给你。

    *  关闭上下文,不关闭一直占用着内存

    *  显示UIImageView

    *  保存图片,写到文件,UIImage不能写,需要转换成NSData二进制数据

    *  UIImageJPEGRepresentation:可以设置图片质量

    *  UIImagePNGRepresentation:把图片转换成png格式的二进制数据,png格式默认是最高清的。

    *  写到桌面

- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 保存源图片
    UIImage *oldImage = [UIImage imageNamed:@"img"];
    
    // 1,开启上下文(创建一个上下文,不是获取,是创建)
    // size 新的图片大小
    // opaque YES 不透明 NO 透明
    // scale  缩放比例
    UIGraphicsBeginImageContextWithOptions(oldImage.size, NO, 0.0);
    
    // 2,将源图片画进上下文
    [oldImage drawAtPoint:CGPointZero];
    
    // 要写进上下文的文字
    NSString *text =  @"我要高薪 !";
    
    // 写进上下文的文字属性
    NSDictionary *dict = @{
                           NSFontAttributeName : [UIFont systemFontOfSize:15],
                           NSForegroundColorAttributeName : [UIColor redColor]
    };
    
    // 3,将文字写进上下文
    // 先画图再写文字
    [text drawAtPoint:CGPointMake(120, 170) withAttributes:dict];
    
    // 4,获取新的图片(将上下文中的内容生成新的图片,即,整合了图片和文字后的图片)
    UIImage *newImage =  UIGraphicsGetImageFromCurrentImageContext();
    
    // 5,关闭上下文(不关闭的话,上写文一直存在,耗内存)
    UIGraphicsEndImageContext();
    
    // 在屏幕上显示图片
    _imageView.image = newImage;
    
    // 6,把图片转换成png格式的二进制数据(png就是最清晰的)
    NSData *data = UIImagePNGRepresentation(newImage);
    
    // 7,写入桌面(保存在桌面)
    [data writeToFile:@"/Users/apple/Desktop/newImage.png" atomically:YES];
}

2>图片裁剪简介

* 图片裁剪

    * 思路:先设置裁剪区域,把图片画上去,超出裁剪区域的自动裁剪掉。

    * 加载旧图片,根据旧图片,获取上下文尺寸。

    * 上下文的尺寸 =新图片的尺寸

    * 开启一个多大的上下文?:和图片尺寸一样大,避免压缩图片。如果比图片尺寸小,会压缩图片。

    * 设置裁剪区域:正切于图片的圆

    * 绘制旧图片

    * 获取新图片

    * 关闭上下文

// 没有圆环的裁剪
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 1.加载旧的图片
    UIImage *oldImage = [UIImage imageNamed:@"阿狸头像"];
    
    // 2.开启上下文
    UIGraphicsBeginImageContextWithOptions(oldImage.size, NO, 0.0);
    
    // 3.画圆:正切于上下文
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, oldImage.size.width, oldImage.size.height)];
    
    // 4.设为裁剪区域
    [path addClip];
    
    // 5.画图片
    [oldImage drawAtPoint:CGPointZero];
    
    // 6.生成一个新的图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 7.关闭上下文
    UIGraphicsEndImageContext();
    
    // 8.显示到View上
    _imageView.image = newImage;
}

*   带圆环裁剪:在裁剪的图片外边加个小圆环。

    * 分析思路:先画一个大圆,在设置裁剪区域,把图片画上去,超出裁剪区域的自动裁剪掉。

    * 加载旧图片,根据旧图片,获取上下文尺寸。

    * 确定圆环宽度 borderW

    * 上下文的尺寸 =新图片的尺寸

    * 确定新的上下文尺寸: newImageW : oldImageW +2 * borderW  newImageH : oldImageH +2 * borderW

    * 绘制大圆:

        1.获取上下文2.添加路径到上下文3.设置大圆的颜色 =圆环的颜色 4.渲染

    * 设置裁剪区域,和图片尺寸一样大,只不过,x,y不一样,x=borderW,y=borderW.

    * 绘制旧图片

    * 获取新图片

    * 关闭上下文

    * 抽分类,3个参数,图片名称,圆环宽度,圆环颜色

// 带有圆环的裁剪
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 圆环的宽度
    CGFloat borderW = 5;
    
    // 加载旧的图片
    UIImage *oldImage =  [UIImage imageNamed:@"阿狸头像"];
    
    // 设置最外的圆环所在矩形的宽高
    CGFloat imageW = oldImage.size.width + 2 * borderW;
    CGFloat imageH = oldImage.size.height + 2 * borderW;
    
    // 设置最外圆环所在矩形(正方形)的宽高
    // 判断是否为长方形,若是长方形,则取最短的那个
    CGFloat circirW = imageW > imageH ? imageH : imageW;
    
    // 开启上下文
    UIGraphicsBeginImageContextWithOptions(CGSizeMake(circirW, circirW), NO, 0.0);
    
    // 大圆
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, circirW, circirW)];
    
    // 下面要把画的圆添加到上下,所以得获取当前上下文
    // 获取当前上下文(获取的是位图上下文,而之前在drawRect:中获取的是layer上下文)
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 添加到上下文
    CGContextAddPath(ctx, path.CGPath);
    
    // 设置大圆填充颜色
    [[UIColor whiteColor] set];
    
    // 渲染
    CGContextFillPath(ctx);
    
    CGRect clipR = CGRectMake(borderW, borderW, oldImage.size.width, oldImage.size.height);
    
    // 内小圆:正切于旧图片的圆
    UIBezierPath *clipPath = [UIBezierPath bezierPathWithOvalInRect:clipR];
    
    // 设置裁剪区域
    [clipPath addClip];
    
    // 画图片
    [oldImage drawAtPoint:CGPointMake(borderW, borderW)];
    
    // 获取新的图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 关闭上下文
    UIGraphicsEndImageContext();
    
    // 显示到View上
    _imageView.image = newImage;
}

3屏幕截图简介

*    屏幕截图:把屏幕的内容截屏生成一张新的图片

    * 通常开发中,都是把控制器的内容截屏,生成新的图片

    * 控制器怎么显示?根据view

    * view怎么显示?根据layer图层

    * layer渲染到位图上下文

    * 注意:图层只能用渲染,图片和文字可以用draw

    * 渲染在哪?新的图片

    * 开启图片上下文,和视图一样的尺寸

    * 写入桌面

    * 抽分类

// 将控制器的view截屏(控制器的view不包含状态栏)
- (void)viewDidLoad
{
    [super viewDidLoad];
    
    // 开启上下文
    UIGraphicsBeginImageContextWithOptions(self.view.bounds.size, NO, 0.0);
    
    // 获取上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    
    // 渲染控制器view的图层到上下文
    // 图层只能用渲染不能用draw,只有图片和文字能用draw
    [self.view.layer renderInContext:ctx];
    
    // 获取截屏图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    
    // 关闭上下文
    UIGraphicsEndImageContext();
    
    // 转换成二进制的PNG图片
    NSData *data = UIImagePNGRepresentation(newImage);
    // 保存到指定文件夹
    [data writeToFile:@"/Users/apple/Desktop/layer.png" atomically:YES];
}

4>手势解锁

    * 演示实例程序

    * 分析界面有几个控件:背景:UIImageView白色圆圈:按钮(点击他,会出现另外一种图片,按钮可以设置不同状态下的图片。)单独视图:(画线是有范围的,当超出view就不能画线了)

    * HMLoadView:自定义视图(好处:代码可复用的其它项目),在视图一创建的时候,就添加9个按钮。

    * initWithCoderinitWithFrame方法添加按钮。

    * 九宫格布局:

        tolcol = 3

        计算row,col按钮的xycol,row有关系,col = i % tolcol row = i / tolcol

        计算边距 margin = (view.bounds.size.width - tolcol * btnW) / (tolcol +1)

        btnX = margin + (btnW + margin) * col

        btnY = (btnW + margin) * row

    * 圆的选中

      * 点击按钮就为选中的图片怎么做?监听按钮点击。

      * 不能addTarget:不能及时显示选中图片。

      * 监听touchBegin,判断点在不在按钮的frame上。

      * touchBegin不调用?原因:事件交给按钮处理,应该把事件交给解锁视图。让按钮不接收事件。

      * 设置按钮不允许交互,2个用处:1.不接收事件2.取消高亮效果一举两得

    * 遍历所有按钮,看触摸点在哪个按钮上,就选中谁,CGRectContainsPoint传入的参数必须是同一个坐标系

      * 实现touchMove方法:因为手指移动的时候,也需要判断点在不在按钮上。

      * 抽方法,因为touchMove的方法里,也需要做同样的事情。

            1> pointWithTouches根据touches集合取出触摸点

            2> buttonWithPoint根据触摸点,获取触摸按钮

   * 圆的连线

     * 哪些需要连线?被选中按钮之间都需要连线,还有一个多余的线

     * 先把选中的按钮全部连线?为什么,因为多余的那根线是从最后一个按钮的圆心开始画,手指移动在哪就画哪。

     * 搞个数组保存下所有选中按钮,在drawRect方法中遍历所有按钮,连线

     * UIBezierPath画线,不需要上下文。

     * 需要多少个UIBezierPath对象?一个,路径都是连续的,不相连的,才需要创建新的UIBezierPath

     * 遍历数组,描述路径

        1>起点:第一个按钮的圆心

        2>添加一根线到其他按钮的圆心

     * 设置路径的颜色和线宽

     * 什么时候渲染?把所有路径都描述完就,渲染一次就够了。

     * [path stroke] 就能渲染到视图上了。

     * 画多余的那根线,记住手指移动的位置。

     * setNeedDisplay 没有画出来线?因为drawRect只会调用一次,需要每次手指移动的时候,都需要重绘

     * touchBegin不调用setNeedDisplay?那时候重绘也没用,只有起点。

     * lineJoinStyle:有尖尖的东西,线段连接样式的问题,设置为平的。

     * 已经选中的按钮,不需要再次选中,和画线

     * 手指抬起,取消所有选中按钮,并且清空数组,清空线条

     * drawRect判断下没有选中数组,不需要画线。

     * 如何判断用户是否输入正确?给选中按钮绑定tag,遍历所有选中按钮,把tag拼接成一个字符串。

//  CYPaintView.m
#import "CYLockView.h"

@interface CYLockView ()
@property(nonatomic, strong) NSMutableArray *selectedBtns; // 被选中的按钮
@property(nonatomic, assign) CGPoint curPoint; // 当前触摸点
@end

@implementation CYLockView
// 手动创建视图的时候才会调用
- (instancetype)initWithFrame:(CGRect)frame{
    if (self = [super initWithFrame:frame]) {
        
    }
    return self;
}
/**
 - (id)initWithCoder:(NSCoder *)aDecoder
 - (id)initWithFrame:(CGRect)frame
 init说明这两个方法都是构造类的方法
 在构造类的方法中可以做些初始化子类的事情
 */
- (NSMutableArray *)selectedBtns{
    if (_selectedBtns == nil) {
        _selectedBtns = [NSMutableArray array];
    }
    return _selectedBtns;
}
#pragma mark - 初始化按钮
// 解析XIB的时候,加载按钮
- (id)initWithCoder:(NSCoder *)aDecoder{
    if (self = [super initWithCoder:aDecoder]) {
        [self addBtns];
    }
    return self;
}
// 添加按钮
- (void)addBtns{
    for (int i = 0; i < 9; i++) {
        UIButton *btn = [UIButton buttonWithType:UIButtonTypeCustom];
        [btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_normal"] forState:UIControlStateNormal];
        [btn setBackgroundImage:[UIImage imageNamed:@"gesture_node_highlighted"] forState:UIControlStateSelected]; // 设置为选中模式
        // 不允许按钮交互,好处:1. 点击的时候不会高亮,2. 将点击事件交给self.view来处理
        btn.userInteractionEnabled = NO;
        btn.tag = i; // 设置tag用于标记按钮
        [self addSubview:btn];
    }
}
// 设置按钮的位置
- (void)layoutSubviews{
    [super layoutSubviews];// 这一句一定要有
    
    CGFloat btnW = 74;
    CGFloat btnH = 74;
    CGFloat btnX = 0;
    CGFloat btnY = 0;
    NSUInteger totalCol = 3;
    CGFloat margin = (self.bounds.size.width - totalCol * btnW) / (totalCol + 1);
    
    for (int i = 0; i < self.subviews.count; i++) {
        UIButton *btn = self.subviews[i];
        
        CGFloat row = i / totalCol;
        CGFloat col = i % totalCol;
        btnX = margin + (margin + btnW) * col;
        btnY = (margin + btnH) * row;
        btn.frame = CGRectMake(btnX, btnY, btnW, btnH);
    }
}

#pragma mark - 触摸屏幕,相应的按钮响应
// 通过给定触摸点判断该点在不在所有按钮上,在的话则选中该按钮
- (void)pointInButtonWithTouches:(NSSet *)touches{
    // 1,获取触摸对象
    UITouch *touch = [touches anyObject];
    // 2,获取触摸点
    CGPoint point = [touch locationInView:self];
    _curPoint = point;
    // 3,遍历所有按钮,判断点是否在该按钮上
    for (UIButton *btn in self.subviews) {
        // 判断点是否在按钮上的区域缩小
        CGFloat wh = 30;
        CGFloat x = btn.center.x - wh * 0.5;
        CGFloat y = btn.center.y - wh * 0.5;
        CGRect frame = CGRectMake(x, y, wh, wh);
        // 判断点在不在CGRect上
        // 判断的时候要保证它们在一个坐标系上,btn.frame和point在一个坐标系上,所以这里就不能用btn.bounds
        if (CGRectContainsPoint(frame, point) && btn.selected == NO) {// 点在按钮上,并且按钮没被选中
            btn.selected = YES;  // 选中按钮
            [self.selectedBtns addObject:btn];  // 添加选中按钮
        }
    }
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
    // “选中”触摸点所在的按钮
    [self pointInButtonWithTouches:touches];
}
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
    // “选中”触摸点所在的按钮
    [self pointInButtonWithTouches:touches];
    
    // 重绘
    [self setNeedsDisplay];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event{
    // 1,拼接密码
    NSMutableString *pwd = [[NSMutableString alloc] init];
    for (UIButton *btn in self.selectedBtns) {
        [pwd appendFormat:@"%ld", (long)btn.tag];
    }
    NSLog(@"%@", pwd);
    
    // 2,取消所有选中按钮的“选中”
    // 这个方法是让数组的每个元素都调用@selector的方法,并且还可以传个参数,这里传@NO这个参数
    // 就是把按钮中所有的按钮的“选中”都取消了
    [self.selectedBtns makeObjectsPerformSelector:@selector(setSelected:) withObject:@NO];
    
    // 3,清除画线,即:把“选中按钮”数组清空
    [self.selectedBtns removeAllObjects];
    
    // 4,重绘
    [self setNeedsDisplay];
}

// 画线
- (void)drawRect:(CGRect)rect {
    if(!self.selectedBtns.count) return;
    
    // 创建路径
    UIBezierPath *path = [UIBezierPath bezierPath];
    
    // 把所有选中的按钮之间都连线
    for (int i = 0; i < self.selectedBtns.count; i++) {
        UIButton *btn = self.selectedBtns[i];
        if (i == 0) { // 起点
            [path moveToPoint:btn.center];
        }else{ // 其它点
            [path addLineToPoint:btn.center];
        }
    }
    
    // 最后一个选中按钮与当前触摸点之间连线
    [path addLineToPoint:_curPoint];
    
    // 设置状态信息
    [[UIColor greenColor] set];  // 颜色
    path.lineWidth = 8;  // 路径宽度
    path.lineJoinStyle = kCGLineJoinRound;  // 圆角
    
    // 渲染到视图
    // 之前画线还需要获取上下文,这里直接用这个方法,把那些东西都封装在这个方法里了
    // 用之前的画线方法也行(获取上下文的方法)
    [path stroke];
}
@end

5>画板

    演示程序:

    分析控件:ToolBar(使用它的好处:不需要管里面子控件的frame),画板view,自定义工具条(方便屏幕适配,能迅速固定里面子控件的位置)

    自动布局

    绘图思路:先描述路径,在渲染。

    绘图功能:

        * touchBegin设置画线起点:开始触摸的点

        * 怎么设置起点?创建UIBezierPath,贝塞尔路径才能设置起点

        * touchMove:手指移动到哪就画哪,addLine到移动的点

        * setNeedDisplay,路径描述完了,就渲染到视图就好了。

        * 只能画一条线?drawRect方法每次都会把之前的清掉,重新绘制

        * 怎么绘制多条路径?搞个数组保存上一次的

        * 设置线宽:每次滑动滑块,就改变下一次路径的宽度

        * 监听滑块的值,把值传递给paintView,设置路径的线宽。

        * 不能在drawRect写,会导致所有路径都是一个线宽,应该是每个路径都记录自己的线宽,而且线宽只需要设置一次,在路径一创建的时候就设置。

        * 设置颜色:

        * 自定义UIBezierPath,保存颜色,实现一条路径对应一个颜色

    辅助功能:

    * 清屏:清空所有路径数组

    * 撤销:移除最后一条路径

    * 橡皮擦:设置画笔为白色

    * 保存:

        1>把画板内容截屏

        2>把图片保存到相册 UIImageWriteToSavedPhotosAlbum

        3>保存相册的回调方法不能乱写,必须按照规定 image:didFinishSavingWithError:contextInfo:

    * 照片选择

        1>通常都是去相册里去照片

        2> UIImagePickerController,就可以去手机相册了

        3>Modal,没有导航控制器,不能push

        4>设置代理,获取图片

        5>把图片传递给paintView

        6>添加到路径,然后重绘。:画图片也需要顺序的

    * 照片处理

        1> PPT画思路:搞一个和画板一样的透明view,里面搞个UIImageView来显示我们从照片库选择的图片,然后对UIImageView进行旋转,缩放等操作

        2> UImageView不能放在layoutSubViews里面设置尺寸,因为要设置他的形变,默认会调用他父类的layoutSubViews,导致一些莫名其妙的原因

        3>在传图片的时候设置他的尺寸,和位置,让他和图片一样的尺寸,显示在中间

        4>长按操作:在长按结束的时候,做操作

            1.默认高亮状态,先变浅在恢复,设置alpha

            2.动画结束后,把自己截屏,传给控制器里,在交给paintView显示

            3.需要移除父视图,使命完成了,而且不移除,不能绘制东西,永远添加到paintView上面

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

@interface HMPaintView : UIView
@property (nonatomic, assign) CGFloat width;  // 路径宽
@property (nonatomic, strong) UIColor *color; // 路径颜色
// 接受由相册中选择的图片,只是中间的一步,后来运用HMHandleImageView后,此属性便失去作用
@property (nonatomic, strong) UIImage *image;
// 清屏
- (void)clearScreen;
// 撤销
- (void)undo;
@end
//  HMPaintView.m
#import "HMPaintView.h"
#import "HMPaintPath.h"

@interface HMPaintView ()
@property (nonatomic, strong) UIBezierPath *path;
@property (nonatomic, strong) NSMutableArray *paths;
@end

@implementation HMPaintView
#pragma mark - 初始化线宽
- (void)awakeFromNib
{
    _width = 2;
}
#pragma mark - 懒加载路径数组
- (NSMutableArray *)paths
{
    if (_paths == nil) {
        _paths = [NSMutableArray array];
    }
    return _paths;
}

#pragma mark - 确定起点
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 获取触摸点
    CGPoint pos = [self pointWithTouches:touches];
    
    // 创建路径
    HMPaintPath *path = [HMPaintPath paintPathWithLineWidth:_width color:_color startPoint:pos];
    
    _path = path;
    [self.paths addObject:path];
}
// 获取触摸点
- (CGPoint)pointWithTouches:(NSSet *)touches
{
    UITouch *touch = [touches anyObject];
    
    return [touch locationInView:self];
}
#pragma mark - 确定路径终点
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{
    // 获取触摸点
    CGPoint pos = [self pointWithTouches:touches];
    // 确定终点
    [_path addLineToPoint:pos];
    // 重绘
    [self setNeedsDisplay];
}

#pragma mark - 绘制路径
- (void)drawRect:(CGRect)rect
{
    // 如果路径数组为空则返回,比如清屏之后
    if (!self.paths.count) return;
    
    // 遍历路径数组中的所有的路径
    for (HMPaintPath *path in self.paths) {
        // 判断路径数组元素是否为image,因为image和线段画法不同
        if ([path isKindOfClass:[UIImage class]]) { // UIImage
            UIImage *image = (UIImage *)path;
            [image drawAtPoint:CGPointZero];
        }else{ // HMPaintPath
            [path.color set];
            [path stroke];
        }
    }
}

#pragma mark - 清屏
- (void)clearScreen
{
    // 清空路径数组
    [self.paths removeAllObjects];
    // 重绘
    [self setNeedsDisplay];
}

#pragma mark - 撤销
- (void)undo
{
    // 移除最后一次画的路径
    [self.paths removeLastObject];
    // 重绘
    [self setNeedsDisplay];
}

#pragma mark - 设置图片时,就把图片画在画板上
- (void)setImage:(UIImage *)image
{
    _image = image;
    
    [self.paths addObject:image];
    
    [self setNeedsDisplay];
}

@end

//  HMViewController.m
#import "HMViewController.h"
#import "MBProgressHUD+MJ.h"
#import "HMPaintView.h"
#import "HMHandleImageView.h"

@interface HMViewController ()<UINavigationControllerDelegate,UIImagePickerControllerDelegate>
@property (weak, nonatomic) IBOutlet HMPaintView *paintView;
@end

@implementation HMViewController
#pragma mark - 保存
- (IBAction)save:(id)sender {// 把画板截屏
    // 1,开启上下文
    UIGraphicsBeginImageContextWithOptions(_paintView.bounds.size, NO, 0.0);
    // 2,获取当前上下文
    CGContextRef ctx = UIGraphicsGetCurrentContext();
    // 3,把画板上的内容渲染到上下文
    [_paintView.layer renderInContext:ctx];
    // 4,获取新的图片
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    // 5,关闭上下文
    UIGraphicsEndImageContext();
    
    // 6,把图片保存到用户的相册里面
    // self为回调方法的调用者,@selector后的方法是不能随便调用的,是有规定的:必须调用image:didFinishSavingWithError:contextInfo:
    // 这个方法可以在UIImageWriteToSavedPhotosAlbum()头文件中找到
    UIImageWriteToSavedPhotosAlbum(newImage, self, @selector(image:didFinishSavingWithError:contextInfo:), nil);
}
// 保存相册后必须回调此方法
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
    if (error) { // 保存失败
        [MBProgressHUD showError:@"保存失败"];
    }else{ // 保存成功
        [MBProgressHUD showSuccess:@"保存成功"];
    }
}

#pragma mark - 选择照片
- (IBAction)selectedPicture:(id)sender {
    // 去用户的相册
    // 照片选择器
    // 去用户的相册必须用这个控制器(保护用户的信息)
    // 通过图像选择器使用摄像头和照片库
    UIImagePickerController *picker = [[UIImagePickerController alloc] init];
    // 图像选择器会将一系列图片或者视频作为他的源。此外,还可指定摄像头作为源
    // 数据源:默认是UIImagePickerControllerSourceTypePhotoLibrary
    // UIImagePickerControllerSourceTypePhotoLibrary,指定用户将从现有的媒体库重选取照片或视频。照片将被返回到委托
    // UIImagePickerControllerSourceTypeCamera,让用户使用内置摄像头拍照或者录像。但是,在使用之前最好用if([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]){}判断一下设备是否有内置摄像头,如果所指定的源当前可用,方法isSourceTypeAvailabel:将返回YES。
    // UIImagePickerControllerSourceTypeSavedPhotosAlbum,指定用户将从现有照片库重选择照片,蛋选择范围仅限于最近实用的相册。此选项也可以在没有摄像头的设备上运行,虽然用处不大,但是扔可用来选取之前的屏幕快照。
    picker.sourceType =  UIImagePickerControllerSourceTypeSavedPhotosAlbum;
    
    // 设置delegate
    // 如果用户没有按取消,那么用户拍摄的或从库重选择的图像或视频会被传送到委托。无论用户选择了一个媒体文件还是取消了,委托都有责任解除UIImagePickerController,让用户返回到应用。
    picker.delegate = self;
    
    // 展示照片选择器(类型,push/pop等,把控制器展示出来)
    // 方法并不局限于呈现图像选取器;通过对当前可见视图的视图控制器调用此方法,可以将任何视图控制器以模态(Modal)的方式呈现给用户。
    [self presentViewController:picker animated:YES completion:nil];
}

// 代理方法:当用户成功拍摄了照片或视频,或从照片库中选择了相应项之后,调用此方法
// 第一个参数是之前创建的UIImagePickerController的指针
/*
 info:{
 UIImagePickerControllerMediaType = "public.image";
 UIImagePickerControllerOriginalImage = "<UIImage: 0x7ff50c86e990> size {1008, 669} orientation 0 scale 1.000000";
 UIImagePickerControllerReferenceURL = "assets-library://asset/asset.JPG?id=2A456415-B1AE-424A-9795-A0625A768EBD&ext=JPG";
 }
 */
// 第二个参数包含三个键值,1,如果需要在图像选择器重编辑(并且用户确实对图像或视频进行了编辑),那么包含可选的编辑信息;2.存储在键值UIImagePickerControllerOriginalImage下是,未编辑的原始图像;3.用户所选照片或者当前所选视频的URL;

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{NSLog(@"dict-------info:%@", info);
    
    // 1,获取选中图片
    // 可以跳进UIImagePickerControllerOriginalImage的头文件查看其它info的键值信息
    UIImage *newImage = info[UIImagePickerControllerOriginalImage];
    
    // 2,创建处理图片的view
    HMHandleImageView *handleView = [[HMHandleImageView alloc] initWithFrame:self.paintView.frame];
    
    // 4,定义block:相当于自己的小弟,到时候直接吩咐做事
    // 给block赋值
    handleView.block = ^(UIImage *image){
        
        _paintView.image = image;
    };
    
    // 3,将选择图片传给HMHandleImageView
    handleView.image = newImage;
    
    // 5,添加到子视图
    [self.view addSubview:handleView];
    
    // 6,取消Modal
    [self dismissViewControllerAnimated:YES completion:nil];
}

#pragma mark 橡皮擦
- (IBAction)eraser:(id)sender {
    _paintView.color = [UIColor whiteColor];
}

#pragma mark 撤销
- (IBAction)undo:(id)sender {
    [_paintView undo];
}

#pragma mark 清屏
- (IBAction)clearScreen:(id)sender {
    [_paintView clearScreen];
}

#pragma mark 颜色选择
- (IBAction)colorClick:(UIButton *)sender {
    // 颜色传递给画板View
    _paintView.color = sender.backgroundColor;
}

#pragma mark 监听滑块的值
- (IBAction)valueChange:(UISlider *)sender {
    // 将变化的值传递给画板View
    _paintView.width = sender.value;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
	// Do any additional setup after loading the view, typically from a nib.
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end

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

@interface HMPaintPath : UIBezierPath
@property (nonatomic, strong) UIColor *color;
+ (instancetype)paintPathWithLineWidth:(CGFloat)width color:(UIColor *)color startPoint:(CGPoint)startP;
@end
//  HMPaintPath.m
#import "HMPaintPath.h"

@implementation HMPaintPath
+ (instancetype)paintPathWithLineWidth:(CGFloat)width color:(UIColor *)color startPoint:(CGPoint)startP
{
    HMPaintPath *path = [[self alloc] init];
    path.lineWidth = width;
    path.color = color;
    [path moveToPoint:startP];
    
    return path;
}
@end

//  HMHandleImageView.h
#import <UIKit/UIKit.h>
// 1,定义block
typedef void(^HMHandleImageViewBlock)(UIImage *image);

@interface HMHandleImageView : UIView
@property (nonatomic, strong) UIImage *image;
// 2,定义属性block
@property (nonatomic, copy) HMHandleImageViewBlock block;
@end
//  HMHandleImageView.m
#import "HMHandleImageView.h"
#import "UIImage+Tool.h"

@interface HMHandleImageView()<UIGestureRecognizerDelegate>
@property (nonatomic, weak) UIImageView *imageView;
@end

@implementation HMHandleImageView

- (id)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        
        // 1,添加UIImageView
        [self addImageView];
        // 2,添加手势
        [self addGestureRecognizers];
    }
    return self;
}
#pragma mark 添加图片
- (void)addImageView
{
    UIImageView *imageView = [[UIImageView alloc] initWithFrame:self.bounds];
    
    imageView.userInteractionEnabled = YES;
    
    _imageView = imageView;
    [self addSubview:imageView];
}
#pragma mark 传给一个图片就展示到UIImageView上
- (void)setImage:(UIImage *)image
{
    _image = image;
    
    _imageView.image = image;
}

#pragma mark - 添加手势
- (void)addGestureRecognizers
{
    // 1.长按
    UILongPressGestureRecognizer *longPress = [[UILongPressGestureRecognizer alloc] initWithTarget:self action:@selector(longPress:)];
    [_imageView addGestureRecognizer:longPress];
    
    // 2,捏合
    [self addPinch];
    // 3,旋转
    [self addRotation];
}

#pragma mark 长按
- (void)longPress:(UILongPressGestureRecognizer *)longPrss
{
    // 长按手势会触发两次,所以先判断一下是哪次触发
    if (longPrss.state == UIGestureRecognizerStateBegan) {
        
        [UIView animateWithDuration:0.5 animations:^{
            
            _imageView.alpha = 0.3;
        } completion:^(BOOL finished) {
            
            [UIView animateWithDuration:0.5 animations:^{
                _imageView.alpha = 1;
            } completion:^(BOOL finished) {
                
                // 1.截屏
                UIImage *newImage = [UIImage imageWithCaptureView:self];
                
                // 2.把图片传给控制器
                // 调用block
                _block(newImage);
            
                // 3.把自己移除父控件(不移除则它一直蒙在画板上,进而在画板中无法继续画东西)
                [self removeFromSuperview];
            }];
        }];
    }
}

#pragma mark - 捏合
- (void)addPinch
{
    UIPinchGestureRecognizer *pinch = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(pinch:)];
    // 设置代理的原因:想要同时支持多个手势
    pinch.delegate = self;
    [_imageView addGestureRecognizer:pinch];
    
}
- (void)pinch:(UIPinchGestureRecognizer *)pinch
{
    _imageView.transform = CGAffineTransformScale(_imageView.transform, pinch.scale, pinch.scale);
    
    // 复位
    pinch.scale = 1;
}
// Simultaneous:同时
// 默认是不支持多个手势
// 当你使用一个手势的时候就会调用
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
    return YES;
}

#pragma mark - 旋转
- (void)addRotation
{
    // rotation
    UIRotationGestureRecognizer *rotation = [[UIRotationGestureRecognizer alloc] initWithTarget:self action:@selector(rotation:)];
    rotation.delegate = self;
    [_imageView addGestureRecognizer:rotation];
}
- (void)rotation:(UIRotationGestureRecognizer *)rotation
{
    //    _imagView.transform = CGAffineTransformMakeRotation(rotation.rotation);
    _imageView.transform = CGAffineTransformRotate(_imageView.transform, rotation.rotation);
    
    // 复位
    rotation.rotation = 0;
}

@end

           





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值