Swift之二維碼的生成、識別和掃描

最近在項目中遇到了涉及二維碼相關(guān)的問題, 這里想記錄總結(jié)一下二維碼相關(guān)技術(shù)

一. 二維碼的介紹

  • 二維條碼/二維碼是用某種特定的幾何圖形按一定規(guī)律在平面分布的黑白相間的圖形記錄數(shù)據(jù)符號信息的
  • 總結(jié): 用圖形記錄標記一些信息,方便通過圖形識別來獲取信息
  • 應(yīng)用場景
    • 信息獲仁中(名片筐高、地圖云头、WIFI密碼、資料)
    • 手機電商(用戶掃碼骡技、手機直接購物下單)
    • 手機支付(掃描商品二維碼,通過銀行或第三方支付提供的手機端通道完成支付)
    • 微信添加好友

二. 二維碼的生成

  • 生成二維碼的方式
    • 采用第三方框架(放棄)
      • ZXing/ZBar
      • 框架不支持64位(2015年2月1號起, - 不允許不支持64位處理器的APP 上架)
    • 系統(tǒng)自帶API
  • 生成二維碼的步驟
    • 創(chuàng)建二維碼濾鏡--CIFilter
    • 恢復(fù)濾鏡的默認屬性
    • 設(shè)置濾鏡的輸入數(shù)據(jù)
    • 將傳入的字符串轉(zhuǎn)換成Data(OC為NSData)數(shù)據(jù)
    • 通過KVC來設(shè)置輸入的內(nèi)容inputMessage

1. 二維碼容錯率

filter?.setValue("H", forKey: "inputCorrectionLevel")
  • inputCorrectionLevel 是一個單字母(@"L", @"M", @"Q", @"H" 中的一個)羞反,表示不同級別的容錯率布朦,默認為 @"M".
  • QR碼有容錯能力,QR碼圖形如果有破損昼窗,仍然可以被機器讀取內(nèi)容是趴,最高可以到7%~30%面積破損仍可被讀取,相對而言,容錯率愈高澄惊,QR碼圖形面積愈大唆途。所以一般折衷使用15%容錯能力。
  • L水平 7%的字碼可被修正.
  • M水平 15%的字碼可被修正
  • Q水平 25%的字碼可被修正
  • H水平 30%的字碼可被修正
  • 代碼:
/* *  @param inputMsg 二維碼保存的信息
   *  @param fgImage  前景圖片  */
func generateCode(inputMsg: String, fgImage: UIImage?) -> UIImage {
    //1. 將內(nèi)容生成二維碼
    //1.1 創(chuàng)建濾鏡
    let filter = CIFilter(name: "CIQRCodeGenerator")
    
    //1.2 恢復(fù)默認設(shè)置
    filter?.setDefaults()
    
    //1.3 設(shè)置生成的二維碼的容錯率
    //value = @"L/M/Q/H"
    filter?.setValue("H", forKey: "inputCorrectionLevel")
    
    // 2.設(shè)置輸入的內(nèi)容(KVC)
    // 注意:key = inputMessage, value必須是NSData類型
    let inputData = inputMsg.data(using: .utf8)
    filter?.setValue(inputData, forKey: "inputMessage")
    
    //3. 獲取輸出的圖片
    guard let outImage = filter?.outputImage else { return UIImage() }
    
    //4. 獲取高清圖片
    let hdImage = getHDImage(outImage)
    
    //5. 判斷是否有前景圖片
    if fgImage == nil{
        return hdImage
    }
    
    //6. 獲取有前景圖片的二維碼
    return getResultImage(hdImage: hdImage, fgImage: fgImage!)
}

2. 獲取高清圖片

//4. 獲取高清圖片
fileprivate func getHDImage(_ outImage: CIImage) -> UIImage {
    let transform = CGAffineTransform(scaleX: 10, y: 10)
    //放大圖片
    let ciImage = outImage.transformed(by: transform)
    
    return UIImage(ciImage: ciImage)
}

3. 將圖片合成到二維碼中

  • 需要用到圖形上下文
  • 將二維碼畫到圖形上下文
  • 將圖片合成到圖行上下文
