二维码扫码效果(多个二维码识别和点选)

类似微信扫码效果,会标记识别到的二维码,并且识别到多个二维码时,可以允许用户点选。

二维码坐标解析步骤:

1摄像头扫描

首先说明:摄像头获取的视频流和屏幕尺寸比例不一定一样,获取的二维码坐标及角点是以视频流的坐标系为基准的比例值(范围0-1)。
本次代码以videoPreviewLayer(用于展示摄像头捕获的视频流)frame设为全屏,setVideoGravity参数设为AVLayerVideoGravityResizeAspectFill为例进行说明。


左边的计算差值,右边的计算需要补值,原理一样

扫描代理方法返回的数据为AVMetadataMachineReadableCodeObject数组(若是设置为人脸扫描等其它模式,则返回对应类型的数组)
AVMetadataMachineReadableCodeObject关键属性:
type:数据类型(二维码,条形码等码类型);
stringValue:二维码或条形码的值;
bounds:二维码在源视频流对应的帧图像中的位置和大小。
corners:二维码的四个角的点坐标。
其中bounds和corners的基准坐标系是视频源中对应的帧画面坐标系。
我们主要用corners来计算二维码在videoPreviewLayer上显示的位置及对应的视图坐标系下的frame。
然后添加透明遮罩层在对应的位置添加按钮及点击事件。

计算位置的具体步骤为:

评论区有说系统自带方法是可以直接计算frame的:transformedMetadataObjectForMetadataObject方法
有验证过的可以在评论区补充~

//在这个方法里面:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    AVMetadataObject *newObj = [connection.videoPreviewLayer transformedMetadataObjectForMetadataObject:obj];
    //newObj.bounds 就是转为app坐标系的了。
}
//connection

以上方法可用的话,下面这步骤可以不用看了。
1,获取差值。

初始坐标系,以iphoneXS流分辨率720*1280为例和屏幕尺寸有差值

2,交换X,Y轴。

3,翻转X轴。

4,带入差值计算(若是其它填充方式则需要修改差值计算,有可能需要增加补值)

此时4个角点坐标已转换完毕,但是二维码不一定与屏幕角度相同,通过角点计算出二维码的长方形范围坐标作为二维码frame,并在其中心点添加按钮:
左边为理想情况,右边为实际情况,红色虚线frame即为二维码坐标

具体可看方法:-(CGRect)makeFrameWithCodeObject:(AVMetadataMachineReadableCodeObject *)objc;

2,图片选择扫描:

图片扫描返回的数据为:CIQRCodeFeature主要参数:
messageString:码值;
bounds:码位置
其中bounds是二维码在图片坐标系上的位置,单位是像素,坐标系方向受图片方向影响。
关于图片方向:
uiimage方向属性imageOrientation:

typedef NS_ENUM(NSInteger, UIImageOrientation) {
    UIImageOrientationUp,            // default orientation
    UIImageOrientationDown,          // 180 deg rotation
    UIImageOrientationLeft,          // 90 deg CCW
    UIImageOrientationRight,         // 90 deg CW
    UIImageOrientationUpMirrored,    // as above but image mirrored along other axis. horizontal flip
    UIImageOrientationDownMirrored,  // horizontal flip
    UIImageOrientationLeftMirrored,  // vertical flip
    UIImageOrientationRightMirrored, // vertical flip
};

手机展示图片时会受图片方向影响,比如无论是横持还是竖持甚至倒持手机拍摄一个“↑“,在相册展示时箭头都是朝上的。我们在imageView展示时也是一样。但是二维码识别返回的数据中:
靠近音量调节的角为原点(0,0)。长边为X轴,短边为Y轴,二维码的origin为靠近原点的角
所以需要坐标系转换。
默认图片拍摄的坐标系为手机朝左横持为下图的样子(即图片方向为UIImageOrientationUp的图片,是以下图手持方向拍摄的)

默认UIImageOrientationUp时的照片方向,绿点为frame.origin

正常竖屏展示的样子(正常开发时的坐标系)及二维码frame.origin点为:(UIImageOrientationUp的图片,在app内展示的样子,及我们需要的origin点的位置)


与上一张图做比较

对比后,我们应该要做Y轴反转和比例缩放计算
y轴翻转计算:图片的H-二维码的H-二维码的y值。
在计算图片缩放比列后就可以得出在正常坐标系下二维码在图片的位置。


其它几种拍照下的坐标系示例:


左边为常用的拍照场景,UIImageOrientationRight。右边为UIImageOrientationDown

可以看出:
UIImageOrientationRight方向的图片需要XY交换,
UIImageOrientationDown方向的需要翻转x值。
其他属性可以类推。

另外带镜像属性的图片暂时没有找到,未做测试。

代码部分

.h:

#import 
typedef void(^ScanResultBlock)(NSString* result); //二维码点选操作
typedef void(^ScanOutputs)(NSArray* results);//@[@{@"code":"",@"frame":@""}]

