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

news2024/12/26 14:15:36

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来识别出内容字符串。最后实现响应的业务逻辑。

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++ 管理系统实战

C 管理系统实战 1. 目录结构 2. workClass.h /********************************************************************************* file : employee.h* author : zhong* brief : None* attention : None* date : 2023/8/10*****…

使用阿里云服务器搭建ThinkPHP框架全流程_新手入门

阿里云百科分享使用lay服务器搭建ThinkPHP全流程&#xff0c;ThinkPHP是一款免费、开源、快速、简单、面向对象的轻量级PHP开发框架&#xff0c;遵循Apache2开源协议发布&#xff0c;是为了敏捷Web应用开发和简化企业应用开发而诞生的。本篇教程介绍如何使用云市场镜像快速搭建…

学术论文GPT源码解读:从chatpaper、chatwithpaper到gpt_academic

前言 之前7月中旬&#xff0c;我曾在微博上说准备做“20个LLM大型项目的源码解读” 针对这个事&#xff0c;目前的最新情况是 已经做了的&#xff1a;LLaMA、Alpaca、ChatGLM-6B、deepspeedchat、transformer、langchain、langchain-chatglm知识库准备做的&#xff1a;chatpa…

b站如何调整视频播放倍速3倍

b站pc网页端目前最大倍速为2倍&#xff0c;可以手动调节倍速的一个办法 视频页面-按下f12-点击console-复制粘贴代码-按下enter回车键 下面是代码&#xff0c;3可以换成自己想要的倍速&#xff0c;最大可以16倍速 document. querySelector(video).playbackRate3

C++笔记之静态成员函数的使用场景

C笔记之静态成员函数的使用场景 C静态成员函数的核心特点是不与特定类实例相关&#xff0c;可通过类名直接调用&#xff0c;用于执行与类相关的操作而无需创建类对象。其主要用途是在类级别上共享功能&#xff0c;管理全局状态或提供工具函数。 code review! 文章目录 C笔记之…

移远RM500U-CN模块直连嵌入式ubuntu实现拨号上网

目录 1 平台&#xff1a; 2 需要准备的资料 3 参考文档 4 编译环境与驱动移植 4.1 内核驱动添加厂家ID和产品ID 4. 2.添加零包处理 4.3 增加复位恢复机制 4.4 增加批量输出 批量输出 URB 的数量和容量 的数量和容量 4.5 内核配置与编译 5 QM500U-CN拨号&#xff08;在开…

tensorflow / tensorflow-gpu cuda cudNN tensorRT 安装,启用显卡加速

tensorflow / tensorflow-gpu cuda cudNN tensorRT 安装,启用显卡加速 说明 Tensorflow-GPU 已被移除。请安装 tensorflow 。 tensorflow 通过 Nvidia CUDA 支持 GPU 加速操作。 自 2019 年 9月发布 的 TensorFlow2.1 以来&#xff0c;tensorFlow 和 tensorflow-GPU 一直是同…

NFT Insider#102:The Sandbox重新上线LAND桥接服务,YGG加入Base生态

引言&#xff1a;NFT Insider由NFT收藏组织WHALE Members(https://twitter.com/WHALEMembers)、BeepCrypto&#xff08;https://twitter.com/beep_crypto&#xff09;联合出品&#xff0c;浓缩每周NFT新闻&#xff0c;为大家带来关于NFT最全面、最新鲜、最有价值的讯息。每期周…

【JVM】类装载的执行过程

文章目录 类装载的执行过程1.加载2.验证3.准备4.解析5.初始化6.使用7.卸载 类装载的执行过程 类装载总共分为7个过程&#xff0c;分别是 加载&#xff0c;验证&#xff0c;准备、解析、初始化、使用、卸载 1.加载 将类的字节码文件加载到内存(元空间&#xff09;中。这一步会…

实时时钟+闹钟

在江科大实时时钟的基础上添加闹钟的配置&#xff0c;参考http://t.csdn.cn/YDlYy。 实现功能 &#xff1a;每隔time秒蜂鸣器响一次、设置闹钟的年月日时分秒&#xff0c;到时间蜂鸣器响。 前三个函数没有变&#xff0c;添加 void RTC_AlarmInit(void) 闹钟的中断配置void…

分享Python技术下AutojsPro7云控代码

引言 有图有真相&#xff0c;那短视频就更是真相了。下面是三大语言的短视频。 Java源码版云控示例&#xff1a; Java源码版云控示例在线视频 Net源码版云控示例&#xff1a; Net源码版云控示例在线视频亚丁号-知识付费平台 支付后可见 扫码付费可见 Python源码版云控示例…

STM32CubeMX之freeRTOS消息通知(有点全能)

任务通知是任务自带的程序&#xff0c;不需要单独去创建 一&#xff1a; 二&#xff1a; 进入前不清除数据&#xff0c;退出清除数据参数 0x0000000000 0xffffffff的意思 三&#xff1a; 这里就是发送过去&#xff0c;然后把其存到了num中 不要有太多疑问&#xff0c;并不是发…

【Linux】TCP协议简介

TCP协议简介 TCP协议格式面向连接1.连接管理机制2.包序管理 可靠传输1.保证数据可靠到达对端2.保证数据的传输效率 面向字节流&#xff34;&#xff23;&#xff30;粘包问题 TCP协议格式 16位源端口号和16位目的端口号&#xff1a;标识数据从哪个进程来&#xff0c;到哪个进程…

阿里云Windows服务器安装部署MySQL数据库流程

阿里云百科分享如何在Windows系统ECS实例上手动部署MySQL数据库。 目录 前提条件 操作步骤 前提条件 使用本教程进行操作前&#xff0c;请确保您已经注册了阿里云账号。如还未注册&#xff0c;请先完成账号注册。操作系统&#xff1a;Windows Server 2012准备一台ECS云服务…

JDK、JRE、JVM:揭秘Java的关键三者关系

文章目录 JDK&#xff1a;Java开发工具包JRE&#xff1a;Java运行环境JVM&#xff1a;Java虚拟机关系概述 案例示例&#xff1a;Hello World结语 在Java世界中&#xff0c;你可能经常听到JDK、JRE和JVM这几个概念&#xff0c;它们分别代表了Java开发工具包、Java运行环境和Java…

计算机丢失msvcr71.dll解决办法,总结三个常见的解决方法

修复msvcr71.dll文件的过程中&#xff0c;我对系统动态链接库文件的重要性有了更深入的了解。这个文件对于许多使用Visual C编译的软件来说是必不可少的&#xff0c;缺失或损坏可能导致软件无法正常运行。因此&#xff0c;当遇到类似问题时&#xff0c;及时解决并修复这个文件是…

页面文件太小,无法完成操作。

1、右键“我的电脑”&#xff0c;选择“属性”&#xff1b; 2、点击“高级系统设置”&#xff1b; 3、点击“高级”&#xff0c;再点击“设置”&#xff1b; 4、选择“高级”&#xff0c;选择“程序”&#xff0c;点击“更改”&#xff1b; 5、 不要勾选“自动管理所有驱动器…

Spring Boot+Mybatis实现增删改查接口开发+测试(超详细建议收藏)

前言 Java也是测试必知必会的内容&#xff0c;特别是现在类似spring boot 等Java框架更是成为主流。之前实现的图书增删改查是用Python实现的&#xff0c;没看过的请移步&#xff1a;Flaskmysql 实现增删改查接口开发测试&#xff08;图文教程附源码&#xff09;&#xff0c;本…

教你如何使用AES对接口参数进行加密

教你如何使用AES对接口参数进行加密 前言 我们作为程序猿&#xff0c;在浏览网站的时候偶尔也会打开控制台看看请求的接口&#xff0c;我们会发现有些接口的传输是 “乱码” &#xff0c;那么这个乱码究竟是什么呢&#xff1f;为什么要这么做&#xff1f; 其实这个所谓的 “…

无涯教程-Perl - qq函数

描述 可以使用此函数代替双引号。这实际上不是一个函数,更像是一个运算符,但是如果您在其他程序员的程序中看到它却不记得它是什么,那么可能会在这里看。实际上,您可以使用任何一组定界符,而不仅仅是括号。 语法 以下是此函数的简单语法- qq ( string )返回值 该函数返回双…