iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能

iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能

在iOS开发中,会遇到扫一扫功能,扫一扫是使用摄像头扫码二维码或者条形码,获取对应二维码或条形码内容字符串。通过获得的字符串进行跳转或者打开某个页面开启下一步的业务逻辑。

https://blog.csdn.net/gloryFlow/article/details/132249830
https://img-blog.csdnimg.cn/b6b9b7416e7b45e9ab06c02083ac091f.jpeg#pic_center

一、使用前权限设置

扫一扫功能需要开启相机权限,需要在info.plist文件中添加NSCameraUsageDescription

例如:

<key>NSCameraUsageDescription</key>
<string>开启相机权限,活动扫一扫更快捷</string>
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>开启定位权限</string>
<key>NSLocationAlwaysUsageDescription</key>
<string>开启定位权限</string>
<key>NSLocationWhenInUseUsageDescription</key>
<string>开启定位权限</string>
<key>NSMicrophoneUsageDescription</key>
<string>开启麦克风权限</string>
<key>NSPhotoLibraryAddUsageDescription</key>
<string>添加照片需要您的同意</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>开启照片权限</string>

这里还有其他权限,暂时扫一扫只需要NSCameraUsageDescription。

二、AVCaptureSession扫一扫功能

2.1 需要了解的几个类

  • AVCaptureSession

AVCaptureSession是iOS提供的一个管理和协调输入设备到输出设备之间数据流的对象。

  • AVCaptureDevice

AVCaptureDevice是指硬件设备。

  • AVCaptureDeviceInput

AVCaptureDeviceInput是用来从AVCaptureDevice对象捕获Input数据。

  • AVCaptureMetadataOutput

AVCaptureMetadataOutput是用来处理AVCaptureSession产生的定时元数据的捕获输出的。

  • AVCaptureVideoDataOutput

AVCaptureVideoDataOutput是用来处理视频数据输出的。

  • AVCaptureVideoPreviewLayer

AVCaptureVideoPreviewLayer是相机捕获的视频预览层,是用来展示视频的。

2.2 实现扫一扫功能

在熟悉几个类之后,我们可以初始化session了。
我们为AVCaptureSession添加功能实现所需要的input与output

/** 创建扫描器 */
- (void)loadScanView {
    
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
    
    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    if (self.scanConfig.scannerArea == SDScannerAreaDefault) {
        metadataOutput.rectOfInterest = CGRectMake([self.scannerView scannerOriginX]/self.view.frame.size.height, [self.scannerView scannerOriginY]/self.view.frame.size.width, [self.scannerView scannerWidth]/self.view.frame.size.height, [self.scannerView scannerWidth]/self.view.frame.size.width);
    }
    
    AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    [videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
    
    self.session = [[AVCaptureSession alloc]init];
    [self.session setSessionPreset:AVCaptureSessionPresetHigh];
    if ([self.session canAddInput:deviceInput]) {
        [self.session addInput:deviceInput];
    }
    if ([self.session canAddOutput:metadataOutput]) {
        [self.session addOutput:metadataOutput];
    }
    if ([self.session canAddOutput:videoDataOutput]) {
        [self.session addOutput:videoDataOutput];
    }
    
    metadataOutput.metadataObjectTypes = [SDQrScanTool metadataObjectType:self.scanConfig.scannerType];
    
    AVCaptureVideoPreviewLayer *videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
    videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    videoPreviewLayer.frame = self.view.layer.bounds;
    [self.videoPreView.layer insertSublayer:videoPreviewLayer atIndex:0];
    
    [self.session startRunning];
}

AVCaptureMetadataOutput实现了方法,设置了delegate

[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];

实现AVCaptureMetadataOutputObjectsDelegate的代理方法

- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection;

该方法每当输出捕获并发出新对象时,委托都会收到此消息,获得对应metadataObjectTypes。通过此方法,我们可以当扫描二维码时候获得对应的结果。

#pragma mark -- AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    
    // 获取扫一扫结果
    if (metadataObjects && metadataObjects.count > 0) {
        
        [self pauseScanning];
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects[0];
        NSString *stringValue = metadataObject.stringValue;
        //AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[self.lay transformedMetadataObjectForMetadataObject:metadataObjects.lastObject];
        //[self changeVideoScale:metadataObject];

        [self handleScanValue:stringValue];
    }
}

在AVCaptureVideoDataOutput实现了

[videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];

这里实现了代理AVCaptureVideoDataOutputSampleBufferDelegate中方法

- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;

当捕获新的视频采样缓冲区时,将使用captureOutput:didOutputSampleBuffer:fromConnection:delegate方法将其提供给采样缓冲区代理。

在此方法中,可以观察亮度值,决定是否需要开启灯光。

#pragma mark -- AVCaptureVideoDataOutputSampleBufferDelegate
/** 此方法会实时监听亮度值 */
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    
    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 (![self.scannerView flashlightOn]) {
        if (brightnessValue < -1.0) {
            [self.scannerView showFlashlight:YES];
        } else {
            [self.scannerView hideFlashlight:YES];
        }
    }
}

2.3 设置metadataOutput的metadataObjectTypes

扫一扫时候,我们需要设置metadataOutput的metadataObjectTypes,决定扫描的二维码、条形码等。

/** 根据扫描器类型配置支持编码格式 */
+ (NSArray *)metadataObjectType:(SDScannerType)scannerType {
    switch (scannerType) {
        case SDScannerTypeQRCode:
        {
            return @[AVMetadataObjectTypeQRCode];
        }
            break;
        case SDScannerTypeBarCode:
        {
            return @[AVMetadataObjectTypeEAN13Code,
                     AVMetadataObjectTypeEAN8Code,
                     AVMetadataObjectTypeUPCECode,
                     AVMetadataObjectTypeCode39Code,
                     AVMetadataObjectTypeCode39Mod43Code,
                     AVMetadataObjectTypeCode93Code,
                     AVMetadataObjectTypeCode128Code,
                     AVMetadataObjectTypePDF417Code];
        }
            break;
        case SDScannerTypeBoth:
        {
            return @[AVMetadataObjectTypeQRCode,
                     AVMetadataObjectTypeEAN13Code,
                     AVMetadataObjectTypeEAN8Code,
                     AVMetadataObjectTypeUPCECode,
                     AVMetadataObjectTypeCode39Code,
                     AVMetadataObjectTypeCode39Mod43Code,
                     AVMetadataObjectTypeCode93Code,
                     AVMetadataObjectTypeCode128Code,
                     AVMetadataObjectTypePDF417Code];
        }
            break;
        default:
            break;
    }
}

根据扫描器类型配置支持编码格式,我们根据不同类型进行设置。

2.4 扫描开启与关闭