@interface XYQRScanViewController : UIViewController
@property (strong, nonatomic, readonly) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@property (assign, nonatomic, readonly) BOOL isScaning;
@property (copy, nonatomic) ScanOutputs scanOutputs;//可选的实现扫描多个二维码后的预测操作。比如自动选择某个二维码

- (void)startScan;
- (void)stopScan;

//+ (NSArray  *)scanWithImage:(UIImage*)image; //暂未实现
- (instancetype)initWithScanResultBlock:(ScanResultBlock)block;//二维码点选操作

@end

.m

.m:

#import "XYQRScanViewController.h"
#import "UIColor+DynamicColor.h"
#import "HDXYScanImageSelectController.h"

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define kDevice_Is_iPhoneX (SCREEN_WIDTH >= 375.f && SCREEN_HEIGHT >= 812.f)
#define kSafeArea_Top (kDevice_Is_iPhoneX? 44: 20 )

@interface XYQRScanViewController ()

@property (nonatomic, strong) UILabel *promptLabel;

@property (nonatomic, strong) UILabel *titleLabel;
@property (nonatomic, strong) UIView *bottomView;

@property (strong, nonatomic) UIView *myScanBGView;
@property (strong, nonatomic) UIImageView *scanRectView, *lineView;
@property (strong, nonatomic) UILabel *tipLabel;
@property (strong, nonatomic) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@property (strong, nonatomic) CIDetector *detector;
@property (strong, nonatomic) UIButton *lightButton;
@property (strong,nonatomic)UIButton *photoButton;
@property (strong,nonatomic)UIButton *codeButton;
@property (strong, nonatomic) UIView *topBg;
@property (nonatomic,assign)CGRect scanRect;
@property (nonatomic,strong)UIView *bottomContentView;
@property (nonatomic,strong)UILabel *cricleLabel;
//@property (nonatomic,strong)MLMSegmentHead *segemetHead;
@property (nonatomic,strong)UIButton *popButton;
@property (assign, nonatomic, readwrite) BOOL isScaning;
@property (nonatomic, copy) void(^scanBlock)(NSString *scanStr);//扫码回调
@end

@implementation XYQRScanViewController

- (instancetype)initWithScanResultBlock:(ScanResultBlock)block
{
    self = [super init];
    if (self) {
        _scanBlock = block;
    }
    return self;
}

- (CIDetector *)detector{
    if (!_detector) {
        _detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];
    }
    return _detector;
}

- (void)viewDidLoad {
    [super viewDidLoad];
     UIColor *color = [UIColor colorWithDarkModeColor:BlackColor normalColor:HexF7F8FA];
     self.view.backgroundColor = color;
     NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
     
     [nc addObserver:self
            selector:@selector(applicationDidBecomeActive:)
                name:UIApplicationDidBecomeActiveNotification
              object:nil];
     [nc addObserver:self
            selector:@selector(applicationWillResignActive:)
                name:UIApplicationWillResignActiveNotification
              object:nil];
     
     [self configUI];
}
-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
     self.navigationController.navigationBar.hidden =  NO;
    [self startScan];
}

-(void)viewWillDisappear:(BOOL)animated{
    [super viewWillDisappear:animated];

}

- (void)setupNavigationBar {
     self.navigationController.navigationBar.translucent = YES;
     [self.navigationController.navigationBar setBackgroundImage:[UIImage createImageWithColor:[UIColor clearColor]] forBarMetrics:UIBarMetricsDefault];
     NSDictionary *textAttributes = @{
                                      NSFontAttributeName: [UIFont boldSystemFontOfSize:18],
                                      NSForegroundColorAttributeName:WhiteColor
                                      };
     [self.navigationController.navigationBar setTitleTextAttributes:textAttributes];
     
     self.navigationItem.title = @"扫码";
     UIButton *backButton = [UIButton buttonWithType:UIButtonTypeCustom];
     backButton.frame = CGRectMake(0, 0, 40, 40);
     [backButton setImage:[UIImage imageNamed:@"nav_back_w"] forState:UIControlStateNormal];
     [backButton addTarget:self action:@selector(scanBack) forControlEvents:UIControlEventTouchUpInside];
     backButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentLeft;
     self.navigationItem.leftBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:backButton];
     UIButton *rightButton = [UIButton buttonWithType:UIButtonTypeCustom];
     rightButton.frame = CGRectMake(0, 0, 40, 40);
     [rightButton setTitle:@"相册" forState:UIControlStateNormal];
     [rightButton setTitleColor:WhiteColor forState:UIControlStateNormal];
     rightButton.titleLabel.font = FontRegular(16);
     rightButton.alpha = 0.8;
     [rightButton addTarget:self action:@selector(rightBarButtonItenAction) forControlEvents:UIControlEventTouchUpInside];
     rightButton.contentHorizontalAlignment = UIControlContentHorizontalAlignmentRight;
     self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:rightButton];
}
-(void)configUI{
     [self setupNavigationBar];
    _isScaning = YES;
     [self.view.layer addSublayer:self.videoPreviewLayer];
     [self.view addSubview:self.titleLabel];
     [self.view addSubview:self.promptLabel];

    [self.view addSubview:self.myScanBGView];

    [self.view addSubview:self.lineView];

    [self scanLineStartAction];

}

