swift - 二维码
kygz5479
8年前
<h2>二维码简介</h2> <ol> <li>概念 <ul> <li>二维码:是用某种特定的几何图形按一定规律在平面(二维方向上)分布的黑白相间的图形记录数据符号信息的;</li> <li>生成二维码:根据给定的信息, 将其按照二维码的编码方式生成一张图片</li> <li>读取二维码:识别二维码图像里面存储的数据</li> </ul> </li> <li>二维码的使用场景 <ul> <li>信息获取(名片、WIFI密码、资料)</li> <li>手机电商(用户扫码、手机直接购物下单)</li> <li>加好友(QQ, 微信, 扫一扫加好友)</li> <li>手机支付(扫描商品二维码,通过银行或第三方支付提供的手机端通道完成支付)</li> </ul> </li> <li>二维码生成方式 <ul> <li>从iOS7开始集成了二维码的生成和读取功能</li> <li>此前被广泛使用的zbarsdk目前不支持64位处理器,2015年2月1号起, 不允许不支持64位处理器的APP 上架</li> </ul> </li> <li>二维码读取 <ul> <li>直接从图片中识别,最低支持iOS8.0</li> <li>利用摄像头扫描识别,需要真机设备</li> </ul> </li> </ol> <h2>生成/识别/读取二维码</h2> <ol> <li> <p>生成二维码</p> <ol> <li> <p>导入CoreImage框架</p> <ul> <li> <p>一些图片处理操作的功能, 都是用这个框架实现, 比如: 滤镜效果, 毛玻璃, 美颜相机....</p> <pre> <code class="language-swift">import CoreImage</code></pre> </li> </ul> </li> <li> <p>通过滤镜CIFilter生成二维码</p> <pre> <code class="language-swift"> /** 友情提示: 学习实用技术, 不要太在意语言, 把所有注意力, 放在步骤的实现上面 */</code></pre> </li> </ol> </li> </ol> <pre> <code class="language-swift"> let content = inputTV.text // 生成二维码 // 1. 创建二维码滤镜 let filter = CIFilter(name: "CIQRCodeGenerator") // 1.1 恢复滤镜默认设置 filter?.setDefaults() // 2. 设置滤镜的输入内容 // 通过KVC 给里面一个inputMessage 赋值 // 输入的内容类型一定是NSData let data = content.dataUsingEncoding(NSUTF8StringEncoding) filter?.setValue(data, forKey: "inputMessage") // 3. 从滤镜里面取出结果图片 // 3.1 注意: 取出的图片是ciimage, 并且大小是23* 23 所以需要我们单独处理 // (23.0, 23.0) guard let outImage = filter?.outputImage else { return } // 3.1 图片处理 // 使用这种方式, 可以把图片放大处理, 而且保证不失真 let transform = CGAffineTransformMakeScale(20, 20) let resultImage = outImage.imageByApplyingTransform(transform) // 把CIImage转换成为UIImage let image = UIImage(CIImage: resultImage) print(image.size) // 4. 显示结果 qrCodeImageView.image = image</code></pre> <ol> <li> <p>自定义二维码</p> <ul> <li>所谓自定义二维码, 就是指给二维码添加一些图片(前景或者背景图片), 或者改变下颜色</li> <li>可以添加前景图片的前提是因为二维码具备一定的"纠错率" <ul> <li>如果二维码被部分遮挡, 可以根据其他部分, 计算出遮挡部分内容;</li> <li>但是要保证三个角不能被遮挡; 三个角用作扫描定位使用(可能用户倒着拍, 斜着拍等等)</li> </ul> </li> <li>通过KVC 设置滤镜的 inputCorrectionLevel (纠错率) <ul> <li>@"L", @"M", @"Q", @"H" 中的一个</li> <li>L水平 7%的字码可被修正</li> <li>M水平 15%的字码可被修正</li> <li>Q水平 25%的字码可被修正</li> <li>H水平 30%的字码可被修正</li> </ul> </li> <li> <p>自定义二维码代码</p> <pre> <code class="language-swift">override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) { view.endEditing(true) /** 友情提示: 学习实用技术, 不要太在意语言, 把所有注意力, 放在步骤的实现上面 */ let content = inputTV.text // 生成二维码 // 1. 创建二维码滤镜 let filter = CIFilter(name: "CIQRCodeGenerator") // 1.1 恢复滤镜默认设置 filter?.setDefaults() // 2. 设置滤镜的输入内容 // 通过KVC 给里面一个inputMessage 赋值 // 输入的内容类型一定是NSData let data = content.dataUsingEncoding(NSUTF8StringEncoding) filter?.setValue(data, forKey: "inputMessage") // 3.2 设置二维码纠错率 // 纠错率越高, 二维码图片,越复杂, 扫描识别的时间越长 filter?.setValue("M", forKey: "inputCorrectionLevel") // 3. 从滤镜里面取出结果图片 // 3.1 注意: 取出的图片是ciimage, 并且大小是23* 23 所以需要我们单独处理 // (23.0, 23.0) guard let outImage = filter?.outputImage else { return } // 3.1 图片处理 // 使用这种方式, 可以把图片放大处理, 而且保证不失真 let transform = CGAffineTransformMakeScale(20, 20) let resultImage = outImage.imageByApplyingTransform(transform) // 把CIImage转换成为UIImage let image = UIImage(CIImage: resultImage) print(image.size) // 3.3 自定义二维码 let center = UIImage(named: "erha.png") let hechengImage = createImage(image, centerImage: center!) // 4. 显示结果 qrCodeImageView.image = hechengImage } func createImage(sourceImage: UIImage, centerImage: UIImage) -> UIImage { let size = sourceImage.size // 1. 开启上下文 UIGraphicsBeginImageContext(size) // 2. 绘制大图片 sourceImage.drawInRect(CGRectMake(0, 0, size.width, size.height)) // 3. 绘制小图片 let w: CGFloat = 90 let h: CGFloat = 90 let x: CGFloat = (size.width - w) * 0.5 let y: CGFloat = (size.height - h) * 0.5 centerImage.drawInRect(CGRectMake(x, y, w, h)) // 4. 获取合成的图片 let resultImage = UIGraphicsGetImageFromCurrentImageContext() // 5. 关闭上下文 UIGraphicsEndImageContext() // 6. 返回结果 return resultImage }</code></pre> </li> </ul> </li> <li> <p>识别二维码</p> <ul> <li> <p>识别图片二维码</p> <pre> <code class="language-swift">// 1. 创建一个二维码探测器 let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]) // 2. 探测二维码图片的特征 guard let image = qrCodeImage.image else { return } let imageCI = CIImage(image: image) let features = detector.featuresInImage(imageCI!) // 3. 处理识别到的特征值 print(features) for feature in features { if feature.isKindOfClass(CIQRCodeFeature) { let qrCodeFeature = feature as! CIQRCodeFeature print(qrCodeFeature.messageString) // 绘制识别到的二维码图片 } }</code></pre> </li> <li> <p>扫描二维码</p> <ul> <li> <p>二维码扫描功能</p> <pre> <code class="language-swift">func scan() -> () { // 1. 获取摄像头设备 // 1.1 把摄像头当做一个输入设备 let device = AVCaptureDevice.defaultDeviceWithMediaType(AVMediaTypeVideo) var input: AVCaptureDeviceInput? do { input = try AVCaptureDeviceInput(device: device) }catch { print(error) return } // 2. 创建一个(元数据)输出处理对象 let output = AVCaptureMetadataOutput() // 2.1 设置代理, 拿到处理的结果 output.setMetadataObjectsDelegate(self, queue: dispatch_get_main_queue()) // 容错处理 // 如果已经添加过了, 就不会再次添加 if session.canAddInput(input) && session.canAddOutput(output) { session.addInput(input) session.addOutput(output) } // 设置元数据输出处理对象, 处理数据的类型 // 处理所有支持的类型 output.availableMetadataObjectTypes // 二维码 // 一定要注意 // 这行代码, 只能写在, 输出对象, 添加到会话之后 output.metadataObjectTypes = [AVMetadataObjectTypeQRCode] // 4. 启动会话(让输入开始采集数据, 让输出, 开始处理数据) session.startRunning() // 4.1 添加视频预览图层 // 可以让用户看到扫描的二维码 // 不是必须() let layer = AVCaptureVideoPreviewLayer(session: session) layer.frame = view.layer.bounds view.layer.insertSublayer(layer, atIndex: 0) }</code></pre> </li> <li> <p>二维码边框描绘</p> <pre> <code class="language-swift">extension ScanVC: AVCaptureMetadataOutputObjectsDelegate { func captureOutput(captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [AnyObject]!, fromConnection connection: AVCaptureConnection!) { // 最后如果没有扫描到二维码内容的时候, 也会调用一次 removeQRCodeFrame() print(metadataObjects) for metaObj in metadataObjects { if metaObj.isKindOfClass(AVMetadataMachineReadableCodeObject) { // 就是把坐标转换成为, 在layer层上面的真实坐标 let transformObj = layer?.transformedMetadataObjectForMetadataObject(metaObj as! AVMetadataObject) let qrCodeObj = transformObj as! AVMetadataMachineReadableCodeObject // corners: 二维码的四个角 // 得到的结果, 是点对应的字典组成的数组 // 并且, 点坐标, 没法直接使用 // 需要借助layer, 进行转换成为, 我们可以直接处理的坐标 print(qrCodeObj.corners) drawQRCodeFrame(qrCodeObj) // stringValue: 二维码的具体内容 print(qrCodeObj.stringValue) } } } func drawQRCodeFrame(qrCodeObj: AVMetadataMachineReadableCodeObject) -> () { // 借助一个图形layer // 1. 创建形状图层 let shapLayer = CAShapeLayer() shapLayer.fillColor = UIColor.clearColor().CGColor shapLayer.strokeColor = UIColor.redColor().CGColor shapLayer.lineWidth = 6 // 2. 给layer, 设置一个形状路径, 让layer来展示 let corners = qrCodeObj.corners let path = UIBezierPath() var index = 0 for corner in corners { // { // X = "154.7997282646955"; // Y = "431.3352825435441"; // } var point = CGPointZero CGPointMakeWithDictionaryRepresentation((corner as! CFDictionary), &point) // 如果第一个点, 移动路径过去, 当做起点 if index == 0 { path.moveToPoint(point) }else { path.addLineToPoint(point) } // 如果不是第一个点, 添加一个线到这个点 index += 1 } path.closePath() // 2.1 根据四个角对应的坐标, 转换成为一个path // 2.2 给layer 的path 进行赋值 shapLayer.path = path.CGPath // 3. 添加形状图层, 到需要展示的图层上面 layer?.addSublayer(shapLayer) } func removeQRCodeFrame() -> () { guard let subLayers = layer?.sublayers else { return } for subLayer in subLayers { if subLayer.isKindOfClass(CAShapeLayer) { subLayer.removeFromSuperlayer() } } } }</code></pre> </li> <li> <p>二维码扫描区域限定</p> <pre> <code class="language-swift">// 5. 设置输出的兴趣区域(限定扫描区域) // 注意事项: // 1. 里面的取值, 是一个0-->1的比例 // 2. 坐标相对应的坐标系是: 右上角为0, 0 (横屏状态下的坐标系) let size = UIScreen.mainScreen().bounds.size let x: CGFloat = backView.frame.origin.x / size.width let y: CGFloat = backView.frame.origin.y / size.height let w: CGFloat = backView.frame.size.width / size.width let h: CGFloat = backView.frame.size.height / size.height output.rectOfInterest = CGRectMake(y, x, h, w)</code></pre> </li> </ul> </li> <li>使用注意 <ul> <li><em>读取二维码需要导入AVFoundation框架</em></li> <li><em>利用摄像头识别二维码中的内容(模拟器不行)</em></li> </ul> </li> </ul> </li> </ol> <p><br> </p> <p>文/<a href="/misc/goto?guid=4959674332762826839">大L君</a>(简书)</p> <p> </p>