//獲取前景圖片
fileprivate func getResultImage(hdImage: UIImage, fgImage: UIImage) -> UIImage {
    let hdSize = hdImage.size
    //1. 開啟圖形上下文
    UIGraphicsBeginImageContext(hdSize)
    
    //2. 將高清圖片畫到上下文
    hdImage.draw(in: CGRect(x: 0, y: 0, width: hdSize.width, height: hdSize.height))
    
    //3. 將前景圖片畫到上下文
    let fgWidth: CGFloat = 80
    fgImage.draw(in: CGRect(x: (hdSize.width - fgWidth) / 2, y: (hdSize.height - fgWidth) / 2, width: fgWidth, height: fgWidth))
    
    //4. 獲取上下文
    guard let resultImage = UIGraphicsGetImageFromCurrentImageContext() else { return UIImage() }
    
    //5. 關(guān)閉上下文
    UIGraphicsEndImageContext()
    
    return resultImage
}

后續(xù)會研究彩色二維碼的黑科技, 敬請期待...

識別二維碼

識別圖片中二維碼步驟

  • 創(chuàng)建探測器
    • 屬于CoreImage框架(CIDetector)
  • 獲取CIImage類型的圖片
  • 獲取圖片中所有符合特征的內(nèi)容(CIQRCodeFeature)
  • 遍歷所有的特性(CIQRCodeFeature)
  • 獲取特征中代表的信息(messageString)
  • 識別二維碼的代碼實現(xiàn)
/* *  @param qrCodeImage 二維碼的圖片
   *  @return 結(jié)果的數(shù)組 */
func recognitionQRCode(qrCodeImage: UIImage) -> [String]? {
    //1. 創(chuàng)建過濾器
    let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: nil, options: nil)
    
    //2. 獲取CIImage
    guard let ciImage = CIImage(image: qrCodeImage) else { return nil }
    
    //3. 識別二維碼
    guard let features = detector?.features(in: ciImage) else { return nil }
    
    //4. 遍歷數(shù)組, 獲取信息
    var resultArr = [String]()
    for feature in features {
        resultArr.append(feature.type)
    }
    
    return resultArr
}

三. 二維碼的掃描

  • 創(chuàng)建輸入設(shè)備(攝像頭)
    • 獲取攝像頭設(shè)備
    • 創(chuàng)建輸入對象
  • 創(chuàng)建輸出設(shè)置(元數(shù)據(jù))
    • 創(chuàng)建輸出對象
    • 設(shè)置輸出對象的代理(在代理中獲取掃描到的數(shù)據(jù))
    • 設(shè)置輸出數(shù)據(jù)的類型
  • 創(chuàng)建捕捉會話
    • 將輸入添加到會話中
    • 將輸出添加到會話中
  • 添加預(yù)覽圖片(方便用于查看)
    • 創(chuàng)建圖層,將圖片添加到View圖層中
  • 開始掃描

1. 懶加載輸入輸出中間會話

//輸入輸出中間橋梁(會話)
fileprivate lazy var session : AVCaptureSession = AVCaptureSession()

2. 初始化掃描設(shè)備

2.1. 注意: AVCaptureMetadataOutputObjectsDelegate的代理設(shè)置, 該協(xié)議中的方法會將掃描的結(jié)果返回

fileprivate func addScaningVideo(){
    //1.獲取輸入設(shè)備(攝像頭)
    guard let device = AVCaptureDevice.default(for: .video) else { return }
    
    //2.根據(jù)輸入設(shè)備創(chuàng)建輸入對象
    guard let deviceInput = try? AVCaptureDeviceInput(device: device) else { return }
    
    //3.創(chuàng)建原數(shù)據(jù)的輸出對象
    let metadataOutput = AVCaptureMetadataOutput()
    
    //4.設(shè)置代理監(jiān)聽輸出對象輸出的數(shù)據(jù)掸驱,在主線程中刷新
    metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
    
    //5.創(chuàng)建會話(橋梁)
    //        let session = AVCaptureSession()
    
    //6.添加輸入和輸出到會話
    if session.canAddInput(deviceInput) {
        session.addInput(deviceInput)
    }
    if session.canAddOutput(metadataOutput) {
        session.addOutput(metadataOutput)
    }
    
    //7.告訴輸出對象要輸出什么樣的數(shù)據(jù)(二維碼還是條形碼),要先創(chuàng)建會話才能設(shè)置
    metadataOutput.metadataObjectTypes = [.qr, .code128, .code39, .code93, .code39Mod43, .ean8, .ean13, .upce, .pdf417, .aztec]
    
    //8.創(chuàng)建預(yù)覽圖層
    let previewLayer: AVCaptureVideoPreviewLayer = AVCaptureVideoPreviewLayer(session: session)
    previewLayer.videoGravity = .resizeAspectFill
    previewLayer.frame = view.bounds
    view.layer.insertSublayer(previewLayer, at: 0)
    
    //9.設(shè)置有效掃描區(qū)域(默認整個屏幕區(qū)域)(每個取值0~1, 以屏幕右上角為坐標原點)
    let rect = CGRect(x: scanImageView.frame.minY / kScreenHeight, y: scanImageView.frame.minX / kScreenWidth, width: scanImageView.frame.height / kScreenHeight, height: scanImageView.frame.width / kScreenWidth)
    metadataOutput.rectOfInterest = rect
    
    //10. 開始掃描
    session.startRunning()
}