扫一扫使用了摄像头,这里关闭开启需要通过AVCaptureSession来控制

恢复扫一扫功能

/**
 恢复扫一扫功能
 */
- (void)resumeScanning {
    if (self.session) {
        [self.session startRunning];
        [self.scannerView startLineAnimation];
    }
}

暂停扫一扫

/**
 暂停扫一扫
 */
- (void)pauseScanning {
    if (self.session) {
        [self.session stopRunning];
        [self.scannerView stopLineAnimation];
    }
}

2.5 开启扫一扫或者打开相册的权限检查

  • 校验是否有相机权限
+ (void)checkCameraAuthorizationStatus:(void(^)(BOOL granted))permissionGranted
{
    AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    
    switch (videoAuthStatus) {
            // 已授权
        case AVAuthorizationStatusAuthorized:
        {
            permissionGranted(YES);
        }
            break;
            // 未询问用户是否授权
        case AVAuthorizationStatusNotDetermined:
        {
            // 提示用户授权
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                permissionGranted(granted);
            }];
        }
            break;
            // 用户拒绝授权或权限受限
        case AVAuthorizationStatusRestricted:
        case AVAuthorizationStatusDenied:
        {
            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"请在”设置-隐私-相机”选项中,允许访问你的相机" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
            [alert show];
            permissionGranted(NO);
        }
            break;
        default:
            break;
    }
}
  • 校验是否有相册权限
/** 校验是否有相册权限 */
+ (void)checkAlbumAuthorizationStatus:(void(^)(BOOL granted))permissionGranted {
    
    PHAuthorizationStatus photoAuthStatus = [PHPhotoLibrary authorizationStatus];
    switch (photoAuthStatus) {
            // 已授权
        case PHAuthorizationStatusAuthorized:
        {
            permissionGranted(YES);
        }
            break;
            // 未询问用户是否授权
        case PHAuthorizationStatusNotDetermined:
        {
            [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
                permissionGranted(status == PHAuthorizationStatusAuthorized);
            }];
        }
            break;
            // 用户拒绝授权或权限受限
        case PHAuthorizationStatusRestricted:
        case PHAuthorizationStatusDenied:
        {
            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"请在”设置-隐私-相片”选项中,允许访问你的相册" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
            [alert show];
            permissionGranted(NO);
        }
            break;
        default:
            break;
    }
    
}

2.6 扫描线条动画

实现扫一扫功能,我们需要实现一下扫一扫的动画效果,看起来界面更加提升用户体验。

我们使用基础动画CABasicAnimation来实现扫描线条的动画效果。

  • 开启动画
/** 添加扫描线条动画 */
- (void)startLineAnimation {
    
    // 若已添加动画,则先移除动画再添加
    [self.scannerLineView.layer removeAllAnimations];
    
    CABasicAnimation *lineAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    lineAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, [self scannerWidth] - kScannerLineHeight, 1)];
    lineAnimation.duration = 4;
    lineAnimation.repeatCount = MAXFLOAT;
    lineAnimation.autoreverses = YES; // 动画结束时执行逆动画
    [self.scannerLineView.layer addAnimation:lineAnimation forKey:scannerLineViewAnmationKey];
    // 重置动画运行速度为1.0
    self.scannerLineView.layer.speed = 2.0;
}
  • 暂停动画
/** 暂停扫描器动画 */
- (void)stopLineAnimation {
    // 取出当前时间,转成动画暂停的时间
    CFTimeInterval pauseTime = [self.scannerLineView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    // 设置动画的时间偏移量,指定时间偏移量的目的是让动画定格在该时间点的位置
    self.scannerLineView.layer.timeOffset = pauseTime;
    // 将动画的运行速度设置为0, 默认的运行速度是1.0
    self.scannerLineView.layer.speed = 0;
}

2.7 界面的其他手电筒操作

在我们需要开启和关闭手电筒,比如在比较黑暗的时候开启手电筒,在有光亮的地方关闭手电筒。

  • 显示手电筒
/** 显示手电筒 */
- (void)showFlashlight:(BOOL)animated {
    if (animated) {
        [UIView animateWithDuration:0.6 animations:^{
            self.lightTipsLabel.alpha = 1.0;
            self.lightButton.alpha = 1.0;
            self.tipsLabel.alpha = 0;
        } completion:^(BOOL finished) {
            self.lightButton.enabled = YES;
        }];
    } else {
        self.lightTipsLabel.alpha = 1.0;
        self.lightButton.alpha = 1.0;
        self.tipsLabel.alpha = 0;
        self.lightButton.enabled = YES;
    }
}
  • 藏手电筒
/** 隐藏手电筒 */
- (void)hideFlashlight:(BOOL)animated {
    self.lightButton.enabled = NO;
    if (animated) {
        [UIView animateWithDuration:0.6 animations:^{
            self.lightTipsLabel.alpha = 0;
            self.lightButton.alpha = 0;
            self.tipsLabel.alpha = 1.0;
        } completion:^(BOOL finished) {
        }];
    } else {
        self.tipsLabel.alpha = 1.0;
        self.lightTipsLabel.alpha = 0;
        self.lightButton.alpha = 0;
    }
}

2.8 绘制扫描区域

通过UIBezierPath绘制出扫描区域,扫描区域之外的则显示透明度为0.7的黑色遮罩效果。

/**
 draw绘制

 @param rect rect
 */
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    // 半透明区域
    [[UIColor colorWithWhite:0 alpha:0.7] setFill];
    UIRectFill(rect);
    
    // 透明区域
    CGRect scanner_rect = CGRectMake([self scannerOriginX], [self scannerOriginY], [self scannerWidth], [self scannerWidth]);
    [[UIColor clearColor] setFill];
    UIRectFill(scanner_rect);
    
    // 边框
    UIBezierPath *borderPath = [UIBezierPath bezierPathWithRect:CGRectMake([self scannerOriginX], [self scannerOriginY], [self scannerWidth], [self scannerWidth])];
    borderPath.lineCapStyle = kCGLineCapRound;
    borderPath.lineWidth = kScannerBorderWidth;
    [self.config.scannerBorderColor set];
    [borderPath stroke];
    
    for (int index = 0; index < 4; ++index) {
        
        UIBezierPath *tempPath = [UIBezierPath bezierPath];
        tempPath.lineWidth = kScannerCornerWidth;
        [self.config.scannerCornerColor set];
        
        switch (index) {
                // 左上角棱角
            case 0:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX] + kScannerCornerLength, [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] + kScannerCornerLength)];
            }
                break;
                // 右上角
            case 1:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth] - kScannerCornerLength, [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY] + kScannerCornerLength)];
            }
                break;
                // 左下角
            case 2:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] + [self scannerWidth] - kScannerCornerLength)];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] + [self scannerWidth])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + kScannerCornerLength, [self scannerOriginY] + [self scannerWidth])];
            }
                break;
                // 右下角
            case 3:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth] - kScannerCornerLength, [self scannerOriginY] + [self scannerWidth])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY] + [self scannerWidth])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY] + [self scannerWidth] - kScannerCornerLength)];
            }
                break;
            default:
                break;
        }
        [tempPath stroke];
    }
}

