之前写过一篇利用AVCaptureSession扫描二维码的博客,但是那个页面是全屏的,在实际应用中,一般都是中间有一个框,然后二维码对准中间框的时候才能识别。这是怎么实现的呢,经过查找资料,现将方法纪录,供日后查阅。
CAShapeLayer简介
关于CAShapeLayer的介绍网上有很多,我就引用一段吧。原文链接
CAShapeLayer是一个通过矢量图形而不是bitmap来绘制的图层子类。你指定诸如颜色和线宽等属性,用CGPath来定义想要绘制的图形,最后CAShapeLayer就自动渲染出来了。当然,你也可以用Core Graphics直接向原始的CALyer的内容中绘制一个路径,相比直下,使用CAShapeLayer有以下一些优点:
- 渲染快速。CAShapeLayer使用了硬件加速,绘制同一图形会比用Core Graphics快很多。
- 高效使用内存。一个CAShapeLayer不需要像普通CALayer一样创建一个寄宿图形,所以无论有多大,都不会占用太多的内存
- 不会被图层边界剪裁掉。一个CAShapeLayer可以在边界之外绘制。你的图层路径不会像在使用Core Graphics的普通CALayer一样被剪裁掉
- 不会出现像素化。当你给CAShapeLayer做3D变换时,它不像一个有寄宿图的普通图层一样变得像素化
实现扫描界面
首先当然是需要配备一张有扫描框的图片:
我们定义一个ScanView,用来展示整个扫描界面,使用XIB或其他合适方式将图片添加到该View中,我这里使用XIB方式,并设置好约束。
@interface CMQRCodeScanView : UIView {
__weak IBOutlet UIImageView *scanImageView;
}
@end
首先定义两个与屏幕相关的宏,并定义图片的大小(扫描区域的大小),一般来说要比图片大小要稍小一点,不然看起来效果有点差强人意,这里我图片设置的大小是240。
#define UI_IOS_WINDOW_WIDTH CGRectGetWidth([UIScreen mainScreen].bounds)
#define UI_IOS_WINDOW_HEIGHT CGRectGetHeight([UIScreen mainScreen].bounds)
#define kScanRegionSize 232
awakeFromNib代码说明
定义一个CAShapeLayer类变量,并在-awakeFromNib方法中编写相关代码
-(void)awakeFromNib {
[super awakeFromNib];
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, UI_IOS_WINDOW_WIDTH, UI_IOS_WINDOW_HEIGHT));
CGPathAddRect(path, NULL, CGRectMake((UI_IOS_WINDOW_WIDTH - kScanRegionSize) / 2, (UI_IOS_WINDOW_HEIGHT - kScanRegionSize) / 2, kScanRegionSize, kScanRegionSize));
_shapeLayer = [[CAShapeLayer alloc] init];
_shapeLayer.fillColor = [UIColor colorWithWhite:0.0f alpha:0.7f].CGColor;
_shapeLayer.fillRule = kCAFillRuleEvenOdd;
_shapeLayer.path = path;
[self.layer insertSublayer:_shapeLayer atIndex:0];
return ;
}
因为除了中间的扫描区域之外,其他的全部要置灰,所以要添加两条路径,一条是屏幕大小,还有一条是扫描区域大小。然后将路径赋给CAShapeLayer的path属性。
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectMake(0, 0, UI_IOS_WINDOW_WIDTH, UI_IOS_WINDOW_HEIGHT));
CGPathAddRect(path, NULL, CGRectMake((UI_IOS_WINDOW_WIDTH - kScanRegionSize) / 2, (UI_IOS_WINDOW_HEIGHT - kScanRegionSize) / 2, kScanRegionSize, kScanRegionSize));
_shapeLayer.path = path;
然后设置其他区域的相关属性,如填充颜色、填充规则。主要是填充规则,需要设置为kCAFillRuleEvenOdd,关于这个属性的介绍也有很多文章介绍(其实我自己也不太明白,不过我的目的是实现)
_shapeLayer.fillColor = [UIColor colorWithWhite:0.0f alpha:0.7f].CGColor;
_shapeLayer.fillRule = kCAFillRuleEvenOdd;
最后将该CAShapeLayer插入到最底层即可实现。如果需要显示摄像头的拍摄页面,只需要将AVCaptureVideoPreviewLayer预览层插入到CAShapeLayer之下,并设置ROI区域为扫描框区域即可。
ROI区域的说明
虽然知道要设置ROI区域,但是当我自己设置的时候还是会出了点问题,刚开始,我设置的是扫描框的矩形区域,但是发现扫描不到二维码,于是查看文档得知道,ROI区域的默认值是(0.0, 0.0, 1.0, 1.0),原来ROI区域用的是比例,所以设置代码应该如下:
[outPut setRectOfInterest:CGRectMake((UI_IOS_WINDOW_WIDTH - kScanRegionSize) / (2 * UI_IOS_WINDOW_WIDTH), (UI_IOS_WINDOW_HEIGHT - kScanRegionSize) / (2 * UI_IOS_WINDOW_HEIGHT), (UI_IOS_WINDOW_WIDTH + kScanRegionSize) / (2 * UI_IOS_WINDOW_WIDTH), (kScanRegionSize + UI_IOS_WINDOW_HEIGHT) / (2 *UI_IOS_WINDOW_HEIGHT))];