二维码
- 二维码概念
- 二维码是用某种特点的集合图形按一定规律在平面(二维方向上)分布的黑白相间的图形,用于记录数据符号信息;
- 二维码的使用场景
- 信息获取(名片,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.创建一个二维码滤镜
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.创建图形上下文
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创建依赖于设备的灰度颜色通道
let cs = CGColorSpaceCreateDeviceGray();
// 3.2创建位图上下文
let bitmapRef = CGContext(data: nil, width: Int(width), height: Int(height), bitsPerComponent: 8, bytesPerRow: 0, space: cs, bitmapInfo: 0)
// 4.创建上下文
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)
}
}
效果
识别二维码
- 步骤
- 创建二维码探测器
- 探测图片特征
- 读取图片特征
识别二维码实例
extension QRCodeTool {
/// 识别图片中的二维码
///
/// - Parameter sourceImage: 原始图片
/// - Returns: 将识别到的二维码绘制边框并返回图片
class func detectorQRCode(sourceImage : UIImage) -> UIImage? {
// 0.创建上下文
let context = CIContext()
// 1.创建二维码的探测器
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.创建图形上下文
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
- 获取摄像头,作为输入设备
- 创建输出对象,并设置代理,扫描到结果后就会通过代理告诉外界
- 创建捕捉会话,将输入和输出连接起来
- 开启会话,执行扫描操作
- 注意点
- 访问用户的摄像头需要请求授权
- info.plist配置Privacy - Camera Usage Description
- 在使用输出设备设置代理以及队列时注意传的是主队列还是全局队列
- 使用会话连接输入和输出之前先判断是否可以添加
- 必须制定扫描的类型,并在将输出添加到会话后再添加,否则会崩溃
- 保证捕捉会话不被销毁
- 当扫描结果时会不断的调用代理方法
class QRCodeTool: NSObject {
/// 扫描二维码
// 单例对象
static let shareInstance = QRCodeTool()
///懒加载
/// 创建会话(顺便添加输入和输出对象)
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.创建输出对象
let output = AVCaptureMetadataOutput()
output.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
// 4.创建捕捉会话
let session = AVCaptureSession()
// 5.添加输入对象和输出对象到会话中
if session.canAddInput(input!) && session.canAddOutput(output) {
session.addInput(input!)
session.addOutput(output)
// 注意:如果要扫描任何码制,必须制定扫描的类型
// 必须在添加了输出对象之后再设置
output.metadataObjectTypes = [AVMetadataObjectTypeQRCode]
}
return session
}()
/// 创建预览图层
/// 将预览图层添加到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
}
// 设置扫描区域(内部会以坐标原点在右上角设置)
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(二维码类型)
// 这里只处理二维码
// 当移除屏幕的时候,系统会额外调用一次该方法,内容是空
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()
// 创建形状图层
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()
}
}
}
}
}
效果