用iOS来实现一个手动控制扫描范围的扫描框

我们先来看需求
我们的需求是,通过调用相机,获取图像资源,根据手势控制截取范围,获取图片
让我们一步一步来实现吧

1658478252099516.gif

话不多说,直接上代码

引入相机需要的库
#import <AVFoundation/AVFoundation.h>
一些必要的属性
@property (nonatomic, strong) AVCaptureDevice *device;
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureDeviceInput *input;
@property (nonatomic, strong) AVCaptureMetadataOutput *output;
@property (nonatomic, strong) AVCapturePhotoOutput *ImageOutPut;
初始化相机
- (void)initCamera {
    self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    self.input = [[AVCaptureDeviceInput alloc]initWithDevice:self.device error:nil];
    self.output = [[AVCaptureMetadataOutput alloc] init];
    self.ImageOutPut = [[AVCapturePhotoOutput alloc] init];
    self.session = [[AVCaptureSession alloc]init];
    self.session.sessionPreset = AVCaptureSessionPreset1920x1080;
    if ([self.session canSetSessionPreset:AVCaptureSessionPresetInputPriority]) {
        self.session.sessionPreset = AVCaptureSessionPresetInputPriority;
    }
    if([self.sessioncanAddInput:self.input]) {
        [self.sessionaddInput:self.input];
    }
    if ([self.session canAddOutput:self.ImageOutPut]) {
        [self.session addOutput:self.ImageOutPut];
    }
    self.previewLayer = [[AVCaptureVideoPreviewLayer alloc]initWithSession:self.session];
    self.previewLayer.frame = CGRectMake(0, 0, ScreenWidth, ScreenHeight - k_bottomControlHeight);
    self.previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    [self.view.layer addSublayer:self.previewLayer];
    self.frameView = [[ARTRecPicFrameView alloc] initWithFrame:self.previewLayer.frame];
    [self.view addSubview:self.frameView];
    self.frameView.frame = self.previewLayer.frame;
    if ([_device lockForConfiguration:nil]) {
        //自动白平衡
        if ([_device isWhiteBalanceModeSupported:AVCaptureWhiteBalanceModeAutoWhiteBalance]) {
            [_device setWhiteBalanceMode:AVCaptureWhiteBalanceModeAutoWhiteBalance];
        }
        [_device unlockForConfiguration];
    }
}
开始相机的图像采集
-(void)sessionStartRunning{
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        [self.session startRunning];
    });
}

当然相机权限的判断可以自己写一下啊,网上一大堆,这里不再贴代码。

采集图像的硬件初始化了,接下来我们要去实现一下这个覆盖层
我命名为

@property (nonatomic, strong) RecPicFrameView * frameView;

这个view的会覆盖在相机采集区域的上方,并且根据手势调整采集区域大小,输出采集区域的frame

触点的枚举
解释一下,采集框有八个点,手指触碰到这八个点,计算方式有所不同,因此区分开来,根据实际触碰的点来进行相应的采集框frame调整
typedef NS_ENUM(NSUInteger, DotRegion) {
    DR_Center,
    DR_LeftTop,
    DR_LeftMid,
    DR_LeftBottom,
    DR_RightTop,
    DR_RightMid,
    DR_RightBottom,
    DR_TopMid,
    DR_BottomMid
};
@interface ARTRecPicFrameView : UIView
@property (nonatomic, assign) CGRect resizerFrame;

@property (nonatomic, assign) BOOL pointInside;
@property (nonatomic, assign) ARTDotRegion currDR;
@property (nonatomic, assign) CGRect contentFrame;
@property (nonatomic, assign) CGFloat maxResizeX;
@property (nonatomic, assign) CGFloat maxResizeY;
@property (nonatomic, assign) CGFloat minImageWH;
@property (nonatomic, weak) UIPanGestureRecognizer *panGR;
@property (nonatomic, weak) ARTFrameResizerBlurView *blurView;
@property (nonatomic, weak) CAShapeLayer *maskLayer;
// 线框图层
@property (nonatomic, weak) CAShapeLayer *frameLayer;
// 左右上中下点图层
@property (nonatomic, weak) CAShapeLayer *dotsLayer;
@end
交互、计算、绘制的代码如下
- (instancetype)initWithFrame:(CGRect)frame {
    self = [super initWithFrame:frame];
        if (self) {
            self.contentFrame = frame;
            self.maxResizeX = frame.origin.x*2 + frame.size.width;
            self.maxResizeY = frame.origin.y*2 + frame.size.height;
            self.minImageWH = 70.0f;
            _scopeWH = 50.0;
            _halfScopeWH = _scopeWH * 0.5;
            [self layoutWithFrame:frame];
        }
    return self;
}