- (void)scanLineStartAction{
    
    [self scanLineStopAction];
    
    CAAnimationGroup *group = [CAAnimationGroup animation];
    CABasicAnimation *scanAnimation = [CABasicAnimation animationWithKeyPath:@"position.y"];
    scanAnimation.fromValue = @(80);
    scanAnimation.toValue = @(SCREEN_HEIGHT-160-95);
    scanAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionLinear];
    
    CABasicAnimation *opacityAnimation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    opacityAnimation.fromValue = @(1);
    opacityAnimation.toValue = @(0);
    opacityAnimation.repeatCount = CGFLOAT_MAX;
    opacityAnimation.duration = 0.5;
    opacityAnimation.beginTime = 2;
    
    group.animations = @[scanAnimation,opacityAnimation];
    group.removedOnCompletion = NO;
    group.fillMode = kCAFillModeForwards;
    group.duration  = 2.5;
    group.repeatCount = CGFLOAT_MAX;
    
    [self.lineView.layer addAnimation:group forKey:@"basic"];
}
-(void)scanLineStopAction{
    
    [self.lineView.layer removeAllAnimations];
}

-(void)scanBack{
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
    }
}

- (void)rightBarButtonItenAction {
    WeakSelf(ws);
     
     UIImagePickerController *picker = [UIImagePickerController new];
     picker.delegate = (id)self;
     picker.allowsEditing = NO;
     picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
     picker.modalPresentationStyle = UIModalPresentationFullScreen;
     [ws.navigationController presentViewController:picker animated:YES completion:nil];

}
-(void)dealloc {
    
    [self.videoPreviewLayer removeFromSuperlayer];
    self.videoPreviewLayer = nil;
    [self scanLineStopAction];
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    NSLog(@"%s",__func__);
}
#pragma mark -----public

- (void)playSoundName:(NSString *)name {
    /// 静态库 path 的获取
    NSString *path = [[NSBundle mainBundle] pathForResource:name ofType:nil];
    if (!path) {
        /// 动态库 path 的获取
        path = [[NSBundle bundleForClass:[self class]] pathForResource:name ofType:nil];
    }
    NSURL *fileUrl = [NSURL fileURLWithPath:path];
    
    SystemSoundID soundID = 0;
    AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
    AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, NULL, NULL);
    AudioServicesPlaySystemSound(soundID);
}

- (void)startScan{
    _isScaning = YES;
    [self.videoPreviewLayer.session startRunning];
    [self scanLineStartAction];
}
- (void)stopScan{
    _isScaning = NO;
    [self.videoPreviewLayer.session stopRunning];
    [self scanLineStopAction];
}
-(void)openFlash:(UIButton*)button{
    button.selected = !button.selected;
    if (button.selected) {
        [self turnTorchOn:YES];
    }
    else{
        [self turnTorchOn:NO];
    }
}

- (void)turnTorchOn:(BOOL)on
{
    Class captureDeviceClass = NSClassFromString(@"AVCaptureDevice");
    if (captureDeviceClass != nil) {
        AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
        
        if ([device hasTorch] && [device hasFlash]){
            [device lockForConfiguration:nil];
            if (on) {
                device.torchMode = AVCaptureTorchModeOn;
            } else {

                device.torchMode = AVCaptureTorchModeOff;
            }
            [device unlockForConfiguration];
        }
    }
}
#pragma mark -----Notification
- (void)applicationDidBecomeActive:(UIApplication *)application {
    [self startScan];
}

- (void)applicationWillResignActive:(UIApplication *)application {
    [self stopScan];
}

#pragma mark ------imagepickDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info
{
     WeakSelf(weakSelf);
     [picker dismissViewControllerAnimated:YES completion:^{
         [weakSelf handleImageInfo:info];
     }];
}



- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
    [picker dismissViewControllerAnimated:YES completion:nil];
    [self startScan];
}

+(UIViewController *)showSelectControllerWithImage:(UIImage *)image dataArray:(NSArray *)array block:(ScanResultBlock)block
{
    HDXYScanImageSelectController *controller = [HDXYScanImageSelectController new];
    controller.image =image;
    controller.tagArray = array;
    controller.scanBlock = block;
    return controller;
}