三、实现打开相册,识别图片二维码

我们打开相册,识别相册中图片的二维码,识别图片中的二维码需要CIDetector。

具体代码如下

#pragma mark -- UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    UIImage *pickImage = info[UIImagePickerControllerOriginalImage];
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
    // 获取选择图片中识别结果
    NSArray *features = [detector featuresInImage:[CIImage imageWithData:UIImagePNGRepresentation(pickImage)]];
    
    [picker dismissViewControllerAnimated:YES completion:^{
        if (features.count > 0) {
            CIQRCodeFeature *feature = features[0];
            NSString *stringValue = feature.messageString;
            [self handleScanValue:stringValue];
        } else {
            [self readFromAlbumFailed];
        }
    }];
}

四、实现扫一扫及识别图片中二维码的全部代码

实现扫一扫及识别图片中二维码的全部代码,主要是

SDQrScanViewController:UIViewController
SDQrScanView:UIView显示界面
SDQrScanTool:扫一扫权限及设置
SDQrScanConfig:扫一扫边框颜色等

完整代码如下

SDQrScanConfig.h

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

/**
 扫描类型
 */
typedef NS_ENUM(NSInteger, SDScannerType) {
    SDScannerTypeQRCode,
    SDScannerTypeBarCode,
    SDScannerTypeBoth,
};


/**
 扫描区域
 */
typedef NS_ENUM(NSInteger, SDScannerArea) {
    SDScannerAreaDefault,
    SDScannerAreaFullScreen,
};


/**
 扫一扫基础配置文件
 */
@interface SDQrScanConfig : NSObject

/**
 类型
 */
@property (nonatomic, assign) SDScannerType scannerType;

/**
 扫描区域
 */
@property (nonatomic, assign) SDScannerArea scannerArea;

/**
 棱角颜色
 */
@property (nonatomic, strong) UIColor *scannerCornerColor;

/**
 边框颜色
 */
@property (nonatomic, strong) UIColor *scannerBorderColor;

/**
 指示器风格
 */
@property (nonatomic, assign) UIActivityIndicatorViewStyle indicatorViewStyle;

@end

SDQrScanConfig.m

#import "SDQrScanConfig.h"

@implementation SDQrScanConfig

- (instancetype)init {
    self = [super init];
    if (self) {
        self.scannerCornerColor = [UIColor colorWithRed:63/255.0 green:187/255.0 blue:54/255.0 alpha:1.0];
        self.scannerBorderColor = [UIColor whiteColor];
        self.indicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
        self.scannerType = SDScannerTypeQRCode;
    }
    return self;
}

@end

SDQrScanTool.h

#import <Foundation/Foundation.h>
#import <Photos/PHPhotoLibrary.h>
#import <AVFoundation/AVFoundation.h>
#import "SDQrScanConfig.h"

@interface SDQrScanTool : NSObject

/**
 校验是否有相机权限
 
 @param permissionGranted 获取相机权限回调
 */
+ (void)checkCameraAuthorizationStatus:(void(^)(BOOL granted))permissionGranted;

/**
 校验是否有相册权限
 
 @param permissionGranted 获取相机权限回调
 */
+ (void)checkAlbumAuthorizationStatus:(void(^)(BOOL granted))permissionGranted;

/**
 根据扫描器类型配置支持编码格式
 
 @param scannerType 扫描器类型
 @return 编码格式组成的数组
 */
+ (NSArray *)metadataObjectType:(SDScannerType)scannerType;


/**
 根据扫描器类型配置导航栏标题

 @param scannerType 扫描器类型
 @return 标题
 */
+ (NSString *)navigationItemTitle:(SDScannerType)scannerType;

/**
 手电筒开关

 @param on YES:打开 NO:关闭
 */
+ (void)flashlightOn:(BOOL)on;

@end

SDQrScanTool.m

#import "SDQrScanTool.h"

@implementation SDQrScanTool

/** 校验是否有相机权限 */
+ (void)checkCameraAuthorizationStatus:(void(^)(BOOL granted))permissionGranted
{
    AVAuthorizationStatus videoAuthStatus = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
    
    switch (videoAuthStatus) {
            // 已授权
        case AVAuthorizationStatusAuthorized:
        {
            permissionGranted(YES);
        }
            break;
            // 未询问用户是否授权
        case AVAuthorizationStatusNotDetermined:
        {
            // 提示用户授权
            [AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
                permissionGranted(granted);
            }];
        }
            break;
            // 用户拒绝授权或权限受限
        case AVAuthorizationStatusRestricted:
        case AVAuthorizationStatusDenied:
        {
            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"请在”设置-隐私-相机”选项中,允许访问你的相机" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
            [alert show];
            permissionGranted(NO);
        }
            break;
        default:
            break;
    }
}

/** 校验是否有相册权限 */
+ (void)checkAlbumAuthorizationStatus:(void(^)(BOOL granted))permissionGranted {
    
    PHAuthorizationStatus photoAuthStatus = [PHPhotoLibrary authorizationStatus];
    switch (photoAuthStatus) {
            // 已授权
        case PHAuthorizationStatusAuthorized:
        {
            permissionGranted(YES);
        }
            break;
            // 未询问用户是否授权
        case PHAuthorizationStatusNotDetermined:
        {
            [PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {
                permissionGranted(status == PHAuthorizationStatusAuthorized);
            }];
        }
            break;
            // 用户拒绝授权或权限受限
        case PHAuthorizationStatusRestricted:
        case PHAuthorizationStatusDenied:
        {
            UIAlertView *alert = [[UIAlertView alloc]initWithTitle:@"请在”设置-隐私-相片”选项中,允许访问你的相册" message:nil delegate:nil cancelButtonTitle:@"确定" otherButtonTitles:nil];
            [alert show];
            permissionGranted(NO);
        }
            break;
        default:
            break;
    }
    
}

