当前位置: 移动技术网 > IT编程>移动开发>IOS > iOS 裁剪工具

iOS 裁剪工具

2019年12月03日  | 移动技术网IT编程  | 我要评论

范若思,野火宝贝搞定你,tokyo hot n0461

下载

demo和工具下载链接spcliptool

使用说明

[[spcliptool sharecliptool] sp_cliporiginimage:pickerimage complete:^(uiimage * _nonnull image) {
    // 获取到裁剪后的image 后续操作
}];

需求

图片裁剪,效果如下图,支持图片拖拽,缩放,裁剪框自由变换大小。

思路

两个uiimageview,一个做背景,并加上蒙版效果,另外一个通过蒙版控制显示区域,并且保证两个uiimageview平移和缩放的时候完全重叠。最后使用一个uiview来做交互,绘制三分网格线(专业术语我不知道叫啥,截图时一个参照,2/3 ≈0.667 接近黄金比0.618)。

注意

  • 坐标系转换问题。
  • mask灵活使用问题。
  • 手势的处理和三分网格线绘制的时候,计算线条宽度和长度需要特别注意。

  • 为了增强用户体验,在裁剪框边缘交互设计的时候,注意额外增加用户的可操控范围。

实现

  • 初始化两个uiimageview,一个做背景图(backgroudimageview),一个用来显示裁剪区域(clipimageview),拖拽手势加到了clipimageview。
- (void)setupimageview {
    // backgroudimageview 
    uiimageview *backgroudimageview = [[uiimageview alloc] initwithframe:self.view.bounds];
    backgroudimageview.contentmode = uiviewcontentmodescaleaspectfit;
    backgroudimageview.image = self.originimage;
    [self.view addsubview:backgroudimageview];
    self.backgroudimageview = backgroudimageview;
    backgroudimageview.layer.mask = [[calayer alloc] init];
    backgroudimageview.layer.mask.frame = backgroudimageview.bounds;
    backgroudimageview.layer.mask.backgroundcolor = [uicolor colorwithwhite:1 alpha:0.5].cgcolor;
    
    // clipimageview
    uiimageview *clipimageview = [[uiimageview alloc] initwithframe:backgroudimageview.frame];
    clipimageview.userinteractionenabled = yes;
    clipimageview.image = backgroudimageview.image;
    clipimageview.contentmode = backgroudimageview.contentmode;
    [self.view addsubview:clipimageview];
    self.clipimageview = clipimageview;
    clipimageview.layer.mask = [[calayer alloc] init];
    clipimageview.layer.mask.backgroundcolor = [uicolor whitecolor].cgcolor;
    clipimageview.layer.mask.bordercolor = [uicolor whitecolor].cgcolor;
    clipimageview.layer.mask.borderwidth = 1;
    [clipimageview.layer.mask removeallanimations];
    
    uipangesturerecognizer *pangesture = [[uipangesturerecognizer alloc] initwithtarget:self action:@selector(imagepan:)];
    [clipimageview addgesturerecognizer:pangesture];
}
  • 初始化用于裁剪交互的spclipview
- (void)setupclipview {
    spclipview *clipview = [[spclipview alloc] init];
    clipview.backgroundcolor = [uicolor clearcolor];
    // 打开下面两行注释,可以查看真实clipview的大小。
//    clipview.layer.bordercolor = [uicolor whitecolor].cgcolor;
//    clipview.layer.borderwidth = 1;
    [self.view addsubview:clipview];
    self.clipview = clipview;
    // 获取真实frame
    dispatch_after(dispatch_time(dispatch_time_now, (int64_t)(0.1 * nsec_per_sec)), dispatch_get_main_queue(), ^{
        clipview.frame = cgrectmake(0, 0, self.view.width / 1.5, self.view.height / 1.5);
          clipview.center = self.view.center;
        self.backgroudimageview.frame = self.view.bounds;
        self.clipimageview.frame = self.backgroudimageview.frame;
        [self dealmask];
    });

    uipangesturerecognizer *pangesture = [[uipangesturerecognizer alloc] initwithtarget:self action:@selector(clippan:)];
    [clipview addgesturerecognizer:pangesture];
    
    uipinchgesturerecognizer *pinchgesture = [[uipinchgesturerecognizer alloc] initwithtarget:self action:@selector(pinchgestureaction:)];
    [self.view addgesturerecognizer:pinchgesture];
}
  • 手势处理
#pragma mark- uipangesturerecognizer
- (void)clippan:(uipangesturerecognizer *)pangesture {
    cgpoint point = [pangesture translationinview:self.clipview];
    self.clipview.origin = [self.clipview convertpoint:point toview:self.view];
    [self expandclipview:pangesture];
    [self dealguideline:pangesture];
    [self dealmask];
    [pangesture settranslation:cgpointmake(0, 0) inview:self.view];
}