#pragma mark 图片识别
- (void)handleImageInfo:(NSDictionary *)info{

     [self startScan];
     UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
     if (!image){
          image = [info objectForKey:UIImagePickerControllerOriginalImage];
     }
     __block NSString *resultStr = nil;
     __block BOOL haveCode = NO;
//图片识别二维码
     NSArray *features = [self.detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
//判断是否识别出二维码
     [features enumerateObjectsUsingBlock:^(CIQRCodeFeature *obj, NSUInteger idx, BOOL *stop) {
        if (obj.messageString.length > 0) {
             resultStr = obj.messageString;
             NSLog(@"%@",resultStr);
             haveCode = resultStr.length>0?YES:NO;
             *stop = YES;
          }
     }];
     [self stopScan];
     NSMutableArray *muchArray = [NSMutableArray new];
     if (haveCode) {
          [features enumerateObjectsUsingBlock:^(CIQRCodeFeature *obj, NSUInteger idx, BOOL *stop) {
               NSMutableDictionary *dic = [NSMutableDictionary new];
               CGRect frame =obj.bounds;
               NSString *frameStr = NSStringFromCGRect(frame);
               [dic setObject:frameStr forKey:@"frame"];
               NSString *code = obj.messageString;
               [dic setObject:code forKey:@"code"];
               [muchArray addObject:dic];
          }];
     }
     if (self.scanOutputs) {
          self.scanOutputs(muchArray);
          return;
     }
     WeakSelf(weakSelf);
     [self.navigationController pushViewController:[XYQRScanViewController showSelectControllerWithImage:image dataArray:muchArray block:^(NSString *result) {
          if (result && result.length>0) {
               if (weakSelf.scanBlock) {
                    [weakSelf.navigationController popViewControllerAnimated:NO];
                    weakSelf.scanBlock(result);
               }
          }
     }] animated:NO];
     return;
}
#pragma mark ------AVCaptureVideoDataOutputSampleBufferDelegate
//弱光识别
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    
    static BOOL isStop = false;
    if (isStop) return;
    
    isStop = true;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
        //0.5秒一次弱光监听
        isStop = false;
    });
    
    CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);
    NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];
    CFRelease(metadataDict);
    NSDictionary *exifMetadata = [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];
    float brightnessValue = [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] floatValue];

    if (brightnessValue < 0 && !self.lightButton.selected && self.lightButton.isHidden) {
        self.lightButton.hidden = NO;
    }
    if (brightnessValue > 0 && !self.lightButton.selected && !self.lightButton.isHidden) {
        self.lightButton.hidden = YES;
    }
}

#pragma mark ------AVCaptureMetadataOutputObjectsDelegate,扫描结果输出流代理

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
    //判断是否有数据,是否是二维码数据
    NSMutableArray *muchArray = [NSMutableArray new];
    if (metadataObjects.count > 0) {
        [metadataObjects enumerateObjectsUsingBlock:^(AVMetadataMachineReadableCodeObject *obj, NSUInteger idx, BOOL *stop) {
            if ([obj.type isEqualToString:AVMetadataObjectTypeQRCode]) {
                [muchArray addObject:obj];
            }
        }];
        if (muchArray.count>0) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self analyseResultAry:muchArray];
            });
        }
    }

}
- (void)analyseResultAry:(NSArray *)resultArray{
     if (!_isScaning) {
          return;
     }
     [self stopScan];
     [self playSoundName:@"SGQRCode.bundle/sound.caf"];
     NSMutableArray *muchArray = [NSMutableArray new];
     [resultArray enumerateObjectsUsingBlock:^(AVMetadataMachineReadableCodeObject *result, NSUInteger idx, BOOL *stop) {
          NSMutableDictionary *dic = [NSMutableDictionary new];
          NSString *code = result.stringValue;
          [dic setObject:code forKey:@"code"];
        
          CGRect frame = [self makeFrameWithCodeObject: result];
          NSString *frameStr = NSStringFromCGRect(frame);
          [dic setObject:frameStr forKey:@"frame"];
          [muchArray addObject:dic];
     }];
     
     if (muchArray.count>=1) {
          UIImage *image;
          WeakSelf(weakSelf);
          if (self.scanOutputs) {
               self.scanOutputs(muchArray);
               return;
          }

          UIViewController *viewController = [XYQRScanViewController showSelectControllerWithImage:image dataArray:muchArray block:^(NSString *result) {
               weakSelf.popButton.hidden = NO;
               weakSelf.lineView.hidden = NO;
               if (weakSelf.scanBlock) {
                    if (result) {
                         [weakSelf.navigationController popViewControllerAnimated:YES];
                         weakSelf.scanBlock(result);
                    }else{
                         [weakSelf startScan];
                    }
               }
          }];
          viewController.modalPresentationStyle =UIModalPresentationOverFullScreen;
          [self.navigationController presentViewController:viewController animated:NO completion:nil];
          self.popButton.hidden = YES;
          self.lineView.hidden = YES;
          return;
     }
     [self startScan];
}

/*
 AVMetadataMachineReadableCodeObject,输出的点位坐标是其在原始数据流上的坐标,与屏幕视图坐标不一样,(坐标系,值都会有差别)
     将坐标值转为屏幕显示的图像视图(self.videoPreviewLayer)上的坐标值
 */