- (void)layoutSubviews {
    [super layoutSubviews];
    
    CGRect frame = self.frame;
    self.contentFrame = frame;
    NSLog(@"x=%.2f y=%.2f",self.frame.size.width,self.frame.size.height);
    
    self.maxResizeX = frame.origin.x*2 + frame.size.width;
    self.maxResizeY = frame.origin.y*2 + frame.size.height;
    self.minImageWH = 70.0f;
    [self layoutWithFrame:frame];
}

- (void)layoutWithFrame:(CGRect)frame {
    CGFloat H_distance = 60;
    CGFloat V_distance = 80;
    CGRect initialFrame = CGRectMake(H_distance, V_distance, frame.size.width-2*H_distance, frame.size.height-2*V_distance);
    
    self.resizerFrame = initialFrame;
    
    for (ARTFrameResizerBlurView *blur in self.subviews) {
        [blur removeFromSuperview];
    }
    ARTFrameResizerBlurView *blurView = [[ARTFrameResizerBlurView alloc] initWithFrame:frame blurEffect:nil bgColor:UIColor.blackColor maskAlpha:0.5];
    blurView.userInteractionEnabled = NO;
    [self addSubview:blurView];
    self.blurView = blurView;
    
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = blurView.bounds;
    maskLayer.fillColor = [UIColor blackColor].CGColor;
    maskLayer.fillRule = kCAFillRuleEvenOdd;
    blurView.layer.mask = maskLayer;
    self.maskLayer = maskLayer;
    
    self.frameLayer.lineWidth = 3.0f;
    self.frameLayer.opacity = 1;
    UIBezierPath *framePath = [UIBezierPath bezierPathWithRoundedRect:initialFrame cornerRadius:2];
    self.frameLayer.path = framePath.CGPath;

    self.dotsLayer.lineWidth = 3.0f;
    self.dotsLayer.fillColor = UIColor.clearColor.CGColor;
    self.dotsLayer.strokeColor = UIColor.whiteColor.CGColor;
    self.dotsLayer.opacity = 1;
    UIBezierPath *dotsPath = [self __classicDotsPathWithFrame:initialFrame];
    self.dotsLayer.path = dotsPath.CGPath;

    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.blurView.bounds];
    [maskPath appendPath:framePath];
    self.maskLayer.path = maskPath.CGPath;
    
    UIPanGestureRecognizer *panGR = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(panHandle:)];
    [self addGestureRecognizer:panGR];
    self.panGR = panGR;
}

- (void)panHandle:(UIPanGestureRecognizer *)panGR {
    switch (panGR.state) {
        case UIGestureRecognizerStateBegan: {
            [self startImageresizer];
        } break;
        case UIGestureRecognizerStateChanged: {
            [self __panChangedHandleWithTranslation:[panGR translationInView:self]];
        } break;
        case UIGestureRecognizerStateEnded:
        case UIGestureRecognizerStateCancelled:
        case UIGestureRecognizerStateFailed: {
            [self endedImageresizer];
        } break;
        default:
            break;
    }
    [panGR setTranslation:CGPointZero inView:self];
}

#pragma mark 开始/结束拖拽
- (void)startImageresizer {
    if (_pointInside) {
    }
}
- (void)endedImageresizer {
    if (!_pointInside) {
        return;
    }
    _pointInside = NO;
}

- (CAShapeLayer *)frameLayer {
    if (!_frameLayer) {
        _frameLayer = [self __createShapeLayer:1.0];
        _frameLayer.fillColor = UIColor.clearColor.CGColor;
    }
    return _frameLayer;
}

- (CAShapeLayer *)dotsLayer {
    if (!_dotsLayer) _dotsLayer = [self __createShapeLayer:0];
    return _dotsLayer;
}

- (CAShapeLayer *)__createShapeLayer:(CGFloat)lineWidth {
    CAShapeLayer *shapeLayer = [CAShapeLayer layer];
    shapeLayer.lineWidth = lineWidth;
    [self.layer addSublayer:shapeLayer];
    return shapeLayer;
}

