# ****扫描二维码
## ****第三方框架
ZXing
Android使用多ZBar
iOS使用多提示:以上两个框架都是老牌二维码框架,不过都不支持 64 位
目前在 iOS 开发中普遍使用苹果的
AVFoundation
框架,但是不支持图片识别功能AVFoundation
只支持通过摄像头扫描识别
## ****识别原理
g)
## ****代码实现
- 拍摄会话
/// 拍摄会话,是扫描的桥梁
lazy var session: AVCaptureSession = {
return AVCaptureSession()
}()
- 摄像头输入设备
// 2. 输入设备
lazy var inputDevice: AVCaptureDeviceInput? = {
let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo)
do {
return try AVCaptureDeviceInput(device: device)
} catch {
print(error)
return nil
}
}()
- 数据输出
/// 数据输出
lazy var dataOutput: AVCaptureMetadataOutput = {
return AVCaptureMetadataOutput()
}()
- 建立通道,设置会话
private func setupSession() {
// 1. 判断是否能够添加设备
if !session.canAddInput(inputDevice) {
print("无法添加输入设备")
return
}
// 2. 判断能否添加输出数据
if !session.canAddOutput(outputData) {
print("无法添加输出数据")
return
}
// 3. 添加设备
session.addInput(inputDevice)
session.addOutput(outputData)
print(outputData.availableMetadataObjectTypes)
// 4. 设置扫描数据类型
outputData.metadataObjectTypes = outputData.availableMetadataObjectTypes
// 5. 设置输出代理
outputData.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue())
}
注意,一定要把输出设备添加到会话后,才有可用数据类型
- 实现协议方法
// MARK: - AVCaptureMetadataOutputObjectsDelegate
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
println(metadataObjects)
}
必须要启动会话,才能开始扫描
/// 开始扫描
private func startScan() {
session.startRunning()
}
- 添加预览视图
/// 预览图层
lazy var previewLayer: AVCaptureVideoPreviewLayer = {
return AVCaptureVideoPreviewLayer(session: self.session)
}()
- 设置图层
/// 设置图层
func setupLayers() {
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer.frame = view.bounds
view.layer.insertSublayer(previewLayer, atIndex: 0)
}
进一步体会一下此处的
self.
闭包内部需要使用
self.
在
OC
中使用self.
能够调用属性的getter
方法,确保对象一定能够拿到修改扫描代理方法,提取数值
// MARK: - 扫描数据代理
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
for object in metadataObjects {
print(object)
}
}
# ****绘制线条
## AVMetadataMachineReadableCodeObject
- bounds
- corners
## ****代码实现
- 坐标转换
// MARK: - 扫描数据代理
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
for object in metadataObjects {
let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
print(dataObject)
}
}
- 转换结果
# 转换前
corners { 0.4,0.6 0.5,0.6 0.5,0.4 0.4,0.4 },
time 155921691680958,
stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"
# 转换后
corners { 116.6,226.1 117.2,304.4 196.1,304.9 195.7,224.9 },
time 155921691680958,
stringValue "http://weibo.cn/qr/userinfo?uid=5365823342"
转换的目的是将采集到的坐标转换成能够识别的坐标数值
- 绘制图层
/// 绘制图层
lazy var drawLayer: CALayer = {
return CALayer()
}()
- 添加图层
/// 设置图层
func setupLayers() {
drawLayer.frame = view.bounds
view.layer.insertSublayer(drawLayer, atIndex: 0)
previewLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
previewLayer.frame = view.bounds
view.layer.insertSublayer(previewLayer, atIndex: 0)
}
注意:一定要用
insertSublayer
,否则会遮挡住TabBar
- 创建路径 & 绘制条码形状
/// 绘制条码形状
private func drawCornersShape(dataObject: AVMetadataMachineReadableCodeObject) {
// 判断数组是否为空
if dataObject.corners.isEmpty {
return
}
let layer = CAShapeLayer()
layer.lineWidth = 4
layer.strokeColor = UIColor.greenColor().CGColor
layer.fillColor = UIColor.clearColor().CGColor
layer.path = cornersPath(dataObject.corners)
// 添加到绘图图层
drawLayer.addSublayer(layer)
}
/// 创建边线路径
///
/// -parameter corners: 边角顶点数组
private func cornersPath(corners: NSArray) -> CGPathRef {
let path = UIBezierPath()
var point = CGPoint()
// 1. 移动到第一个点
var index = 0
CGPointMakeWithDictionaryRepresentation((corners[index++] as! CFDictionaryRef), &point)
path.moveToPoint(point)
// 2. 遍历剩余的点
while index < corners.count {
CGPointMakeWithDictionaryRepresentation((corners[index++] as! CFDictionaryRef), &point)
path.addLineToPoint(point)
}
// 3. 关闭路径
path.closePath()
return path.CGPath
}
注意
corners
是保存CFDictionary
对象的数组一定要判断
corners
是否包含数据,否则会崩溃清空绘图图层
/// 清空绘图图层
private func clearDrawLayer() {
if drawLayer.sublayers == nil {
return
}
for layer in drawLayer.sublayers! {
layer.removeFromSuperlayer()
}
}
注意:一定要判断
subLayers
否则会崩溃
- 调整后的代码
func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) {
clearDrawLayer()
for object in metadataObjects {
if object is AVMetadataMachineReadableCodeObject {
let dataObject = previewLayer.transformedMetadataObjectForMetadataObject(object as! AVMetadataObject) as! AVMetadataMachineReadableCodeObject
drawCornersShape(dataObject)
print(dataObject.stringValue)
}
}
}
一定要判断一下 object 的类型,否则遇到非
CodeObject
会直接崩溃
# ****生成二维码
/// 生成二维码
private func generateQRCodeImage() -> UIImage {
// 1. 生成二维码
let qrFilter = CIFilter(name: "CIQRCodeGenerator")!
qrFilter.setDefaults()
qrFilter.setValue("任玉飞".dataUsingEncoding(NSUTF8StringEncoding), forKey: "inputMessage")
let ciImage = qrFilter.outputImage
// 2. 缩放处理
let transform = CGAffineTransformMakeScale(10, 10)
let transformImage = ciImage.imageByApplyingTransform(transform)
// 3. 颜色滤镜
let colorFilter = CIFilter(name: "CIFalseColor")!
colorFilter.setDefaults()
colorFilter.setValue(transformImage, forKey: "inputImage")
// 前景色
colorFilter.setValue(CIColor(color: UIColor.blackColor()), forKey: "inputColor0")
// 背景色
colorFilter.setValue(CIColor(color: UIColor.whiteColor()), forKey: "inputColor1")
let outputImage = colorFilter.outputImage
return insertAvatarImage(UIImage(CIImage: outputImage), avatar: UIImage(named: "avatar")!)
}
- 插入头像
func insertAvatarImage(qrimage: UIImage, avatar: UIImage) -> UIImage {
UIGraphicsBeginImageContext(qrimage.size)
let rect = CGRect(origin: CGPointZero, size: qrimage.size)
qrimage.drawInRect(rect)
let w = rect.width * 0.2
let x = (rect.width - w) * 0.5
let y = (rect.height - w) * 0.5
avatar.drawInRect(CGRect(x: x, y: y, width: w, height: w))
let image = UIGraphicsGetImageFromCurrentImageContext()
UIGraphicsEndImageContext()
return image
}
查阅滤镜
print(CIFilter.filterNamesInCategory(kCICategoryBuiltIn))
代码地址
github地址