/** 根据扫描器类型配置支持编码格式 */
+ (NSArray *)metadataObjectType:(SDScannerType)scannerType {
    switch (scannerType) {
        case SDScannerTypeQRCode:
        {
            return @[AVMetadataObjectTypeQRCode];
        }
            break;
        case SDScannerTypeBarCode:
        {
            return @[AVMetadataObjectTypeEAN13Code,
                     AVMetadataObjectTypeEAN8Code,
                     AVMetadataObjectTypeUPCECode,
                     AVMetadataObjectTypeCode39Code,
                     AVMetadataObjectTypeCode39Mod43Code,
                     AVMetadataObjectTypeCode93Code,
                     AVMetadataObjectTypeCode128Code,
                     AVMetadataObjectTypePDF417Code];
        }
            break;
        case SDScannerTypeBoth:
        {
            return @[AVMetadataObjectTypeQRCode,
                     AVMetadataObjectTypeEAN13Code,
                     AVMetadataObjectTypeEAN8Code,
                     AVMetadataObjectTypeUPCECode,
                     AVMetadataObjectTypeCode39Code,
                     AVMetadataObjectTypeCode39Mod43Code,
                     AVMetadataObjectTypeCode93Code,
                     AVMetadataObjectTypeCode128Code,
                     AVMetadataObjectTypePDF417Code];
        }
            break;
        default:
            break;
    }
}

/** 根据扫描器类型配置导航栏标题 */
+ (NSString *)navigationItemTitle:(SDScannerType)scannerType {
    switch (scannerType) {
        case SDScannerTypeQRCode:
        {
            return @"二维码";
        }
            break;
        case SDScannerTypeBarCode:
        {
            return @"条码";
        }
            break;
        case SDScannerTypeBoth:
        {
            return @"二维码/条码";
        }
            break;
        default:
            break;
    }
}

/** 手电筒开关 */
+ (void)flashlightOn:(BOOL)on {
    AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    if ([captureDevice hasTorch] && [captureDevice hasFlash]) {
        [captureDevice lockForConfiguration:nil];
        if (on) {
            [captureDevice setTorchMode:AVCaptureTorchModeOn];
            [captureDevice setFlashMode:AVCaptureFlashModeOn];
        }else
        {
            [captureDevice setTorchMode:AVCaptureTorchModeOff];
            [captureDevice setFlashMode:AVCaptureFlashModeOff];
        }
        [captureDevice unlockForConfiguration];
    }
}

@end

SDQrScanView.h

#import <UIKit/UIKit.h>
#import "SDQrScanTool.h"
#import "SDBaseControllerView.h"

@interface SDQrScanView : SDBaseControllerView

- (instancetype)initWithFrame:(CGRect)frame config:(SDQrScanConfig *)config;

/**
 启动扫描线条动画
 */
- (void)startLineAnimation;

/**
 停止扫描线条动画
 */
- (void)stopLineAnimation;

/**
 添加指示器
 */
- (void)addActivityIndicator;

/**
 移除指示器
 */
- (void)removeActivityIndicator;

/**
 扫描器坐标点X

 @return 坐标点X
 */
- (CGFloat)scannerOriginX;

/**
 扫描器坐标点Y
 
 @return 坐标点Y
 */
- (CGFloat)scannerOriginY;

/**
 扫描器宽度
 
 @return 宽度
 */
- (CGFloat)scannerWidth;

/**
 显示手电筒
 @param animated 是否附带动画
 */
- (void)showFlashlight:(BOOL)animated;

/**
 隐藏手电筒
 @param animated 是否附带动画
 */
- (void)hideFlashlight:(BOOL)animated;

/**
 设置手电筒开关
 @param on YES:开  NO:关
 */
- (void)setFlashlightOn:(BOOL)on;

/**
 获取手电筒当前开关状态
 @return YES:开  NO:关
 */
- (BOOL)flashlightOn;

@end

SDQrScanView.m

#import "SDQrScanView.h"
#import "objc/runtime.h"

static const CGFloat kScannerScale = 0.7;           //屏幕宽度的比例

static const CGFloat kBottomSpace = 50.0;           //居中对齐后向上偏移的距离

static const CGFloat kScannerLineHeight = 10.0;     //扫描器线条高度

static const CGFloat kTipsHeight = 50.0;            //底部提示高度

static const CGFloat kLightSize = 20.0f;            //灯光size

static const CGFloat kLightTipsHeight = 15.0f;      //灯光提示间距

static const CGFloat kLightTipsPadding = 10.0f;     //灯光提示间距

static const CGFloat kScannerBorderWidth = 1.0f;    //扫描器边框宽度

static const CGFloat kScannerCornerWidth = 3.0f;    //扫描器棱角宽度

static const CGFloat kScannerCornerLength = 20.0f;  //扫描器棱角长度

NSString *const scannerLineViewAnmationKey = @"scannerLineViewAnmationKey"; //扫描线条动画Key值

@interface SDQrScanView()

@property (nonatomic, strong) UIImageView *scannerLineView; /** 扫描线条 */
@property (nonatomic, strong) UIActivityIndicatorView *activityIndicator; /** 加载指示器 */
@property (nonatomic, strong) UIButton *lightButton;  /** 手电筒开关 */
@property (nonatomic, strong) UILabel *lightTipsLabel;   /** 手电筒提示文字 */
@property (nonatomic, strong) UILabel *tipsLabel;  /** 扫描器下方提示文字 */

@property (nonatomic, strong) SDQrScanConfig *config;

@property (nonatomic, assign) BOOL lightOn; //手电筒开关是否打开

@end

@implementation SDQrScanView

- (instancetype)initWithFrame:(CGRect)frame config:(SDQrScanConfig *)config {
    self = [super initWithFrame:frame];
    if (self) {
        self.config = config;
        [self setupViews];
        [self bringSubviewToFront:self.navigationBar];
    }
    return self;
}

- (void)setupViews {
    self.backgroundColor = [UIColor clearColor];
    [self addSubview:self.scannerLineView];
    [self addSubview:self.tipsLabel];
    [self addSubview:self.lightButton];
    [self addSubview:self.lightTipsLabel];
    
    [self startLineAnimation];
}

- (void)layoutSubviews {
    [super layoutSubviews];
    CGFloat width = CGRectGetWidth(self.bounds);
    CGFloat height = CGRectGetHeight(self.bounds);
    CGFloat scannerWidth = kScannerScale * width;
    CGFloat originX = (width - scannerWidth)/2;
    CGFloat originY = (height - scannerWidth)/2 - kBottomSpace;

    self.scannerLineView.frame = CGRectMake(originX, originY, scannerWidth, kScannerLineHeight);
    
    self.tipsLabel.frame = CGRectMake(0, originY + scannerWidth, width, kTipsHeight);
    
    self.lightButton.frame = CGRectMake((width - kLightSize)/2.0, CGRectGetMinY(self.tipsLabel.frame) - kLightTipsPadding - kLightTipsHeight - kLightSize, kLightSize, kLightSize);

    self.lightTipsLabel.frame = CGRectMake(originX, CGRectGetMinY(self.tipsLabel.frame) - kLightTipsPadding - kLightTipsHeight, scannerWidth, kLightTipsHeight);
}

