swift - 二維碼
二維碼簡介
- 概念
- 二維碼:是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;
- 生成二維碼:根據給定的信息, 將其按照二維碼的編碼方式生成一張圖片
- 讀取二維碼:識別二維碼圖像里面存儲的數據
- 二維碼的使用場景
- 信息獲取(名片、WIFI密碼、資料)
- 手機電商(用戶掃碼、手機直接購物下單)
- 加好友(QQ, 微信, 掃一掃加好友)
- 手機支付(掃描商品二維碼,通過銀行或第三方支付提供的手機端通道完成支付)
- 二維碼生成方式
- 從iOS7開始集成了二維碼的生成和讀取功能
- 此前被廣泛使用的zbarsdk目前不支持64位處理器,2015年2月1號起, 不允許不支持64位處理器的APP 上架
- 二維碼讀取
- 直接從圖片中識別,最低支持iOS8.0
- 利用攝像頭掃描識別,需要真機設備
生成/識別/讀取二維碼
-
生成二維碼
-
導入CoreImage框架
-
一些圖片處理操作的功能, 都是用這個框架實現, 比如: 濾鏡效果, 毛玻璃, 美顏相機....
import CoreImage
-
-
通過濾鏡CIFilter生成二維碼
/** 友情提示: 學習實用技術, 不要太在意語言, 把所有注意力, 放在步驟的實現上面 */
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>
-
自定義二維碼
- 所謂自定義二維碼, 就是指給二維碼添加一些圖片(前景或者背景圖片), 或者改變下顏色
- 可以添加前景圖片的前提是因為二維碼具備一定的"糾錯率"
- 如果二維碼被部分遮擋, 可以根據其他部分, 計算出遮擋部分內容;
- 但是要保證三個角不能被遮擋; 三個角用作掃描定位使用(可能用戶倒著拍, 斜著拍等等) </ul> </li>
- 通過KVC 設置濾鏡的 inputCorrectionLevel (糾錯率)
- @"L", @"M", @"Q", @"H" 中的一個
- L水平 7%的字碼可被修正
- M水平 15%的字碼可被修正
- Q水平 25%的字碼可被修正
- H水平 30%的字碼可被修正 </ul> </li>
-
自定義二維碼代碼
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>
識別二維碼
-
識別圖片二維碼
// 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>
掃描二維碼
-
二維碼掃描功能
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>
二維碼邊框描繪
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>
二維碼掃描區域限定
// 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>
- 使用注意
- 讀取二維碼需要導入AVFoundation框架
- 利用攝像頭識別二維碼中的內容(模擬器不行) </ul> </li> </ul> </li> </ol>
文/大L君(簡書)
-