2.2 代理方法的實現(xiàn)

  • 需要將掃描的結(jié)果轉(zhuǎn)化成機器可讀的編碼數(shù)據(jù),才能獲取二維碼的相關(guān)信息
extension ScaningViewController: AVCaptureMetadataOutputObjectsDelegate {
    func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
        //1. 取出掃描到的數(shù)據(jù): metadataObjects
        //2. 以震動的形式告知用戶掃描成功
        AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
        
        //3. 關(guān)閉session
        session.stopRunning()
        
        //4. 遍歷結(jié)果
        var resultArr = [String]()
        for result in metadataObjects {
            //轉(zhuǎn)換成機器可讀的編碼數(shù)據(jù)
            if let code = result as? AVMetadataMachineReadableCodeObject {
                resultArr.append(code.stringValue ?? "")
            }else {
                resultArr.append(result.type.rawValue)
            }
        }
        
        //5. 將結(jié)果
        let vc = ShowViewController()
        vc.scanDataArr = resultArr
        navigationController?.pushViewController(vc, animated: true)
    }
}

項目地址: Github

  • 感謝大家的支持

其他相關(guān)文章


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市毕贼,隨后出現(xiàn)的幾起案子温赔,更是在濱河造成了極大的恐慌,老刑警劉巖鬼癣,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件陶贼,死亡現(xiàn)場離奇詭異,居然都是意外死亡待秃,警方通過查閱死者的電腦和手機骇窍,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來锥余,“玉大人腹纳,你說我怎么就攤上這事。” “怎么了嘲恍?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵足画,是天一觀的道長。 經(jīng)常有香客問我佃牛,道長淹辞,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任俘侠,我火速辦了婚禮象缀,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘爷速。我一直安慰自己央星,他們只是感情好,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布惫东。 她就那樣靜靜地躺著莉给,像睡著了一般。 火紅的嫁衣襯著肌膚如雪廉沮。 梳的紋絲不亂的頭發(fā)上颓遏,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天,我揣著相機與錄音滞时,去河邊找鬼叁幢。 笑死,一個胖子當著我的面吹牛坪稽,可吹牛的內(nèi)容都是我干的曼玩。 我是一名探鬼主播,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼刽漂,長吁一口氣:“原來是場噩夢啊……” “哼演训!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起贝咙,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤样悟,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后庭猩,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窟她,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年蔼水,在試婚紗的時候發(fā)現(xiàn)自己被綠了震糖。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡趴腋,死狀恐怖吊说,靈堂內(nèi)的尸體忽然破棺而出论咏,到底是詐尸還是另有隱情,我是刑警寧澤颁井,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布厅贪,位于F島的核電站,受9級特大地震影響雅宾,放射性物質(zhì)發(fā)生泄漏养涮。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一眉抬、第九天 我趴在偏房一處隱蔽的房頂上張望贯吓。 院中可真熱鬧,春花似錦蜀变、人聲如沸悄谐。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽尊沸。三九已至威沫,卻和暖如春贤惯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背棒掠。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工孵构, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人烟很。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓颈墅,卻偏偏與公主長得像,于是被迫代替她去往敵國和親雾袱。 傳聞我的和親對象是個殘疾皇子恤筛,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353