- (void)imagepan:(uipangesturerecognizer *)pangesture {
    cgpoint point = [pangesture translationinview:self.clipimageview];
    self.clipimageview.origin = [self.clipimageview convertpoint:point toview:self.view];
    self.backgroudimageview.center = self.clipimageview.center;
    [self dealguideline:pangesture];
    [self dealmask];
    [pangesture settranslation:cgpointmake(0, 0) inview:self.view];
}

#pragma mark- uipinchgesturerecognizer
- (void)pinchgestureaction:(uipinchgesturerecognizer *)pinchgesture {
    switch (pinchgesture.state) {
        case uigesturerecognizerstatebegan: {
            if (lastscale <= minscale) {
                lastscale = minscale;
            }else if (lastscale >= maxscale) {
                lastscale = maxscale;
            }
            self.clipimageviewcenter = self.clipimageview.center;
            self.clipview.showguideline = yes;
        }
        case uigesturerecognizerstatechanged: {
            cgfloat currentscale = lastscale + pinchgesture.scale - 1;
            if (currentscale > minscale && currentscale < maxscale) {
                [self dealviewscale:currentscale];
            }
        }
            break;
        case uigesturerecognizerstateended:
            lastscale += (pinchgesture.scale - 1);
            self.clipview.showguideline = no;
            [self.clipview setneedsdisplay];
        default:
            break;
    }
}

#pragma mark- action
- (void)dealviewscale:(cgfloat)currentscale {
    self.clipimageview.width = currentscale * self.view.width;
    self.clipimageview.height = currentscale * self.view.height;
    self.clipimageview.center = self.clipimageviewcenter;
    self.backgroudimageview.frame = self.clipimageview.frame;
    self.backgroudimageview.layer.mask.frame = self.backgroudimageview.bounds;
    [self.backgroudimageview.layer.mask removeallanimations];
    [self dealmask];
}

- (void)expandclipview:(uipangesturerecognizer *)pangesture {
    cgpoint point = [pangesture translationinview:self.clipimageview];
    cgfloat margin = 60;
    cgfloat minvalue = margin;
    if (pangesture.numberoftouches) {
        cgpoint location = [pangesture locationoftouch:0 inview:pangesture.view];
        if (location.x < margin) {
            self.clipview.width = max(self.clipview.width -= point.x, minvalue);
        }
        if ((self.clipview.width - location.x) < margin) {
            self.clipview.frame = cgrectmake(self.clipview.x - point.x, self.clipview.y, self.clipview.width + point.x, self.clipview.height);
        }
        if (location.y < margin) {
            self.clipview.height = max(self.clipview.height -= point.y, minvalue);
        }
        if ((self.clipview.height - location.y) < margin) {
            self.clipview.frame = cgrectmake(self.clipview.x , self.clipview.y - point.y, self.clipview.width, self.clipview.height + point.y);
        }
    }
}

- (void)dealguideline:(uipangesturerecognizer *)pangesture  {
    switch (pangesture.state) {
        case uigesturerecognizerstatebegan:
            self.clipview.showguideline = yes;
            break;
        case uigesturerecognizerstateended:
            self.clipview.showguideline = no;
            break;
        default:
            break;
    }
}

- (void)dealmask {
    // 额外增加拖拉区域 增强边缘手势体验
    cgfloat margin = 30;
    cgrect rect = [self.view convertrect:self.clipview.frame toview:self.clipimageview];
    self.clipimageview.layer.mask.frame = cgrectmake(rect.origin.x + margin, rect.origin.y + margin, rect.size.width - 2 * margin, rect.size.height - 2 * margin);
    [self.clipview setneedsdisplay];
    [self.clipimageview.layer.mask removeallanimations];
}
  • 图片裁剪
- (void)clipimage {
    
    cgsize size = self.view.bounds.size;
    uigraphicsbeginimagecontextwithoptions(size, no, [uiscreen mainscreen].scale);
    [self.view drawviewhierarchyinrect:self.view.bounds afterscreenupdates:no];
    uiimage *image = uigraphicsgetimagefromcurrentimagecontext();
    cgimageref cgimage = [image cgimage];
    cgrect rect = [self.clipimageview convertrect:self.clipimageview.layer.mask.frame toview:self.view];
    
    // 边框线条宽度值
    cgfloat borderw = 1;
    cgimageref cgclipimage = cgimagecreatewithimageinrect(cgimage, cgrectmake((rect.origin.x + borderw / 2) * image.scale, (rect.origin.y + borderw / 2) * image.scale, (rect.size.width - borderw) * image.scale, (rect.size.height - borderw) * image.scale));
    uigraphicsendimagecontext();
    if (self.complete) {
        self.complete([uiimage imagewithcgimage:cgclipimage]);

    }
    [self dismissviewcontrolleranimated:yes completion:nil];
}
  • 裁剪区域绘制

    在这里,裁剪区域的矩形框我并没有直接采用clipview的fram大小,而是在其内部绘制了一个矩形框,为了让用户在调节边缘的时候更灵活,不然只有当手指在边框内部边缘才能触发调节边框大小的事件。如下图,可以看到clipview真实的大小(外框)。

