iOS development-realize the function of scanning the QR code and recognizing the QR code in the picture

iOS development-realize the function of scanning the QR code and recognizing the QR code in the picture

In iOS development, you will encounter the scan function, which uses the camera to scan the QR code or barcode to obtain the corresponding QR code or barcode content string. Jump through the obtained string or open a certain page to start the next business logic.

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

1. Permission settings before use

The scan function needs to enable the camera permission, and you need to add NSCameraUsageDescription in the info.plist file

For example:

<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>

There are other permissions here. For the time being, only NSCameraUsageDescription is needed for scanning.

Two, AVCaptureSession scan function

2.1 Several classes that need to be understood

  • AVCaptureSession

AVCaptureSession is an object provided by iOS to manage and coordinate the data flow between input devices and output devices.

  • AVCaptureDevice

AVCaptureDevice refers to the hardware device.

  • AVCaptureDeviceInput

AVCaptureDeviceInput is used to capture Input data from the AVCaptureDevice object.

  • AVCaptureMetadataOutput

AVCaptureMetadataOutput is used to process the capture output of timed metadata generated by AVCaptureSession.

  • AVCaptureVideoDataOutput

AVCaptureVideoDataOutput is used to process video data output.

  • AVCaptureVideoPreviewLayer

AVCaptureVideoPreviewLayer is the video preview layer captured by the camera, which is used to display the video.

2.2 Realize the scan function

After getting familiar with a few classes, we can initialize the session.
We add functions to AVCaptureSession to implement the input and output required

/** 创建扫描器 */
- (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 implements the method and sets the delegate

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

Implement the proxy method of AVCaptureMetadataOutputObjectsDelegate

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

Whenever this method captures and emits a new object, the delegate will receive this message and obtain the corresponding metadataObjectTypes. Through this method, we can obtain the corresponding results when scanning the QR code.

#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];
    }
}

Implemented in AVCaptureVideoDataOutput

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

The method in the proxy AVCaptureVideoDataOutputSampleBufferDelegate is implemented here

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

When a new video sample buffer is captured, it is provided to the sample buffer delegate using the captureOutput:didOutputSampleBuffer:fromConnection:delegate method.

In this method, you can observe the brightness value and decide whether you need to turn on the light.

#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 Set the metadataObjectTypes of metadataOutput

When scanning, we need to set the metadataObjectTypes of metadataOutput to determine the scanned QR code, barcode, etc.

/** 根据扫描器类型配置支持编码格式 */
+ (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;
    }
}

Configure the supported encoding format according to the scanner type, and we set it according to different types.

2.4 Scanning on and off

The camera is used for scanning, and it needs to be controlled by AVCaptureSession to turn it off and on.

Restore scan function

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

Pause scan

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

2.5 Turn on the permission check of scanning or opening the photo album

  • Check if there is camera permission
+ (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;
    }
}
  • Check if there is album permission
/** 校验是否有相册权限 */
+ (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 Scanning line animation

To realize the scanning function, we need to realize the animation effect of scanning, and it seems that the interface will enhance the user experience.

We use the basic animation CABasicAnimation to realize the animation effect of scanning lines.

  • turn on animation
/** 添加扫描线条动画 */
- (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;
}
  • pause animation
/** 暂停扫描器动画 */
- (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 Other flashlight operations on the interface

When we need to turn on and off the flashlight, for example, turn on the flashlight when it is dark, and turn off the flashlight when there is light.

  • Show flashlight
/** 显示手电筒 */
- (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;
    }
}
  • hidden flashlight
/** 隐藏手电筒 */
- (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 Draw scan area

The scanning area is drawn through UIBezierPath, and the black mask effect with a transparency of 0.7 is displayed outside the scanning area.

/**
 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];
    }
}

3. Open the photo album and recognize the QR code of the picture

We open the album and identify the QR code of the picture in the album. CIDetector is needed to identify the QR code in the picture.

The specific code is as follows

#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];
        }
    }];
}

4. Scan and recognize all the codes of the QR code in the picture

Realize scanning and identifying all the codes of the QR code in the picture, mainly

SDQrScanViewController: UIViewController
SDQrScanView: UIView display interface
SDQrScanTool: scan permission and settings
SDQrScanConfig: scan border color, etc.

The complete code is as follows

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

So far, the function of scanning the QR code and recognizing the QR code in the picture has been realized.

V. Summary

iOS development-realize the function of scanning the QR code and recognizing the QR code in the picture. Scanning is to use the camera to scan the QR code or barcode to obtain the corresponding content string of the QR code or barcode. The QR code in the identification figure uses CIDetector to identify the content string. Finally, implement the business logic of the response.

Learning records, keep improving every day.

Guess you like

Origin blog.csdn.net/gloryFlow/article/details/132249830
Recommended