二维码(QRCode)的本质其实就是一个字符串,主要用到的是AVFoundation框架,我们的最终目的就是将二维码图片转换成字符串展示出来。
项目下载地址 https://github.com/Bobo168/QR-code
使用方法:
#import "ViewController.h"
#import "SGQRCodeScanManager.h"
#import "WCQRCodeScanningVC.h"
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.title = @"bobo_Ma";
UIButton *btn = [[UIButton alloc]initWithFrame:CGRectMake(100, 250, 150, 150)];
[btn addTarget:self action:@selector(click) forControlEvents:UIControlEventTouchUpInside];
[btn setTitle:@"点我扫一扫" forState:UIControlStateNormal];
[btn setTitleColor:[UIColor redColor] forState:UIControlStateNormal];
[self.view addSubview:btn];
}
-(void)click{
WCQRCodeScanningVC *WBVC = [[WCQRCodeScanningVC alloc] init];
[self QRCodeScanVC:WBVC];
}
- (void)QRCodeScanVC:(UIViewController *)scanVC {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (device) {
AVAuthorizationStatus status = [AVCaptureDevice authorizationStatusForMediaType:AVMediaTypeVideo];
switch (status) {
case AVAuthorizationStatusNotDetermined: {
[AVCaptureDevice requestAccessForMediaType:AVMediaTypeVideo completionHandler:^(BOOL granted) {
if (granted) {
dispatch_sync(dispatch_get_main_queue(), ^{
[self.navigationController pushViewController:scanVC animated:YES];
});
NSLog(@"用户同意了访问相机权限 - - %@", [NSThread currentThread]);
} else {
NSLog(@"用户拒绝了访问相机权限 - - %@", [NSThread currentThread]);
}
}];
break;
}
case AVAuthorizationStatusAuthorized: {
[self.navigationController pushViewController:scanVC animated:YES];
break;
}
case AVAuthorizationStatusDenied: {
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"请去-> [设置 - 隐私 - 相机 - SGQRCodeExample] 打开访问开关" preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
}];
[alertC addAction:alertA];
[self presentViewController:alertC animated:YES completion:nil];
break;
}
case AVAuthorizationStatusRestricted: {
NSLog(@"因为系统原因, 无法访问相册");
break;
}
default:
break;
}
return;
}
UIAlertController *alertC = [UIAlertController alertControllerWithTitle:@"温馨提示" message:@"未检测到您的摄像头" preferredStyle:(UIAlertControllerStyleAlert)];
UIAlertAction *alertA = [UIAlertAction actionWithTitle:@"确定" style:(UIAlertActionStyleDefault) handler:^(UIAlertAction * _Nonnull action) {
}];
[alertC addAction:alertA];
[self presentViewController:alertC animated:YES completion:nil];
}
核心代码说明:
// 扫码管理控制器类
SGQRCodeScanManager.h
#import
#import
@class SGQRCodeScanManager;
@protocol SGQRCodeScanManagerDelegate
@required
/** 二维码扫描获取数据的回调方法 (metadataObjects: 扫描二维码数据信息) */
- (void)QRCodeScanManager:(SGQRCodeScanManager *)scanManager didOutputMetadataObjects:(NSArray *)metadataObjects;
@optional
/** 根据光线强弱值打开手电筒的方法 (brightnessValue: 光线强弱值) */
- (void)QRCodeScanManager:(SGQRCodeScanManager *)scanManager brightnessValue:(CGFloat)brightnessValue;
@end
@interface SGQRCodeScanManager : NSObject
/** 快速创建单利方法 */
+ (instancetype)sharedManager;
/** SGQRCodeScanManagerDelegate */
@property (nonatomic, weak) id delegate;
/**
* 创建扫描二维码会话对象以及会话采集数据类型和扫码支持的编码格式的设置,必须实现的方法
*
* @param sessionPreset 会话采集数据类型
* @param metadataObjectTypes 扫码支持的编码格式
* @param currentController SGQRCodeScanManager 所在控制器
*/
- (void)setupSessionPreset:(NSString *)sessionPreset metadataObjectTypes:(NSArray *)metadataObjectTypes currentController:(UIViewController *)currentController;
/** 开启会话对象扫描 */
- (void)startRunning;
/** 停止会话对象扫描 */
- (void)stopRunning;
/** 移除 videoPreviewLayer 对象 */
- (void)videoPreviewLayerRemoveFromSuperlayer;
/** 播放音效文件 */
- (void)playSoundName:(NSString *)name;
/** 重置根据光线强弱值打开手电筒的 delegate 方法 */
- (void)resetSampleBufferDelegate;
/** 取消根据光线强弱值打开手电筒的 delegate 方法 */
- (void)cancelSampleBufferDelegate;
@end
// 扫码管理控制器类
SGQRCodeScanManager.m
#import "SGQRCodeScanManager.h"
@interface SGQRCodeScanManager ()
@property (nonatomic, strong) AVCaptureSession *session;
@property (nonatomic, strong) AVCaptureVideoDataOutput *videoDataOutput;
@property (nonatomic, strong) AVCaptureVideoPreviewLayer *videoPreviewLayer;
@end
@implementation SGQRCodeScanManager
static SGQRCodeScanManager *_instance;
+ (instancetype)sharedManager {
return [[self alloc] init];
}
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
_instance = [super allocWithZone:zone];
});
return _instance;
}
-(id)copyWithZone:(NSZone *)zone {
return _instance;
}
-(id)mutableCopyWithZone:(NSZone *)zone {
return _instance;
}
- (void)setupSessionPreset:(NSString *)sessionPreset metadataObjectTypes:(NSArray *)metadataObjectTypes currentController:(UIViewController *)currentController {
if (sessionPreset == nil) {
@throw [NSException exceptionWithName:@"SGQRCode" reason:@"setupSessionPreset:metadataObjectTypes:currentController: 方法中的 sessionPreset 参数不能为空" userInfo:nil];
}
if (metadataObjectTypes == nil) {
@throw [NSException exceptionWithName:@"SGQRCode" reason:@"setupSessionPreset:metadataObjectTypes:currentController: 方法中的 metadataObjectTypes 参数不能为空" userInfo:nil];
}
if (currentController == nil) {
@throw [NSException exceptionWithName:@"SGQRCode" reason:@"setupSessionPreset:metadataObjectTypes:currentController: 方法中的 currentController 参数不能为空" userInfo:nil];
}
// 1、获取摄像设备
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// 2、创建摄像设备输入流
AVCaptureDeviceInput *deviceInput = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
// 3、创建元数据输出流
AVCaptureMetadataOutput *metadataOutput = [[AVCaptureMetadataOutput alloc] init];
[metadataOutput setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 3(1)、创建摄像数据输出流
self.videoDataOutput = [[AVCaptureVideoDataOutput alloc] init];
[_videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
// 设置扫描范围(每一个取值0~1,以屏幕右上角为坐标原点)
// 注:微信二维码的扫描范围是整个屏幕,这里并没有做处理(可不用设置);
// 如需限制扫描框范围,打开下一句注释代码并进行相应调整
// metadataOutput.rectOfInterest = CGRectMake(0.05, 0.2, 0.7, 0.6);
// 4、创建会话对象
_session = [[AVCaptureSession alloc] init];
// 会话采集率: AVCaptureSessionPresetHigh
_session.sessionPreset = sessionPreset;
// 5、添加元数据输出流到会话对象
[_session addOutput:metadataOutput];
// 5(1)添加摄像输出流到会话对象;与 3(1) 构成识了别光线强弱
[_session addOutput:_videoDataOutput];
// 6、添加摄像设备输入流到会话对象
[_session addInput:deviceInput];
// 7、设置数据输出类型,需要将数据输出添加到会话后,才能指定元数据类型,否则会报错
// 设置扫码支持的编码格式(如下设置条形码和二维码兼容)
// @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]
metadataOutput.metadataObjectTypes = metadataObjectTypes;
// 8、实例化预览图层, 传递_session是为了告诉图层将来显示什么内容
_videoPreviewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
// 保持纵横比;填充层边界
_videoPreviewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
CGFloat x = 0;
CGFloat y = 0;
CGFloat w = [UIScreen mainScreen].bounds.size.width;
CGFloat h = [UIScreen mainScreen].bounds.size.height;
_videoPreviewLayer.frame = CGRectMake(x, y, w, h);
[currentController.view.layer insertSublayer:_videoPreviewLayer atIndex:0];
// 9、启动会话
[_session startRunning];
}
#pragma mark - - - AVCaptureMetadataOutputObjectsDelegate
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
if (self.delegate && [self.delegate respondsToSelector:@selector(QRCodeScanManager:didOutputMetadataObjects:)]) {
[self.delegate QRCodeScanManager:self didOutputMetadataObjects:metadataObjects];
}
}
#pragma mark - - - AVCaptureVideoDataOutputSampleBufferDelegate的方法
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputSampleBuffer:(CMSampleBufferRef)sampleBuffer fromConnection:(AVCaptureConnection *)connection {
// 这个方法会时时调用,但内存很稳定
CFDictionaryRef metadataDict = CMCopyDictionaryOfAttachments(NULL,sampleBuffer, kCMAttachmentMode_ShouldPropagate);
NSDictionary *metadata = [[NSMutableDictionary alloc] initWithDictionary:(__bridge NSDictionary*)metadataDict];
CFRelease(metadataDict);
NSDictionary *exifMetadata = [[metadata objectForKey:(NSString *)kCGImagePropertyExifDictionary] mutableCopy];
float brightnessValue = [[exifMetadata objectForKey:(NSString *)kCGImagePropertyExifBrightnessValue] floatValue];
NSLog(@"%f",brightnessValue);
if (self.delegate && [self.delegate respondsToSelector:@selector(QRCodeScanManager:brightnessValue:)]) {
[self.delegate QRCodeScanManager:self brightnessValue:brightnessValue];
}
}
- (void)startRunning {
[_session startRunning];
}
- (void)stopRunning {
[_session stopRunning];
}
- (void)videoPreviewLayerRemoveFromSuperlayer {
[_videoPreviewLayer removeFromSuperlayer];
}
- (void)resetSampleBufferDelegate {
[_videoDataOutput setSampleBufferDelegate:self queue:dispatch_get_main_queue()];
}
- (void)cancelSampleBufferDelegate {
[_videoDataOutput setSampleBufferDelegate:nil queue:dispatch_get_main_queue()];
}
- (void)playSoundName:(NSString *)name {
NSString *audioFile = [[NSBundle mainBundle] pathForResource:name ofType:nil];
NSURL *fileUrl = [NSURL fileURLWithPath:audioFile];
SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
AudioServicesPlaySystemSound(soundID); // 播放音效
}
void soundCompleteCallback(SystemSoundID soundID, void *clientData){
}
@end
//扫码界面扫描区域控制类,可以在该类中修改扫描区域的边框颜色、扫码方式、边角位置、扫描线的动画时间等。
SGQRCodeScanningView.h
#import
typedef enum : NSUInteger {
/// 默认与边框线同中心点
CornerLoactionDefault,
/// 在边框线内部
CornerLoactionInside,
/// 在边框线外部
CornerLoactionOutside
} CornerLoaction;
typedef enum : NSUInteger {
/// 单线扫描样式
ScanningAnimationStyleDefault,
/// 网格扫描样式
ScanningAnimationStyleGrid
} ScanningAnimationStyle;
@interface SGQRCodeScanningView : UIView
/** 扫描样式,默认 ScanningAnimationStyleDefault */
@property (nonatomic, assign) ScanningAnimationStyle scanningAnimationStyle;
/** 扫描线名 */
@property (nonatomic, copy) NSString *scanningImageName;
/** 边框颜色,默认白色 */
@property (nonatomic, strong) UIColor *borderColor;
/** 边角位置,默认 CornerLoactionDefault */
@property (nonatomic, assign) CornerLoaction cornerLocation;
/** 边角颜色,默认微信颜色 */
@property (nonatomic, strong) UIColor *cornerColor;
/** 边角宽度,默认 2.f */
@property (nonatomic, assign) CGFloat cornerWidth;
/** 扫描区周边颜色的 alpha 值,默认 0.2f */
@property (nonatomic, assign) CGFloat backgroundAlpha;
/** 扫描线动画时间,默认 0.02 */
@property (nonatomic, assign) NSTimeInterval animationTimeInterval;
/** 添加定时器 */
- (void)addTimer;
/** 移除定时器(切记:一定要在Controller视图消失的时候,停止定时器) */
- (void)removeTimer;
@end
//扫码界面扫描区域控制类
SGQRCodeScanningView.m
#import "SGQRCodeScanningView.h"
/** 扫描内容的 W 值 */
#define scanBorderW 0.7 * self.frame.size.width
/** 扫描内容的 x 值 */
#define scanBorderX 0.5 * (1 - 0.7) * self.frame.size.width
/** 扫描内容的 Y 值 */
#define scanBorderY 0.5 * (self.frame.size.height - scanBorderW)
@interface SGQRCodeScanningView ()
@property (nonatomic, strong) UIView *contentView;
@property (nonatomic, strong) NSTimer *timer;
@property (nonatomic, strong) UIImageView *scanningline;
@end
@implementation SGQRCodeScanningView
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
self.backgroundColor = [UIColor clearColor];
[self initialization];
}
return self;
}
- (void)awakeFromNib {
[super awakeFromNib];
[self initialization];
}
- (void)initialization {
_scanningAnimationStyle = ScanningAnimationStyleDefault;
_borderColor = [UIColor whiteColor];
_cornerLocation = CornerLoactionDefault;
_cornerColor = [UIColor colorWithRed:85/255.0f green:183/255.0 blue:55/255.0 alpha:1.0];
_cornerWidth = 2.0;
_backgroundAlpha = 0.5;
_animationTimeInterval = 0.02;
_scanningImageName = @"SGQRCode.bundle/QRCodeScanningLine";
}
- (UIView *)contentView {
if (!_contentView) {
_contentView = [[UIView alloc] init];
_contentView.frame = CGRectMake(scanBorderX, scanBorderY, scanBorderW, scanBorderW);
_contentView.clipsToBounds = YES;
_contentView.backgroundColor = [UIColor clearColor];
}
return _contentView;
}
- (void)drawRect:(CGRect)rect {
[super drawRect:rect];
/// 边框 frame
CGFloat borderW = scanBorderW;
CGFloat borderH = borderW;
CGFloat borderX = scanBorderX;
CGFloat borderY = scanBorderY;
CGFloat borderLineW = 0.2;
/// 空白区域设置
[[[UIColor blackColor] colorWithAlphaComponent:self.backgroundAlpha] setFill];
UIRectFill(rect);
// 获取上下文,并设置混合模式 -> kCGBlendModeDestinationOut
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetBlendMode(context, kCGBlendModeDestinationOut);
// 设置空白区
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRect:CGRectMake(borderX + 0.5 * borderLineW, borderY + 0.5 *borderLineW, borderW - borderLineW, borderH - borderLineW)];
[bezierPath fill];
// 执行混合模式
CGContextSetBlendMode(context, kCGBlendModeNormal);
/// 边框设置
UIBezierPath *borderPath = [UIBezierPath bezierPathWithRect:CGRectMake(borderX, borderY, borderW, borderH)];
borderPath.lineCapStyle = kCGLineCapButt;
borderPath.lineWidth = borderLineW;
[self.borderColor set];
[borderPath stroke];
CGFloat cornerLenght = 20;
/// 左上角小图标
UIBezierPath *leftTopPath = [UIBezierPath bezierPath];
leftTopPath.lineWidth = self.cornerWidth;
[self.cornerColor set];
CGFloat insideExcess = fabs(0.5 * (self.cornerWidth - borderLineW));
CGFloat outsideExcess = 0.5 * (borderLineW + self.cornerWidth);
if (self.cornerLocation == CornerLoactionInside) {
[leftTopPath moveToPoint:CGPointMake(borderX + insideExcess, borderY + cornerLenght + insideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX + insideExcess, borderY + insideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX + cornerLenght + insideExcess, borderY + insideExcess)];
} else if (self.cornerLocation == CornerLoactionOutside) {
[leftTopPath moveToPoint:CGPointMake(borderX - outsideExcess, borderY + cornerLenght - outsideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX - outsideExcess, borderY - outsideExcess)];
[leftTopPath addLineToPoint:CGPointMake(borderX + cornerLenght - outsideExcess, borderY - outsideExcess)];
} else {
[leftTopPath moveToPoint:CGPointMake(borderX, borderY + cornerLenght)];
[leftTopPath addLineToPoint:CGPointMake(borderX, borderY)];
[leftTopPath addLineToPoint:CGPointMake(borderX + cornerLenght, borderY)];
}
[leftTopPath stroke];
/// 左下角小图标
UIBezierPath *leftBottomPath = [UIBezierPath bezierPath];
leftBottomPath.lineWidth = self.cornerWidth;
[self.cornerColor set];
if (self.cornerLocation == CornerLoactionInside) {
[leftBottomPath moveToPoint:CGPointMake(borderX + cornerLenght + insideExcess, borderY + borderH - insideExcess)];
[leftBottomPath addLineToPoint:CGPointMake(borderX + insideExcess, borderY + borderH - insideExcess)];
[leftBottomPath addLineToPoint:CGPointMake(borderX + insideExcess, borderY + borderH - cornerLenght - insideExcess)];
} else if (self.cornerLocation == CornerLoactionOutside) {
[leftBottomPath moveToPoint:CGPointMake(borderX + cornerLenght - outsideExcess, borderY + borderH + outsideExcess)];
[leftBottomPath addLineToPoint:CGPointMake(borderX - outsideExcess, borderY + borderH + outsideExcess)];
[leftBottomPath addLineToPoint:CGPointMake(borderX - outsideExcess, borderY + borderH - cornerLenght + outsideExcess)];
} else {
[leftBottomPath moveToPoint:CGPointMake(borderX + cornerLenght, borderY + borderH)];
[leftBottomPath addLineToPoint:CGPointMake(borderX, borderY + borderH)];
[leftBottomPath addLineToPoint:CGPointMake(borderX, borderY + borderH - cornerLenght)];
}
[leftBottomPath stroke];
/// 右上角小图标
UIBezierPath *rightTopPath = [UIBezierPath bezierPath];
rightTopPath.lineWidth = self.cornerWidth;
[self.cornerColor set];
if (self.cornerLocation == CornerLoactionInside) {
[rightTopPath moveToPoint:CGPointMake(borderX + borderW - cornerLenght - insideExcess, borderY + insideExcess)];
[rightTopPath addLineToPoint:CGPointMake(borderX + borderW - insideExcess, borderY + insideExcess)];
[rightTopPath addLineToPoint:CGPointMake(borderX + borderW - insideExcess, borderY + cornerLenght + insideExcess)];
} else if (self.cornerLocation == CornerLoactionOutside) {
[rightTopPath moveToPoint:CGPointMake(borderX + borderW - cornerLenght + outsideExcess, borderY - outsideExcess)];
[rightTopPath addLineToPoint:CGPointMake(borderX + borderW + outsideExcess, borderY - outsideExcess)];
[rightTopPath addLineToPoint:CGPointMake(borderX + borderW + outsideExcess, borderY + cornerLenght - outsideExcess)];
} else {
[rightTopPath moveToPoint:CGPointMake(borderX + borderW - cornerLenght, borderY)];
[rightTopPath addLineToPoint:CGPointMake(borderX + borderW, borderY)];
[rightTopPath addLineToPoint:CGPointMake(borderX + borderW, borderY + cornerLenght)];
}
[rightTopPath stroke];
/// 右下角小图标
UIBezierPath *rightBottomPath = [UIBezierPath bezierPath];
rightBottomPath.lineWidth = self.cornerWidth;
[self.cornerColor set];
if (self.cornerLocation == CornerLoactionInside) {
[rightBottomPath moveToPoint:CGPointMake(borderX + borderW - insideExcess, borderY + borderH - cornerLenght - insideExcess)];
[rightBottomPath addLineToPoint:CGPointMake(borderX + borderW - insideExcess, borderY + borderH - insideExcess)];
[rightBottomPath addLineToPoint:CGPointMake(borderX + borderW - cornerLenght - insideExcess, borderY + borderH - insideExcess)];
} else if (self.cornerLocation == CornerLoactionOutside) {
[rightBottomPath moveToPoint:CGPointMake(borderX + borderW + outsideExcess, borderY + borderH - cornerLenght + outsideExcess)];
[rightBottomPath addLineToPoint:CGPointMake(borderX + borderW + outsideExcess, borderY + borderH + outsideExcess)];
[rightBottomPath addLineToPoint:CGPointMake(borderX + borderW - cornerLenght + outsideExcess, borderY + borderH + outsideExcess)];
} else {
[rightBottomPath moveToPoint:CGPointMake(borderX + borderW, borderY + borderH - cornerLenght)];
[rightBottomPath addLineToPoint:CGPointMake(borderX + borderW, borderY + borderH)];
[rightBottomPath addLineToPoint:CGPointMake(borderX + borderW - cornerLenght, borderY + borderH)];
}
[rightBottomPath stroke];
}
#pragma mark - - - 添加定时器
- (void)addTimer {
CGFloat scanninglineX = 0;
CGFloat scanninglineY = 0;
CGFloat scanninglineW = 0;
CGFloat scanninglineH = 0;
if (self.scanningAnimationStyle == ScanningAnimationStyleGrid) {
[self addSubview:self.contentView];
[_contentView addSubview:self.scanningline];
scanninglineW = scanBorderW;
scanninglineH = scanBorderW;
scanninglineX = 0;
scanninglineY = - scanBorderW;
_scanningline.frame = CGRectMake(scanninglineX, scanninglineY, scanninglineW, scanninglineH);
} else {
[self addSubview:self.scanningline];
scanninglineW = scanBorderW;
scanninglineH = 12;
scanninglineX = scanBorderX;
scanninglineY = scanBorderY;
_scanningline.frame = CGRectMake(scanninglineX, scanninglineY, scanninglineW, scanninglineH);
}
self.timer = [NSTimer timerWithTimeInterval:self.animationTimeInterval target:self selector:@selector(beginRefreshUI) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:_timer forMode:NSRunLoopCommonModes];
}
#pragma mark - - - 移除定时器
- (void)removeTimer {
[self.timer invalidate];
self.timer = nil;
[self.scanningline removeFromSuperview];
self.scanningline = nil;
}
#pragma mark - - - 执行定时器方法
- (void)beginRefreshUI {
__block CGRect frame = _scanningline.frame;
static BOOL flag = YES;
if (self.scanningAnimationStyle == ScanningAnimationStyleGrid) {
if (flag) {
frame.origin.y = - scanBorderW;
flag = NO;
[UIView animateWithDuration:self.animationTimeInterval animations:^{
frame.origin.y += 2;
_scanningline.frame = frame;
} completion:nil];
} else {
if (_scanningline.frame.origin.y >= - scanBorderW) {
CGFloat scanContent_MaxY = - scanBorderW + self.frame.size.width - 2 * scanBorderX;
if (_scanningline.frame.origin.y >= scanContent_MaxY) {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
frame.origin.y = - scanBorderW;
_scanningline.frame = frame;
flag = YES;
});
} else {
[UIView animateWithDuration:self.animationTimeInterval animations:^{
frame.origin.y += 2;
_scanningline.frame = frame;
} completion:nil];
}
} else {
flag = !flag;
}
}
} else {
if (flag) {
frame.origin.y = scanBorderY;
flag = NO;
[UIView animateWithDuration:self.animationTimeInterval animations:^{
frame.origin.y += 2;
_scanningline.frame = frame;
} completion:nil];
} else {
if (_scanningline.frame.origin.y >= scanBorderY) {
CGFloat scanContent_MaxY = scanBorderY + self.frame.size.width - 2 * scanBorderX;
if (_scanningline.frame.origin.y >= scanContent_MaxY - 10) {
frame.origin.y = scanBorderY;
_scanningline.frame = frame;
flag = YES;
} else {
[UIView animateWithDuration:self.animationTimeInterval animations:^{
frame.origin.y += 2;
_scanningline.frame = frame;
} completion:nil];
}
} else {
flag = !flag;
}
}
}
}
- (UIImageView *)scanningline {
if (!_scanningline) {
_scanningline = [[UIImageView alloc] init];
_scanningline.image = [UIImage imageNamed:self.scanningImageName];
}
return _scanningline;
}
#pragma mark - - - set
- (void)setScanningAnimationStyle:(ScanningAnimationStyle)scanningAnimationStyle {
_scanningAnimationStyle = scanningAnimationStyle;
}
- (void)setScanningImageName:(NSString *)scanningImageName {
_scanningImageName = scanningImageName;
}
- (void)setBorderColor:(UIColor *)borderColor {
_borderColor = borderColor;
}
- (void)setCornerLocation:(CornerLoaction)cornerLocation {
_cornerLocation = cornerLocation;
}
- (void)setCornerColor:(UIColor *)cornerColor {
_cornerColor = cornerColor;
}
- (void)setCornerWidth:(CGFloat)cornerWidth {
_cornerWidth = cornerWidth;
}
- (void)setBackgroundAlpha:(CGFloat)backgroundAlpha {
_backgroundAlpha = backgroundAlpha;
}
- (void)setAnimationTimeInterval:(NSTimeInterval)animationTimeInterval {
_animationTimeInterval = animationTimeInterval;
}
@end
//扫码界面控制器类,可以修改手电筒图片,扫描完成的音效等。
WCQRCodeScanningVC.h
#import
@interface WCQRCodeScanningVC : UIViewController
@end
//扫码界面控制器类
WCQRCodeScanningVC.m
#import "WCQRCodeScanningVC.h"
#import "SGQRCode.h"
#import "ScanSuccessJumpVC.h"
@interface WCQRCodeScanningVC ()
@property (nonatomic, strong) SGQRCodeScanManager *manager;
@property (nonatomic, strong) SGQRCodeScanningView *scanningView;
@property (nonatomic, strong) UIButton *flashlightBtn;
@property (nonatomic, strong) UILabel *promptLabel;
@property (nonatomic, assign) BOOL isSelectedFlashlightBtn;
@property (nonatomic, strong) UIView *bottomView;
@end
@implementation WCQRCodeScanningVC
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.scanningView addTimer];
[_manager resetSampleBufferDelegate];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.scanningView removeTimer];
[self removeFlashlightBtn];
[_manager cancelSampleBufferDelegate];
}
- (void)dealloc {
NSLog(@"WCQRCodeScanningVC - dealloc");
[self removeScanningView];
}
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view from its nib.
self.view.backgroundColor = [UIColor clearColor];
self.automaticallyAdjustsScrollViewInsets = NO;
[self.view addSubview:self.scanningView];
[self setupNavigationBar];
[self setupQRCodeScanning];
[self.view addSubview:self.promptLabel];
/// 为了 UI 效果
[self.view addSubview:self.bottomView];
}
- (void)setupNavigationBar {
self.navigationItem.title = @"扫一扫";
}
- (SGQRCodeScanningView *)scanningView {
if (!_scanningView) {
_scanningView = [[SGQRCodeScanningView alloc] initWithFrame:CGRectMake(0, 0, self.view.frame.size.width, 0.9 * self.view.frame.size.height)];
}
return _scanningView;
}
- (void)removeScanningView {
[self.scanningView removeTimer];
[self.scanningView removeFromSuperview];
self.scanningView = nil;
}
- (void)setupQRCodeScanning {
self.manager = [SGQRCodeScanManager sharedManager];
NSArray *arr = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
// AVCaptureSessionPreset1920x1080 推荐使用,对于小型的二维码读取率较高
[_manager setupSessionPreset:AVCaptureSessionPreset1920x1080 metadataObjectTypes:arr currentController:self];
_manager.delegate = self;
}
#pragma mark - - - SGQRCodeScanManagerDelegate
- (void)QRCodeScanManager:(SGQRCodeScanManager *)scanManager didOutputMetadataObjects:(NSArray *)metadataObjects {
NSLog(@"metadataObjects - - %@", metadataObjects);
if (metadataObjects != nil && metadataObjects.count > 0) {
[scanManager playSoundName:@"SGQRCode.bundle/sound.caf"];
[scanManager stopRunning];
[scanManager videoPreviewLayerRemoveFromSuperlayer];
AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
ScanSuccessJumpVC *jumpVC = [[ScanSuccessJumpVC alloc] init];
jumpVC.comeFromVC = ScanSuccessJumpComeFromWC;
jumpVC.jump_URL = [obj stringValue];
[self.navigationController pushViewController:jumpVC animated:YES];
} else {
NSLog(@"暂未识别出扫描的二维码");
}
}
- (void)QRCodeScanManager:(SGQRCodeScanManager *)scanManager brightnessValue:(CGFloat)brightnessValue {
if (brightnessValue < - 1) {
[self.view addSubview:self.flashlightBtn];
} else {
if (self.isSelectedFlashlightBtn == NO) {
[self removeFlashlightBtn];
}
}
}
- (UILabel *)promptLabel {
if (!_promptLabel) {
_promptLabel = [[UILabel alloc] init];
_promptLabel.backgroundColor = [UIColor clearColor];
CGFloat promptLabelX = 0;
CGFloat promptLabelY = 0.73 * self.view.frame.size.height;
CGFloat promptLabelW = self.view.frame.size.width;
CGFloat promptLabelH = 25;
_promptLabel.frame = CGRectMake(promptLabelX, promptLabelY, promptLabelW, promptLabelH);
_promptLabel.textAlignment = NSTextAlignmentCenter;
_promptLabel.font = [UIFont boldSystemFontOfSize:13.0];
_promptLabel.textColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6];
_promptLabel.text = @"将二维码/条码放入框内, 即可自动扫描";
}
return _promptLabel;
}
- (UIView *)bottomView {
if (!_bottomView) {
_bottomView = [[UIView alloc] initWithFrame:CGRectMake(0, CGRectGetMaxY(self.scanningView.frame), self.view.frame.size.width, self.view.frame.size.height - CGRectGetMaxY(self.scanningView.frame))];
_bottomView.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:0.5];
}
return _bottomView;
}
#pragma mark - - - 闪光灯按钮
- (UIButton *)flashlightBtn {
if (!_flashlightBtn) {
// 添加闪光灯按钮
_flashlightBtn = [UIButton buttonWithType:(UIButtonTypeCustom)];
CGFloat flashlightBtnW = 30;
CGFloat flashlightBtnH = 30;
CGFloat flashlightBtnX = 0.5 * (self.view.frame.size.width - flashlightBtnW);
CGFloat flashlightBtnY = 0.55 * self.view.frame.size.height;
_flashlightBtn.frame = CGRectMake(flashlightBtnX, flashlightBtnY, flashlightBtnW, flashlightBtnH);
NSString *openImageName = @"SGQRCode.bundle/SGQRCodeFlashlightOpenImage";
[_flashlightBtn setBackgroundImage:[UIImage imageNamed:openImageName] forState:(UIControlStateNormal)];
NSString *closeImageName = @"SGQRCode.bundle/SGQRCodeFlashlightCloseImage";
[_flashlightBtn setBackgroundImage:[UIImage imageNamed:closeImageName] forState:(UIControlStateSelected)];
[_flashlightBtn addTarget:self action:@selector(flashlightBtn_action:) forControlEvents:UIControlEventTouchUpInside];
}
return _flashlightBtn;
}
- (void)flashlightBtn_action:(UIButton *)button {
if (button.selected == NO) {
[SGQRCodeHelperTool SG_openFlashlight];
self.isSelectedFlashlightBtn = YES;
button.selected = YES;
} else {
[self removeFlashlightBtn];
}
}
- (void)removeFlashlightBtn {
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[SGQRCodeHelperTool SG_CloseFlashlight];
self.isSelectedFlashlightBtn = NO;
self.flashlightBtn.selected = NO;
[self.flashlightBtn removeFromSuperview];
});
}
@end
//扫描结果展示类
ScanSuccessJumpVC.h
#import
typedef enum : NSUInteger {
ScanSuccessJumpComeFromWB,
ScanSuccessJumpComeFromWC
} ScanSuccessJumpComeFrom;
@interface ScanSuccessJumpVC : UIViewController
/** 判断从哪个控制器 push 过来 */
@property (nonatomic, assign) ScanSuccessJumpComeFrom comeFromVC;
/** 接收扫描的二维码信息 */
@property (nonatomic, copy) NSString *jump_URL;
/** 接收扫描的条形码信息 */
@property (nonatomic, copy) NSString *jump_bar_code;
@end
//扫描结果展示类
ScanSuccessJumpVC.m
#import "ScanSuccessJumpVC.h"
#import "SGWebView.h"
@interface ScanSuccessJumpVC ()
@property (nonatomic , strong) SGWebView *webView;
@end
@implementation ScanSuccessJumpVC
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view.
self.view.backgroundColor = [UIColor whiteColor];
[self setupNavigationItem];
if (self.jump_bar_code) {
[self setupLabel];
} else {
[self setupWebView];
}
}
- (void)setupNavigationItem {
UIButton *left_Button = [[UIButton alloc] init];
[left_Button setTitle:@"返回" forState:UIControlStateNormal];
[left_Button setTitleColor:[UIColor colorWithRed: 21/ 255.0f green: 126/ 255.0f blue: 251/ 255.0f alpha:1.0] forState:(UIControlStateNormal)];
[left_Button sizeToFit];
[left_Button addTarget:self action:@selector(left_BarButtonItemAction) forControlEvents:UIControlEventTouchUpInside];
UIBarButtonItem *left_BarButtonItem = [[UIBarButtonItem alloc] initWithCustomView:left_Button];
self.navigationItem.leftBarButtonItem = left_BarButtonItem;
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:(UIBarButtonSystemItemRefresh) target:self action:@selector(right_BarButtonItemAction)];
}
- (void)left_BarButtonItemAction {
if (self.comeFromVC == ScanSuccessJumpComeFromWB) {
[self.navigationController popViewControllerAnimated:YES];
}
if (self.comeFromVC == ScanSuccessJumpComeFromWC) {
[self.navigationController popToRootViewControllerAnimated:YES];
}
}
- (void)right_BarButtonItemAction {
[self.webView reloadData];
}
// 添加Label,加载扫描过来的内容
- (void)setupLabel {
// 提示文字
UILabel *prompt_message = [[UILabel alloc] init];
prompt_message.frame = CGRectMake(0, 200, self.view.frame.size.width, 30);
prompt_message.text = @"您扫描的条形码结果如下: ";
prompt_message.textColor = [UIColor redColor];
prompt_message.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:prompt_message];
// 扫描结果
CGFloat label_Y = CGRectGetMaxY(prompt_message.frame);
UILabel *label = [[UILabel alloc] init];
label.frame = CGRectMake(0, label_Y, self.view.frame.size.width, 30);
label.text = self.jump_bar_code;
label.textAlignment = NSTextAlignmentCenter;
[self.view addSubview:label];
}
// 添加webView,加载扫描过来的内容
- (void)setupWebView {
CGFloat webViewX = 0;
CGFloat webViewY = 0;
CGFloat webViewW = [UIScreen mainScreen].bounds.size.width;
CGFloat webViewH = [UIScreen mainScreen].bounds.size.height;
self.webView = [SGWebView webViewWithFrame:CGRectMake(webViewX, webViewY, webViewW, webViewH)];
if (self.comeFromVC == ScanSuccessJumpComeFromWB) {
_webView.progressViewColor = [UIColor orangeColor];
};
[_webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:self.jump_URL]]];
_webView.SGQRCodeDelegate = self;
[self.view addSubview:_webView];
}
- (void)webView:(SGWebView *)webView didFinishLoadWithURL:(NSURL *)url {
NSLog(@"didFinishLoad");
self.title = webView.navigationItemTitle;
}
@end
//加载二维码URL类
SGWebView.h
#import
@class SGWebView;
@protocol SGWebViewDelegate
@optional
/** 页面开始加载时调用 */
- (void)webViewDidStartLoad:(SGWebView *)webView;
/** 内容开始返回时调用 */
- (void)webView:(SGWebView *)webView didCommitWithURL:(NSURL *)url;
/** 页面加载失败时调用 */
- (void)webView:(SGWebView *)webView didFinishLoadWithURL:(NSURL *)url;
/** 页面加载完成之后调用 */
- (void)webView:(SGWebView *)webView didFailLoadWithError:(NSError *)error;
@end
@interface SGWebView : UIView
/** SGDelegate */
@property (nonatomic, weak) id SGQRCodeDelegate;
/** 进度条颜色(默认蓝色) */
@property (nonatomic, strong) UIColor *progressViewColor;
/** 导航栏标题 */
@property (nonatomic, copy) NSString *navigationItemTitle;
/** 导航栏存在且有穿透效果(默认导航栏存在且有穿透效果) */
@property (nonatomic, assign) BOOL isNavigationBarOrTranslucent;
/** 类方法创建 SGWebView */
+ (instancetype)webViewWithFrame:(CGRect)frame;
/** 加载 web */
- (void)loadRequest:(NSURLRequest *)request;
/** 加载 HTML */
- (void)loadHTMLString:(NSString *)HTMLString;
/** 刷新数据 */
- (void)reloadData;
@end
//加载二维码URL类
SGWebView.m
#import "SGWebView.h"
#import
@interface SGWebView ()
/// WKWebView
@property (nonatomic, strong) WKWebView *wkWebView;
/// 进度条
@property (nonatomic, strong) UIProgressView *progressView;
@end
@implementation SGWebView
static CGFloat const navigationBarHeight = 64;
static CGFloat const progressViewHeight = 2;
- (instancetype)initWithFrame:(CGRect)frame {
if (self = [super initWithFrame:frame]) {
[self addSubview:self.wkWebView];
[self addSubview:self.progressView];
}
return self;
}
+ (instancetype)webViewWithFrame:(CGRect)frame {
return [[self alloc] initWithFrame:frame];
}
- (WKWebView *)wkWebView {
if (!_wkWebView) {
_wkWebView = [[WKWebView alloc] initWithFrame:self.bounds];
_wkWebView.navigationDelegate = self;
_wkWebView.UIDelegate = self;
// KVO
[self.wkWebView addObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress)) options:0 context:nil];
}
return _wkWebView;
}
- (UIProgressView *)progressView {
if (!_progressView) {
_progressView = [[UIProgressView alloc] initWithProgressViewStyle:UIProgressViewStyleDefault];
_progressView.trackTintColor = [UIColor clearColor];
// 高度默认有导航栏且有穿透效果
_progressView.frame = CGRectMake(0, navigationBarHeight, self.frame.size.width, progressViewHeight);
// 设置进度条颜色
_progressView.tintColor = [UIColor greenColor];
}
return _progressView;
}
- (void)setProgressViewColor:(UIColor *)progressViewColor {
_progressViewColor = progressViewColor;
if (progressViewColor) {
_progressView.tintColor = progressViewColor;
}
}
- (void)setIsNavigationBarOrTranslucent:(BOOL)isNavigationBarOrTranslucent {
_isNavigationBarOrTranslucent = isNavigationBarOrTranslucent;
if (isNavigationBarOrTranslucent == YES) { // 导航栏存在且有穿透效果
_progressView.frame = CGRectMake(0, navigationBarHeight, self.frame.size.width, progressViewHeight);
} else { // 导航栏不存在或者没有有穿透效果
_progressView.frame = CGRectMake(0, 0, self.frame.size.width, progressViewHeight);
}
}
/// KVO
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
if ([keyPath isEqualToString:NSStringFromSelector(@selector(estimatedProgress))] && object == self.wkWebView) {
self.progressView.alpha = 1.0;
BOOL animated = self.wkWebView.estimatedProgress > self.progressView.progress;
[self.progressView setProgress:self.wkWebView.estimatedProgress animated:animated];
if(self.wkWebView.estimatedProgress >= 0.97) {
[UIView animateWithDuration:0.1 delay:0.3 options:UIViewAnimationOptionCurveEaseOut animations:^{
self.progressView.alpha = 0.0;
} completion:^(BOOL finished) {
[self.progressView setProgress:0.0 animated:NO];
}];
}
} else {
[super observeValueForKeyPath:keyPath ofObject:object change:change context:context];
}
}
#pragma mark - - - 加载的状态回调(WKNavigationDelegate)
/// 页面开始加载时调用
- (void)webView:(WKWebView *)webView didStartProvisionalNavigation:(WKNavigation *)navigation {
if (self.SGQRCodeDelegate && [self.SGQRCodeDelegate respondsToSelector:@selector(webViewDidStartLoad:)]) {
[self.SGQRCodeDelegate webViewDidStartLoad:self];
}
}
/// 当内容开始返回时调用
- (void)webView:(WKWebView *)webView didCommitNavigation:(WKNavigation *)navigation {
if (self.SGQRCodeDelegate && [self.SGQRCodeDelegate respondsToSelector:@selector(webView:didCommitWithURL:)]) {
[self.SGQRCodeDelegate webView:self didCommitWithURL:self.wkWebView.URL];
}
self.navigationItemTitle = self.wkWebView.title;
}
/// 页面加载完成之后调用
- (void)webView:(WKWebView *)webView didFinishNavigation:(WKNavigation *)navigation {
self.navigationItemTitle = self.wkWebView.title;
if (self.SGQRCodeDelegate && [self.SGQRCodeDelegate respondsToSelector:@selector(webView:didFinishLoadWithURL:)]) {
[self.SGQRCodeDelegate webView:self didFinishLoadWithURL:self.wkWebView.URL];
}
self.progressView.alpha = 0.0;
}
/// 页面加载失败时调用
- (void)webView:(WKWebView *)webView didFailNavigation:(WKNavigation *)navigation withError:(NSError *)error {
if (self.SGQRCodeDelegate && [self.SGQRCodeDelegate respondsToSelector:@selector(webView:didFailLoadWithError:)]) {
[self.SGQRCodeDelegate webView:self didFailLoadWithError:error];
}
self.progressView.alpha = 0.0;
}
/// 加载 web
- (void)loadRequest:(NSURLRequest *)request {
[self.wkWebView loadRequest:request];
}
/// 加载 HTML
- (void)loadHTMLString:(NSString *)HTMLString {
[self.wkWebView loadHTMLString:HTMLString baseURL:nil];
}
/// 刷新数据
- (void)reloadData {
[self.wkWebView reload];
}
/// dealloc
- (void)dealloc {
[self.wkWebView removeObserver:self forKeyPath:NSStringFromSelector(@selector(estimatedProgress))];
}
@end
//手电筒控制类
SGQRCodeHelperTool.h
#import
@interface SGQRCodeHelperTool : NSObject
/** 打开手电筒 */
+ (void)SG_openFlashlight;
/** 关闭手电筒 */
+ (void)SG_CloseFlashlight;
@end
//手电筒控制类
SGQRCodeHelperTool.m
#import "SGQRCodeHelperTool.h"
#import
@implementation SGQRCodeHelperTool
/** 打开手电筒 */
+ (void)SG_openFlashlight {
AVCaptureDevice *captureDevice = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error = nil;
if ([captureDevice hasTorch]) {
BOOL locked = [captureDevice lockForConfiguration:&error];
if (locked) {
captureDevice.torchMode = AVCaptureTorchModeOn;
[captureDevice unlockForConfiguration];
}
}
}
/** 关闭手电筒 */
+ (void)SG_CloseFlashlight {
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([device hasTorch]) {
[device lockForConfiguration:nil];
[device setTorchMode: AVCaptureTorchModeOff];
[device unlockForConfiguration];
}
}
@end