#pragma mark -- 手电筒点击事件
- (void)flashlightClicked:(UIButton *)button {
    button.selected = !button.selected;
    [self setFlashlightOn:self.lightButton.selected];
}

/** 添加扫描线条动画 */
- (void)startLineAnimation {
    
    // 若已添加动画,则先移除动画再添加
    [self.scannerLineView.layer removeAllAnimations];
    
    CABasicAnimation *lineAnimation = [CABasicAnimation animationWithKeyPath:@"transform"];
    lineAnimation.toValue = [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, [self scannerWidth] - kScannerLineHeight, 1)];
    lineAnimation.duration = 4;
    lineAnimation.repeatCount = MAXFLOAT;
    lineAnimation.autoreverses = YES; // 动画结束时执行逆动画
    [self.scannerLineView.layer addAnimation:lineAnimation forKey:scannerLineViewAnmationKey];
    // 重置动画运行速度为1.0
    self.scannerLineView.layer.speed = 2.0;
}

/** 暂停扫描器动画 */
- (void)stopLineAnimation {
    // 取出当前时间,转成动画暂停的时间
    CFTimeInterval pauseTime = [self.scannerLineView.layer convertTime:CACurrentMediaTime() fromLayer:nil];
    // 设置动画的时间偏移量,指定时间偏移量的目的是让动画定格在该时间点的位置
    self.scannerLineView.layer.timeOffset = pauseTime;
    // 将动画的运行速度设置为0, 默认的运行速度是1.0
    self.scannerLineView.layer.speed = 0;
}

/** 显示手电筒 */
- (void)showFlashlight:(BOOL)animated {
    if (animated) {
        [UIView animateWithDuration:0.6 animations:^{
            self.lightTipsLabel.alpha = 1.0;
            self.lightButton.alpha = 1.0;
            self.tipsLabel.alpha = 0;
        } completion:^(BOOL finished) {
            self.lightButton.enabled = YES;
        }];
    } else {
        self.lightTipsLabel.alpha = 1.0;
        self.lightButton.alpha = 1.0;
        self.tipsLabel.alpha = 0;
        self.lightButton.enabled = YES;
    }
}

/** 隐藏手电筒 */
- (void)hideFlashlight:(BOOL)animated {
    self.lightButton.enabled = NO;
    if (animated) {
        [UIView animateWithDuration:0.6 animations:^{
            self.lightTipsLabel.alpha = 0;
            self.lightButton.alpha = 0;
            self.tipsLabel.alpha = 1.0;
        } completion:^(BOOL finished) {
        }];
    } else {
        self.tipsLabel.alpha = 1.0;
        self.lightTipsLabel.alpha = 0;
        self.lightButton.alpha = 0;
    }
}

/** 添加指示器 */
- (void)addActivityIndicator {
    if (!self.activityIndicator) {
        self.activityIndicator = [[UIActivityIndicatorView alloc]initWithActivityIndicatorStyle:self.config.indicatorViewStyle];
        self.activityIndicator.center = self.center;
        [self addSubview:self.activityIndicator];
    }
    [self.activityIndicator startAnimating];
}

/**
 移除指示器
 */
- (void)removeActivityIndicator {
    if (self.activityIndicator) {
        [self.activityIndicator removeFromSuperview];
        self.activityIndicator = nil;
    }
}

/**
 设置手电筒开关

 @param on 是否打开,YES打开,NO,关闭
 */
- (void)setFlashlightOn:(BOOL)on {
    [SDQrScanTool flashlightOn:on];
    self.lightTipsLabel.text = on ? @"轻触关闭":@"轻触照亮";
    self.lightButton.selected = on;
    self.lightOn = on;
}

/**
 获取手电筒当前开关状态

 @return 开关状态, YES 打开状态, NO 关闭状态
 */
- (BOOL)flashlightOn {
    return self.lightOn;
}

/**
 draw绘制

 @param rect rect
 */
- (void)drawRect:(CGRect)rect {
    [super drawRect:rect];
    
    // 半透明区域
    [[UIColor colorWithWhite:0 alpha:0.7] setFill];
    UIRectFill(rect);
    
    // 透明区域
    CGRect scanner_rect = CGRectMake([self scannerOriginX], [self scannerOriginY], [self scannerWidth], [self scannerWidth]);
    [[UIColor clearColor] setFill];
    UIRectFill(scanner_rect);
    
    // 边框
    UIBezierPath *borderPath = [UIBezierPath bezierPathWithRect:CGRectMake([self scannerOriginX], [self scannerOriginY], [self scannerWidth], [self scannerWidth])];
    borderPath.lineCapStyle = kCGLineCapRound;
    borderPath.lineWidth = kScannerBorderWidth;
    [self.config.scannerBorderColor set];
    [borderPath stroke];
    
    for (int index = 0; index < 4; ++index) {
        
        UIBezierPath *tempPath = [UIBezierPath bezierPath];
        tempPath.lineWidth = kScannerCornerWidth;
        [self.config.scannerCornerColor set];
        
        switch (index) {
                // 左上角棱角
            case 0:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX] + kScannerCornerLength, [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] + kScannerCornerLength)];
            }
                break;
                // 右上角
            case 1:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth] - kScannerCornerLength, [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY] + kScannerCornerLength)];
            }
                break;
                // 左下角
            case 2:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] + [self scannerWidth] - kScannerCornerLength)];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] + [self scannerWidth])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + kScannerCornerLength, [self scannerOriginY] + [self scannerWidth])];
            }
                break;
                // 右下角
            case 3:
            {
                [tempPath moveToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth] - kScannerCornerLength, [self scannerOriginY] + [self scannerWidth])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY] + [self scannerWidth])];
                [tempPath addLineToPoint:CGPointMake([self scannerOriginX] + [self scannerWidth], [self scannerOriginY] + [self scannerWidth] - kScannerCornerLength)];
            }
                break;
            default:
                break;
        }
        [tempPath stroke];
    }
}

#pragma mark - 扫描器坐标点位置
/**
 扫描器坐标点X
 
 @return 坐标点X
 */
- (CGFloat)scannerOriginX {
    CGFloat width = CGRectGetWidth(self.bounds);
    CGFloat scannerWidth = kScannerScale * width;
    CGFloat originX = (width - scannerWidth)/2;
    
    return originX;
}

/**
 扫描器坐标点Y
 
 @return 坐标点Y
 */
- (CGFloat)scannerOriginY {
    CGFloat width = CGRectGetWidth(self.bounds);
    CGFloat height = CGRectGetHeight(self.bounds);
    CGFloat scannerWidth = kScannerScale * width;
    CGFloat originY = (height - scannerWidth)/2 - kBottomSpace;
    return originY;
}

