利用系統(tǒng)自帶框架實(shí)現(xiàn)掃一掃功能
實(shí)現(xiàn)功能前的項(xiàng)目配置
因?yàn)樵擁?xiàng)目要使用到相機(jī)和相冊(cè)。所以我們要在info.plist中設(shè)置詢(xún)問(wèn)用戶(hù)是否允許訪(fǎng)問(wèn)的權(quán)限。因?yàn)樾枰{(diào)用攝像頭,所以要在真機(jī)上運(yùn)行(在模擬器運(yùn)行會(huì)崩潰)。
功能分析
從功能需求分析來(lái)看,掃一掃該功能可以分為以下幾個(gè)功能點(diǎn):
- 在啟動(dòng)設(shè)備時(shí)設(shè)置loading view
- 使用CGContextRef繪制掃一掃界面UI
- 使用NSTimer實(shí)現(xiàn)掃描線(xiàn)動(dòng)畫(huà)
- 使用AVFoundation框架實(shí)現(xiàn)掃描功能
- 實(shí)現(xiàn)掃描二維碼圖片(系統(tǒng)只支持二維碼,不支持條形碼),調(diào)用系統(tǒng)閃光燈
- 在掃描完成后將值傳給上一個(gè)界面(Block反向傳值)
具體實(shí)現(xiàn)
- 在啟動(dòng)設(shè)備時(shí)設(shè)置loading view
1.創(chuàng)建繼承UIActivityIndicatorView的LoadView,在.m文件中寫(xiě)初始化代碼:
- (id)initWithFrame:(CGRect)frame {
self = [super initWithFrame:frame];
if (self) {
// 菊花背景的大小
self.frame = CGRectMake((ScreenWidth - 100)/2, (ScreenHeight - 100)/2, 100, 100);
// 菊花的背景色
self.backgroundColor = [UIColor blackColor];
self.layer.cornerRadius = 10;
// 菊花的顏色和格式(白色、白色大、灰色)
self.activityIndicatorViewStyle = UIActivityIndicatorViewStyleWhiteLarge;
// 在菊花下面添加文字
UILabel *label = [[UILabel alloc]initWithFrame:CGRectMake(10, 60, 80, 40)];
label.text = @"loading...";
label.font = [UIFont systemFontOfSize:14];
label.textAlignment = NSTextAlignmentCenter;
label.textColor = [UIColor whiteColor];
[self addSubview:label];
}
return self;
}
2.將LoadView添加到bgView中:
- (void)setupBgView {
_bgView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
_bgView.backgroundColor = [UIColor blackColor];
LoadView *loadView = [[LoadView alloc]init];
[_bgView addSubview:loadView];
// 動(dòng)畫(huà)開(kāi)始
[loadView startAnimating];
}
- 使用CGContextRef繪制掃一掃界面UI
1.創(chuàng)建繼承與UIView的ScanView,在.m文件中寫(xiě)下面的繪制代碼:
- (void)drawRect:(CGRect)rect {
CGFloat rectWidth = 50;
CGFloat rectHeight = 200;
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat black[4] = {0.0, 0.0, 0.0, _alphaValue};
CGContextSetFillColor(context, black);
//top
CGRect rect1 = CGRectMake(0, 0, self.frame.size.width, rectHeight);
CGContextFillRect(context, rect1);
//left
rect1 = CGRectMake(0, rectHeight, rectWidth, rectHeight);
CGContextFillRect(context, rect1);
//bottom
rect1 = CGRectMake(0, rectHeight * 2, self.frame.size.width, self.frame.size.height - rectHeight * 2);
CGContextFillRect(context, rect1);
//right
rect1 = CGRectMake(self.frame.size.width - rectWidth, rectHeight, rectWidth, rectHeight);
CGContextFillRect(context, rect1);
CGContextStrokePath(context);
//中間畫(huà)矩形(正方形)
CGContextSetStrokeColorWithColor(context, [UIColor whiteColor].CGColor);
CGContextSetLineWidth(context, 1);
CGContextAddRect(context, CGRectMake(rectWidth, rectHeight, self.frame.size.width - rectWidth * 2, rectHeight));
CGContextStrokePath(context);
CGFloat lineWidth = 10;
CGContextSetStrokeColorWithColor(context, [UIColor greenColor].CGColor);
CGContextSetRGBFillColor(context, 1.0, 1.0, 1.0, 1.0);
// Draw them with a 2.0 stroke width so they are a bit more visible.
CGContextSetLineWidth(context, 2.0);
//左上角水平線(xiàn)
CGContextMoveToPoint(context, rectWidth, rectHeight);
CGContextAddLineToPoint(context, rectWidth + lineWidth, rectHeight);
//左上角垂直線(xiàn)
CGContextMoveToPoint(context, rectWidth, rectHeight);
CGContextAddLineToPoint(context, rectWidth, rectHeight + lineWidth);
//左下角水平線(xiàn)
CGContextMoveToPoint(context, rectWidth, rectHeight * 2);
CGContextAddLineToPoint(context, rectWidth + lineWidth, rectHeight * 2);
//左下角垂直線(xiàn)
CGContextMoveToPoint(context, rectWidth, rectHeight * 2 - lineWidth);
CGContextAddLineToPoint(context, rectWidth, rectHeight * 2);
//右上角水平線(xiàn)
CGContextMoveToPoint(context, self.frame.size.width - rectWidth - lineWidth, rectHeight);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight);
//右上角垂直線(xiàn)
CGContextMoveToPoint(context, self.frame.size.width - rectWidth, rectHeight);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight + lineWidth);
//右下角水平線(xiàn)
CGContextMoveToPoint(context, self.frame.size.width - rectWidth - lineWidth, rectHeight * 2);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight * 2);
//右下角垂直線(xiàn)
CGContextMoveToPoint(context, self.frame.size.width - rectWidth, rectHeight * 2 - lineWidth);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight * 2);
CGContextStrokePath(context);
}
2.將scanView添加到self.view中:
- (void)setupScanView {
_scan = [[ScanView alloc]initWithFrame:self.view.bounds];
_scan.backgroundColor = [UIColor clearColor];
_slideLineView = [[UIView alloc]initWithFrame:CGRectMake(_viewWidth, 201, ScreenWidth - _viewWidth * 2, 1)];
_slideLineView.backgroundColor = [UIColor greenColor];
[_scan addSubview:_slideLineView];
[self.view addSubview:_scan];
[self setupSubView];
}
3.設(shè)置self.view中的閃光燈按鈕和訪(fǎng)問(wèn)相冊(cè)按鈕:
- (void)setupSubView {
_titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 500, ScreenWidth, 50.0)];
_titleLabel.text = @"請(qǐng)將二維碼放入框內(nèi)";
_titleLabel.textAlignment = NSTextAlignmentCenter;
_titleLabel.textColor = [UIColor whiteColor];
[_scan addSubview:_titleLabel];
_lightButton = [[UIButton alloc]initWithFrame:CGRectMake(100, 580, 50, 50)];
[_lightButton setTitle:@"light" forState:UIControlStateNormal];
[_lightButton addTarget:self action:@selector(lightButtonDidTouch) forControlEvents:UIControlEventTouchUpInside];
[_scan addSubview:_lightButton];
_imageButton = [[UIButton alloc]initWithFrame:CGRectMake(200, 580, 50, 50)];
[_imageButton setTitle:@"相冊(cè)" forState:UIControlStateNormal];
[_imageButton addTarget:self action:@selector(imageButtonDidTouch) forControlEvents:UIControlEventTouchUpInside];
[_scan addSubview:_imageButton];
}
4.閃光燈按鈕的點(diǎn)擊事件:
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
if (![device hasTorch]) {
NSLog(@"no torch");
}else {
[device lockForConfiguration:nil];
if (!self.isOpen) {
[device setTorchMode: AVCaptureTorchModeOn];
self.isOpen = YES;
}
else {
[device setTorchMode: AVCaptureTorchModeOff];
self.isOpen = NO;
}
[device unlockForConfiguration];
}
5.訪(fǎng)問(wèn)相冊(cè)按鈕的點(diǎn)擊事件:
- (void)imageButtonDidTouch {
[_timer invalidate];
_timer = nil;
UIImagePickerController *picker = [[UIImagePickerController alloc]init];
//設(shè)置圖片源(相簿)
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
//設(shè)置代理
picker.delegate = self;
//設(shè)置可以編輯
picker.allowsEditing = YES;
//打開(kāi)拾取器界面
[self presentViewController:picker animated:YES completion:nil];
}
#pragma mark UIImagePickerControllerDelegate methods
//完成選擇圖片
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
// 銷(xiāo)毀控制器
[picker dismissViewControllerAnimated:YES completion:nil];
// 根據(jù)URL找到CIImage
CIImage *ciImage = [[CIImage alloc]initWithCGImage:image.CGImage];
if (ciImage){
// 創(chuàng)建CIDetector
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:nil options:@{ CIDetectorAccuracy: CIDetectorAccuracyHigh }];
NSArray *features = [detector featuresInImage:ciImage];
if ([features count] > 0) {
for (CIFeature *feature in features) {
if (![feature isKindOfClass:[CIQRCodeFeature class]]) {
continue;
}
CIQRCodeFeature *qrFeature = (CIQRCodeFeature *)feature;
NSString *code = qrFeature.messageString;
if (self.resultBlock) {
self.resultBlock(code);
[self scanSuccess];
}
//輸出掃描字符串
[self.navigationController popViewControllerAnimated:YES];
}
}else {
[self setupTimer];
}
}
}
//取消選擇圖片
-(void)imagePickerControllerDidCancel:(UIImagePickerController *)picker {
[picker dismissViewControllerAnimated:YES completion:nil];
}
- 使用NSTimer實(shí)現(xiàn)掃描線(xiàn)動(dòng)畫(huà)
實(shí)現(xiàn)掃描線(xiàn)代碼如下:
- (void)setupTimer {
_timer = [NSTimer scheduledTimerWithTimeInterval:1.8 target:self selector:@selector(animationView) userInfo:nil repeats:YES];
[_timer fire];
}
- (void)animationView {
[UIView animateWithDuration:1.5 animations:^{
_slideLineView.transform = CGAffineTransformMakeTranslation(0, 200);
} completion:^(BOOL finished) {
_slideLineView.transform = CGAffineTransformIdentity;
}];
}
- 使用AVFoundation實(shí)現(xiàn)掃描功能
1.導(dǎo)入<AVFoundation/AVFoundation.h>,遵守AVCaptureMetadataOutputObjectsDelegate。
初始化代碼如下:
- (void)setupAVFoundation {
//獲取攝像設(shè)備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//創(chuàng)建輸入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
//創(chuàng)建輸出流
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];
//設(shè)置代理 在主線(xiàn)程里刷新
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//初始化鏈接對(duì)象
_session = [[AVCaptureSession alloc]init];
//高質(zhì)量采集率
[_session setSessionPreset:AVCaptureSessionPresetHigh];
[_session addInput:input];
[_session addOutput:output];
//設(shè)置掃碼支持的編碼格式(如下設(shè)置條形碼和二維碼兼容)
output.metadataObjectTypes=@[AVMetadataObjectTypeQRCode,AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code];
_previewLayer = [AVCaptureVideoPreviewLayer layerWithSession:_session];
_previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill;
_previewLayer.frame = self.view.layer.bounds;
[self.view.layer insertSublayer:_previewLayer atIndex:0];
//開(kāi)始捕獲
[_session startRunning];
//移除loading view
[_bgView removeFromSuperview];
}
2.實(shí)現(xiàn)AVCaptureMetadataOutputObjectsDelegate
#pragma mark 輸出的代理
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection {
if (metadataObjects.count > 0) {
[_timer invalidate];
_timer = nil;
[_session stopRunning];
AVMetadataMachineReadableCodeObject *metadataObject = [metadataObjects objectAtIndex: 0];
if (self.resultBlock) {
self.resultBlock(metadataObject.stringValue);
[self scanSuccess];
}
//輸出掃描字符串
[self.navigationController popViewControllerAnimated:YES];
}
}
//掃描成功的提示音
- (void)scanSuccess {
AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
AudioServicesPlaySystemSound(1109);
}
結(jié)束語(yǔ)
至此,即可實(shí)現(xiàn)利用原生框架掃描二維碼的功能,使用原生有一個(gè)缺陷就是無(wú)法掃描圖片中的條形碼。如要實(shí)現(xiàn)這個(gè)功能可以使用 ZXingObjC 框架。
完整項(xiàng)目地址,第十個(gè)