扫一扫这个功能我们并不陌生,微信QQ加好友扫一扫,路边搞推广的扫一扫,吃饭付钱扫一扫。看来这个扫一扫应用的地方还真不少啊!打开手机看了看安装的APP,社交类APP有扫一扫,支付类的APP有扫一扫,连浏览器都有扫一扫。看来这个扫一扫不可小视!说不定哪天在你的项目中你的boss就要你给他加个扫一扫的功能呢!对于iOS开发要想做扫一扫这个功能,并不是很难,我们需要做的就是处理二维码(QRCode)而已。在iOS7之前需要借助第三方库ZBar和ZXing。在iOS7以后用自带API就能轻松搞定。不用在项目中导入臃肿的第三方库和担心编译出问题了。
二维条码是指在一维条码的基础上扩展出另一维具有可读性的条码,使用黑白矩形图案表示二进制数据,被设备扫描后可获取其中所包含的信息。----维基百科
详细的生成细节和原理可以看看二维码的生成细节和原理这篇文章。
首先我们来想一想具体的步骤,大概流程应该是:1.打开设备的摄像头-->2.进行二维码图像捕获-->3.获取捕获的图像进行解析-->4.取得解析结果进行后续处理。这些流程需要用到
AVFoundation
这个库,注意导入。
//获取一个AVCaptureDevice对象,可以理解为打开摄像头这样的动作
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
//获取一个AVCaptureDeviceInput对象,将上面的'摄像头'作为输入设备
AVCaptureDeviceInput *input = [AVCaptureDeviceInput deviceInputWithDevice:_device error:nil];
//拍完照片以后,需要一个AVCaptureMetadataOutput对象将获取的'图像'输出,以便进行对其解析
AVCaptureMetadataOutput *output = [[AVCaptureMetadataOutput alloc]init];
//获取输出需要设置代理,在代理方法中获取
[output setMetadataObjectsDelegate:self queue:dispatch_get_main_queue()];
//设置输出类型,如AVMetadataObjectTypeQRCode是二维码类型,下面还增加了条形码。如果扫描的是条形码也能识别
output.metadataObjectTypes = @[AVMetadataObjectTypeEAN13Code,
AVMetadataObjectTypeEAN8Code,
AVMetadataObjectTypeCode128Code,
AVMetadataObjectTypeQRCode];
上面完成了捕获的设置,但是并未正在开始'扫描',要完成一次扫描的过程,需要用到AVCaptureSession
这个类,这个session类把一次扫描看做一次会话,会话开始后才是正在的'扫描'开始,具体代码如下。
AVCaptureSession *session = [[AVCaptureSession alloc]init];
[session setSessionPreset:AVCaptureSessionPresetHigh];//扫描的质量
if ([session canAddInput:input]){
[session addInput:input];//将输入添加到会话中
}
if ([session canAddOutput:output]){
[session addOutput:output];//将输出添加到会话中
}
接下来我们要做的不是立即开始会话(开始扫描),如果你现在调用会话的startRunning
方法的话,你会发现屏幕是一片黑,这时由于我们还没有设置相机的取景器的大小。设置取景器需要用到AVCaptureVideoPreviewLayer
这个类。具体代码如下:
AVCaptureVideoPreviewLayer *preview =[AVCaptureVideoPreviewLayer layerWithSession:session];
preview.videoGravity = AVLayerVideoGravityResize;
[preview setFrame:self.view.bounds];//设置取景器的frame
[self.view.layer insertSublayer:preview atIndex:0];
接下来我们就可以调用session的startRunning
方法了,这时我们的扫描就真正开始了。想要获得扫描的结果,需要实现session的代理方法- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
,代码如下:
- (void)captureOutput:(AVCaptureOutput *)captureOutput didOutputMetadataObjects:(NSArray *)metadataObjects fromConnection:(AVCaptureConnection *)connection
{
[self.session stopRunning];//停止会话
[self.preview removeFromSuperlayer];//移除取景器
if (metadataObjects.count > 0) {
AVMetadataMachineReadableCodeObject *obj = metadataObjects[0];
NSString *result = obj.stringValue;//这就是扫描的结果啦
//对结果进行处理...
}
}
如果要做到为用户考虑的话,还得加入照明的功能或者设置兴趣区域。
使用过微信中的扫一扫的话,你应该发现在扫描界面中间有一个矩形限定框,这个框就是兴趣区域了。这个兴趣区域是
AVCaptureMetadataOutput
的rectOfInterest
属性。rectOfInterest
的值的范围都是0-1,是按比例取值而不是实际尺寸,所以设置的时候要注意的一点。还要注意rectOfInterest
都是按照横屏来计算的,所以当竖屏的情况下x轴和y轴要交换一下。代码如下:
CGSize size = self.view.bounds.size;
CGSize transparentAreaSize = CGSizeMake(200,200);
CGRect cropRect = CGRectMake((size.width - transparentAreaSize.width)/2, (size.height - transparentAreaSize.height)/2, transparentAreaSize.width, transparentAreaSize.height);
output.rectOfInterest = CGRectMake(cropRect.origin.y/size.width,
cropRect.origin.x/size.height,
cropRect.size.height/size.height,
cropRect.size.width/size.width);
加入照明功能能让用户在光照条件不好的情况下顺利的进行进行扫描操作,代码如下:
AVCaptureDevice *device = [AVCaptureDevice defaultDeviceWithMediaType:AVMediaTypeVideo];
NSError *error;
if (device.hasTorch) { // 判断设备是否有闪光灯
BOOL b = [device lockForConfiguration:&error];
if (!b) {
if (error) {
NSLog(@"lock torch configuration error:%@", error.localizedDescription);
}
return;
}
device.torchMode = (device.torchMode == AVCaptureTorchModeOff ? AVCaptureTorchModeOn : AVCaptureTorchModeOff);
[device unlockForConfiguration];
}
从图片中直接读取二维码的功能在iOS7上面苹果没有实现,不过在iOS上已经填补了这一功能。主要用到的是读取主要用到CoreImage。
废话不多说,直接上代码。
+ (NSString *)scQRReaderForImage:(UIImage *)qrimage{
CIContext *context = [CIContext contextWithOptions:nil];
CIDetector *detector = [CIDetector detectorOfType:CIDetectorTypeQRCode context:context options:@{CIDetectorAccuracy:CIDetectorAccuracyHigh}];
CIImage *image = [CIImage imageWithCGImage:qrimage.CGImage];
NSArray *features = [detector featuresInImage:image];
CIQRCodeFeature *feature = [features firstObject];
NSString *result = feature.messageString;
return result;
}
既然讲到要从相册获取照片,那么顺便把从相册获取照片也讲一下吧。
从相册获取照片主要用到的是
UIImagePickerController
,这是苹果给我们分装好的一个相册选取的控制器。实现起来也是很简单的。
- (void)readerImage{
UIImagePickerController *photoPicker = [[UIImagePickerController alloc] init];
photoPicker.delegate = self;
photoPicker.sourceType = UIImagePickerControllerSourceTypePhotoLibrary;
photoPicker.view.backgroundColor = [UIColor whiteColor];
[self presentViewController:photoPicker animated:YES completion:NULL];
}
当我们从照片库选择取了照片后要带调用UIImagePickerController
的代理方法获取选择的照片。
- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info{
[self dismissViewControllerAnimated:YES completion:^{
//code is here ...
}];
UIImage *srcImage = [info objectForKey:UIImagePickerControllerOriginalImage];
NSString *result = [QRCScanner scQRReaderForImage:srcImage];//调用上面讲过的方法对图片中的二维码进行处理
[self.navigationController popViewControllerAnimated:YES];
}
生成二维码和从图片中读取二维码一样要用到
CoreImage
,具体步骤如下:
- (UIImage *)makeQRCodeForString(NSString *)string{
NSString *text = string;
NSData *stringData = [text dataUsingEncoding: NSUTF8StringEncoding];
//生成
CIFilter *qrFilter = [CIFilter filterWithName:@"CIQRCodeGenerator"];
[qrFilter setValue:stringData forKey:@"inputMessage"];
[qrFilter setValue:@"M" forKey:@"inputCorrectionLevel"];
//二维码颜色
UIColor *onColor = [UIColor redColor];
UIColor *offColor = [UIColor blueColor];
//上色,如果只要白底黑块的QRCode可以跳过这一步
CIFilter *colorFilter = [CIFilter filterWithName:@"CIFalseColor"
keysAndValues:
@"inputImage",qrFilter.outputImage,
@"inputColor0",[CIColor colorWithCGColor:onColor.CGColor],
@"inputColor1",[CIColor colorWithCGColor:offColor.CGColor],
nil];
//绘制
CIImage *qrImage = colorFilter.outputImage;
CGSize size = CGSizeMake(300, 300);
CGImageRef cgImage = [[CIContext contextWithOptions:nil] createCGImage:qrImage fromRect:qrImage.extent];
UIGraphicsBeginImageContext(size);
CGContextRef context = UIGraphicsGetCurrentContext();
CGContextSetInterpolationQuality(context, kCGInterpolationNone);
CGContextScaleCTM(context, 1.0, -1.0);//生成的QRCode就是上下颠倒的,需要翻转一下
CGContextDrawImage(context, CGContextGetClipBoundingBox(context), cgImage);
UIImage *codeImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
CGImageRelease(cgImage);
return [UIImage imageWithCIImage:qrImage];
}
当然如果你不想写这个代码的话,你也可以使用一个轻量级的开源代码libqrencode来帮你实现。它的使用非常简单,导入UIImage+MDQRCode这个扩展后,使用UIImage的类方法就可以调用了。
+ (UIImage *)mdQRCodeForString:(NSString *)qrString size:(CGFloat)size;
+ (UIImage *)mdQRCodeForString:(NSString *)qrString size:(CGFloat)size fillColor:(UIColor *)fillColor;