iOS开发 您所在的位置:网站首页 safari怎么扫描二维码ipad iOS开发

iOS开发

2024-06-29 22:59| 来源: 网络整理| 查看: 265

iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能

在iOS开发中,会遇到扫一扫功能,扫一扫是使用摄像头扫码二维码或者条形码,获取对应二维码或条形码内容字符串。通过获得的字符串进行跳转或者打开某个页面开启下一步的业务逻辑。

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

一、使用前权限设置

扫一扫功能需要开启相机权限,需要在info.plist文件中添加NSCameraUsageDescription

例如:

NSCameraUsageDescription 开启相机权限,活动扫一扫更快捷 NSLocationAlwaysAndWhenInUseUsageDescription 开启定位权限 NSLocationAlwaysUsageDescription 开启定位权限 NSLocationWhenInUseUsageDescription 开启定位权限 NSMicrophoneUsageDescription 开启麦克风权限 NSPhotoLibraryAddUsageDescription 添加照片需要您的同意 NSPhotoLibraryUsageDescription 开启照片权限

这里还有其他权限,暂时扫一扫只需要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]; } metadataOutputadataObjectTypes = [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 *)metadataObjects fromConnection:(AVCaptureConnection *)connection;

该方法每当输出捕获并发出新对象时,委托都会收到此消息,获得对应metadataObjectTypes。通过此方法,我们可以当扫描二维码时候获得对应的结果。

#pragma mark -- AVCaptureMetadataOutputObjectsDelegate - (void)captureOutput:(AVCaptureOutput *)output didOutputMetadataObjects:(NSArray *)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 [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.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; } 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 // 左上角棱角 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:(NSDictionary *)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]; } }]; } 四、实现扫一扫及识别图片中二维码的全部代码

实现扫一扫及识别图片中二维码的全部代码,主要是

SDQrScanViewController:UIViewController SDQrScanView:UIView显示界面 SDQrScanTool:扫一扫权限及设置 SDQrScanConfig:扫一扫边框颜色等

完整代码如下

SDQrScanConfig.h

#import #import #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 #import #import #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 #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 // 左上角棱角 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 #import #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 () @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]; } metadataOutputadataObjectTypes = [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 *)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 [self.scannerView hideFlashlight:YES]; } } } - (void)showAlbum { // 校验相册权限 [SDQrScanTool checkAlbumAuthorizationStatus:^(BOOL granted) { if (granted) { [self imagePicker]; } }]; } #pragma mark -- UIImagePickerControllerDelegate - (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)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

至此实现了二维码扫一扫Scan及识别图片中二维码功能。

五、小结

iOS开发-实现二维码扫一扫Scan及识别图片中二维码功能。扫一扫是使用摄像头扫码二维码或者条形码,获取对应二维码或条形码内容字符串。识别图中二维码通过CIDetector来识别出内容字符串。最后实现响应的业务逻辑。

学习记录,每天不停进步。



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有