-(CGRect)makeFrameWithCodeObject:(AVMetadataMachineReadableCodeObject *)objc
{
     //将二维码坐标转化为扫码控件输出视图上的坐标
     CGSize isize = CGSizeMake(720.0, 1280.0); // 尺寸可以考虑不要写死,当前设置的是captureSession.sessionPreset = AVCaptureSessionPreset1280x720;
//     CGSize isize = self.view.frame.size; //扫码控件的输出尺寸,
     float Wout = 0.00;
     float Hout = 0.00;
     BOOL wMore = YES;
    /*取分辨率与输出的layer尺寸差,
     此处以AVLayerVideoGravityResizeAspectFill填充方式为例,判断扫描的范围更宽还是更长,并计算出超出部分的尺寸,后续计算减去这部分。
     如果是其它填充方式,计算方式不一样(比如AVLayerVideoGravityResizeAspect,则计算计算留白的尺寸,并后续补足这部分)
     */
     if (isize.width/isize.height > self.videoPreviewLayer.bounds.size.width/self.videoPreviewLayer.bounds.size.height) {
          //当更宽时,计算扫描的坐标x为0 的点比输出视图的0点差多少(输出视图为全屏时,即屏幕外有多少)
          wMore = YES;
          Wout = (isize.width/isize.height)* self.videoPreviewLayer.bounds.size.height;
          Wout = Wout - self.videoPreviewLayer.bounds.size.width;
          Wout = Wout/2;
     }else{
          // 当更长时,计算y轴超出多少。
          wMore = NO;
          Hout = (isize.height/isize.width)* self.videoPreviewLayer.bounds.size.width;
          Hout = Hout  - self.videoPreviewLayer.bounds.size.height;
          Hout = Hout/2;
     }
    
     CGPoint point1 = CGPointZero;
     CGPoint point2 = CGPointZero;
     CGPoint point3 = CGPointZero;
     CGPoint point4 = CGPointZero;
     /*
      源坐标系下frame和角点,都是比例值,即源视频流尺寸下的百分比值。
      例子:frame :(x = 0.26720550656318665, y = 0.0014114481164142489), size = (width = 0.16406852006912231, height = 0.29584407806396484))
      objc.corners:{0.26823519751360592, 0.29203594744002659}
                    {0.4312740177700658, 0.29725551905635411}
                    {0.4294213439632073, 0.012761536345436197}
                    {0.26720551457151021, 0.0014114481640513654}
      */
     CGRect frame = objc.bounds;//在源坐标系的frame,
     NSArray *array = objc.corners;//源坐标系下二维码的角点
     CGPoint P = frame.origin;
     CGSize S = frame.size;
     
     //获取点
     for (int n = 0; n< array.count; n++) {
          
          CGPoint point = CGPointZero;
          CFDictionaryRef dict = (__bridge CFDictionaryRef)(array[n]);
          CGPointMakeWithDictionaryRepresentation(dict, &point);
          NSLog(@"二维码角点%@",NSStringFromCGPoint(point));
          //交换xy轴
          point.x = point.y +  point.x;
          point.y = point.x - point.y;
          point.x = point.x - point.y;
          //x轴反转
          point.x = (1-point.x);
          //point乘以比列。减去尺寸差,
          if (wMore) {
               point.x = (point.x * (isize.width/isize.height)* self.videoPreviewLayer.bounds.size.height) - Wout;
               point.y = self.videoPreviewLayer.bounds.size.height *(point.y);
          }else{
               point.x = self.videoPreviewLayer.bounds.size.width *(point.x);
               point.y = (point.y) * (isize.height/isize.width)* self.videoPreviewLayer.bounds.size.width - Hout;
          }
          if (n == 0) {
               point1 = point;
          }
          if (n == 1) {
               point2 = point;
          }
          if (n == 2) {
               point3 = point;
          }
          if (n == 3) {
               point4 = point;
          }
     }
     //通过获取最小和最大的X,Y值,二维码在视图上的frame(前面得到的点不一定是正方形的二维码,也可能是菱形的或者有一定旋转角度的)
     float minX = point1.x;
     minX = minX>point2.x?point2.x:minX;
     minX = minX>point3.x?point3.x:minX;
     minX = minX>point4.x?point4.x:minX;
        
     float minY = point1.y;
     minY = minY>point2.y?point2.y:minY;
     minY = minY>point3.y?point3.y:minY;
     minY = minY>point4.y?point4.y:minY;
     P.x = minX;
     P.y = minY;
        
     float maxX = point1.x;
     maxX = maxX self.view.bounds.size.width/self.view.bounds.size.height) {
//                   //当更宽时,计算扫描的坐标x为0 的点比输出视图的0点差多少(输出视图为全屏时,即屏幕外有多少)
//                   wMore = YES;
//                   Wout = (isize.width/isize.height)* self.videoPreviewLayer.bounds.size.height;
//                   Wout = Wout - self.videoPreviewLayer.bounds.size.width;
//                   Wout = Wout/2;
//              }else{
//                   // 当更长时,计算y轴超出多少。
//                   wMore = NO;
//                   Hout = (isize.height/isize.width)* self.videoPreviewLayer.bounds.size.width;
//                   Hout = Hout  - self.videoPreviewLayer.bounds.size.height;
//                   Hout = Hout/2;
//              }
//              if (wMore) {
//                   captureMetadataOutput.rectOfInterest = CGRectMake(0,Wout/720.0,1,(720.0-2*Wout)/720.0);
//              }else{
//                   captureMetadataOutput.rectOfInterest = CGRectMake(Hout/1280.0,0,(1280.0-2*Hout)/1280.0,0);
//              }
//              captureMetadataOutput.rectOfInterest = CGRectMake(0,0,0.5,0.5);
         }
     }
     return _videoPreviewLayer;
}

