当前位置: > > > Swift - 二维码QRCode的读取(从图片读取 ,或通过摄像头扫描)

Swift - 二维码QRCode的读取(从图片读取 ,或通过摄像头扫描)

(本文代码已升级至Swift4)

一、直接读取图片中的二维码

使用 CIDetector 可以很方便的检测并读取二维码。下面是一个从 UIImage 中读取二维码的样例,我们要把图片上所有的二维码信息都打印出来。


代码如下:
import UIKit

class ViewController: UIViewController {
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let qrcodeImg =  UIImage(named: "codeBg.png")
        let ciImage:CIImage=CIImage(image:qrcodeImg!)!
        
        let context = CIContext(options: nil)
        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context,
                                  options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
        
        let features = detector?.features(in: ciImage)
        
        print("扫描到二维码个数:\(features?.count ?? 0)")
        //遍历所有的二维码,并框出
        for feature in features as! [CIQRCodeFeature] {
            print(feature.messageString ?? "")
        }
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
控制台输出如下:
扫描到二维码个数:2
http://www.hangge.com
http://www.hangge.com
可以看到两个二维码都成功的读取到了。

(注:这个我原来用模拟器一直检测不到二维码,使用真机调试就没问题。感谢网友“落叶”的提醒,模拟器选iphone5s及以上设备也是可以检测到的。

二、从相册中选择图片读取二维码

1,Info.plist 配置

由于苹果安全策略更新,在使用 Xcode8 开发时,需要在 Info.plist 配置请求照片的相关描述字段(Privacy - Photo Library Usage Description

2,样例代码

import UIKit

class ViewController: UIViewController, UIImagePickerControllerDelegate,
UINavigationControllerDelegate {
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    //选取相册
    @IBAction func fromAlbum(_ sender: AnyObject) {
        //判断设置是否支持图片库
        if UIImagePickerController.isSourceTypeAvailable(.photoLibrary){
            //初始化图片控制器
            let picker = UIImagePickerController()
            //设置代理
            picker.delegate = self
            //指定图片控制器类型
            picker.sourceType = .photoLibrary
            //弹出控制器,显示界面
            self.present(picker, animated: true, completion: {
                () -> Void in
            })
        }else{
            print("读取相册错误")
        }
    }
    
    //选择图片成功后代理
    func imagePickerController(_ picker: UIImagePickerController,
                               didFinishPickingMediaWithInfo info: [String : Any]) {
        //获取选择的原图
        let image = info[UIImagePickerControllerOriginalImage] as! UIImage
        
        //二维码读取
        let ciImage:CIImage=CIImage(image:image)!
        let context = CIContext(options: nil)
        let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context,
                                  options: [CIDetectorAccuracy:CIDetectorAccuracyHigh])
        if let features = detector?.features(in: ciImage) {
            print("扫描到二维码个数:\(features.count)")
            //遍历所有的二维码,并框出
            for feature in features as! [CIQRCodeFeature] {
                print(feature.messageString ?? "")
            }
        }
        
        //图片控制器退出
        picker.dismiss(animated: true, completion: {
            () -> Void in
        })
    }
    
    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}

三、使用摄像头扫描读取二维码

(1)扫描主要使用的是 AVFoundation,用起来方便简单
(2)通过 AVCaptureMetadataOutput rectOfInterest 属性,可以设置探测探测区域。同时给这个探测区域添加个方框,只有在框中的二维码才会被扫描到。


1,效果图


2,Info.plist 配置

由于苹果安全策略更新,在使用 Xcode8 开发时,需要在 Info.plist 配置请求摄像头的相关描述字段(Privacy - Camera Usage Description


3,样例代码

import UIKit
import AVFoundation

class ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate,
UIAlertViewDelegate{
    
    var scanRectView:UIView!
    var device:AVCaptureDevice!
    var input:AVCaptureDeviceInput!
    var output:AVCaptureMetadataOutput!
    var session:AVCaptureSession!
    var preview:AVCaptureVideoPreviewLayer!
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    //通过摄像头扫描
    @IBAction func fromCamera(_ sender: AnyObject) {
        do{
            self.device = AVCaptureDevice.default(for: AVMediaType.video)
            
            self.input = try AVCaptureDeviceInput(device: device)
            
            self.output = AVCaptureMetadataOutput()
            output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
            
            self.session = AVCaptureSession()
            if UIScreen.main.bounds.size.height<500 {
                self.session.sessionPreset = AVCaptureSession.Preset.vga640x480
            }else{
                self.session.sessionPreset = AVCaptureSession.Preset.high
            }
            
            self.session.addInput(self.input)
            self.session.addOutput(self.output)
            
            self.output.metadataObjectTypes = [AVMetadataObject.ObjectType.qr]
            
            //计算中间可探测区域
            let windowSize = UIScreen.main.bounds.size
            let scanSize = CGSize(width:windowSize.width*3/4, height:windowSize.width*3/4)
            var scanRect = CGRect(x:(windowSize.width-scanSize.width)/2,
                                  y:(windowSize.height-scanSize.height)/2,
                                  width:scanSize.width, height:scanSize.height)
            //计算rectOfInterest 注意x,y交换位置
            scanRect = CGRect(x:scanRect.origin.y/windowSize.height,
                              y:scanRect.origin.x/windowSize.width,
                              width:scanRect.size.height/windowSize.height,
                              height:scanRect.size.width/windowSize.width);
            //设置可探测区域
            self.output.rectOfInterest = scanRect
            
            self.preview = AVCaptureVideoPreviewLayer(session:self.session)
            self.preview.videoGravity = AVLayerVideoGravity.resizeAspectFill
            self.preview.frame = UIScreen.main.bounds
            self.view.layer.insertSublayer(self.preview, at:0)
            
            //添加中间的探测区域绿框
            self.scanRectView = UIView();
            self.view.addSubview(self.scanRectView)
            self.scanRectView.frame = CGRect(x:0, y:0, width:scanSize.width,
                                             height:scanSize.height);
            self.scanRectView.center = CGPoint( x:UIScreen.main.bounds.midX,
                                                y:UIScreen.main.bounds.midY)
            self.scanRectView.layer.borderColor = UIColor.green.cgColor
            self.scanRectView.layer.borderWidth = 1;
            
            //开始捕获
            self.session.startRunning()
        }catch _ {
            //打印错误消息
            let alertController = UIAlertController(title: "提醒",
                        message: "请在iPhone的\"设置-隐私-相机\"选项中,允许本程序访问您的相机",
                        preferredStyle: .alert)
            let cancelAction = UIAlertAction(title: "确定", style: .cancel, handler: nil)
            alertController.addAction(cancelAction)
            self.present(alertController, animated: true, completion: nil)
        }
    }
    
    //摄像头捕获
     func metadataOutput(_ output: AVCaptureMetadataOutput,
                         didOutput metadataObjects: [AVMetadataObject],
                         from connection: AVCaptureConnection) {
        var stringValue:String?
        if metadataObjects.count > 0 {
            let metadataObject = metadataObjects[0] as! AVMetadataMachineReadableCodeObject
            stringValue = metadataObject.stringValue
            
            if stringValue != nil{
                self.session.stopRunning()
            }
        }
        self.session.stopRunning()
        //输出结果
        let alertController = UIAlertController(title: "二维码",
                                                message: stringValue,preferredStyle: .alert)
        let okAction = UIAlertAction(title: "确定", style: .default, handler: {
            action in
            //继续扫描
            self.session.startRunning()
        })
        alertController.addAction(okAction)
        self.present(alertController, animated: true, completion: nil)
    }

    override func didReceiveMemoryWarning() {
        super.didReceiveMemoryWarning()
    }
}
评论9
  • 9楼
    2017-12-30 15:11
    1515551264@qq.com

    站长回复

    多谢夸奖。

  • 8楼
    2017-10-13 17:08
    sixgod

    [discovery] errors encountered while discovering extensions: Error Domain=PlugInKit Code=13 "query cancelled" UserInfo={NSLocalizedDescription=query cancelled}
    出现上面错误是为什么啊?

    站长回复

    我把代码更新成Swift4的了,你可以再试下。

  • 7楼
    2017-08-07 15:56
    xjh

    This app has crashed because it attempted to access privacy-sensitive data without a usage description. The app's Info.plist must contain an NSCameraUsageDescription key with a string value explaining to the user how the app uses this data.
    出现上面错误描述怎么办

    站长回复

    现在info.plist文件里要添加相关的权限请求配置,我把文章更新了,你可以再看下。

  • 6楼
    2017-03-19 21:58
    tm

    航哥 求问有没有什么好用的名片 ocr 扫描的接口

    站长回复

    你可以试试汉王云的云名片API,不过我还没用过。

  • 5楼
    2017-02-09 14:57
    N个二维码

    你好,我想请教一下,如果一张图片上有N个二维码,我想通过扫描识别二维码信息,并且每个二维码信息通过回车/换行操作,这个程序能用吗?如果要修改的话怎么修改?谢啦

    站长回复

    N个二维码以可以使用啊,不用修改代码。因为各个扫描出来的二维码是在[CIQRCodeFeature]数组中,互不干扰,内部信息是否有换行什么的没什么影响。

  • 4楼
    2016-05-22 19:37
    宁静

    你好,我想请问一下获取了二维码的内容,例如我生成一个三行文字的二维码,如何解析每一行的内容出来。

    站长回复

    扫描的话会把二维码中的内容(三行文字)都读取出来,然后再对其拆分即可。
    let splitedarray = feature.messageString.componentsSeparatedByString("\n")

  • 3楼
    2016-03-15 19:02
    swift

    2,3案例无法正常演示了

    站长回复

    刚测试了下,可以使用

  • 2楼
    2015-12-21 11:20
    幸福小饽

    我想问一下iPhone5,5c及以下产品不能使用CIDetector识别二维码是什么原因呢?是硬件不支持吗?

    站长回复

    是模拟器不支持二维码识别,真机的话iPhone5,5c都是没问题的

  • 1楼
    2015-11-02 12:52
    落叶

    第一个实例用模拟器选iphone5s 及以上设备可以检测到,iphone5 4 4s都不行,真机就可以

    站长回复

    刚测试了下,确实是这样的。多谢提醒。