swift - 二維碼

kygz5479 8年前發布 | 17K 次閱讀 Swift 二維碼 Apple Swift開發

二維碼簡介

  1. 概念
    • 二維碼:是用某種特定的幾何圖形按一定規律在平面(二維方向上)分布的黑白相間的圖形記錄數據符號信息的;
    • 生成二維碼:根據給定的信息, 將其按照二維碼的編碼方式生成一張圖片
    • 讀取二維碼:識別二維碼圖像里面存儲的數據
  2. 二維碼的使用場景
    • 信息獲取(名片、WIFI密碼、資料)
    • 手機電商(用戶掃碼、手機直接購物下單)
    • 加好友(QQ, 微信, 掃一掃加好友)
    • 手機支付(掃描商品二維碼,通過銀行或第三方支付提供的手機端通道完成支付)
  3. 二維碼生成方式
    • 從iOS7開始集成了二維碼的生成和讀取功能
    • 此前被廣泛使用的zbarsdk目前不支持64位處理器,2015年2月1號起, 不允許不支持64位處理器的APP 上架
  4. 二維碼讀取
    • 直接從圖片中識別,最低支持iOS8.0
    • 利用攝像頭掃描識別,需要真機設備

生成/識別/讀取二維碼

  1. 生成二維碼

    1. 導入CoreImage框架

      • 一些圖片處理操作的功能, 都是用這個框架實現, 比如: 濾鏡效果, 毛玻璃, 美顏相機....

        import CoreImage
    2. 通過濾鏡CIFilter生成二維碼

       /** 友情提示: 學習實用技術, 不要太在意語言, 把所有注意力, 放在步驟的實現上面 */
    </li> </ol>
           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> 
    

    1. 自定義二維碼

      • 所謂自定義二維碼, 就是指給二維碼添加一些圖片(前景或者背景圖片), 或者改變下顏色
      • 可以添加前景圖片的前提是因為二維碼具備一定的"糾錯率"
        • 如果二維碼被部分遮擋, 可以根據其他部分, 計算出遮擋部分內容;
        • 但是要保證三個角不能被遮擋; 三個角用作掃描定位使用(可能用戶倒著拍, 斜著拍等等)
        • </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君(簡書)

                   

 本文由用戶 kygz5479 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!