@end

展示点选按钮

.h:

#import 

@interface HDXYScanImageSelectController : UIViewController
@property (nonatomic, copy) void(^scanBlock)(NSString *scanStr);//扫码点选回调
/*
 选择图片扫码结果展示:传入image。
 摄像头扫码结果展示:image不需要传入,后面会自动生成透明图片尺寸与扫码页面视频流的展示大小一致,如果扫码页面视频流不是全图则需要修改这部分逻辑
 */
@property(nonatomic,copy)UIImage *image;
@property(nonatomic,copy)NSArray *tagArray;

@end

.m:


#import "HDXYScanImageSelectController.h"

#define maxBtnSize 54
#define minBtnSize 18

#define SCREEN_WIDTH [UIScreen mainScreen].bounds.size.width
#define SCREEN_HEIGHT [UIScreen mainScreen].bounds.size.height
#define kDevice_Is_iPhoneX (SCREEN_WIDTH >= 375.f && SCREEN_HEIGHT >= 812.f)
#define kSafeArea_Top (kDevice_Is_iPhoneX? 44: 20 )
#define kSafeArea_Bottom ((kDevice_Is_iPhoneX? 34: 0))

@interface HDXYScanImageSelectController ()
@property (nonatomic,strong)UIButton *popButton;
@property (nonatomic,strong)UILabel *downLable;
@property (nonatomic,strong)UIView *bigDarkView;
@property BOOL noImage;
@end

@implementation HDXYScanImageSelectController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self.view addSubview:self.popButton];
    [self.popButton mas_makeConstraints:^(MASConstraintMaker *make) {
        make.left.equalTo(self.view).offset(25);
        make.top.equalTo(self.view).offset(12.5+(kDevice_Is_iPhoneX?44:20));
    }];
    // Do any additional setup after loading the view.
}

-(void)viewWillAppear:(BOOL)animated
{
    self.view.backgroundColor = [UIColor blackColor];
    self.navigationController.navigationBar.hidden =  YES;
    self.noImage = NO;
    if (!self.image) {
        self.noImage = YES;
        self.image = [self createImageWithColor:[UIColor colorWithRed:0.1 green:0.1 blue:0.1 alpha:0.5]];
        self.view.backgroundColor = [UIColor clearColor];
    }

    UIImageView *imageView = [self buildImageView];
    
    [self.view addSubview:imageView];
    if (self.tagArray && self.tagArray.count>0) {
        [self.tagArray enumerateObjectsUsingBlock:^(NSDictionary *dataDic, NSUInteger idx, BOOL *stop) {
            UIButton *btn = [self buildBtnWithDic:dataDic andNumber:idx];
            [imageView addSubview:btn];
        }];

        [self.view addSubview:self.downLable];
        [self.downLable mas_makeConstraints:^(MASConstraintMaker *make) {
                make.centerX.mas_equalTo(self.view.mas_centerX);
                make.bottom.mas_offset(-40-kSafeArea_Bottom);
        }];
        [self.view bringSubviewToFront:self.popButton];
    }else{
        [self.view addSubview:self.bigDarkView];
        [self.bigDarkView mas_makeConstraints:^(MASConstraintMaker *make) {
            make.left.right.top.bottom.mas_equalTo(self.view);
        }];
    }
    [self.view bringSubviewToFront:self.popButton];
    [super viewWillAppear:animated];
}
-(void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];
   //只有一个二维码时,展示一下就自动选择
    if (self.tagArray.count == 1) {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.25 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSDictionary *dataDic = self.tagArray[0];
            [self doingWithMessage:dataDic[@"code"]];
        });
    }
}