/**
 扫描器宽度
 
 @return 宽度
 */
- (CGFloat)scannerWidth {
    CGFloat width = CGRectGetWidth(self.bounds);
    CGFloat scannerWidth = kScannerScale * width;
    return scannerWidth;
}

#pragma mark - SETTER/GETTER
/**
 扫描线条
 
 @return 扫描线条ImageView
 */
- (UIImageView *)scannerLineView {
    if (!_scannerLineView) {
        _scannerLineView = [[UIImageView alloc] initWithFrame:CGRectZero];
        _scannerLineView.image = [UIImage imageNamed:@"ScannerLine"];
    }
    return _scannerLineView;
}

/**
 扫描器下方提示文字
 
 @return 下方提示文字Label
 */
- (UILabel *)tipsLabel {
    if (!_tipsLabel) {
        _tipsLabel = [[UILabel alloc]initWithFrame:CGRectZero];
        _tipsLabel.textAlignment = NSTextAlignmentCenter;
        _tipsLabel.textColor = [UIColor lightGrayColor];
        _tipsLabel.text = @"将二维码/条码放入框内,即可自动扫描";
        _tipsLabel.font = [UIFont systemFontOfSize:12];
    }
    return _tipsLabel;
}

/**
 手电筒开关按钮
 
 @return 开关按钮Button
 */
- (UIButton *)lightButton {
    if (!_lightButton) {
        _lightButton = [UIButton buttonWithType:UIButtonTypeCustom];
        _lightButton.enabled = NO;
        _lightButton.alpha = 0;
        [_lightButton addTarget:self action:@selector(flashlightClicked:) forControlEvents:UIControlEventTouchUpInside];
        [_lightButton setBackgroundImage:[UIImage imageNamed:@"Flashlight_Off"] forState:UIControlStateNormal];
        [_lightButton setBackgroundImage:[UIImage imageNamed:@"Flashlight_On"] forState:UIControlStateSelected];
    }
    return _lightButton;
}

/**
 手电筒提示文字

 @return 提示文字控件Label
 */
- (UILabel *)lightTipsLabel {
    if (!_lightTipsLabel) {
        _lightTipsLabel = [[UILabel alloc] initWithFrame:CGRectZero];
        _lightTipsLabel.font = [UIFont systemFontOfSize:12];
        _lightTipsLabel.textColor = [UIColor whiteColor];
        _lightTipsLabel.text = @"轻触照亮";
        _lightTipsLabel.alpha = 0;
        _lightTipsLabel.textAlignment = NSTextAlignmentCenter;
    }
    return _lightTipsLabel;
}


@end

SDQrScanViewController.h

#import <UIKit/UIKit.h>
#import <AVFoundation/AVFoundation.h>
#import "SDQrScanTool.h"
#import "SDBaseViewController.h"

@interface SDQrScanViewController : SDBaseViewController

@property (nonatomic, strong) SDQrScanConfig *scanConfig;

@end

SDQrScanViewController.m

#import "SDQrScanViewController.h"
#import "SDQrScanView.h"

@interface SDQrScanViewController ()<AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegate>

@property (nonatomic, strong) SDQrScanView *scannerView;
@property (nonatomic, strong) AVCaptureSession *session;

@property (nonatomic, strong) UIView *videoPreView; //视频预览显示视图

@end

@implementation SDQrScanViewController

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

- (SDQrScanConfig *)scanConfig {
    if (!_scanConfig) {
        _scanConfig = [[SDQrScanConfig alloc] init];
    }
    return _scanConfig;
}

- (SDQrScanView *)scannerView {
    if (!_scannerView) {
        _scannerView = [[SDQrScanView alloc] initWithFrame:self.view.bounds config:self.scanConfig];
    }
    return _scannerView;
}

- (UIView *)videoPreView {
    if (!_videoPreView) {
        _videoPreView = [[UIView alloc] initWithFrame:self.view.bounds];
    }
    _videoPreView.backgroundColor = [UIColor clearColor];
    return _videoPreView;
}


#pragma mark - Configure NavigationBar
- (void)configureNavigationBar {
    SDNavButtonItem *leftButtonItem = [[SDNavButtonItem alloc] initWithTitle:nil image:[UIImage imageNamed:@"ic_nav_back_gray"] target:self action:@selector(leftBarClicked)];
    
    SDNavButtonItem *rightButtonItem = [[SDNavButtonItem alloc] initWithTitle:nil image:[UIImage imageNamed:@"ic_nav_common_download"] target:self action:@selector(rightBarClicked)];
    
    self.scannerView.navigationBar.navTitleView = [[SDNavigationTitleView alloc] initWidthTitle:@"扫一扫" subView:nil];
    self.scannerView.navigationBar.leftNavItem = leftButtonItem;
    // self.scannerView.navigationBar.rightNavItem = rightButtonItem;
}

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

- (void)rightBarClicked {
    // 扫一扫
}

#pragma mark - loadView
- (void)loadView {
    [super loadView];
}

- (void)viewDidLoad {
    [super viewDidLoad];
    self.navigationItem.title = @"扫一扫";
    [self configureNavigationBar];
    [self setupScannerLayer];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appDidBecomeActive:)
                                                 name:UIApplicationDidBecomeActiveNotification
                                               object:nil];
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(appWillResignActive:)
                                                 name:UIApplicationWillResignActiveNotification
                                               object:nil];
}

- (void)viewWillAppear:(BOOL)animated {
    [super viewWillAppear:animated];
    [self resumeScanning];
}

- (void)viewWillDisappear:(BOOL)animated
{
    [super viewWillDisappear:animated];
    [self.scannerView setFlashlightOn:NO];
    [self.scannerView hideFlashlight:YES];
}

- (void)setupScannerLayer {
    
    self.view.backgroundColor = [UIColor blackColor];
    UIBarButtonItem *albumItem = [[UIBarButtonItem alloc]initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(showAlbum)];
    [albumItem setTintColor:[UIColor blackColor]];
    self.navigationItem.rightBarButtonItem = albumItem;
    
    [self.view addSubview:self.videoPreView];
    [self.view addSubview:self.scannerView];
    
    // 校验相机权限
    [SDQrScanTool checkCameraAuthorizationStatus:^(BOOL granted) {
        if (granted) {
            dispatch_async(dispatch_get_main_queue(), ^{
                [self loadScanView];
            });
        }
    }];
}

