o2o商城网站建设方案,网站建设培训课程,南通seo快速排名,机票酒店网站建设iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能
在iOS开发中#xff0c;会遇到扫一扫功能#xff0c;扫一扫是使用摄像头扫码二维码或者条形码#xff0c;获取对应二维码或条形码内容字符串。通过获得的字符串进行跳转或者打开某个页面开启下一步的业务逻辑。
https…iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能
在iOS开发中会遇到扫一扫功能扫一扫是使用摄像头扫码二维码或者条形码获取对应二维码或条形码内容字符串。通过获得的字符串进行跳转或者打开某个页面开启下一步的业务逻辑。
https://blog.csdn.net/gloryFlow/article/details/132249830
一、使用前权限设置
扫一扫功能需要开启相机权限需要在info.plist文件中添加NSCameraUsageDescription
例如
keyNSCameraUsageDescription/key
string开启相机权限活动扫一扫更快捷/string
keyNSLocationAlwaysAndWhenInUseUsageDescription/key
string开启定位权限/string
keyNSLocationAlwaysUsageDescription/key
string开启定位权限/string
keyNSLocationWhenInUseUsageDescription/key
string开启定位权限/string
keyNSMicrophoneUsageDescription/key
string开启麦克风权限/string
keyNSPhotoLibraryAddUsageDescription/key
string添加照片需要您的同意/string
keyNSPhotoLibraryUsageDescription/key
string开启照片权限/string这里还有其他权限暂时扫一扫只需要NSCameraUsageDescription。
二、AVCaptureSession扫一扫功能
2.1 需要了解的几个类
AVCaptureSession
AVCaptureSession是iOS提供的一个管理和协调输入设备到输出设备之间数据流的对象。
AVCaptureDevice
AVCaptureDevice是指硬件设备。
AVCaptureDeviceInput
AVCaptureDeviceInput是用来从AVCaptureDevice对象捕获Input数据。
AVCaptureMetadataOutput
AVCaptureMetadataOutput是用来处理AVCaptureSession产生的定时元数据的捕获输出的。
AVCaptureVideoDataOutput
AVCaptureVideoDataOutput是用来处理视频数据输出的。
AVCaptureVideoPreviewLayer
AVCaptureVideoPreviewLayer是相机捕获的视频预览层是用来展示视频的。
2.2 实现扫一扫功能
在熟悉几个类之后我们可以初始化session了。 我们为AVCaptureSession添加功能实现所需要的input与output
/** 创建扫描器 */
- (void)loadScanView {AVCaptureDevice *device [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];AVCaptureDeviceInput *deviceInput [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];AVCaptureMetadataOutput *metadataOutput [[AVCaptureMetadataOutput alloc] init];[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];if (self.scanConfig.scannerArea SDScannerAreaDefault) {metadataOutput.rectOfInterest CGRectMake([self.scannerView scannerOriginX]/self.view.frame.size.height, [self.scannerView scannerOriginY]/self.view.frame.size.width, [self.scannerView scannerWidth]/self.view.frame.size.height, [self.scannerView scannerWidth]/self.view.frame.size.width);}AVCaptureVideoDataOutput *videoDataOutput [[AVCaptureVideoDataOutput alloc] init];[videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];self.session [[AVCaptureSession alloc]init];[self.session setSessionPreset:AVCaptureSessionPresetHigh];if ([self.session canAddInput:deviceInput]) {[self.session addInput:deviceInput];}if ([self.session canAddOutput:metadataOutput]) {[self.session addOutput:metadataOutput];}if ([self.session canAddOutput:videoDataOutput]) {[self.session addOutput:videoDataOutput];}metadataOutput.metadataObjectTypes [SDQrScanTool metadataObjectType:self.scanConfig.scannerType];AVCaptureVideoPreviewLayer *videoPreviewLayer [AVCaptureVideoPreviewLayer layerWithSession:_session];videoPreviewLayer.videoGravity AVLayerVideoGravityResizeAspectFill;videoPreviewLayer.frame self.view.layer.bounds;[self.videoPreView.layer insertSublayer:videoPreviewLayer atIndex:0];[self.session startRunning];
}AVCaptureMetadataOutput实现了方法设置了delegate
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];实现AVCaptureMetadataOutputObjectsDelegate的代理方法
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray__kindof AVMetadataObject * *)metadataObjects fromConnection:(AVCaptureConnection *)connection;该方法每当输出捕获并发出新对象时委托都会收到此消息获得对应metadataObjectTypes。通过此方法我们可以当扫描二维码时候获得对应的结果。
#pragma mark -- AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray__kindof AVMetadataObject * *)metadataObjects fromConnection:(AVCaptureConnection *)connection {// 获取扫一扫结果if (metadataObjects metadataObjects.count 0) {[self pauseScanning];AVMetadataMachineReadableCodeObject *metadataObject metadataObjects[0];NSString *stringValue metadataObject.stringValue;//AVMetadataMachineReadableCodeObject *obj (AVMetadataMachineReadableCodeObject *)[self.lay transformedMetadataObjectForMetadataObject:metadataObjects.lastObject];//[self changeVideoScale:metadataObject];[self handleScanValue:stringValue];}
}在AVCaptureVideoDataOutput实现了
[videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];这里实现了代理AVCaptureVideoDataOutputSampleBufferDelegate中方法
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection;当捕获新的视频采样缓冲区时将使用captureOutput:didOutputSampleBuffer:fromConnection:delegate方法将其提供给采样缓冲区代理。
在此方法中可以观察亮度值决定是否需要开启灯光。
#pragma mark -- AVCaptureVideoDataOutputSampleBufferDelegate
/** 此方法会实时监听亮度值 */
- (void)captureOutput:(AVCaptureOutput *)output didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {CFDictionaryRef metadataDict CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);NSDictionary *metadata [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];CFRelease(metadataDict);NSDictionary *exifMetadata [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];// 亮度值float brightnessValue [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] floatValue];if (![self.scannerView flashlightOn]) {if (brightnessValue -1.0) {[self.scannerView showFlashlight:YES];} else {[self.scannerView hideFlashlight:YES];}}
}2.3 设置metadataOutput的metadataObjectTypes
扫一扫时候我们需要设置metadataOutput的metadataObjectTypes决定扫描的二维码、条形码等。
/** 根据扫描器类型配置支持编码格式 */(NSArray *)metadataObjectType:(SDScannerType)scannerType {switch (scannerType) {case SDScannerTypeQRCode:{return [AVMetadataObjectTypeQRCode];}break;case SDScannerTypeBarCode:{return [AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeUPCECode,AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode39Mod43Code,AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypePDF417Code];}break;case SDScannerTypeBoth:{return [AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code,AVMetadataObjectTypeEAN8Code,AVMetadataObjectTypeUPCECode,AVMetadataObjectTypeCode39Code,AVMetadataObjectTypeCode39Mod43Code,AVMetadataObjectTypeCode93Code,AVMetadataObjectTypeCode128Code,AVMetadataObjectTypePDF417Code];}break;default:break;}
}根据扫描器类型配置支持编码格式我们根据不同类型进行设置。
2.4 扫描开启与关闭
扫一扫使用了摄像头这里关闭开启需要通过AVCaptureSession来控制
恢复扫一扫功能
/**恢复扫一扫功能*/
- (void)resumeScanning {if (self.session) {[self.session startRunning];[self.scannerView startLineAnimation];}
}暂停扫一扫
/**暂停扫一扫*/
- (void)pauseScanning {if (self.session) {[self.session stopRunning];[self.scannerView stopLineAnimation];}
}2.5 开启扫一扫或者打开相册的权限检查
校验是否有相机权限 (void)checkCameraAuthorizationStatus:(void(^)(BOOL granted))permissionGranted
{AVAuthorizationStatus videoAuthStatus [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];switch (videoAuthStatus) {// 已授权case AVAuthorizationStatusAuthorized:{permissionGranted(YES);}break;// 未询问用户是否授权case AVAuthorizationStatusNotDetermined:{// 提示用户授权[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {permissionGranted(granted);}];}break;// 用户拒绝授权或权限受限case AVAuthorizationStatusRestricted:case AVAuthorizationStatusDenied:{UIAlertView *alert [[UIAlertView alloc]initWithTitle:请在”设置-隐私-相机”选项中允许访问你的相机 message:nil delegate:nil cancelButtonTitle:确定 otherButtonTitles:nil];[alert show];permissionGranted(NO);}break;default:break;}
}校验是否有相册权限
/** 校验是否有相册权限 */(void)checkAlbumAuthorizationStatus:(void(^)(BOOL granted))permissionGranted {PHAuthorizationStatus photoAuthStatus [PHPhotoLibrary authorizationStatus];switch (photoAuthStatus) {// 已授权case PHAuthorizationStatusAuthorized:{permissionGranted(YES);}break;// 未询问用户是否授权case PHAuthorizationStatusNotDetermined:{[PHPhotoLibrary requestAuthorization:^(PHAuthorizationStatus status) {permissionGranted(status PHAuthorizationStatusAuthorized);}];}break;// 用户拒绝授权或权限受限case PHAuthorizationStatusRestricted:case PHAuthorizationStatusDenied:{UIAlertView *alert [[UIAlertView alloc]initWithTitle:请在”设置-隐私-相片”选项中允许访问你的相册 message:nil delegate:nil cancelButtonTitle:确定 otherButtonTitles:nil];[alert show];permissionGranted(NO);}break;default:break;}}2.6 扫描线条动画
实现扫一扫功能我们需要实现一下扫一扫的动画效果看起来界面更加提升用户体验。
我们使用基础动画CABasicAnimation来实现扫描线条的动画效果。
开启动画
/** 添加扫描线条动画 */
- (void)startLineAnimation {// 若已添加动画则先移除动画再添加[self.scannerLineView.layer removeAllAnimations];CABasicAnimation *lineAnimation [CABasicAnimation animationWithKeyPath:transform];lineAnimation.toValue [NSValue valueWithCATransform3D:CATransform3DMakeTranslation(0, [self scannerWidth] - kScannerLineHeight, 1)];lineAnimation.duration 4;lineAnimation.repeatCount MAXFLOAT;lineAnimation.autoreverses YES; // 动画结束时执行逆动画[self.scannerLineView.layer addAnimation:lineAnimation forKey:scannerLineViewAnmationKey];// 重置动画运行速度为1.0self.scannerLineView.layer.speed 2.0;
}暂停动画
/** 暂停扫描器动画 */
- (void)stopLineAnimation {// 取出当前时间转成动画暂停的时间CFTimeInterval pauseTime [self.scannerLineView.layer convertTime:CACurrentMediaTime() fromLayer:nil];// 设置动画的时间偏移量指定时间偏移量的目的是让动画定格在该时间点的位置self.scannerLineView.layer.timeOffset pauseTime;// 将动画的运行速度设置为0 默认的运行速度是1.0self.scannerLineView.layer.speed 0;
}2.7 界面的其他手电筒操作
在我们需要开启和关闭手电筒比如在比较黑暗的时候开启手电筒在有光亮的地方关闭手电筒。
显示手电筒
/** 显示手电筒 */
- (void)showFlashlight:(BOOL)animated {if (animated) {[UIView animateWithDuration:0.6 animations:^{self.lightTipsLabel.alpha 1.0;self.lightButton.alpha 1.0;self.tipsLabel.alpha 0;} completion:^(BOOL finished) {self.lightButton.enabled YES;}];} else {self.lightTipsLabel.alpha 1.0;self.lightButton.alpha 1.0;self.tipsLabel.alpha 0;self.lightButton.enabled YES;}
}藏手电筒
/** 隐藏手电筒 */
- (void)hideFlashlight:(BOOL)animated {self.lightButton.enabled NO;if (animated) {[UIView animateWithDuration:0.6 animations:^{self.lightTipsLabel.alpha 0;self.lightButton.alpha 0;self.tipsLabel.alpha 1.0;} completion:^(BOOL finished) {}];} else {self.tipsLabel.alpha 1.0;self.lightTipsLabel.alpha 0;self.lightButton.alpha 0;}
}2.8 绘制扫描区域
通过UIBezierPath绘制出扫描区域扫描区域之外的则显示透明度为0.7的黑色遮罩效果。
/**draw绘制param rect rect*/
- (void)drawRect:(CGRect)rect {[super drawRect:rect];// 半透明区域[[UIColor colorWithWhite:0 alpha:0.7] setFill];UIRectFill(rect);// 透明区域CGRect scanner_rect CGRectMake([self scannerOriginX], [self scannerOriginY], [self scannerWidth], [self scannerWidth]);[[UIColor clearColor] setFill];UIRectFill(scanner_rect);// 边框UIBezierPath *borderPath [UIBezierPath bezierPathWithRect:CGRectMake([self scannerOriginX], [self scannerOriginY], [self scannerWidth], [self scannerWidth])];borderPath.lineCapStyle kCGLineCapRound;borderPath.lineWidth kScannerBorderWidth;[self.config.scannerBorderColor set];[borderPath stroke];for (int index 0; index 4; index) {UIBezierPath *tempPath [UIBezierPath bezierPath];tempPath.lineWidth kScannerCornerWidth;[self.config.scannerCornerColor set];switch (index) {// 左上角棱角case 0:{[tempPath moveToPoint:CGPointMake([self scannerOriginX] kScannerCornerLength, [self scannerOriginY])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] kScannerCornerLength)];}break;// 右上角case 1:{[tempPath moveToPoint:CGPointMake([self scannerOriginX] [self scannerWidth] - kScannerCornerLength, [self scannerOriginY])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX] [self scannerWidth], [self scannerOriginY])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX] [self scannerWidth], [self scannerOriginY] kScannerCornerLength)];}break;// 左下角case 2:{[tempPath moveToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] [self scannerWidth] - kScannerCornerLength)];[tempPath addLineToPoint:CGPointMake([self scannerOriginX], [self scannerOriginY] [self scannerWidth])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX] kScannerCornerLength, [self scannerOriginY] [self scannerWidth])];}break;// 右下角case 3:{[tempPath moveToPoint:CGPointMake([self scannerOriginX] [self scannerWidth] - kScannerCornerLength, [self scannerOriginY] [self scannerWidth])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX] [self scannerWidth], [self scannerOriginY] [self scannerWidth])];[tempPath addLineToPoint:CGPointMake([self scannerOriginX] [self scannerWidth], [self scannerOriginY] [self scannerWidth] - kScannerCornerLength)];}break;default:break;}[tempPath stroke];}
}三、实现打开相册识别图片二维码
我们打开相册识别相册中图片的二维码识别图片中的二维码需要CIDetector。
具体代码如下
#pragma mark -- UIImagePickerControllerDelegate
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionaryNSString *,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];}}];
}四、实现扫一扫及识别图片中二维码的全部代码
实现扫一扫及识别图片中二维码的全部代码主要是
SDQrScanViewControllerUIViewController SDQrScanViewUIView显示界面 SDQrScanTool扫一扫权限及设置 SDQrScanConfig扫一扫边框颜色等
完整代码如下
SDQrScanConfig.h
#import Foundation/Foundation.h
#import UIKit/UIKit.h
#import SDQrScanConfig.h/**扫描类型*/
typedef NS_ENUM(NSInteger, SDScannerType) {SDScannerTypeQRCode,SDScannerTypeBarCode,SDScannerTypeBoth,
};/**扫描区域*/
typedef NS_ENUM(NSInteger, SDScannerArea) {SDScannerAreaDefault,SDScannerAreaFullScreen,
};/**扫一扫基础配置文件*/
interface SDQrScanConfig : NSObject/**类型*/
property (nonatomic, assign) SDScannerType scannerType;/**扫描区域*/
property (nonatomic, assign) SDScannerArea scannerArea;/**棱角颜色*/
property (nonatomic, strong) UIColor *scannerCornerColor;/**边框颜色*/
property (nonatomic, strong) UIColor *scannerBorderColor;/**指示器风格*/
property (nonatomic, assign) UIActivityIndicatorViewStyle indicatorViewStyle;endSDQrScanConfig.m
#import SDQrScanConfig.himplementation 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;
}endSDQrScanTool.h
#import Foundation/Foundation.h
#import Photos/PHPhotoLibrary.h
#import AVFoundation/AVFoundation.h
#import SDQrScanConfig.hinterface 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;endSDQrScanTool.m
#import SDQrScanTool.himplementation 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];}
}endSDQrScanView.h
#import UIKit/UIKit.h
#import SDQrScanTool.h
#import SDBaseControllerView.hinterface SDQrScanView : SDBaseControllerView- (instancetype)initWithFrame:(CGRect)frame config:(SDQrScanConfig *)config;/**启动扫描线条动画*/
- (void)startLineAnimation;/**停止扫描线条动画*/
- (void)stopLineAnimation;/**添加指示器*/
- (void)addActivityIndicator;/**移除指示器*/
- (void)removeActivityIndicator;/**扫描器坐标点Xreturn 坐标点X*/
- (CGFloat)scannerOriginX;/**扫描器坐标点Yreturn 坐标点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;endSDQrScanView.m
#import SDQrScanView.h
#import objc/runtime.hstatic 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; //灯光sizestatic 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; //手电筒开关是否打开endimplementation 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.0self.scannerLineView.layer.speed 2.0;
}/** 暂停扫描器动画 */
- (void)stopLineAnimation {// 取出当前时间转成动画暂停的时间CFTimeInterval pauseTime [self.scannerLineView.layer convertTime:CACurrentMediaTime() fromLayer:nil];// 设置动画的时间偏移量指定时间偏移量的目的是让动画定格在该时间点的位置self.scannerLineView.layer.timeOffset pauseTime;// 将动画的运行速度设置为0 默认的运行速度是1.0self.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 - 扫描器坐标点位置
/**扫描器坐标点Xreturn 坐标点X*/
- (CGFloat)scannerOriginX {CGFloat width CGRectGetWidth(self.bounds);CGFloat scannerWidth kScannerScale * width;CGFloat originX (width - scannerWidth)/2;return originX;
}/**扫描器坐标点Yreturn 坐标点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;
}endSDQrScanViewController.h
#import UIKit/UIKit.h
#import AVFoundation/AVFoundation.h
#import SDQrScanTool.h
#import SDBaseViewController.hinterface SDQrScanViewController : SDBaseViewControllerproperty (nonatomic, strong) SDQrScanConfig *scanConfig;endSDQrScanViewController.m
#import SDQrScanViewController.h
#import SDQrScanView.hinterface SDQrScanViewController ()AVCaptureMetadataOutputObjectsDelegate, AVCaptureVideoDataOutputSampleBufferDelegate, UIImagePickerControllerDelegate, UINavigationControllerDelegateproperty (nonatomic, strong) SDQrScanView *scannerView;
property (nonatomic, strong) AVCaptureSession *session;property (nonatomic, strong) UIView *videoPreView; //视频预览显示视图endimplementation 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:selfselector:selector(appDidBecomeActive:)name:UIApplicationDidBecomeActiveNotificationobject:nil];[[NSNotificationCenter defaultCenter] addObserver:selfselector:selector(appWillResignActive:)name:UIApplicationWillResignActiveNotificationobject: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:(NSDictionaryNSString *,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 其他falseCGPointMakeWithDictionaryRepresentation(dict, point);NSLog(X:%f -- Y:%f,point.x,point.y);CGPoint point2 CGPointZero;CGPointMakeWithDictionaryRepresentation((__bridge CFDictionaryRef)array[2], point2);NSLog(X:%f -- Y:%f,point2.x,point2.y);NSLog(bounds:%,NSStringFromCGRect(objc.bounds));}#pragma mark -- App 从后台进入前台
- (void)appDidBecomeActive:(NSNotification *)notify {[self resumeScanning];
}#pragma mark -- App 从前台进入后台
- (void)appWillResignActive:(NSNotification *)notify {[self pauseScanning];
}/**恢复扫一扫功能*/
- (void)resumeScanning {if (self.session) {[self.session startRunning];[self.scannerView startLineAnimation];}
}/**暂停扫一扫*/
- (void)pauseScanning {if (self.session) {[self.session stopRunning];[self.scannerView stopLineAnimation];}
}#pragma mark -- 扫一扫API
/**处理扫一扫结果param value 扫描结果*/
- (void)handleScanValue:(NSString *)value {NSLog(handleScanValue %, value);
}/**相册选取图片无法读取数据*/
- (void)readFromAlbumFailed {NSLog(readFromAlbumFailed);
}- (void)didReceiveMemoryWarning {[super didReceiveMemoryWarning];// Dispose of any resources that can be recreated.
}end至此实现了二维码扫一扫Scan及识别图片中二维码功能。
五、小结
iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能。扫一扫是使用摄像头扫码二维码或者条形码获取对应二维码或条形码内容字符串。识别图中二维码通过CIDetector来识别出内容字符串。最后实现响应的业务逻辑。
学习记录每天不停进步。