-(UIButton *)buildBtnWithDic:(NSDictionary *)dic andNumber:(NSInteger)number
{
     CGSize startImageSize = [HDXYScanImageSelectController getSizeWithImage:self.image];
     CGFloat fixelW = startImageSize.width;
     CGFloat fixelH = startImageSize.height;
    //计算UIimageView的尺寸,
     if (fixelH/fixelW > self.view.bounds.size.height/self.view.bounds.size.width) {
          fixelW = (fixelW/fixelH) * self.view.bounds.size.height;
          fixelH = self.view.bounds.size.height;
     }else{
          fixelH = (fixelH/fixelW) * self.view.bounds.size.width;
          fixelW = self.view.bounds.size.width;
     }
     float scale =  fixelW/startImageSize.width;
     CGRect btnFrame =  CGRectFromString(dic[@"frame"]);
     //根据图片方向调整frame
     if (!self.noImage) {
          btnFrame = [HDXYScanImageSelectController exChangeFrame:btnFrame withImage:self.image];
     }
     //坐标等比缩放
     btnFrame = CGRectMake(btnFrame.origin.x*scale, btnFrame.origin.y *scale, btnFrame.size.width *scale, btnFrame.size.height *scale);

    //按钮初步大小调整
    btnFrame = CGRectMake(btnFrame.origin.x + btnFrame.size.width/4,btnFrame.origin.y + btnFrame.size.height/4, btnFrame.size.width/2, btnFrame.size.height/2);
    if (btnFrame.size.width != btnFrame.size.height) {
        float w = btnFrame.size.width;
        float h = btnFrame.size.height;
        btnFrame = CGRectMake(btnFrame.origin.x, btnFrame.origin.y + (h-w)/2, w, w);
    }
    if (btnFrame.size.width > maxBtnSize) {
        float w = btnFrame.size.width;
        float h = btnFrame.size.height;
        btnFrame = CGRectMake(btnFrame.origin.x + (w-maxBtnSize)/2, btnFrame.origin.y + (h-maxBtnSize)/2, maxBtnSize, maxBtnSize);
    }
    if (btnFrame.size.width < minBtnSize) {
        float w = btnFrame.size.width;
        float h = btnFrame.size.height;
        btnFrame = CGRectMake(btnFrame.origin.x + (w-minBtnSize)/2, btnFrame.origin.y + (h-minBtnSize)/2, minBtnSize, minBtnSize);
    }
    btnFrame.size.width = btnFrame.size.height;
    UIButton *btn = [[UIButton alloc]init];
    btn.frame = btnFrame;

    [btn setBackgroundImage:[UIImage imageNamed:@"scanMany"] forState:UIControlStateNormal];
     btn.backgroundColor = [UIColor yellowColor];
    btn.alpha = 0.99;
    btn.tag = number;
    btn.clipsToBounds = YES;
    [btn addTarget:self action:@selector(clickBtn:) forControlEvents:UIControlEventTouchUpInside];
    [btn.layer addAnimation:[self opacityForever_Animation:0.7] forKey:nil];
    return btn;
}


-(void)clickBtn:(UIButton *)btn
{
    NSInteger number = btn.tag;
    NSString *messageStr = self.tagArray[number][@"code"];
    [self doingWithMessage:messageStr];
    
}

-(void)clickBigBackView:(id)sender
{
    [self doingWithMessage:nil];
}
-(void)doingWithMessage:(NSString *)msg
{
    if (self.navigationController) {
        [self.navigationController popViewControllerAnimated:YES];
        if (self.scanBlock) {
            self.scanBlock(msg);
        }
    }else{
        [self dismissViewControllerAnimated:NO completion:^{
            if (self.scanBlock) {
                self.scanBlock(msg);
            }
        }];
    }
}

#pragma mark === 永久闪烁的动画 ======
-(CABasicAnimation *)opacityForever_Animation:(float)time
{
    CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:@"opacity"];
    animation.fromValue = [NSNumber numberWithFloat:0.99f];
    animation.toValue = [NSNumber numberWithFloat:0.3f];
    animation.autoreverses = YES;
    animation.duration = time;
    animation.repeatCount = MAXFLOAT;
    animation.removedOnCompletion = NO;
    animation.fillMode = kCAFillModeForwards;
    animation.timingFunction=[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseIn];///没有的话是均匀的动画。
    return animation;
}


