iOS系统原生二维码扫描与生成
iOS系统原生二维码代码
1.扫描识别各种类型的码 (条形码, 二维码 , 彩色码 等)
2.添加扫描视图的范围(中间框框)
3.开启和关闭闪光灯
4.识别相册中的二维码图片
5.生成二维码图片
先展示下效果图:
1. 二维码扫描识别
1.首先导入库文件
#import <AVFoundation/AVFoundation.h>
2.接着签订需要的代理, 并创建所需要的属性(这里我只讲述关键代码)
AVCaptureMetadataOutputObjectsDelegate : 用于扫描获取到数据后的回调 , (metadataObjects: 扫描二维码数据信息)
#import <AVFoundation/AVFoundation.h>
#import "QRScanView.h"
// 二维码扫描器
@interface QrCodeReaderViewController () <AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate,
UIImagePickerControllerDelegate, UINavigationControllerDelegate>
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *layer;
@property (nonatomic, strong) AVCaptureMetadataOutput *output;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;
@property (nonatomic, assign) CGRect scanRect;
@property (nonatomic, weak) QRScanView *scanView;
@end
@implementation QrCodeReaderViewController
- (void)viewDidLoad {
[super viewDidLoad];
CGFloat scanWH = 220;
CGFloat scanX = (kScreenWidth - scanWH) * 0.5;
CGFloat scanY = (kScreenHeight - scanWH) * 0.5;
self.scanRect = CGRectMake(scanX, scanY, scanWH, scanWH);
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
if (status == AVAuthorizationStatusAuthorized || status == AVAuthorizationStatusRestricted) {
[self loadScanView];
} else if (status == AVAuthorizationStatusNotDetermined) {
// 请求使用相机权限
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if (granted) {
dispatch_async(dispatch_get_main_queue(), ^{
[self loadScanView];
});
} else {
[[[UIAlertView alloc] initWithTitle:@"无权限访问相机" message:@"无权限访问相机" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
}
}];
} else {
[[[UIAlertView alloc] initWithTitle:@"无权限访问相机" message:@"无权限访问相机" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil] show];
}
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"相册" style:UIBarButtonItemStylePlain target:self action:@selector(analyzeOnClick)];
}
#pragma mark - 从相册解析二维码图片
- (void)analyzeOnClick {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
}
- (void)loadScanView {
//获取摄像设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//创建设备输入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
//创建元数据输出流
self.output = [[AVCaptureMetadataOutput alloc]init];
//为输出流对象设置代理 并在主线程里刷新
[self.output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//初始化链接对象
self.session = [[AVCaptureSession alloc]init];
//设置高质量采集率
[self.session setSessionPreset:AVCaptureSessionPresetHigh];
// 添加设备输入流
[self.session addInput:input];
// 添加设备输出流
[self.session addOutput:self.output];
// 创建摄像数据输出流并将其添加到会话对象上 --> 用于识别光线强弱
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[self.videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
[self.session addOutput:self.videoDataOutput];
//设置扫码支持的编码格式(如下设置条形码和二维码兼容)
self.output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,//二维码
//以下为条形码,如果项目只需要扫描二维码,下面都不要写
AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeUPCECode,
AVMetadataObjectTypeCode39Code,
AVMetadataObjectTypeCode39Mod43Code,
AVMetadataObjectTypeCode93Code,
AVMetadataObjectTypeCode128Code,
AVMetadataObjectTypePDF417Code];
// 实例化预览图层, 用于显示会话对象
self.layer = [AVCaptureVideoPreviewLayer layerWithSession:self.session];
// 保持纵横比;填充层边界
self.layer.videoGravity = AVLayerVideoGravityResizeAspectFill;
// layer.frame = self.view.layer.bounds;
self.layer.frame = [UIScreen mainScreen].bounds;
[self.view.layer insertSublayer:self.layer atIndex:0];
// 在block中使用weak self,不然会导致该controller的内存无法回收
__weak typeof(self) wekself = self;
// 添加通知
[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
// 如果不设置,整个屏幕都可以扫
wekself.output.rectOfInterest = [wekself.layer metadataOutputRectOfInterestForRect:wekself.scanRect];
}];
// 添加扫描视图
QRScanView *scanView = [[QRScanView alloc] initWithScanRect:self.scanRect];
[self.view addSubview:scanView];
self.scanView = scanView;
scanView.offFlashBlock = ^(BOOL flag) {
[wekself offFlashWithMode:(flag ? AVCaptureTorchModeOn : AVCaptureTorchModeOff)];
};
//开始捕获
[self.session startRunning];
}
// 根据Mode是否打开闪光灯
- (void)offFlashWithMode:(AVCaptureTorchMode)mode {
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
if ([captureDevice hasTorch]) {
BOOL locked = [captureDevice lockForConfiguration:&error];
if (locked && error == nil) {
// 打开手电筒 | 关闭手电筒
captureDevice.torchMode = mode;
[captureDevice unlockForConfiguration];
}
}
}
#pragma mark - AVCaptureMetadataOutputObjectsDelegate
// 扫描到数据后的回调
-(void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection{
if (metadataObjects.count>0) {
[self.session stopRunning];
AVMetadataMachineReadableCodeObject *metadataObject = metadataObjects[0];
if (self.qrcodeValueBlock) {
self.qrcodeValueBlock(metadataObject.stringValue);
}
// [self.navigationController popViewControllerAnimated:YES];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self.session startRunning];
});
}
}
#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];
NSLog(@"%f", brightnessValue);
self.scanView.brightnessValue = brightnessValue;
}
#pragma mark - UIImagePickerContorllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
// 对选取照片的处理,如果选取的图片尺寸过大,则压缩选取图片,否则不处理
UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
UIImage *image = [self imageSizeWithScreenImage:originalImage];
// 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) {
[BaseViewController alertWithTitle:@"识别结果为nil"];
} else {
CIQRCodeFeature *feature = [features firstObject];
[BaseViewController alertWithTitle:feature.messageString];
// for (CIQRCodeFeature *feature in features) {
// NSLog(@"%@", feature.messageString);
// }
}
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
// 返回一张不超过屏幕尺寸的image
- (UIImage *)imageSizeWithScreenImage:(UIImage *)image {
CGFloat imageW = image.size.width;
CGFloat imageH = image.size.height;
CGFloat screenW = kScreenWidth;
CGFloat screenH = kScreenHeight;
if (imageW <= screenW && imageH <= screenH) {
return image;
}
CGFloat max = MAX(imageW, imageH);
CGFloat scale = max / (screenH * 2.0);
CGSize size = CGSizeMake(imageW / scale, imageW / scale);
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
- (void)dealloc {
NSLog(@"QrCodeReader - dealloc");
// 将引用置空
[[NSNotificationCenter defaultCenter] removeObserver:self];
[self.session stopRunning];
self.session = nil;
self.layer = nil;
self.output = nil;
self.videoDataOutput = nil;
}
@end
// 自定义扫描视图
@interface QRScanView : UIView
- (instancetype)initWithScanRect:(CGRect)scanRect;
// 是否隐藏开启闪光灯按钮
@property (nonatomic, assign) float brightnessValue;
@property (nonatomic, copy) void (^offFlashBlock)(BOOL flag);
@end
#import "QRScanView.h"
@interface QRScanView()
@property (nonatomic, weak) UIView *lineView;
@property (nonatomic, weak) UIButton *flashBtn;
@end
@implementation QRScanView {
CGRect _scanRect;
dispatch_source_t timer;
}
- (instancetype)initWithScanRect:(CGRect)scanRect {
if (self = [super initWithFrame:[UIScreen mainScreen].bounds]) {
self.backgroundColor = [UIColor clearColor];
_scanRect = scanRect;
UIView *lineView = [[UIView alloc] initWithFrame:CGRectMake(scanRect.origin.x, scanRect.origin.y, scanRect.size.width, 1)];
lineView.backgroundColor = [UIColor greenColor];
lineView.tag = 1;
[self addSubview:lineView];
self.lineView = lineView;
UIButton *flashBtn = [[UIButton alloc] initWithFrame:CGRectMake(scanRect.origin.x, CGRectGetMaxY(scanRect) + 15, scanRect.size.width, 40)];
flashBtn.hidden = YES;
[flashBtn setTitleColor:[UIColor whiteColor] forState:UIControlStateNormal];
[flashBtn setTitle:@"开启闪光灯" forState:UIControlStateNormal];
[flashBtn addTarget:self action:@selector(openFlashOnClick) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:flashBtn];
self.flashBtn = flashBtn;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.7 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self dispatchScanViewAnimation];
});
}
return self;
}
- (void)openFlashOnClick {
self.flashBtn.selected = !self.flashBtn.selected;
[self.flashBtn setTitle:(self.flashBtn.selected ? @"关闭闪光灯" : @"开启闪光灯") forState:UIControlStateNormal];
if (self.offFlashBlock) {
self.offFlashBlock(self.flashBtn.selected);
}
}
- (void)setBrightnessValue:(float)brightnessValue {
_brightnessValue = brightnessValue;
if (brightnessValue < -1) {
self.flashBtn.hidden = NO;
} else if (self.flashBtn.selected == NO && self.flashBtn.hidden == NO) {
self.flashBtn.hidden = YES;
}
}
- (void)dispatchScanViewAnimation {
if (timer) {
dispatch_source_cancel(timer);
timer = nil;
}
timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, dispatch_get_global_queue(1, 1));
dispatch_source_set_timer(timer, DISPATCH_TIME_NOW, 0.003 * NSEC_PER_SEC, 0 * NSEC_PER_SEC);
dispatch_source_set_event_handler(timer, ^{
dispatch_async(dispatch_get_main_queue(), ^{
CGRect frame = self.lineView.frame;
if (self.lineView.tag == 2) {
frame.origin.y -= 0.5;
if (frame.origin.y <= _scanRect.origin.y) {
self.lineView.tag = 1;
}
} else {
frame.origin.y += 0.5;
if (CGRectGetMaxY(frame) >= CGRectGetMaxY(_scanRect)) {
self.lineView.tag = 2;
}
}
self.lineView.frame = frame;
});
});
dispatch_resume(timer);
}
- (void)drawRect:(CGRect)rect {
// Drawing code
CGContextRef ctx = UIGraphicsGetCurrentContext();
[[[UIColor blackColor] colorWithAlphaComponent:0.5] setFill];
CGMutablePathRef screenPath = CGPathCreateMutable();
CGPathAddRect(screenPath, NULL, self.bounds);
CGMutablePathRef scanPath = CGPathCreateMutable();
CGPathAddRect(scanPath, NULL, _scanRect);
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddPath(path, NULL, screenPath);
CGPathAddPath(path, NULL, scanPath);
CGContextAddPath(ctx, path);
/** kCGPathEOFill:奇偶规则填充(被覆盖过奇数点的填充,被覆盖过偶数点的不填充)
就比如说从任意位置p作一条射线,若与该射线相交的多边形边的数目为奇数,则p是在多边形内,
就去填充,否则就不填充。*/
CGContextDrawPath(ctx, kCGPathEOFill);
CGPathRelease(path);
CGPathRelease(screenPath);
CGPathRelease(scanPath);
}
@end
2.二维码扫描范围设置
1.AVCaptureMetadataOutput 扫描数据的输出对象
2.AVCaptureVideoPreviewLayer 扫描视图
3.rectOfInterest 该属性就是设置扫描内容的范围大小,需要使用[layer metadataOutputRectOfInterestForRect:_scanRect]; 方法进行矩形转换获取扫描的范围大小
// 只有添加该通知,在该通知里面设置rectOfInterest属性才有效
[[NSNotificationCenter defaultCenter] addObserverForName:AVCaptureInputPortFormatDescriptionDidChangeNotification object:nil queue:[NSOperationQueue currentQueue] usingBlock:^(NSNotification * _Nonnull note) {
// 如果不设置,整个屏幕都可以扫
output.rectOfInterest = [layer metadataOutputRectOfInterestForRect:_scanRect];
}];
3.开启和关闭闪光灯
// 创建摄像数据输出流并将其添加到会话对象上 --> 用于识别光线强弱
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[self.videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
[self.session addOutput:self.videoDataOutput];
// 根据Mode是否打开闪光灯
- (void)offFlashWithMode:(AVCaptureTorchMode)mode {
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
if ([captureDevice hasTorch]) {
BOOL locked = [captureDevice lockForConfiguration:&error];
if (locked && error == nil) {
// 打开手电筒 | 关闭手电筒
captureDevice.torchMode = mode;
[captureDevice unlockForConfiguration];
}
}
}
#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];
NSLog(@"%f", brightnessValue);
self.scanView.brightnessValue = brightnessValue;
}
4.识别相册中的二维码图片
实现两个代理: UIImagePickerControllerDelegate, UINavigationControllerDelegate
#pragma mark - 从相册解析二维码图片
- (void)analyzeOnClick {
UIImagePickerController *picker = [[UIImagePickerController alloc] init];
picker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
picker.delegate = self;
[self presentViewController:picker animated:YES completion:nil];
}
#pragma mark - UIImagePickerContorllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<UIImagePickerControllerInfoKey,id> *)info {
// 对选取照片的处理,如果选取的图片尺寸过大,则压缩选取图片,否则不处理
UIImage *originalImage = info[UIImagePickerControllerOriginalImage];
UIImage *image = [self imageSizeWithScreenImage:originalImage];
// 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) {
[BaseViewController alertWithTitle:@"识别结果为nil"];
} else {
CIQRCodeFeature *feature = [features firstObject];
[BaseViewController alertWithTitle:feature.messageString];
// for (CIQRCodeFeature *feature in features) {
// NSLog(@"%@", feature.messageString);
// }
}
[picker dismissViewControllerAnimated:YES completion:nil];
}
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
// 返回一张不超过屏幕尺寸的image
- (UIImage *)imageSizeWithScreenImage:(UIImage *)image {
CGFloat imageW = image.size.width;
CGFloat imageH = image.size.height;
CGFloat screenW = kScreenWidth;
CGFloat screenH = kScreenHeight;
if (imageW <= screenW && imageH <= screenH) {
return image;
}
CGFloat max = MAX(imageW, imageH);
CGFloat scale = max / (screenH * 2.0);
CGSize size = CGSizeMake(imageW / scale, imageW / scale);
UIGraphicsBeginImageContext(size);
[image drawInRect:CGRectMake(0, 0, size.width, size.height)];
UIImage *newImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return newImage;
}
5.二维码图片生成
1.需要包含头文件
#import <CoreImage/CoreImage.h>
// 二维码生成器
@interface QrCodeGeneratorViewController ()
@property (weak, nonatomic) IBOutlet UITextView *contentTxv;
@property (weak, nonatomic) IBOutlet UIImageView *qrcodeImv;
- (IBAction)generatorOnClick;
@end
@implementation QrCodeGeneratorViewController
- (IBAction)generatorOnClick {
// 0.导入头文件 #import <CoreImage/CoreImage.h>
// 1.创建过滤器 -- 苹果没有将这个字符封装成常量
CIFilter *filter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
// 2.过滤器恢复默认设置
[filter setDefaults];
// 3.给过滤器添加数据(正则表达式/帐号和密码) -- 通过KVC设置过滤器,只能设置NSData类型
NSString *dataString = self.contentTxv.text;
NSData *data = [dataString dataUsingEncoding:NSUTF8StringEncoding];
[filter setValue:data forKeyPath:@"inputMessage"];
// 4.获取输出的二维码
CIImage *outputImage = [filter outputImage];
// 5.显示二维码
// self.qrcodeImv.image = [UIImage imageWithCIImage:outputImage];
// 显示放大后清晰的二维码图片
self.qrcodeImv.image = [self createNonInterpolatedUIImageFormCIImage:outputImage withSize:120];
}
/**
* 根据CIImage生成指定大小的UIImage
*
* @param image CIImage
* @param size 图片宽度
*/
- (UIImage *)createNonInterpolatedUIImageFormCIImage:(CIImage *)image withSize:(CGFloat) size
{
CGRect extent = CGRectIntegral(image.extent);
CGFloat scale = MIN(size/CGRectGetWidth(extent), size/CGRectGetHeight(extent));
// 1.创建bitmap;
size_t width = CGRectGetWidth(extent) * scale;
size_t height = CGRectGetHeight(extent) * scale;
CGColorSpaceRef cs = CGColorSpaceCreateDeviceGray();
CGContextRef bitmapRef = CGBitmapContextCreate(nil, width, height, 8, 0, cs, (CGBitmapInfo)kCGImageAlphaNone);
CIContext *context = [CIContext contextWithOptions:nil];
CGImageRef bitmapImage = [context createCGImage:image fromRect:extent];
CGContextSetInterpolationQuality(bitmapRef, kCGInterpolationNone);
CGContextScaleCTM(bitmapRef, scale, scale);
CGContextDrawImage(bitmapRef, extent, bitmapImage);
// 2.保存bitmap到图片
CGImageRef scaledImage = CGBitmapContextCreateImage(bitmapRef);
CGContextRelease(bitmapRef);
CGImageRelease(bitmapImage);
return [UIImage imageWithCGImage:scaledImage];
}
@end
posted on 2021-01-29 17:55 Sinner_Yun 阅读(637) 评论(0) 编辑 收藏 举报