二维码扫描实现

1 篇文章 0 订阅

二维码扫描实现

二维码原理

二维码扫描过程示意图
三个回形大方块,是为了给相机定位的;中间的黑白块,黑块代表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

参考链接

再见ZXing 使用系统原生代码处理QRCode

  • 7
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值