- (UIBezierPath *)__classicDotsPathWithFrame:(CGRect)frame {
    UIBezierPath *path = [UIBezierPath bezierPath];
    void (^appendPathBlock)(CGPoint point, CGPoint startPoint, CGPoint endPoint) = ^(CGPoint point, CGPoint startPoint, CGPoint endPoint) {
        [path moveToPoint:startPoint];
        [path addLineToPoint:point];
        [path addLineToPoint:endPoint];
    };
    
    CGPoint originPoint = frame.origin;
    CGPoint midPoint = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
    CGPoint maxPoint = CGPointMake(CGRectGetMaxX(frame), CGRectGetMaxY(frame));
    CGFloat halfArrLineW = 0.0f;
    CGFloat arrLength = 20.0f;
    CGFloat minLength = MIN(midPoint.x - originPoint.x, midPoint.y - originPoint.y);
    if (arrLength > minLength) arrLength = minLength;
    CGPoint point;
    CGPoint startPoint;
    CGPoint endPoint;
    
    point = CGPointMake(originPoint.x - halfArrLineW, originPoint.y - halfArrLineW);
    startPoint = CGPointMake(point.x, point.y + arrLength);
    endPoint = CGPointMake(point.x + arrLength, point.y);
    appendPathBlock(point, startPoint, endPoint);
    
    point = CGPointMake(originPoint.x - halfArrLineW, maxPoint.y + halfArrLineW);
    startPoint = CGPointMake(point.x, point.y - arrLength);
    endPoint = CGPointMake(point.x + arrLength, point.y);
    appendPathBlock(point, startPoint, endPoint);
    
    point = CGPointMake(maxPoint.x + halfArrLineW, originPoint.y - halfArrLineW);
    startPoint = CGPointMake(point.x - arrLength, point.y);
    endPoint = CGPointMake(point.x, point.y + arrLength);
    appendPathBlock(point, startPoint, endPoint);
    
    point = CGPointMake(maxPoint.x + halfArrLineW, maxPoint.y + halfArrLineW);
    startPoint = CGPointMake(point.x - arrLength, point.y);
    endPoint = CGPointMake(point.x, point.y - arrLength);
    appendPathBlock(point, startPoint, endPoint);
    
    if (arrLength > minLength) arrLength = minLength;
    
    point = CGPointMake(originPoint.x - halfArrLineW, midPoint.y);
    startPoint = CGPointMake(point.x, point.y - arrLength);
    endPoint = CGPointMake(point.x, point.y + arrLength);
    appendPathBlock(point, startPoint, endPoint);
    
    point = CGPointMake(maxPoint.x + halfArrLineW, midPoint.y);
    startPoint = CGPointMake(point.x, point.y - arrLength);
    endPoint = CGPointMake(point.x, point.y + arrLength);
    appendPathBlock(point, startPoint, endPoint);
    
    point = CGPointMake(midPoint.x, originPoint.y - halfArrLineW);
    startPoint = CGPointMake(point.x - arrLength, point.y);
    endPoint = CGPointMake(point.x + arrLength, point.y);
    appendPathBlock(point, startPoint, endPoint);

    point = CGPointMake(midPoint.x, maxPoint.y + halfArrLineW);
    startPoint = CGPointMake(point.x - arrLength, point.y);
    endPoint = CGPointMake(point.x + arrLength, point.y);
    appendPathBlock(point, startPoint, endPoint);
    
    return path;
}


