二维码扫描实现
二维码原理
三个回形大方块,是为了给相机定位的;中间的黑白块,黑块代表1,白块代表0,八个一组,组成二进制信息。二维码原理 这个小视频,简单的介绍了下二维码
iOS简单的实现二维码扫描
知道二维码的原理后,就可以简单的实现一下二维码扫描。在iOS7之前,二维码扫描大多数采用的是zxing这个第三方库ZXing和zbar,但是使用的时候会遇到很多坑,之后,苹果自己实现了二维码的扫描方法,扫描迅速识别率高,现在看看如何简单的实现。
实现步骤
//1.获取摄像设备
let device = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo)!
//2.创建输入流
var input: AVCaptureDeviceInput?
do {
input = try AVCaptureDeviceInput.init(device: device)
} catch {
}
//3.判断输入流是否可用
guard input != nil else {
return
}
//4.创建输入流
let output = AVCaptureMetadataOutput.init()
//5.设置代理,在主线程里刷新,注意此时self要签AVCaptureMetadataOutputObjectsDelegate协议
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
//6.创建连接对象,添加输入输出流
session = AVCaptureSession.init()
//高质量采集率
session?.canSetSessionPreset(AVCaptureSessionPresetHigh)
if (session?.canAddInput(input))! {
session?.addInput(input)
}
if (session?.canAddOutput(output))! {
session?.addOutput(output)
}
//7.设置扫码支持的编码格式
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode, AVMetadataObjectTypeEAN13Code, AVMetadataObjectTypeEAN8Code, AVMetadataObjectTypeCode128Code]
//8.设置扫描范围(每一个取值0~1,以屏幕的右上角为原点,扫描的图像其实是旋转了90度)
output.rectOfInterest = CGRect.init(x: (scanY)/APP_HEIGHT, y: (scanX)/APP_WIDTH, width: scanH/APP_HEIGHT, height: scanW/APP_WIDTH)
//9.扫描区域大小的设置(扫描时候可以看见的背景视图,这部分也可以自定义,显示自己想要的布局)
let layer = AVCaptureVideoPreviewLayer.init(session: session)
layer.frame = self.view.frame
layer.session = session
layer.videoGravity = AVLayerVideoGravityResizeAspectFill
self.view.layer.insertSublayer(layer, at: 0)
//10.开始捕获对象
session?.startRunning()
//11.实现代理方法
extension ViewController: AVCaptureMetadataOutputObjectsDelegate {
//扫描结果
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
if metadataObjects.count > 0 {
//获取到信息后停止扫描
session?.stopRunning()
let metaDataObject = metadataObjects.first as! AVMetadataMachineReadableCodeObject
//输出扫描字符串
print("stringValue:\(metaDataObject.stringValue)")
//移除扫描视图
let layer = self.view.layer.sublayers?.first as! AVCaptureVideoPreviewLayer
layer.removeFromSuperlayer()
}
}
}
自定义二维码扫描
上面的方式只能初步学习使用,而实际上的应用开发需要像微信或者支付宝那样的样式,所以需要进一步的开发出我们想要的样式。
扫描区域背景
观察微信的扫描界面发现,除了中间的扫描框之外,周围有一层半透明的视图,所以首先想到的办法是:去除中间的扫描区域,将图像分为四个部分,设置统一的颜色和透明度,添加进view。这种方法当然能实现想要的效果,但是明显不够优雅,所以考虑另一种方法,将当前view的某一区域设置为透明,这样就可以达到想要的效果了。
UIGraphicsGetCurrentContext()
对象有一个clear(_ rect: CGRect)
方法,将指定的区域设置为透明,UIGraphicsGetCurrentContext方法获取上下文,只有在draw(_ rect: CGRect)
方法中才能获取到。
let context = UIGraphicsGetCurrentContext()
if let context = context {
context.setFillColor(red: 40/255.0, green: 40/255.0, blue: 40/255.0, alpha: 0.5)
context.fill(CGRect.init(x: 0, y: 0, width: self.frame.width, height: self.frame.size.height))
context.clear(CGRect.init(x: 100, y: 0, width: 50, height: 50))
}
扫描线
扫描线使用UIView的动画就可以实现了:
//扫描线动画效果
func scanLineAnimation() {
UIView.animate(withDuration: 3, delay: 0, options: .repeat, animations: {
self.lineView.frame.origin.y += (self.scanH - 1)
}) { (finished) in
}
}
四个角的边框
在扫描框上添加白色的边框,用来突出扫描框:
//添加白色边框
func addWhiteRect(context: CGContext, rect: CGRect) {
context.stroke(rect)
context.setStrokeColor(red: 1, green: 1, blue: 1, alpha: 1)
context.setLineWidth(0.8)
context.addRect(rect)
context.strokePath()
}
添加四个角的边框,以顶点为起点,分别向两边延伸一段距离,然后连线,填充颜色:
//扫描框的实现
func addCornerLine(context: CGContext, rect: CGRect) {
//画四个边角
context.setLineWidth(2)
context.setStrokeColor(red: 83/255.0, green: 239/255.0, blue: 111/255.0, alpha: 1)
//左上角
let pointsTopLeftA = [
CGPoint.init(x: rect.origin.x, y: rect.origin.y),
CGPoint.init(x: rect.origin.x + borderWidth, y: rect.origin.y)
]
let pointsTopLeftB: Array = [
CGPoint.init(x: rect.origin.x, y: rect.origin.y),
CGPoint.init(x: rect.origin.x, y: rect.origin.y + borderWidth)
]
addLine(pointA: pointsTopLeftA, pointB: pointsTopLeftB, context: context)
//左下角
let pointsBottomLeftA = [
CGPoint.init(x: rect.origin.x, y: rect.maxY),
CGPoint.init(x: rect.origin.x + borderWidth, y: rect.maxY)
]
let pointsBottomLeftB = [
CGPoint.init(x: rect.origin.x, y: rect.maxY),
CGPoint.init(x: rect.origin.x, y: rect.maxY - borderWidth)
]
addLine(pointA: pointsBottomLeftA, pointB: pointsBottomLeftB, context: context)
//右上角
let pointsTopRightA = [
CGPoint.init(x: rect.maxX, y: rect.origin.y),
CGPoint.init(x: rect.maxX - borderWidth, y: rect.origin.y)
]
let pointsTopRightB = [
CGPoint.init(x: rect.maxX, y: rect.origin.y),
CGPoint.init(x: rect.maxX, y: rect.origin.y + borderWidth)
]
addLine(pointA: pointsTopRightA, pointB: pointsTopRightB, context: context)
//右下角
let pointsBottomRightA = [
CGPoint.init(x: rect.maxX, y: rect.maxY),
CGPoint.init(x: rect.maxX - borderWidth, y: rect.maxY)
]
let pointsBottomRightB = [
CGPoint.init(x: rect.maxX, y: rect.maxY),
CGPoint.init(x: rect.maxX, y: rect.maxY - borderWidth)
]
addLine(pointA: pointsBottomRightA, pointB: pointsBottomRightB, context: context)
context.strokePath()
}
private func addLine(pointA: [CGPoint], pointB: [CGPoint], context: CGContext) {
context.addLines(between: pointA)
context.addLines(between: pointB)
}
横竖屏
如果当前页面要适应横竖屏的情况的话,目前还没想到什么比较好的方式,因为如果在背景图上采用autosizing的画,屏幕旋转后扫描框和扫描线会发生形变,跟预期不太一致,所以只能设置屏幕不能旋转
识别相册中的二维码
主要用到CIDetecor
,将图片转成CIImage,通过features(in image: CIImage) -> [CIFeature]
方法获取CIFeature
的数组对象
func readQRCode(qrImage: UIImage?, _ handle: @escaping completionHandle) {
guard let qrImage = qrImage else {
return
}
let context = CIContext.init(options: nil)
let detector = CIDetector.init(ofType: CIDetectorTypeQRCode, context: context, options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
let image = CIImage.init(cgImage: qrImage.cgImage!)
let features = detector?.features(in: image)
let feature: CIQRCodeFeature = features?.first as! CIQRCodeFeature
let result = feature.messageString
handle(result!)
}
生成二维码
首先将需要的信息转成data,然后使用CIFilter
输出图像,再将CIImage
转换成UIImage
,并放大显示
func createCodeImage(string: String) -> UIImage? {
guard !string.isEmpty else {
return nil
}
//二维码滤镜
let filter = CIFilter.init(name: "CIQRCodeGenerator")
//恢复滤镜的默认属性
filter?.setDefaults()
//将字符串转换成NSData
let data = string.data(using: String.Encoding.utf8)
//通过KVC设置滤镜inputMessage数据
filter?.setValue(data, forKey: "inputMessage")
//获得滤镜输出的图像
let outImage = filter?.outputImage
//将CIImage转换成UIImage,并放大显示
let image = createNonInterpolatedUIImageFormCIImage(image: outImage!, size: 200)
return image
}
///改变二维码大小
private func createNonInterpolatedUIImageFormCIImage(image: CIImage, size: CGFloat) -> UIImage? {
let extent = image.extent.integral
let scale = min(size/extent.width, size/extent.height)
//创建bitmap
let width: size_t = size_t(extent.width * scale)
let height: size_t = size_t(extent.height * scale)
let cs = CGColorSpaceCreateDeviceGray()
let bitmapRef = CGContext.init(data: nil, width: width, height: height, bitsPerComponent: 8, bytesPerRow: 0, space: cs, bitmapInfo: CGImageAlphaInfo.none.rawValue)
let context = CIContext.init(options: nil)
let bitmapImage = context.createCGImage(image, from: extent)
bitmapRef?.interpolationQuality = .high
bitmapRef?.scaleBy(x: scale, y: scale)
bitmapRef?.draw(bitmapImage!, in: extent)
//保存bitmap到图片
let scaledImage = bitmapRef!.makeImage()
return UIImage.init(cgImage:scaledImage!)
}
小结
将二维码的需求分割后,就可以根据需求自定义想要的样式了,这里是我封装的一个二维码的库XELQRCode