iOS开发零散知识OC

iOS系统原生二维码扫描开发

2018-05-16  本文已影响148人  iOS_LeON

项目背景

最近刚从成都回到大连的一家做对日开发的公司, 项目需求是维护既存的业务逻辑, 修改API , 以及三方库更新
业务主体就是利用ZXing实现的二维码扫描, 存本地后批量上传.
因为是2015年的项目, 因此, 之前的ZXing代码库在iOS10 , 11 的系统上无法运行


ZXing_Bug.png

需要的功能

  1. 扫描识别各种类型的码 (条形码, 二维码 , 彩色码 等)
  2. 开关闪光灯
  3. 识别本地图片

方案

  1. 更新ZXing库为ZXingObjC库
  2. 使用系统原生代码进行开发
    (据传言, 有的二维码系统原生的识别不出来, 但是ZXingObjC 可以识别 , 具体真假, 有待考证, 暂时没有碰到)

下面我们就来分别对这两种方案进行实现:

系统原生代码

1. 扫描识别

首先导入库文件<AVFoundation/AVFoundation.h>

#import <AVFoundation/AVFoundation.h>

接着签订需要的代理, 并创建所需要的属性(这里我只讲述关键代码)
AVCaptureMetadataOutputObjectsDelegate : 用于扫描获取到数据后的回调 , (metadataObjects: 扫描二维码数据信息)
AVCaptureVideoDataOutputSampleBufferDelegate : 根据光线强弱值打开手电筒的方法 , (brightnessValue: 光线强弱值)

@interface LYCodeScanManager () <AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate>
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@end

准备工作做完 , 我们就搞起来吧

// 1、获取摄像设备
    AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
    
    // 2、创建摄像设备输入流
    AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
    
    // 3、创建元数据输出流
    AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
    [metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
    
    // 设置扫描范围(每一个取值0~1,以屏幕右上角为坐标原点)
    // 注:微信二维码的扫描范围是整个屏幕,这里并没有做处理(可不用设置);
    // 如需限制扫描框范围,打开下一句注释代码并进行相应调整
//    metadataOutput.rectOfInterest = CGRectMake(0.05, 0.2, 0.7, 0.6);
    
    // 4、创建会话对象
    _session = [[AVCaptureSession alloc] init];
    // 并设置会话采集率
    _session.sessionPreset = AVCaptureSessionPreset1920x1080;
    
    // 5、添加元数据输出流到会话对象
    [_session addOutput:metadataOutput];

   // 创建摄像数据输出流并将其添加到会话对象上,  --> 用于识别光线强弱
    self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
    [_videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
    [_session addOutput:_videoDataOutput];

    // 6、添加摄像设备输入流到会话对象
    [_session addInput:deviceInput];

 // 7、设置数据输出类型(如下设置为条形码和二维码兼容),需要将数据输出添加到会话后,才能指定元数据类型,否则会报错
    metadataOutput.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code,  AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
    
    // 8、实例化预览图层, 用于显示会话对象
    _videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
    // 保持纵横比;填充层边界
    _videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
    CGFloat x = 0;
    CGFloat y = 0;
    CGFloat w = [UIScreen mainScreen].bounds.size.width;
    CGFloat h = [UIScreen mainScreen].bounds.size.height;
    _videoPreviewLayer.frame = CGRectMake(x, y, w, h);
    [currentController.view.layer insertSublayer:_videoPreviewLayer atIndex:0];
    
    // 9、启动会话
    [_session startRunning];

当然, 不要忘了在页面销毁或者你不需要用它的时候, 一定要先让他停下来 : [_session stopRunning] 即可

最后, 我们只需要在delegate回调方法中, 获取识别出的数据就可以了

- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
 NSLog(@"metadataObjects - - %@", metadataObjects);
    if (metadataObjects != nil && metadataObjects.count > 0) {
        AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
       NSLog(@"%@",[obj stringValue]);
    } else {
        NSLog(@"暂未识别出扫描的二维码");
    }
}

这样我们就把扫描出的数据打印出来了

2. 开关闪光灯

刚才 , 我们在创建会话对象时 , 已经把摄像设备的输入流和输出流添加上了, 因此我们这边直接调用AVCaptureVideoDataOutputSampleBufferDelegate的回调方法即可获取到光线的强弱值 , 从而可以控制是否需要闪光灯
(这里创建了一个按钮lightBtn , 如果需要, 则添加上去, 不需要则移除)

- (void)captureOutput:(AVCaptureOutput *)captureOutput 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];
    NSLog(@"%f",brightnessValue);
   if (brightnessValue < - 1) {
        [self.view addSubview:self.lightBtn];
    } else {
        if (self.isSelectedFlashlightBtn == NO) {
            [self removeFlashlightBtn];
        }
    }
}