/** 创建扫描器 */
- (void)loadScanView {
    
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
    
    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    if (self.scanConfig.scannerArea == SDScannerAreaDefault) {
        metadataOutput.rectOfInterest = CGRectMake([self.scannerView scannerOriginX]/self.view.frame.size.height, [self.scannerView scannerOriginY]/self.view.frame.size.width, [self.scannerView scannerWidth]/self.view.frame.size.height, [self.scannerView scannerWidth]/self.view.frame.size.width);
    }
    
    AVCaptureVideoDataOutput *videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    [videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
    
    self.session = [[AVCaptureSession alloc]init];
    [self.session setSessionPreset:AVCaptureSessionPresetHigh];
    if ([self.session canAddInput:deviceInput]) {
        [self.session addInput:deviceInput];
    }
    if ([self.session canAddOutput:metadataOutput]) {
        [self.session addOutput:metadataOutput];
    }
    if ([self.session canAddOutput:videoDataOutput]) {
        [self.session addOutput:videoDataOutput];
    }
    
    metadataOutput.metadataObjectTypes = [SDQrScanTool metadataObjectType:self.scanConfig.scannerType];
    
    AVCaptureVideoPreviewLayer *videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
    videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    videoPreviewLayer.frame = self.view.layer.bounds;
    [self.videoPreView.layer insertSublayer:videoPreviewLayer atIndex:0];
    
    [self.session startRunning];
}

#pragma mark -- 跳转相册
- (void)imagePicker {
    UIImagePickerController *imagePicker = [[UIImagePickerController alloc]init];
    imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    imagePicker.delegate = self;
    [self presentViewController:imagePicker animated:YES completion:nil];
}

#pragma mark -- AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray<__kindof AVMetadataObject *> *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
    
    // 获取扫一扫结果
    if (metadataObjects && metadataObjects.count > 0) {
        
        [self pauseScanning];
        AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects[0];
        NSString *stringValue = metadataObject.stringValue;
        //AVMetadataMachineReadableCodeObject *obj = (AVMetadataMachineReadableCodeObject *)[self.lay transformedMetadataObjectForMetadataObject:metadataObjects.lastObject];
        //[self changeVideoScale:metadataObject];

        [self handleScanValue:stringValue];
    }
}

#pragma mark -- AVCaptureVideoDataOutputSampleBufferDelegate
/** 此方法会实时监听亮度值 */
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
    
    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 (![self.scannerView flashlightOn]) {
        if (brightnessValue < -1.0) {
            [self.scannerView showFlashlight:YES];
        } else {
            [self.scannerView hideFlashlight:YES];
        }
    }
}

- (void)showAlbum {
    // 校验相册权限
    [SDQrScanTool checkAlbumAuthorizationStatus:^(BOOL granted) {
        if (granted) {
            [self imagePicker];
        }
    }];
}

#pragma mark -- UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info
{
    UIImage *pickImage = info[UIImagePickerControllerOriginalImage];
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
    // 获取选择图片中识别结果
    NSArray *features = [detector featuresInImage:[CIImage imageWithData:UIImagePNGRepresentation(pickImage)]];
    
    [picker dismissViewControllerAnimated:YES completion:^{
        if (features.count > 0) {
            CIQRCodeFeature *feature = features[0];
            NSString *stringValue = feature.messageString;
            [self handleScanValue:stringValue];
        } else {
            [self readFromAlbumFailed];
        }
    }];
}

- (void)changeVideoScale:(AVMetadataMachineReadableCodeObject *)objc {
    NSArray *array = objc.corners;
    CGPoint point = CGPointZero;
    int index = 0;
    CFDictionaryRef dict = (__bridge CFDictionaryRef)(array[index++]);
    // 把点转换为不可变字典
    // 把字典转换为点,存在point里,成功返回true 其他false
    CGPointMakeWithDictionaryRepresentation(dict, &point);
    NSLog(@"X:%f -- Y:%f",point.x,point.y);
    CGPoint point2 = CGPointZero;
    CGPointMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)array[2], &point2);
    NSLog(@"X:%f -- Y:%f",point2.x,point2.y);
    
    NSLog(@"bounds:%@",NSStringFromCGRect(objc.bounds));

}


#pragma mark -- App 从后台进入前台
- (void)appDidBecomeActive:(NSNotification *)notify {
    [self resumeScanning];
}

#pragma mark -- App 从前台进入后台
- (void)appWillResignActive:(NSNotification *)notify {
    [self pauseScanning];
}

/**
 恢复扫一扫功能
 */
- (void)resumeScanning {
    if (self.session) {
        [self.session startRunning];
        [self.scannerView startLineAnimation];
    }
}

/**
 暂停扫一扫
 */
- (void)pauseScanning {
    if (self.session) {
        [self.session stopRunning];
        [self.scannerView stopLineAnimation];
    }
}

#pragma mark -- 扫一扫API
/**
 处理扫一扫结果
 @param value 扫描结果
 */
- (void)handleScanValue:(NSString *)value {
    NSLog(@"handleScanValue === %@", value);
}

/**
 相册选取图片无法读取数据
 */
- (void)readFromAlbumFailed {
    NSLog(@"readFromAlbumFailed");
}

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

@end

至此实现了二维码扫一扫Scan及识别图片中二维码功能。

五、小结

iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能。扫一扫是使用摄像头扫码二维码或者条形码,获取对应二维码或条形码内容字符串。识别图中二维码通过CIDetector来识别出内容字符串。最后实现响应的业务逻辑。

学习记录,每天不停进步。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/70751.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

布局性能优化:安卓开发者不可错过的性能优化技巧

作者&#xff1a;麦客奥德彪 当我们开发Android应用时&#xff0c;布局性能优化是一个必不可少的过程。一个高效的布局能够提高用户体验&#xff0c;使应用更加流畅、响应更加迅速&#xff0c;而低效的布局则会导致应用的运行变得缓慢&#xff0c;甚至出现卡顿、崩溃等问题&…

npm ERR! code ERESOLVEnpm ERR! ERESOLVE unable to resolve dependency tree

拉取项目到本地 执行 npm install 报错 遇到这个问题首先确认的就是版本是不是太高了&#xff0c;降一下版本。或者通过yarn命令替代npm install命令安装&#xff0c;同理&#xff0c;启动也可以采用yarn dev 启动代替npm run dev 下面教大家用一个NVM工具&#xff0c;这个工…

SSL握手协议相关概念

下图为握手协议的流程图&#xff0c;具体的解释参考博客&#xff1a; 【下】安全HTTPS-全面详解对称加密&#xff0c;非对称加密&#xff0c;数字签名&#xff0c;数字证书和HTTPS_tenfyguo的博客-CSDN博客 下面梳理一下SSL协议中的一些细节。首先是相关名词&#xff1a;证书、…

深度学习基础知识笔记