+(CGSize)getSizeWithImage:(UIImage *)image
{
    CGFloat fixelW;
    CGFloat fixelH;
    fixelW = CGImageGetWidth(image.CGImage);
    fixelH = CGImageGetHeight(image.CGImage);
    if (image.imageOrientation == UIImageOrientationLeft ||
        image.imageOrientation == UIImageOrientationRight ||
        image.imageOrientation == UIImageOrientationLeftMirrored ||
        image.imageOrientation == UIImageOrientationRightMirrored) {
        
        fixelW = CGImageGetHeight(image.CGImage);
        fixelH = CGImageGetWidth(image.CGImage);
    }
    
    return CGSizeMake(fixelW, fixelH);
}
//根据图片方向调整二维码坐标,图片方向根据拍摄时手机方向而不同
+(CGRect)exChangeFrame:(CGRect)frame withImage:(UIImage *)image
{
     CGFloat fixelW = CGImageGetWidth(image.CGImage);
     CGFloat fixelH = CGImageGetHeight(image.CGImage);
     CGFloat x = frame.origin.x;
     CGFloat y = frame.origin.y;
     CGFloat w = frame.size.width;
     CGFloat h = frame.size.height;
     if (image.imageOrientation == UIImageOrientationUp){
          frame.origin.y = fixelH-y-h;
     }else if (image.imageOrientation == UIImageOrientationLeft){
          //方向逆时针旋转90°拍摄,已实现
          frame.origin.x = fixelH-y-h;
          frame.origin.y = fixelW-x-w;
     }else if (image.imageOrientation == UIImageOrientationRight){
          //顺时针旋转90度拍摄,已实现
          frame.origin.x = y;
          frame.origin.y = x;
     }else if (image.imageOrientation == UIImageOrientationDown){
          //旋转180度拍摄的
          frame.origin.x = fixelW-x-w;
          frame.origin.y = y;
     }else if (image.imageOrientation == UIImageOrientationUpMirrored){
          //左右镜像,已实现未验证
          frame.origin.x = fixelW -x-w;
          frame.origin.y = fixelH-y-h;
        
     }else if (image.imageOrientation == UIImageOrientationDownMirrored){
          //翻转180度后镜像,已实现未验证
          frame.origin.y = y;
          frame.origin.x = x;
     }else if (image.imageOrientation == UIImageOrientationRightMirrored){
          //表示图片被顺时针翻转90°后的镜面图像,已实现未验证
          frame.origin.x = fixelH -y-h;
          frame.origin.y = x;
     }else if (image.imageOrientation == UIImageOrientationLeftMirrored){
          //坐标系逆时针旋转90°并镜像,已实现未验证
          frame.origin.x = y;
          frame.origin.y = fixelW-x-w;
     }
     return frame;
}
-(UIImageView *)buildImageView
{
    CGFloat fixelW = [HDXYScanImageSelectController getSizeWithImage:self.image].width;
    CGFloat fixelH = [HDXYScanImageSelectController getSizeWithImage:self.image].height;
    if (fixelH/fixelW > self.view.bounds.size.height/self.view.bounds.size.width) {
        fixelW = (fixelW/fixelH) * self.view.bounds.size.height;
        fixelH = self.view.bounds.size.height;
    }else{
        fixelH = (fixelH/fixelW) * self.view.bounds.size.width;
        fixelW = self.view.bounds.size.width;
    }
    UIImageView *imageView = [[UIImageView alloc]initWithImage:self.image];
    imageView.bounds = CGRectMake(0, 0, fixelW, fixelH);
    imageView.center = self.view.center;
    imageView.contentMode = UIViewContentModeScaleAspectFit;
    imageView.userInteractionEnabled = YES;
    return imageView;
}
-(UIButton *)popButton{
    if (!_popButton) {
        _popButton = [[UIButton alloc]init];
//        [_popButton setImage:[UIImage imageNamed:@"scan_closed_icon"] forState:UIControlStateNormal];
        [_popButton setTitle:@"取消" forState:UIControlStateNormal];
         [_popButton addTarget:self action:@selector(clickBigBackView:) forControlEvents:UIControlEventTouchUpInside];

    }
    return _popButton;
}
-(UILabel *)downLable
{
    if (!_downLable) {
        _downLable = [UILabel new];
        _downLable.text = @"轻触小蓝点,打开页面";
        [_downLable setTextColor:[UIColor whiteColor]];
        [_downLable setBackgroundColor:[UIColor clearColor]];
        _downLable.textAlignment = NSTextAlignmentCenter;
    }
    return _downLable;
}
//颜色生成image
-(UIImage*) createImageWithColor:(UIColor*) color
{

    CGRect rect=CGRectMake(0.0f, 0.0f, SCREEN_WIDTH, SCREEN_HEIGHT);
    UIGraphicsBeginImageContext(rect.size);
    CGContextRef context = UIGraphicsGetCurrentContext();
    CGContextSetFillColorWithColor(context, [color CGColor]);
    CGContextFillRect(context, rect);
    UIImage *theImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return theImage;
}
-(UIView *)bigDarkView
{
     if (!_bigDarkView) {
          _bigDarkView = [UIView new];
          _bigDarkView.backgroundColor = [UIColor colorWithRed:0.3 green:0.3 blue:0.3 alpha:0.5];
          UITapGestureRecognizer * tapGesture = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(clickBigBackView:)];
          [tapGesture setNumberOfTapsRequired:1];
          [_bigDarkView addGestureRecognizer:tapGesture];
          UILabel *oneLabel = [UILabel new];
          oneLabel.text = @"未发现二维码";
          oneLabel.textColor = [UIColor whiteColor];
          oneLabel.font = [UIFont fontWithName:@"FontWeightStyleRegular" size:17.0];
          oneLabel.textAlignment = NSTextAlignmentCenter;
          [_bigDarkView addSubview:oneLabel];
          [oneLabel mas_makeConstraints:^(MASConstraintMaker *make) {
               make.left.right.mas_equalTo(_bigDarkView);
               make.height.mas_offset(20);
               make.centerY.mas_equalTo(_bigDarkView.mas_centerY).mas_offset(-20);
          }];
          UILabel *twoLabel = [UILabel new];
          twoLabel.text = @"轻触屏幕继续扫描";
          twoLabel.textColor = [UIColor whiteColor];
          twoLabel.font = [UIFont fontWithName:@"FontWeightStyleRegular" size:14.0];;
          twoLabel.textAlignment = NSTextAlignmentCenter;
          [_bigDarkView addSubview:twoLabel];
          [twoLabel mas_makeConstraints:^(MASConstraintMaker *make) {
               make.left.right.mas_equalTo(_bigDarkView);
               make.height.mas_offset(20);
               make.centerY.mas_equalTo(_bigDarkView.mas_centerY);
          }];
     }
     return _bigDarkView;
}
@end

你可能感兴趣的:(二维码扫码效果(多个二维码识别和点选))