@implementation spclipview

- (void)drawrect:(cgrect)rect {
    // drawing code
    cgcontextref currentcontext = uigraphicsgetcurrentcontext();
    cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor whitecolor].cgcolor);
    cgcontextsetlinewidth(currentcontext, 1);
    // 额外增加拖拉区域 增强边缘手势体验,该值应该和上文- (void)dealmask;方法中的margin一致
    cgfloat margin = 30;
  
    // 绘制矩形框
    cgcontextaddrect(currentcontext, cgrectmake(margin, margin, self.width - 2 * margin, self.height - 2 * margin));
    cgcontextstrokepath(currentcontext);
    
    // 绘制三分线
    cgfloat maskw = self.width - 2 * margin;
    cgfloat maskh = self.height - 2 * margin;
    cgcontextsetlinewidth(currentcontext, 0.5);
    if (self.showguideline) {
        cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor whitecolor].cgcolor);
    }else {
        cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor clearcolor].cgcolor);
    }
    cgcontextmovetopoint(currentcontext, margin, maskh / 3 + margin);
    cgcontextaddlinetopoint(currentcontext, self.width - margin, maskh / 3 + margin);
    cgcontextmovetopoint(currentcontext, margin, 2 / 3.0 * maskh + margin);
    cgcontextaddlinetopoint(currentcontext, self.width - margin, 2 / 3.0 * maskh + margin);
    
    cgcontextmovetopoint(currentcontext, maskw / 3 + margin, margin);
    cgcontextaddlinetopoint(currentcontext, maskw  / 3+ margin, self.height - margin);
    cgcontextmovetopoint(currentcontext, 2 / 3.0 * maskw + margin, margin);
    cgcontextaddlinetopoint(currentcontext, 2 / 3.0 * maskw + margin, self.height - margin);
    
    cgcontextstrokepath(currentcontext);
    
    // 绘制四角
    cgfloat cornerl = 15;
    cgfloat cornerlw = 2;
    // 实际的长度
    cgfloat cornerrl = cornerl + cornerlw;
    cgpoint originh = cgpointmake(margin - cornerlw, margin - cornerlw / 2);
    cgpoint originv = cgpointmake(margin - cornerlw / 2, margin - cornerlw);
    cgcontextsetstrokecolorwithcolor(currentcontext, [uicolor whitecolor].cgcolor);
    cgcontextsetlinewidth(currentcontext, cornerlw);
    
    // 左上
    cgcontextmovetopoint(currentcontext, originh.x, originh.y);
    cgcontextaddlinetopoint(currentcontext, originh.x + cornerrl, originh.y);
    cgcontextmovetopoint(currentcontext, originv.x, originv.y);
    cgcontextaddlinetopoint(currentcontext, originv.x, originv.y + cornerrl);
    
    // 左下
    cgcontextmovetopoint(currentcontext, originh.x, originh.y + maskh + cornerlw);
    cgcontextaddlinetopoint(currentcontext, originh.x + cornerrl, originh.y + maskh + cornerlw);
    cgcontextmovetopoint(currentcontext, originv.x, originv.y + maskh + 2 * cornerlw);
    cgcontextaddlinetopoint(currentcontext, originv.x, originv.y + maskh + 2 * cornerlw - cornerrl);
    
    // 右上
    cgcontextmovetopoint(currentcontext, originh.x + maskw + 2 * cornerlw, originh.y);
    cgcontextaddlinetopoint(currentcontext, originh.x + maskw + 2 * cornerlw - cornerrl, originh.y);
    cgcontextmovetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y);
    cgcontextaddlinetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y + cornerrl);
    
    // 右下
    cgcontextmovetopoint(currentcontext, originh.x + maskw + 2 * cornerlw, originh.y + maskh + cornerlw);
    cgcontextaddlinetopoint(currentcontext, originh.x + maskw + 2 * cornerlw - cornerrl, originh.y + maskh + cornerlw);
    cgcontextmovetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y + maskh + 2 * cornerlw);
    cgcontextaddlinetopoint(currentcontext, originv.x + maskw + cornerlw, originv.y + maskh + 2 * cornerlw - cornerrl);

    cgcontextstrokepath(currentcontext);
}

这里一定要注意线条的宽度,线条是有宽度的,绘制路径位于线条的中心位置。

如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复

相关文章:

验证码:
移动技术网