深度学习要解决的问题 1 深度学习要解决的问题2 应用领域3 计算机视觉任务4 视觉任务中遇到的问题5 得分函数6 损失函数7 前向传播整体流程8 返向传播计算方法1 梯度下降 9 神经网络整体架构11 神经元个数对结果的影响12 正则化和激活函数1 正则化2 激活函数 13 神经网络过拟合…

前端开发常见效果

目录 css实现图像填充文字 css实现手风琴效果 css实现网站变灰色 elementUi的导航栏效果 css实现滚动吸附效果 鼠标经过&#xff0c;元素内部放大 css实现图像填充文字 效果图&#xff1a; 代码&#xff1a; <!DOCTYPE html> <html><head><meta c…

5.2 互联网通信安全

数据参考&#xff1a;CISP官方 目录 一、什么是互联网通信安全二、为什么要关注互联网通信安全三、电子邮件应用安全四、即时通讯应用安全 一、什么是互联网通信安全 1、互联网通信应用的概念 通信的进化史 互联网通信技术&#xff08;OSI七层模型&#xff09; 互联网应…

Effective Java笔记(29)优先考虑泛型

一般来说 &#xff0c;将集合声 明参数化&#xff0c;以及使用 JDK 所提供的泛型方法&#xff0c;这些都不太困难 。编写自己的泛型会比较困难一些&#xff0c;但是值得花些时间去学习如何编写 。 以简单的&#xff08;玩具&#xff09;堆校实现为例 &#xff1a; // Object -…

创新引领城市进化:人工智能和大数据塑造智慧城市新面貌

人工智能和大数据等前沿技术正以惊人的速度融入智慧城市的方方面面&#xff0c;为城市的发展注入了强大的智慧和活力。这些技术的应用不仅令城市管理更高效、居民生活更便捷&#xff0c;还为可持续发展和创新奠定了坚实的基础。 在智慧城市中&#xff0c;人工智能技术正成为城市…

分享一组天气组件

先看效果&#xff1a; CSS部分代码&#xff08;查看更多&#xff09;&#xff1a; <style>:root {--bg-color: #E9F5FA;--day-text-color: #4DB0D3;/* 多云 */--cloudy-background: #4DB0D3;--cloudy-temperature: #E6DF95;--cloudy-content: #D3EBF4;/* 晴 */--sunny-b…

TypeScript 中【class类】与 【 接口 Interfaces】的联合搭配使用解读

导读&#xff1a; 前面章节&#xff0c;我们讲到过 接口&#xff08;Interface&#xff09;可以用于对「对象的形状&#xff08;Shape&#xff09;」进行描述。 本章节主要介绍接口的另一个用途&#xff0c;对类的一部分行为进行抽象。 类配合实现接口 实现&#xff08;impleme…

中科亿海微RAM使用

引言 FPGA&#xff08;Field Programmable Gate Array&#xff0c;现场可编程门阵列&#xff09;是一种可编程逻辑设备&#xff0c;能够根据特定应用的需求进行配置和重新编程。在FPGA中&#xff0c;RAM&#xff08;Random Access Memory&#xff0c;随机存取存储器&#xff09…

Maven在IDEA2021版本中全局配置(一次配置处处生效)

前言 我们在开发中&#xff0c;Maven是必不可少的&#xff0c;但是每次都需要设置一遍Maven的仓库和settings.xml。真的是心累&#xff0c;今天教大家全局配置一下。再也不要每次项目都配了&#xff0c;Maven还经常出问题。 解决方案 友情提示&#xff1a;小编的IDEA版本为2…

【Fegin技术专题】「原生态」打开Fegin之RPC技术的开端,你会使用原生态的Fegin吗?(中)

你可以使用 Jersey 和 CXF 这些来写一个 Rest 或 SOAP 服务的java客服端。 你也可以直接使用 Apache HttpClient 来实现。但是 Feign 的目的是尽量的减少资源和代码来实现和 HTTP API 的连接。 *通过自定义的编码解码器以及错误处理&#xff0c;你可以编写任何基于文本的 HTT…

改进DevSecOps框架的 5 大关键技术

Markets and Markets的一项研究显示&#xff0c;全球DevOps的市场规模从2017年的29亿美元增加到2023年的103.1亿美元&#xff0c;预测期的年复合增长率(CAGR)为24.7%。人们对DevOps越来越感兴趣&#xff0c;因为DevOps不仅能够压缩软件的交付周期&#xff0c;还能提高交付的速度…

c++QT文件操作

1 介绍 QT的文件操作来源于其抽象基类QIODevice&#xff0c;中用于处理输入输出设备。提供了统一的接口来处理不同类型的数据源&#xff0c;如文件、套接字、缓冲区等。QIODevice 主要用于读取和写入数据&#xff0c;无论数据来自何种源头&#xff0c;都可以通过 QIODevice 统一…

HDFS中snapshot快照机制

HDFS中snapshot快照机制 介绍作用功能实现相关命令和操作相关命令 介绍 snapshot是数据存储的某一时刻的状态记录&#xff0c;备份&#xff08;backup&#xff09;则是数据存储的某一个时刻的副本HDFS snapshot快照是整个文件系统或某个目录在某个时刻的镜像&#xff0c;该镜像…

安路FPGA的赋值报错——移位处理,加括号

authordaisy.skye的博客_CSDN博客-嵌入式,Qt,Linux领域博主 在使用移位符号用来当作除以号使用时&#xff0c;发现如下问题 其中 cnt_8K 为偶数和奇数时输出的数据不一样 reg [10:0] cnt_8K; reg [10:0] ram1_addra; always(posedge clk_16M) begin if(ram_out_flag )begin if(…

20230811导出Redmi Note12Pro 5G手机的录音机APP的录音

20230811导出Redmi Note12Pro 5G手机的录音机APP的录音 2023/8/11 10:54 redmi note12 pro 录音文件 位置 貌似必须导出录音&#xff0c;录音的源文件不知道存储到哪里了&#xff01; 参考资料&#xff1a; https://jingyan.baidu.com/article/b87fe19e9aa79b1319356842.html 红…

【MySQL】InnoDB存储引擎详解

InnoDB引擎是MySQL5.5版本之后默认的存储引擎 逻辑存储结构 首先是表空间Tablespace&#xff08;ibd文件&#xff09;&#xff1a;一个mysql实力可以对应多个表空间&#xff0c;用于存储及记录&#xff0c;索引等数据 这些存储记录&#xff0c;索引等数据中是用段(Segment)来…

宋浩概率论笔记(四)数字特征

本帖更新数字特征&#xff0c;包含期望、方差、相关系数等&#xff0c;要点在于记忆性质中的各种公式&#xff0c;遇到题目时能迅速利用已知条件计算答案。