- (BOOL)pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    if (![super pointInside:point withEvent:event]) {
        _pointInside = NO;
        return NO;
    }
    
    if (_pointInside) return YES;
        
    _currDR = ARTDR_Center;
    
    if (!_panGR.enabled) {
        _pointInside = NO;
        return NO;
    }
    
    CGRect frame = self.resizerFrame;
    CGFloat scopeWH = _scopeWH;
    CGFloat halfScopeWH = _halfScopeWH;
    
    CGFloat x = frame.origin.x;
    CGFloat y = frame.origin.y;
    CGFloat w = frame.size.width;
    CGFloat h = frame.size.height;
    CGFloat maxX = CGRectGetMaxX(frame);
    CGFloat maxY = CGRectGetMaxY(frame);
    
    CGRect leftTopRect = CGRectMake(x - halfScopeWH, y - halfScopeWH, scopeWH, scopeWH);
    CGRect leftBotRect = CGRectMake(x - halfScopeWH, maxY - halfScopeWH, scopeWH, scopeWH);
    CGRect rightTopRect = CGRectMake(maxX - halfScopeWH, y - halfScopeWH, scopeWH, scopeWH);
    CGRect rightBotRect = CGRectMake(maxX - halfScopeWH, maxY - halfScopeWH, scopeWH, scopeWH);
    
    if (CGRectContainsPoint(leftTopRect, point)) {
        _currDR = ARTDR_LeftTop;
    } else if (CGRectContainsPoint(leftBotRect, point)) {
        _currDR = ARTDR_LeftBottom;
    } else if (CGRectContainsPoint(rightTopRect, point)) {
        _currDR = ARTDR_RightTop;
    } else if (CGRectContainsPoint(rightBotRect, point)) {
        _currDR = ARTDR_RightBottom;
    }
    
    if (_currDR == ARTDR_Center) {
        CGRect leftMidRect = CGRectNull;
        CGRect rightMidRect = CGRectNull;
        CGRect topMidRect = CGRectNull;
        CGRect botMidRect = CGRectNull;

        leftMidRect = CGRectMake(x - halfScopeWH, y + halfScopeWH, scopeWH, h - scopeWH);
        rightMidRect = CGRectMake(maxX - halfScopeWH, y + halfScopeWH, scopeWH, h - scopeWH);
        topMidRect = CGRectMake(x + halfScopeWH, y - halfScopeWH, w - scopeWH, scopeWH);
        botMidRect = CGRectMake(x + halfScopeWH, maxY - halfScopeWH, w - scopeWH, scopeWH);
        
        if (CGRectContainsPoint(leftMidRect, point)) {
            _currDR = ARTDR_LeftMid;
        } else if (CGRectContainsPoint(rightMidRect, point)) {
            _currDR = ARTDR_RightMid;
        } else if (CGRectContainsPoint(topMidRect, point)) {
            _currDR = ARTDR_TopMid;
        } else if (CGRectContainsPoint(botMidRect, point)) {
            _currDR = ARTDR_BottomMid;
        } else {
            _pointInside = NO;
            return NO;
        }
    }
    
    _pointInside = YES;
    return YES;
}

//更新ui
- (void)setResizerFrame:(CGRect)resizerFrame {
    [self __updateImageresizerFrame:resizerFrame animateDuration:-1.0];
}

- (void)__updateImageresizerFrame:(CGRect)resizerFrame animateDuration:(NSTimeInterval)duration {
    _resizerFrame = resizerFrame;
    
    CGFloat radius = 0.1;
    UIBezierPath *framePath = [UIBezierPath bezierPathWithRoundedRect:resizerFrame cornerRadius:radius];
    UIBezierPath *dotsPath;

    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRect:self.blurView.bounds];
    [maskPath appendPath:framePath];
    
    dotsPath = [self __classicDotsPathWithFrame:resizerFrame];
    
    [CATransaction begin];
    [CATransaction setDisableActions:YES];
    _maskLayer.path = maskPath.CGPath;
    _frameLayer.path = framePath.CGPath;
    _dotsLayer.path = dotsPath.CGPath;
    [CATransaction commit];
}

- (void)__panChangedHandleWithTranslation:(CGPoint)translation {
    
    CGFloat x = self.resizerFrame.origin.x;
    CGFloat y = self.resizerFrame.origin.y;
    CGFloat w = self.resizerFrame.size.width;
    CGFloat h = self.resizerFrame.size.height;

    switch (_currDR) {
            
        case ARTDR_LeftTop: {
            w += -2*translation.x;
            h += -2*translation.y;
            break;
        }
        case ARTDR_LeftBottom: {
            w += -2*translation.x;
            h += 2*translation.y;
            break;
        }
        case ARTDR_RightTop: {
            w += 2*translation.x;
            h += -2*translation.y;
            break;
        }
        case ARTDR_RightBottom: {
            w += 2*translation.x;
            h += 2*translation.y;
            break;
        }
        case ARTDR_LeftMid: {
            w += -2*translation.x;
            break;
        }
        case ARTDR_RightMid: {
            w += 2*translation.x;
            break;
        }
        case ARTDR_TopMid: {
            h += -2*translation.y;
            break;
        }
        case ARTDR_BottomMid: {
            h += 2*translation.y;
            break;
        }
        default:
            break;
    }
    
    if (w < _minImageWH) {
        w = _minImageWH;
    } else if (w > self.contentFrame.size.width) {
        w = self.contentFrame.size.width - 2;
    }
    
    if (h < _minImageWH) {
        h = _minImageWH;
    } else if (h > self.contentFrame.size.height) {
        h = self.contentFrame.size.height - 2;
    }
    
    x = (self.frame.size.width - w)/2;
    y = (self.frame.size.height - h)/2;
    
    CGRect imageresizerFrame = CGRectMake(x, y, w, h);
    self.resizerFrame = imageresizerFrame;
}

差不多就这些,有问题的可以私聊我
关联词:iOS可拖拽扫描框,iOS动态扫描框、iOS可以控制大小的扫描框

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容