Desarrollo de iOS: realice la función de escanear el código QR y reconocer el código QR en la imagen

Desarrollo de iOS: realice la función de escanear el código QR y reconocer el código QR en la imagen

En el desarrollo de iOS, encontrará la función de escaneo, que utiliza la cámara para escanear el código QR o el código de barras para obtener el código QR o la cadena de contenido del código de barras correspondiente. Salte a través de la cadena obtenida o abra una página determinada para iniciar la siguiente lógica empresarial.

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

1. Configuración de permisos antes de su uso.

La función de escaneo debe habilitar el permiso de la cámara y debe agregar NSCameraUsageDescription en el archivo info.plist

Por ejemplo:

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

Hay otros permisos aquí. Por el momento, solo se necesita NSCameraUsageDescription para escanear.

Dos, función de escaneo AVCaptureSession

2.1 Varias clases que deben entenderse

  • AVCaptureSession

AVCaptureSession es un objeto proporcionado por iOS para gestionar y coordinar el flujo de datos entre dispositivos de entrada y dispositivos de salida.

  • Dispositivo de captura AV

AVCaptureDevice se refiere al dispositivo de hardware.

  • Entrada de dispositivo de captura AV

AVCaptureDeviceInput se utiliza para capturar datos de entrada del objeto AVCaptureDevice.

  • AVCaptureMetadataSalida

AVCaptureMetadataOutput se utiliza para procesar la salida de captura de metadatos cronometrados generados por AVCaptureSession.

  • AVCaptureVideoDataSalida

AVCaptureVideoDataOutput se utiliza para procesar la salida de datos de vídeo.

  • AVCaptureVideoPreviewLayer

AVCaptureVideoPreviewLayer es la capa de vista previa del video capturada por la cámara, que se utiliza para mostrar el video.

2.2 Realizar la función de escaneo

Después de familiarizarnos con algunas clases, podemos inicializar la sesión.
Agregamos funciones a AVCaptureSession para implementar la entrada y salida requerida

/** 创建扫描器 */
- (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 implementa el método y establece el delegado

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

Implementar el método proxy de AVCaptureMetadataOutputObjectsDelegate

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

Siempre que este método capture y emita un nuevo objeto, el delegado recibirá este mensaje y obtendrá los metadataObjectTypes correspondientes. A través de este método podremos obtener los resultados correspondientes al escanear el código QR.

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

Implementado en AVCaptureVideoDataOutput

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

Aquí se implementa el método en el proxy AVCaptureVideoDataOutputSampleBufferDelegate.

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

Cuando se captura un nuevo búfer de muestra de video, se proporciona al delegado del búfer de muestra mediante el método captureOutput:didOutputSampleBuffer:fromConnection:delegate.

En este método, puedes observar el valor de brillo y decidir si necesitas encender la luz.

#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 Establecer los metadataObjectTypes de metadataOutput

Al escanear, debemos configurar los metadataObjectTypes de metadataOutput para determinar el código QR, código de barras, etc. escaneado.

/** 根据扫描器类型配置支持编码格式 */
+ (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 el formato de codificación admitido según el tipo de escáner y lo configuramos según los diferentes tipos.

2.4 Activación y desactivación del escaneo

La cámara se utiliza para escanear y debe ser controlada por AVCaptureSession para apagarla y encenderla.

Restaurar la función de escaneo

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

Pausar escaneo

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

2.5 Activar la verificación de permiso para escanear o abrir el álbum de fotos

  • Compruebe si hay permiso de la cámara.
+ (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;
    }
}
  • Comprueba si hay permiso para el álbum.
/** 校验是否有相册权限 */
+ (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 Animación de la línea de escaneo

Para realizar la función de escaneo, necesitamos realizar el efecto de animación del escaneo y parece que la interfaz mejorará la experiencia del usuario.

Usamos la animación básica CABasicAnimation para lograr el efecto de animación de las líneas de escaneo.

  • activar la animación
/** 添加扫描线条动画 */
- (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;
}
  • pausar la animación
/** 暂停扫描器动画 */
- (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 Otras operaciones de la linterna en la interfaz

Cuando necesitamos encender y apagar la linterna, por ejemplo, encender la linterna cuando esté oscuro y apagarla cuando haya luz.

  • Mostrar linterna
/** 显示手电筒 */
- (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;
    }
}
  • linterna oculta
/** 隐藏手电筒 */
- (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 Dibujar área de escaneo

El área de escaneo se dibuja a través de UIBezierPath y el efecto de máscara negra con una transparencia de 0,7 se muestra fuera del área de escaneo.

/**
 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. Abra el álbum de fotos y reconozca el código QR de la imagen.

Abrimos el álbum e identificamos el código QR de la imagen en el álbum, se necesita CIDetector para identificar el código QR en la imagen.

El código específico es el siguiente.

#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. Escanea y reconoce todos los códigos del código QR en la imagen.

Realice escanear e identificar todos los códigos del código QR en la imagen, principalmente

SDQrScanViewController: UIViewController
SDQrScanView: interfaz de visualización UIView
SDQrScanTool: permisos y configuraciones de escaneo
SDQrScanConfig: colores de borde de escaneo, etc.

El código completo es el siguiente.

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

Hasta ahora, se ha realizado la función de escanear el código QR y reconocer el código QR en la imagen.

V. Resumen

Desarrollo de iOS: realice la función de escanear el código QR y reconocer el código QR en la imagen. Escanear consiste en utilizar la cámara para escanear el código QR o código de barras para obtener la cadena de contenido correspondiente del código QR o código de barras. El código QR en la figura de identificación utiliza CIDetector para identificar la cadena de contenido. Finalmente, implemente la lógica de negocios de la respuesta.

Registros de aprendizaje, sigue mejorando cada día.

Supongo que te gusta

Origin blog.csdn.net/gloryFlow/article/details/132249830
Recomendado
Clasificación