7A950534-40DF-4627-9C0A-CFE7A830D6FC.png
今天看到一篇新的博客,介紹了系統自帶的方法創建二維碼掃描功能,所以立貼準備把自己二維碼掃描相關的代碼好步驟寫出來 ! 待更新 !
一、原生二維碼實現
**1)控制器代碼相關**
1.導入 Framework : #import <AVFoundation/AVFoundation.h>
2.實現代理協議 :
AVCaptureMetadataOutputObjectsDelegate
UINavigationControllerDelegate
UIImagePickerControllerDelegate
3.屬性相關
/// 二維碼掃描相關
@property (strong,nonatomic)AVCaptureSession * session;
@property (strong,nonatomic)AVCaptureVideoPreviewLayer * previewLayer;
/*** 專門用于保存描邊的圖層 ***/
@property (nonatomic,strong) MSUScanView *scanView;
4.控制器里 : ViewDidLoad 里面實現相關方法
// 導航欄
[self createNavView];
// 中部視圖
[self.view addSubview:self.scanningView];
[self setupMSUCodeScanning];
5.導航欄 (略)
6.二維碼相關
- (void)setupMSUCodeScanning {
// (1)、獲取攝像設備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
// (2)、創建輸入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
// (3)、創建輸出流
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc] init];
// (4)、設置代理 在主線程里刷新
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
// 設置掃描范圍(每一個取值0~1,以屏幕右上角為坐標原點)
// 注:微信二維碼的掃描范圍是整個屏幕,這里并沒有做處理(可不用設置)
output.rectOfInterest = CGRectMake(-0.2, 0.2, 0.7, 0.6);
// output.rectOfInterest = CGRectMake(0.05, 0.2, 0.7, 0.6);
// (5)、初始化鏈接對象(會話對象)
self.session = [[AVCaptureSession alloc] init];
// 高質量采集率
[_session setSessionPreset:AVCaptureSessionPresetHigh];
// (5.1) 添加會話輸入
[_session addInput:input];
// (5.2) 添加會話輸出
[_session addOutput:output];
// (6)、設置輸出數據類型,需要將元數據輸出添加到會話后,才能指定元數據類型,否則會報錯
// 設置掃碼支持的編碼格式(如下設置條形碼和二維碼兼容)
output.metadataObjectTypes = @[AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
// (7)、實例化預覽圖層, 傳遞_session是為了告訴圖層將來顯示什么內容
self.previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
_previewLayer.frame = CGRectMake(0, 64, WIDTH, HEIGHT-64);
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
//( 8)、將圖層插入當前視圖
[self.view.layer insertSublayer:_previewLayer atIndex:0];
// (9)、啟動會話
[_session startRunning];
}
7.生命周期中相關操作及scanningView的初始化
- (void)viewDidAppear:(BOOL)animated {
[super viewDidAppear:animated];
[self.scanView addTimer];
}
- (void)viewWillDisappear:(BOOL)animated {
[super viewWillDisappear:animated];
[self.scanView removeTimer];
}
- (void)dealloc {
NSLog(@"dealloc");
[self removeScanningView];
}
- (MSUScanView *)scanningView {
if (!_scanView) {
_scanView = [MSUScanView scanningViewWithFrame:CGRectMake(0, 64, WIDTH, HEIGHT-64) layer:self.view.layer];
}
return _scanView;
}
//移除掃描視圖
- (void)removeScanningView {
[self.scanningView removeTimer];
[self.scanningView removeFromSuperview];
self.scanView = nil;
}
8.相冊按鈕相關 (MSUPermissionTool 為封裝的權限工具類)
// 相冊按鈕點擊
- (void)photoBtnClick:(UIButton *)sender{
// 1、 獲取攝像設備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (device) {
[MSUPermissionTool getPhotosPermission:^(NSInteger authStatus) {
if (authStatus == 1) {
dispatch_async(dispatch_get_main_queue(), ^{
UIImagePickerController *imagePicker = [[UIImagePickerController alloc] init];
imagePicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; //(選擇類型)表示僅僅從相冊中選取照片
imagePicker.delegate = self;
[self presentViewController:imagePicker animated:YES completion:^{
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleDefault;
}];
});
}else{
UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"溫馨提示" message:@"請去-> [設置 - 隱私 - 照片 - 秀貝] 打開訪問開關" preferredStyle:UIAlertControllerStyleAlert];
[alert addAction:[UIAlertAction actionWithTitle:@"確定" style:UIAlertActionStyleDefault handler:^(UIAlertAction * _Nonnull action) {
//確定按鈕點擊事件處理
}]];
[self presentViewController:alert animated:YES completion:nil];
}
}];
}
}
9.代理相關
#pragma mark - 代理事件
#pragma mark -- 二維碼代理(AVCaptureMetadataOutputObjectsDelegate)
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
// 掃描成功之后的提示音
[self MN_playSoundEffect:@"sound.caf"];
NSString *stringValue;
if ([metadataObjects count] >0){
//停止掃描
[_session stopRunning];
AVMetadataMachineReadableCodeObject * metadataObject = [metadataObjects objectAtIndex:0];
stringValue = metadataObject.stringValue;
//代碼封裝抽離用 , 將掃描方法和掃描結果兩塊處理邏輯分開 此處為發送掃描結果
// [[NSNotificationCenter defaultCenter] postNotificationName:@"MSUCodeResultFromeScanning" object:metadataObject.stringValue];
if ([stringValue hasPrefix:@"http"]) {// 掃描結果為二維碼
NSLog(@"二維碼%@",stringValue);
} else { // 掃描結果為條形碼
NSLog(@"條形碼%@",stringValue);
}
}
}
/** 播放音效文件 */
- (void)MN_playSoundEffect:(NSString *)name {
// 獲取音效
NSString *audioFile = [[NSBundle mainBundle] pathForResource:name ofType:nil];
NSURL *fileUrl = [NSURL fileURLWithPath:audioFile];
// 1、獲得系統聲音ID
SystemSoundID soundID = 0;
AudioServicesCreateSystemSoundID((__bridge CFURLRef)(fileUrl), &soundID);
AudioServicesAddSystemSoundCompletion(soundID, NULL, NULL, soundCompleteCallback, NULL);
// 2、播放音頻
AudioServicesPlaySystemSound(soundID); // 播放音效
}
/** 播放完成回調函數 */
void soundCompleteCallback(SystemSoundID soundID, void *clientData){
//NSLog(@"播放完成...");
}
#pragma mark -- 相冊代理(UIImagePickerControllerDelegate)
/** 相冊選擇 */
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary<NSString *,id> *)info{
[self.view addSubview:self.scanningView];
[self dismissViewControllerAnimated:YES completion:^{
[self scanMSUResultromPhotosInTheAlbum:[info objectForKey:@"UIImagePickerControllerOriginalImage"]];
}];
}
/** 相冊取消 */
- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker{
[self.view addSubview:self.scanningView];
[self dismissViewControllerAnimated:YES completion:nil];
[UIApplication sharedApplication].statusBarStyle = UIStatusBarStyleLightContent;
}
/** 播放完成回調函數 */
- (void)scanMSUResultromPhotosInTheAlbum:(UIImage *)image{
// 對選取照片的處理,如果選取的圖片尺寸過大,則壓縮選取圖片,否則不作處理
image = [UIImage imageSizeWithScreenImage:image];
// CIDetector(CIDetector可用于人臉識別)進行圖片解析,從而使我們可以便捷的從相冊中獲取到二維碼
// 聲明一個CIDetector,并設定識別類型 CIDetectorTypeQRCode
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy : CIDetectorAccuracyHigh }];
// 取得識別結果
NSArray *features = [detector featuresInImage:[CIImage imageWithCGImage:image.CGImage]];
for (int index = 0; index < [features count]; index ++) {
CIQRCodeFeature *feature = [features objectAtIndex:index];
NSString *stringValue = feature.messageString;
NSLog(@"scannedResult - - %@", stringValue);
//代碼封裝抽離用 , 將掃描方法和掃描結果兩塊處理邏輯分開 此處為發送掃描結果
// [[NSNotificationCenter defaultCenter] postNotificationName:@"MSUCodeResultFromeAibum" object:scannedResult];
// 處理相關邏輯地方
}
}
**2)View代碼相關**
**.h文件相關diamante**
/**
* 對象方法創建SGQRCodeScanningView
*
* @param frame frame
* @param layer 父視圖 layer
*/
- (instancetype)initWithFrame:(CGRect)frame layer:(CALayer *)layer;
/**
* 類方法創建SGQRCodeScanningView
*
* @param frame frame
* @param layer 父視圖 layer
*/
+ (instancetype)scanningViewWithFrame:(CGRect )frame layer:(CALayer *)layer;
/** 添加定時器 */
- (void)addTimer;
/** 移除定時器(切記:一定要在Controller視圖消失的時候,停止定時器) */
- (void)removeTimer;
**.m文件代碼相關**
1.宏定義及屬性相關
/** 掃描內容的Y值 */
#define scanContent_Y (self.frame.size.height) * 0.24
/** 掃描內容的X值 */
#define scanContent_X self.frame.size.width * 0.15
#define SGQRCodeScanningLineAnimation 0.05
#define NavHeight 64
@property (nonatomic, strong) AVCaptureDevice *device;
@property (nonatomic, strong) CALayer *tempLayer;
@property (nonatomic, strong) UIImageView *scanningline;
@property (nonatomic, strong) NSTimer *timer;
/** 掃描動畫線(沖擊波) 的高度 */
static CGFloat const scanninglineHeight = 12;
/** 掃描內容外部View的alpha值 */
static CGFloat const scanBorderOutsideViewAlpha = 0.4;
2.視圖代碼相關
- (CALayer *)tempLayer {
if (!_tempLayer) {
_tempLayer = [[CALayer alloc] init];
}
return _tempLayer;
}
- (instancetype)initWithFrame:(CGRect)frame layer:(CALayer *)layer {
if (self = [super initWithFrame:frame]) {
self.tempLayer = layer;
// 布局掃描界面
[self setupSubviews];
}
return self;
}
+ (instancetype)scanningViewWithFrame:(CGRect )frame layer:(CALayer *)layer {
return [[self alloc] initWithFrame:frame layer:layer];
}
- (void)setupSubviews {
// 掃描內容的創建
CALayer *scanContent_layer = [[CALayer alloc] init];
CGFloat scanContent_layerX = scanContent_X;
CGFloat scanContent_layerY = scanContent_Y;
CGFloat scanContent_layerW = self.frame.size.width - 2 * scanContent_X;
CGFloat scanContent_layerH = scanContent_layerW;
scanContent_layer.frame = CGRectMake(scanContent_layerX, scanContent_layerY, scanContent_layerW, scanContent_layerH);
scanContent_layer.borderColor = [[UIColor whiteColor] colorWithAlphaComponent:0.6].CGColor;
scanContent_layer.borderWidth = 0.7;
scanContent_layer.backgroundColor = [UIColor clearColor].CGColor;
[self.tempLayer addSublayer:scanContent_layer];
#pragma mark - - - 掃描外部View的創建
// 頂部layer的創建
CALayer *top_layer = [[CALayer alloc] init];
CGFloat top_layerX = 0;
CGFloat top_layerY = 0;
CGFloat top_layerW = self.frame.size.width;
CGFloat top_layerH = scanContent_layerY - NavHeight;
top_layer.frame = CGRectMake(top_layerX, top_layerY, top_layerW, top_layerH);
top_layer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:scanBorderOutsideViewAlpha].CGColor;
[self.layer addSublayer:top_layer];
// 左側layer的創建
CALayer *left_layer = [[CALayer alloc] init];
CGFloat left_layerX = 0;
CGFloat left_layerY = scanContent_layerY - NavHeight;
CGFloat left_layerW = scanContent_X;
CGFloat left_layerH = scanContent_layerH;
left_layer.frame = CGRectMake(left_layerX, left_layerY, left_layerW, left_layerH);
left_layer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:scanBorderOutsideViewAlpha].CGColor;
[self.layer addSublayer:left_layer];
// 右側layer的創建
CALayer *right_layer = [[CALayer alloc] init];
CGFloat right_layerX = CGRectGetMaxX(scanContent_layer.frame);
CGFloat right_layerY = scanContent_layerY - NavHeight;
CGFloat right_layerW = scanContent_X;
CGFloat right_layerH = scanContent_layerH;
right_layer.frame = CGRectMake(right_layerX, right_layerY, right_layerW, right_layerH);
right_layer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:scanBorderOutsideViewAlpha].CGColor;
[self.layer addSublayer:right_layer];
// 下面layer的創建
CALayer *bottom_layer = [[CALayer alloc] init];
CGFloat bottom_layerX = 0;
CGFloat bottom_layerY = CGRectGetMaxY(scanContent_layer.frame) - NavHeight;
CGFloat bottom_layerW = self.frame.size.width;
CGFloat bottom_layerH = self.frame.size.height - bottom_layerY;
bottom_layer.frame = CGRectMake(bottom_layerX, bottom_layerY, bottom_layerW, bottom_layerH);
bottom_layer.backgroundColor = [[UIColor blackColor] colorWithAlphaComponent:scanBorderOutsideViewAlpha].CGColor;
[self.layer addSublayer:bottom_layer];
// 提示Label
UILabel *promptLabel = [[UILabel alloc] init];
promptLabel.backgroundColor = [UIColor clearColor];
CGFloat promptLabelX = 0;
CGFloat promptLabelY = CGRectGetMaxY(scanContent_layer.frame) + 30 - NavHeight;
CGFloat promptLabelW = self.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.8];
promptLabel.text = @"將二維碼/條碼放入框內, 即可自動掃描";
[self addSubview:promptLabel];
// 添加閃光燈按鈕
UIButton *light_button = [[UIButton alloc] init];
CGFloat light_buttonX = 0;
CGFloat light_buttonY = CGRectGetMaxY(promptLabel.frame) + scanContent_X * 0.5;
CGFloat light_buttonW = self.frame.size.width;
CGFloat light_buttonH = 25;
light_button.frame = CGRectMake(light_buttonX, light_buttonY, light_buttonW, light_buttonH);
[light_button setTitle:@"打開照明燈" forState:UIControlStateNormal];
[light_button setTitle:@"關閉照明燈" forState:UIControlStateSelected];
[light_button setTitleColor:promptLabel.textColor forState:(UIControlStateNormal)];
light_button.titleLabel.font = [UIFont systemFontOfSize:17];
[light_button addTarget:self action:@selector(light_buttonAction:) forControlEvents:UIControlEventTouchUpInside];
[self addSubview:light_button];
#pragma mark - - - 掃描邊角imageView的創建
// 左上側的image
CGFloat margin = 7;
UIImage *left_image = [UIImage imageNamed:@"QRCodeLeftTop"];
UIImageView *left_imageView = [[UIImageView alloc] init];
CGFloat left_imageViewX = CGRectGetMinX(scanContent_layer.frame) - left_image.size.width * 0.5 + margin;
CGFloat left_imageViewY = CGRectGetMinY(scanContent_layer.frame) - left_image.size.width * 0.5 + margin;
CGFloat left_imageViewW = left_image.size.width;
CGFloat left_imageViewH = left_image.size.height;
left_imageView.frame = CGRectMake(left_imageViewX, left_imageViewY, left_imageViewW, left_imageViewH);
left_imageView.image = left_image;
[self.tempLayer addSublayer:left_imageView.layer];
// 右上側的image
UIImage *right_image = [UIImage imageNamed:@"QRCodeRightTop"];
UIImageView *right_imageView = [[UIImageView alloc] init];
CGFloat right_imageViewX = CGRectGetMaxX(scanContent_layer.frame) - right_image.size.width * 0.5 - margin;
CGFloat right_imageViewY = left_imageView.frame.origin.y;
CGFloat right_imageViewW = left_image.size.width;
CGFloat right_imageViewH = left_image.size.height;
right_imageView.frame = CGRectMake(right_imageViewX, right_imageViewY, right_imageViewW, right_imageViewH);
right_imageView.image = right_image;
[self.tempLayer addSublayer:right_imageView.layer];
// 左下側的image
UIImage *left_image_down = [UIImage imageNamed:@"QRCodeLeftBottom"];
UIImageView *left_imageView_down = [[UIImageView alloc] init];
CGFloat left_imageView_downX = left_imageView.frame.origin.x;
CGFloat left_imageView_downY = CGRectGetMaxY(scanContent_layer.frame) - left_image_down.size.width * 0.5 - margin;
CGFloat left_imageView_downW = left_image.size.width;
CGFloat left_imageView_downH = left_image.size.height;
left_imageView_down.frame = CGRectMake(left_imageView_downX, left_imageView_downY, left_imageView_downW, left_imageView_downH);
left_imageView_down.image = left_image_down;
[self.tempLayer addSublayer:left_imageView_down.layer];
// 右下側的image
UIImage *right_image_down = [UIImage imageNamed:@"QRCodeRightBottom"];
UIImageView *right_imageView_down = [[UIImageView alloc] init];
CGFloat right_imageView_downX = right_imageView.frame.origin.x;
CGFloat right_imageView_downY = left_imageView_down.frame.origin.y;
CGFloat right_imageView_downW = left_image.size.width;
CGFloat right_imageView_downH = left_image.size.height;
right_imageView_down.frame = CGRectMake(right_imageView_downX, right_imageView_downY, right_imageView_downW, right_imageView_downH);
right_imageView_down.image = right_image_down;
[self.tempLayer addSublayer:right_imageView_down.layer];
}
#pragma mark - - - 照明燈的點擊事件
- (void)light_buttonAction:(UIButton *)button {
if (button.selected == NO) { // 點擊打開照明燈
[self turnOnLight:YES];
button.selected = YES;
} else { // 點擊關閉照明燈
[self turnOnLight:NO];
button.selected = NO;
}
}
- (void)turnOnLight:(BOOL)on {
self.device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if ([_device hasTorch]) {
[_device lockForConfiguration:nil];
if (on) {
[_device setTorchMode:AVCaptureTorchModeOn];
} else {
[_device setTorchMode: AVCaptureTorchModeOff];
}
[_device unlockForConfiguration];
}
}
- (UIImageView *)scanningline {
if (!_scanningline) {
_scanningline = [[UIImageView alloc] init];
_scanningline.image = [UIImage imageNamed:@"QRCodeScanningLine"];
_scanningline.frame = CGRectMake(scanContent_X * 0.5, scanContent_Y, self.frame.size.width - scanContent_X , scanninglineHeight);
}
return _scanningline;
}
- (void)addScanningline {
// 掃描動畫添加
[self.tempLayer addSublayer:self.scanningline.layer];
}
#pragma mark - - - 添加定時器
- (void)addTimer {
[self addScanningline];
self.timer = [NSTimer scheduledTimerWithTimeInterval:SGQRCodeScanningLineAnimation target:self selector:@selector(timeAction) 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)timeAction {
__block CGRect frame = _scanningline.frame;
static BOOL flag = YES;
if (flag) {
frame.origin.y = scanContent_Y;
flag = NO;
[UIView animateWithDuration:SGQRCodeScanningLineAnimation animations:^{
frame.origin.y += 5;
_scanningline.frame = frame;
} completion:nil];
} else {
if (_scanningline.frame.origin.y >= scanContent_Y) {
CGFloat scanContent_MaxY = scanContent_Y + self.frame.size.width - 2 * scanContent_X;
if (_scanningline.frame.origin.y >= scanContent_MaxY - 10) {
frame.origin.y = scanContent_Y;
_scanningline.frame = frame;
flag = YES;
} else {
[UIView animateWithDuration:SGQRCodeScanningLineAnimation animations:^{
frame.origin.y += 5;
_scanningline.frame = frame;
} completion:nil];
}
} else {
flag = !flag;
}
}
}