lightBtn 点击方法 , 控制开关闪光灯

- (void)lightBtnAction:(UIButton *)button {
    if (button.selected == NO) {
      /** 打开手电筒 */
          AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
          NSError *error = nil;
          if ([captureDevice hasTorch]) {
              BOOL locked = [captureDevice lockForConfiguration:&error];
              if (locked) {
                  captureDevice.torchMode = AVCaptureTorchModeOn;
                  [captureDevice unlockForConfiguration];
              }
          }
          self.isSelectedFlashlightBtn = YES;
          button.selected = YES;
    } else {
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)),                  dispatch_get_main_queue(), ^{
              AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
              if ([device hasTorch]) {
                    [device lockForConfiguration:nil];
                    [device setTorchMode: AVCaptureTorchModeOff];
                    [device unlockForConfiguration];
              }
                self.isSelectedFlashlightBtn = NO;
                self.flashlightBtn.selected = NO;
                [self.flashlightBtn removeFromSuperview];
        });

    }
}

本地图片识别

首先就是当前controller 进入相册页面获取图片, 正常的签订 <UINavigationControllerDelegate, UIImagePickerControllerDelegate> 协议, 然后push 就好了

    UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
    imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
    imagePicker.delegate = self;
    [self presentViewController:imagePicker animated:YES completion:nil];

定义一个方法用于返回一个不超过屏幕尺寸的Image

/// 返回一张不超过屏幕尺寸的 image
+ (UIImage *)LY_imageSizeWithScreenImage:(UIImage *)image {
    CGFloat imageWidth = image.size.width;
    CGFloat imageHeight = image.size.height;
    CGFloat screenWidth = SGQRCodeScreenWidth;
    CGFloat screenHeight = SGQRCodeScreenHeight;
    
    if (imageWidth <= screenWidth && imageHeight <= screenHeight) {
        return image;
    }
    
    CGFloat max = MAX(imageWidth, imageHeight);
    CGFloat scale = max / (screenHeight * 2.0);
    
    CGSize size = CGSizeMake(imageWidth / scale, imageHeight / scale);
    UIGraphicsBeginImageContext(size);
    [image drawInRect:CGRectMake(0, 0, size.width, size.height)];
    UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    
    return newImage;
}

回调方法中调用对图片进行处理

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info {
    // 对选取照片的处理,如果选取的图片尺寸过大,则压缩选取图片,否则不作处理
    UIImage *image = [self LY_imageSizeWithScreenImage:info[UIImagePickerControllerOriginalImage]];
    // CIDetector(CIDetector可用于人脸识别)进行图片解析,从而使我们可以便捷的从相册中获取到二维码
    // 声明一个 CIDetector,并设定识别类型 CIDetectorTypeQRCode
    CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{CIDetectorAccuracy: CIDetectorAccuracyHigh}];
    
    // 取得识别结果
    NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
    
    if (features.count == 0) {
        if (self.isOpenLog) {
            if (self.delegate && [self.delegate respondsToSelector:@selector(QRCodeAlbumManagerDidReadQRCodeFailure:)]) {
                [self.delegate QRCodeAlbumManagerDidReadQRCodeFailure:self];
            }
        }
        [self.currentVC dismissViewControllerAnimated:YES completion:nil];
        return;
        
    } else {
        for (int index = 0; index < [features count]; index ++) {
            CIQRCodeFeature *feature = [features objectAtIndex:index];
            NSString *resultStr = feature.messageString;
            NSLog(@"相册中读取二维码数据信息 - - %@", resultStr);
            self.detectorString = resultStr;
        }
        [self.currentVC dismissViewControllerAnimated:YES completion:^{
           
        }
}

总结 : 总的来说, 还是一个相对比较简单的需求, 建议写个manager去控制, 不然代码太多, 输出类型的地方建议不要把所有的都写上, 会导致识别不出来, 亲测有效 ... 哈哈, 所以需要用什么就写什么就好了

上一篇下一篇

猜你喜欢

热点阅读