由於該項目要使用到相機和相冊。因此咱們要在info.plist中設置詢問用戶是否容許訪問的權限。由於須要調用攝像頭,因此要在真機上運行(在模擬器運行會崩潰)。javascript
從功能需求分析來看,掃一掃該功能能夠分爲如下幾個功能點:html
UIActivityIndicatorView
的LoadView,在.m文件中寫初始化代碼:- (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中:java
- (void)setupBgView {
_bgView = [[UIView alloc]initWithFrame:CGRectMake(0, 0, ScreenWidth, ScreenHeight)];
_bgView.backgroundColor = [UIColor blackColor];
LoadView *loadView = [[LoadView alloc]init];
[_bgView addSubview:loadView];
// 動畫開始
[loadView startAnimating];
}複製代碼
- (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);
//中間畫矩形(正方形)
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);
//左上角水平線
CGContextMoveToPoint(context, rectWidth, rectHeight);
CGContextAddLineToPoint(context, rectWidth + lineWidth, rectHeight);
//左上角垂直線
CGContextMoveToPoint(context, rectWidth, rectHeight);
CGContextAddLineToPoint(context, rectWidth, rectHeight + lineWidth);
//左下角水平線
CGContextMoveToPoint(context, rectWidth, rectHeight * 2);
CGContextAddLineToPoint(context, rectWidth + lineWidth, rectHeight * 2);
//左下角垂直線
CGContextMoveToPoint(context, rectWidth, rectHeight * 2 - lineWidth);
CGContextAddLineToPoint(context, rectWidth, rectHeight * 2);
//右上角水平線
CGContextMoveToPoint(context, self.frame.size.width - rectWidth - lineWidth, rectHeight);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight);
//右上角垂直線
CGContextMoveToPoint(context, self.frame.size.width - rectWidth, rectHeight);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight + lineWidth);
//右下角水平線
CGContextMoveToPoint(context, self.frame.size.width - rectWidth - lineWidth, rectHeight * 2);
CGContextAddLineToPoint(context, self.frame.size.width - rectWidth, rectHeight * 2);
//右下角垂直線
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中:ios
- (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.設置self.view中的閃光燈按鈕和訪問相冊按鈕:git
- (void)setupSubView {
_titleLabel = [[UILabel alloc]initWithFrame:CGRectMake(0, 500, ScreenWidth, 50.0)];
_titleLabel.text = @"請將二維碼放入框內";
_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:@"相冊" forState:UIControlStateNormal];
[_imageButton addTarget:self action:@selector(imageButtonDidTouch) forControlEvents:UIControlEventTouchUpInside];
[_scan addSubview:_imageButton];
}複製代碼
4.閃光燈按鈕的點擊事件:github
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.訪問相冊按鈕的點擊事件:session
- (void)imageButtonDidTouch {
[_timer invalidate];
_timer = nil;
UIImagePickerController *picker = [[UIImagePickerController alloc]init];
//設置圖片源(相簿)
picker.sourceType = UIImagePickerControllerSourceTypeSavedPhotosAlbum;
//設置代理
picker.delegate = self;
//設置能夠編輯
picker.allowsEditing = YES;
//打開拾取器界面
[self presentViewController:picker animated:YES completion:nil];
}
#pragma mark UIImagePickerControllerDelegate methods
//完成選擇圖片
-(void)imagePickerController:(UIImagePickerController *)picker didFinishPickingImage:(UIImage *)image editingInfo:(NSDictionary *)editingInfo {
// 銷燬控制器
[picker dismissViewControllerAnimated:YES completion:nil];
// 根據URL找到CIImage
CIImage *ciImage = [[CIImage alloc]initWithCGImage:image.CGImage];
if (ciImage){
// 建立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];
}複製代碼
- (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;
}];
}複製代碼
- (void)setupAVFoundation {
//獲取攝像設備
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//建立輸入流
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:device error:nil];
//建立輸出流
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];
//設置代理 在主線程裏刷新
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//初始化連接對象
_session = [[AVCaptureSession alloc]init];
//高質量採集率
[_session setSessionPreset:AVCaptureSessionPresetHigh];
[_session addInput:input];
[_session addOutput:output];
//設置掃碼支持的編碼格式(以下設置條形碼和二維碼兼容)
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];
//開始捕獲
[_session startRunning];
//移除loading view
[_bgView removeFromSuperview];
}複製代碼
2.實現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);
}複製代碼
至此,便可實現利用原生框架掃描二維碼的功能,使用原生有一個缺陷就是沒法掃描圖片中的條形碼。如要實現這個功能能夠使用 ZXingObjC
框架。
完整項目地址,第十個ide