概览
二维条码 是指在一维条码的基础上扩展出另一维具有可读性的条码,使用黑白矩形图案表示二进制数据,被设备扫描后可获取其中所包含的信息。一维条码的宽度记载着数据,而其长度没有记载数据。二维条码的长度、宽度均记载着数据。
二维条码在商业活动中应用广泛,特别是在高科技行业、储存运输业、批发零售业等需要对物品进行廉价快捷的标示信息的行业用途广泛。
闲话少叙,二维码的应用已经到了无孔不入的地步,只要是个规模稍大点儿的APP就基本支持二维码的识别,所以,本文就讲讲二维码的识别。
UI绘制 常规的二维码扫描界面是一个灰色的背景和一个正方形的白框来确定二维码的位置。在此我们使用贝塞尔曲线来画出形状,用CAShapeLayer
来进行展示。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 let path1 = UIBezierPath (rect: self .view.bounds)let x = (self .view.bounds.size.width - 200 ) / 2 let path2 = UIBezierPath (rect: CGRect (x: x, y: 150 , width: 200 , height: 200 ))path1.append(path2) let shape = CAShapeLayer ()shape.path = path1.cgPath shape.fillRule = kCAFillRuleEvenOdd shape.fillColor = UIColor .lightGray.cgColor self .view.layer.addSublayer(shape)
到此,扫描二维码的界面就算是构建完了。
摄像头扫描二维码 要使用摄像头扫描二维码,首先需要获取相机的授权。如果没有得到授权,应该提示用户在权限设置中打开相机的权限。
检查相机授权 首先需要检查当前相机的授权状态,授权状态是一个枚举类型,根据不同的枚举值判断下一步应该要做的是什么。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 func checkCameraAuthorization () { let status = AVCaptureDevice .authorizationStatus(for: .video) switch status { case .notDetermined: AVCaptureDevice .requestAccess(for: .video, completionHandler: { (granted) in if granted { self .setupCamera() } }) case .authorized: self .setupCamera() case .denied: print ("前往系统设置打开app相机权限" ) UIApplication .shared.openURL(URL .init (string: "hello" )! ) case .restricted: print ("Camera cannot be used." ) default : break } }
相机设置 相机设置包括device、session、input和output的设置,其中最主要的是rectOfInterest
的设置。rectOfInterest
是一个CGRect
类型的属性,但是它的坐标原点是在右上角,取值范围是0~1。也就是说右上角坐标为(0,0),左下角坐标是(1,1)。为了方便设置,此处谢了一个坐标转换的函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func transaction (rect : CGRect , parentSize : CGSize ) -> CGRect { let x = (parentSize.width - rect.origin.x) / parentSize.width let y = rect.origin.y / parentSize.height let width = rect.size.width / parentSize.width let height = rect.size.height / parentSize.height return CGRect (x: x, y: y, width: width, height: height) }
接下来,检查back camera是否存在,设置session,input和output,最后设置显示相机捕获内容的AVCaptureVideoPreviewLayer
并将其添加到父视图上去。最后调用session
的startRunning
方法开始捕捉。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 func setupCamera () { let devices = AVCaptureDevice .devices(for: .video) for obj in devices { if obj.position == .back { self .device = obj break } } if self .device == nil { print ("Unavailabel back camera." ) return } let input = try! AVCaptureDeviceInput .init (device: self .device! ) let output = AVCaptureMetadataOutput () output.setMetadataObjectsDelegate(self , queue: DispatchQueue .main) let x = (self .view.bounds.size.width - 200 ) / 2 let rect = CGRect (x: x, y: 150 , width: 200 , height: 200 )) output.rectOfInterest = self .transaction(rect: rect, parentSize: self .view.bounds.size) output.metadataObjectTypes = [.qr] self .session = AVCaptureSession () self .session? .sessionPreset = .high self .session? .addInput(input) self .session? .addOutput(output) let previewLayer = AVCaptureVideoPreviewLayer .init (session: self .session! ) self .view.layer.insertSublayer(previewLayer, at: 0 ) self .session? .startRunning() }
输出代理 如果扫描到二维码,那么将会调用扫描成功的代理方法,通过获取扫描到的metadataObjects
数组来获得最终的扫描文字内容。
1 2 3 4 func metadataOutput (_ output : AVCaptureMetadataOutput , didOutput metadataObjects : [AVMetadataObject ], from connection : AVCaptureConnection ) { let object: AVMetadataMachineReadableCodeObject = metadataObjects.first as! AVMetadataMachineReadableCodeObject print (object.stringValue as Any ) }
图片识别二维码 图片识别相当简单,识别二维码主要会用到CoreImage
中的CIDectector
类。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func scanQRCode (image : UIImage ) { let detector = CIDetector .init (ofType: CIDetectorTypeQRCode , context: nil , options: [CIDetectorAccuracy: CIDetectorAccuracyHigh ]) let result = detector? .features(in: image.ciImage! ) guard result else { print ("Cannot detect QRCode on the image." ) return } for feature in result { print ("Message is \(feature.messageString) ." ) } }
使用闪光灯 在黑暗环境下,使用相机扫描二维码时,会需要有光源照明,比如夜晚扫摩拜、ofo之类的场景。此时就会需要打开闪光灯。
1 2 3 4 5 6 7 8 9 10 11 12 func flashLight (sender : UIButton ) { sender.isSelected = ! sender.isSelected self .session? .beginConfiguration() self .device? .lockForConfiguration() self .device? .torchMode = sender.isSelected ? .on : .off self .device? .flashMode = sender.isSelected ? .on : .off self .device? .unlockForConfiguration() self .session? .commitConfiguration() }
后续会补充根据光线强度自动打开闪光灯的部分。