二維碼
- 二維碼概念
- 二維碼是用某種特點的集合圖形按一定規(guī)律在平面(二維方向上)分布的黑白相間的圖形,用于記錄數據符號信息;
- 二維碼的使用場景
- 信息獲取(名片,WIFI密碼,資料)
- 手機電商(用戶掃碼,手機直接購物下單)
- 加好友(QQ,微信,掃一掃加好友)
- 手機支付(掃描商品二維碼,永固歐銀行或第三方支付提供的手機端通道完成支付)
- 二維碼的生成方式
- 從iOS7開始繼承了二維碼的生成和讀取功能
- 此前被廣泛使用的zbars和zxing目前不支持64位處理器(5s以后都是64位處理器)(2015年2月1號開始,不允許不支持64位處理器的APP上架)
生成二維碼
- 步驟
- 獲取文本框輸入的內容
- 生成一個二維碼濾鏡(不同的字符串生成不同的濾鏡)
- 設置二維碼的內容
- 從二維碼濾鏡中獲取生成的二維碼
- 展示圖片(注意:默認生成的二維碼大小為23,放大圖片后就會顯得模糊)
生成二維碼實例
/// 生成二維碼
extension QRCodeTool {
/// 生成二維碼
///
/// - Parameters:
/// - contentStr: 二維碼內容
/// - bigImageWH: 二維碼大小
/// - smallImage: 小圖標
/// - smallImageSize: 小圖標大小
/// - Returns: 生成一個自頂一個二維碼圖片返回
class func gerneratorQRCode(contentStr : String, bigImageWH : CGFloat, smallImage : UIImage, smallImageSize : CGFloat) -> UIImage? {
// 1.創(chuàng)建一個二維碼濾鏡
guard let filter = CIFilter(name: "CIQRCodeGenerator") else {return nil}
// 1.1恢復默認值
filter.setDefaults()
// 2.設置內容
// 注意點
// 如果要設置二維碼內容,必須以KVC的方式設置
// 二維碼的值必須是NSData
let data = contentStr.data(using: .utf8)
filter.setValue(data, forKeyPath: "inputMessage")
// 2.1設置二維碼的級別(糾錯率)
// key : inputCorrectionLevel
// value L: 7% M(默認 ): 15% Q: 25% H: 30%
filter.setValue("H", forKeyPath: "inputCorrectionLevel")
// 3.從二維碼濾鏡中獲取二維碼
guard let outputImage = filter.outputImage else {return nil}
// 4.展示圖片(生成的二維碼圖片的大小為23*23,放大到200*200就會變得很模糊)
// let imageUI = UIImage(ciImage: outputImage)
// print(imageUI.size)
guard let imageUI = createClearImage(outputImage, size: bigImageWH) else {return nil}
// 4.設置二維碼顏色(注意:設置二維碼顏色,必須放在清晰的二維碼之后,如果先設置顏色,再改變清晰度,則設置顏色無效)
let imageCI = CIImage(image: imageUI)
let colorFiler = CIFilter(name: "CIFalseColor")
colorFiler?.setDefaults()
// 設置圖片
colorFiler?.setValue(imageCI, forKeyPath: "inputImage")
// 設置二維碼亞瑟
colorFiler?.setValue(CIColor.init(color: UIColor.yellow), forKeyPath: "inputColor0")
// 設置背景顏色
colorFiler?.setValue(CIColor.init(color: UIColor.red), forKeyPath: "inputColor1")
guard let colorOutputImage = colorFiler?.outputImage else {return nil}
let image = UIImage(ciImage: colorOutputImage)
return createCustomImage(bigImage: image, smallImage: smallImage, smallImageWH: smallImageSize)
}
/// 自定義二維碼
///
/// - Parameters:
/// - bigImage: 二維碼圖片
/// - smallImage: 小圖片
/// - smallImageWH: 小圖片尺寸
/// - Returns: 生成新的二維碼圖片
fileprivate class func createCustomImage(bigImage : UIImage, smallImage : UIImage, smallImageWH : CGFloat) -> UIImage? {
// 0.大圖片的尺寸
let bigImageSize = bigImage.size
// 1.創(chuàng)建圖形上下文
UIGraphicsBeginImageContext(bigImageSize)
// 2.繪制大圖片
bigImage.draw(in: CGRect(x: 0, y: 0, width: bigImageSize.width, height: bigImageSize.height))
// 3.繪制小圖片
smallImage.draw(in: CGRect(x: (bigImageSize.width - smallImageWH) * 0.5, y: (bigImageSize.height - smallImageWH) * 0.5, width: smallImageWH, height: smallImageWH))
// 4.從圖形上下文中取出圖片
let image = UIGraphicsGetImageFromCurrentImageContext()
// 5.關閉圖形上下文
UIGraphicsEndImageContext()
// 6.返回圖片
return image
}
/// 將模糊的二維碼圖片轉為清晰的
///
/// - parameter image: 模糊的二維碼圖片
/// - parameter size: 需要生成的二維碼尺寸
///
/// - returns: 清晰的二維碼圖片
fileprivate class func createClearImage(_ image : CIImage, size : CGFloat ) -> UIImage? {
// 1.調整小數像素到整數像素,將origin下調(12.*->12),size上調(11.*->12)
let extent = image.extent.integral
// 2.將指定的大小與寬度和高度進行對比,獲取最小的比值
let scale = min(size / extent.width, size/extent.height)
// 3.將圖片放大到指定比例
let width = extent.width * scale
let height = extent.height * scale
// 3.1創(chuàng)建依賴于設備的灰度顏色通道
let cs = CGColorSpaceCreateDeviceGray();
// 3.2創(chuàng)建位圖上下文
let bitmapRef = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: cs, bitmapInfo: 0)
// 4.創(chuàng)建上下文
let context = CIContext(options: nil)
// 5.將CIImage轉為CGImage
let bitmapImage = context.createCGImage(image, from: extent)
// 6.設置上下文渲染等級
bitmapRef!.interpolationQuality = .none
// 7.改變上下文的縮放
bitmapRef?.scaleBy(x: scale, y: scale)
// 8.繪制一張圖片在位圖上下文中
bitmapRef?.draw(bitmapImage!, in: extent)
// 9.從位圖上下文中取出圖片(CGImage)
guard let scaledImage = bitmapRef?.makeImage() else {return nil}
// 10.將CGImage轉為UIImage并返回
return UIImage(cgImage: scaledImage)
}
}
效果
識別二維碼
- 步驟
- 創(chuàng)建二維碼探測器
- 探測圖片特征
- 讀取圖片特征
識別二維碼實例
extension QRCodeTool {
/// 識別圖片中的二維碼
///
/// - Parameter sourceImage: 原始圖片
/// - Returns: 將識別到的二維碼繪制邊框并返回圖片
class func detectorQRCode(sourceImage : UIImage) -> UIImage? {
// 0.創(chuàng)建上下文
let context = CIContext()
// 1.創(chuàng)建二維碼的探測器
guard let detector = CIDetector(ofType: CIDetectorTypeQRCode, context: context, options: [CIDetectorAccuracy : CIDetectorAccuracyHigh]) else {return nil}
// 2.探測圖片
guard let sourceImageCI = CIImage(image: sourceImage) else {return nil}
guard let features = detector.features(in: sourceImageCI) as? [CIQRCodeFeature] else {return nil}
// 3.繪制識別的二維碼的邊框
return drawQRCodeBorder(features: features, sourceImage: sourceImage)
}
fileprivate class func drawQRCodeBorder(features : [CIQRCodeFeature], sourceImage : UIImage) -> UIImage? {
// 0.源圖片大小
let sourceImageSize = sourceImage.size
// 1.創(chuàng)建圖形上下文
UIGraphicsBeginImageContext(sourceImageSize)
// 2.繪制圖片
sourceImage.draw(in: CGRect(x: 0, y: 0, width: sourceImageSize.width, height: sourceImageSize.height))
// 3.繪制邊框(bounds的坐標原點在左下角)
// 第二種解決方式
// 獲取圖形上下文
let context = UIGraphicsGetCurrentContext()
// 改變圖形上下文的坐標原點
// 注意:必須放在繪制之前
context?.scaleBy(x: 1, y: -1)
context?.translateBy(x: 0, y: -sourceImageSize.height)
for feature in features {
let bounds = feature.bounds
// 第一種解決方式
// let newBounds = CGRect(x: bounds.origin.x, y:sourceImageSize.height - bounds.origin.y - bounds.height , width: bounds.width, height: bounds.height)
// let path = UIBezierPath(rect: newBounds)
let path = UIBezierPath(rect: bounds)
UIColor.red.set()
path.lineWidth = 4
path.stroke()
}
// 4.取出圖片
let image = UIGraphicsGetImageFromCurrentImageContext()
// 5.關閉圖形上下文
UIGraphicsEndImageContext()
// 6.返回圖片
return image
}
}
效果
掃描二維碼
- 掃描功能
- 功能描述
- 掃描二維碼原理
- 需要使用AVFoundation
- 獲取攝像頭,作為輸入設備
- 創(chuàng)建輸出對象,并設置代理,掃描到結果后就會通過代理告訴外界
- 創(chuàng)建捕捉會話,將輸入和輸出連接起來
- 開啟會話,執(zhí)行掃描操作
- 注意點
- 訪問用戶的攝像頭需要請求授權
- info.plist配置Privacy - Camera Usage Description
- 在使用輸出設備設置代理以及隊列時注意傳的是主隊列還是全局隊列
- 使用會話連接輸入和輸出之前先判斷是否可以添加
- 必須制定掃描的類型,并在將輸出添加到會話后再添加,否則會崩潰
- 保證捕捉會話不被銷毀
- 當掃描結果時會不斷的調用代理方法
class QRCodeTool: NSObject {
/// 掃描二維碼
// 單例對象
static let shareInstance = QRCodeTool()
///懶加載
/// 創(chuàng)建會話(順便添加輸入和輸出對象)
fileprivate lazy var session : AVCaptureSession? = {
// 1.獲取攝像頭設備
guard let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) else {return nil}
// 2.將攝像頭作為輸入對象
var input : AVCaptureDeviceInput?
do {
input = try AVCaptureDeviceInput(device: captureDevice)
} catch {
print(error)
return nil
}
// 3.創(chuàng)建輸出對象
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// 4.創(chuàng)建捕捉會話
let session = AVCaptureSession()
// 5.添加輸入對象和輸出對象到會話中
if session.canAddInput(input!) && session.canAddOutput(output) {
session.addInput(input!)
session.addOutput(output)
// 注意:如果要掃描任何碼制,必須制定掃描的類型
// 必須在添加了輸出對象之后再設置
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
}
return session
}()
/// 創(chuàng)建預覽圖層
/// 將預覽圖層添加到layer上,注意添加到最底層,否則會遮住當前的控件
fileprivate lazy var preViewLayer : AVCaptureVideoPreviewLayer? = {
guard let preViewLayer = AVCaptureVideoPreviewLayer(session: self.session) else {return nil}
return preViewLayer
}()
// 定義閉包
typealias ScanResultBlock = (_ resultStr : String) -> ()
var scanResultBlock : ScanResultBlock?
func scanQRCode(scanView : UIView, preView : UIView, scanResultBlock : @escaping ScanResultBlock) -> () {
// 0.記錄block
self.scanResultBlock = scanResultBlock
// 1.判斷會話和預覽圖層是否有值
if session == nil || preViewLayer == nil {
return
}
if let subLayers = preView.layer.sublayers {
let isHavePreViewLayer = subLayers.contains(preViewLayer!)
if !isHavePreViewLayer {
preView.layer.insertSublayer(preViewLayer!, at: 0)
preViewLayer?.frame = preView.bounds
guard let output = session!.outputs.first as? AVCaptureMetadataOutput else {
return
}
// 設置掃描區(qū)域(內部會以坐標原點在右上角設置)
let x = scanView.frame.origin.y / preView.bounds.height
let y = scanView.frame.origin.x / preView.bounds.width
let w = scanView.bounds.height / preView.bounds.height
let h = scanView.bounds.width / preView.bounds.width
output.rectOfInterest = CGRect(x: x, y: y, width: w, height: h)
}
}
// 2.判斷是否正在掃描
if session!.isRunning {
return
}
// 3.開始掃描
session!.startRunning()
}
/// 控制手機手電筒
/// 步驟
/// 1.需要使用AVFoundation框架
/// 2.獲取手機攝像頭
/// 3.通過攝像頭獲取硬件的控制權
/// 4.設置手電筒的模式
/// 5.取消硬件的控制權
class func turnLight(isOn : Bool) {
// 1.獲取攝像頭
guard let captureDevice = AVCaptureDevice.defaultDevice(withMediaType: AVMediaTypeVideo) else {return}
// 2.獲取設備的控制權
do {
try captureDevice.lockForConfiguration()
} catch {
print(error)
return
}
if isOn { // 打開手電筒
captureDevice.torchMode = .on
} else { // 關閉手電筒
captureDevice.torchMode = .off
}
// 3.釋放設備的控制權
captureDevice.unlockForConfiguration()
}
}
/// AVCaptureMetadataOutputObjectsDelegate
extension QRCodeTool : AVCaptureMetadataOutputObjectsDelegate {
/// 當掃描到結果的時候就會來到該方法(任何碼制都會來到該方法)
///
/// - Parameters:
/// - captureOutput: 輸出
/// - metadataObjects: 掃描到碼制數組
/// - connection: 鏈接
func captureOutput(_ captureOutput: AVCaptureOutput!, didOutputMetadataObjects metadataObjects: [Any]!, from connection: AVCaptureConnection!) {
removeQRCodeBorder()
// 掃描的類型AVMetadataMachineReadableCodeObject(二維碼類型)
// 這里只處理二維碼
// 當移除屏幕的時候,系統(tǒng)會額外調用一次該方法,內容是空
guard let result = metadataObjects.first as? AVMetadataMachineReadableCodeObject else {return}
scanResultBlock!(result.stringValue)
drawQRCodeBorder(result: result)
// guard let results = metadataObjects as? [AVMetadataMachineReadableCodeObject] else {return}
// for result in results {
// // print(result.stringValue, result.corners)
// drawQRCodeBorder(result: result)
// }
}
/// 給二維碼繪制邊框
///
/// - Parameter result: 掃描到的二維碼結果
private func drawQRCodeBorder(result : AVMetadataMachineReadableCodeObject) {
// result.corners:是數據坐標,必須使用預覽圖層將坐標轉為位(上下文)的坐標
guard let resultObj = preViewLayer?.transformedMetadataObject(for: result) as? AVMetadataMachineReadableCodeObject else {return}
// 繪制邊框
let path = UIBezierPath()
var index = 0
for corner in resultObj.corners {
let dictCF = corner as! CFDictionary
let point = CGPoint(dictionaryRepresentation: dictCF)!
// 開始繪制
if index == 0 {
path.move(to: point)
} else {
path.addLine(to: point)
}
index = index + 1
}
// 關閉路徑
path.close()
// 創(chuàng)建形狀圖層
let shapeLayer = CAShapeLayer()
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = 3
shapeLayer.strokeColor = UIColor.red.cgColor
shapeLayer.fillColor = UIColor.clear.cgColor
preViewLayer?.addSublayer(shapeLayer)
}
/// 移除邊框
private func removeQRCodeBorder() {
if let layers = preViewLayer?.sublayers {
for layer in layers {
let shapeLayer = layer as? CAShapeLayer
if shapeLayer != nil {
shapeLayer?.removeFromSuperlayer()
}
}
}
}
}
效果